Project import
diff --git a/squashfs-tools/ACKNOWLEDGEMENTS b/squashfs-tools/ACKNOWLEDGEMENTS
new file mode 100644
index 0000000..2a80700
--- /dev/null
+++ b/squashfs-tools/ACKNOWLEDGEMENTS
@@ -0,0 +1,165 @@
+			ACKNOWLEDGEMENTS
+
+Thanks to everyone who have downloaded Squashfs.  I appreciate people
+using it, and any feedback you have.
+
+The following have provided useful feedback, which has guided
+some of the extra features in squashfs.  This is a randomly ordered
+(roughly in chronological order) list, which is updated when
+I remember...
+
+Acknowledgements for Squashfs 4.3
+---------------------------------
+
+Thanks to Bruno Wolff III and Andy Lutomirski for useful feedback
+during the long development process of Squashfs 4.3.
+
+Acknowledgements for Squashfs 4.2
+---------------------------------
+
+Thanks to Lasse Collin (http://tukaani.org/xz/) for mainlining XZ
+decompression support.
+
+Acknowledgements for Squashfs 4.1
+---------------------------------
+
+Thanks to Chan Jeong <chan.jeong@lge.com> and LG for the patches to support LZO
+compression.
+
+Acknowledgements for Squashfs 4.0
+---------------------------------
+
+Thanks to Tim Bird and CELF (Consumer Electronics Linux Forum) for helping
+fund mainstreaming of Squashfs into the 2.6.29 kernel and the 
+changes to the Squashfs tools to support the new 4.0 file system layout.
+
+Acknowledgements for Squashfs-3.3
+------------------------------------
+
+Peter Korsgaard and others sent patches updating Squashfs to changes in the
+VFS interface for 2.6.22/2.6.23/2.6.24-rc1.  Peter also sent some small patches
+for the Squashfs kernel code.
+
+Vito Di Leo sent a patch extending Mksquashfs to support regex filters.
+While his patched worked, it unfortunately made it easy to make Mksquashfs
+perform unpredictably with poorly choosen regex expressions.  It, however,
+encouraged myself to add support for wildcard pattern matching and regex
+filters in a different way.
+
+Acknowledgements for Squashfs-3.2-r2
+------------------------------------
+
+Junjiro Okajima discovered a couple of SMP issues, thanks.
+
+Junjiro Okajima and Tomas Matejicek have produced some good LZMA patches
+for Squashfs.
+
+Acknowledgements for Squashfs-3.2
+---------------------------------
+
+Peter Korsgaard sent a patch updating Squashfs to changes in the VFS interface
+in Linux 2.6.20.
+
+Acknowledgements for Squashfs-3.1
+---------------------------------
+
+Kenneth Duda and Ed Swierk of Arastra Inc. identified numerous bugs with
+Squashfs, and provided patches which were the basis for some of the
+fixes.  In particular they identified the fragment rounding bug, the
+NFS bug, the initrd bug, and helped identify the 4K stack overflow bug.
+
+Scott James Remnant (Ubuntu) also identified the fragment rounding bug,
+and he also provided a patch.
+
+Ming Zhang identified the Lseek bug in Mksquashfs.  His tests on the
+performance of Mksquashfs on SMP systems encouraged the rewrite of
+Mksquashfs.
+
+Peter Korsgaard, Daniel Olivera and Zilvinas Valinskas noticed
+Squashfs 3.0 didn't compile on Linux-2.6.18-rc[1-4] due to changes
+in the Linux VFS interfaces, and provided patches.
+
+Tomas Matejicek (SLAX) suggested the -force option on Unsquashfs, and noticed
+Unsquashfs didn't return the correct exit status.
+
+Yann Le Doare reported a kernel oops and provided a Qemu image that led
+to the identification of the simultaneously accessing multiply mounted Squashfs
+filesystems bug.
+
+
+Older acknowledgements
+----------------------
+
+Mark Robson - pointed out early on that initrds didn't work
+
+Adam Warner - pointed out that greater than 2GB filesystems didn't work.
+
+John Sutton - raised the problem when archiving the entire filesystem
+(/) there was no way to prevent /proc being archived.  This prompted
+exclude files.
+
+Martin Mueller (LinuxTV) - noticed that the filesystem length in the
+superblock doesn't match the output filesystem length.  This is due to
+padding to a 4K boundary.  This prompted the addition of the -nopad option.
+He also reported a problem where 32K block filesystems hung when used as
+initrds.
+
+Arkadiusz Patyk (Polish Linux Distribution - PLD) reported a problem where 32K
+block filesystems hung when used as a root filesystem mounted as a loopback
+device.
+
+Joe Blow emailed me that I'd forgotten to put anything in the README about
+mounting the squashfs filesystem.
+
+David Fox (Lindows) noticed that the exit codes returned by Mksquashfs were
+wrong.  He also noticed that a lot of time was spent in the duplicate scan
+routine.
+
+Cameron Rich complained that Squashfs did not support FIFOs or sockets.
+
+Steve Chadsey and Thomas Weissmuller noticed that files larger than the
+available memory could not be compressed by Mksquashfs.
+
+"Ptwahyu" and "Hoan" (I have no full names and I don't like giving people's
+email addresses), noticed that Mksquashfs 1.3 SEGV'd occasionally.  Even though
+I had already noticed this bug, it is useful to be informed by other people.
+
+Don Elwell, Murray Jensen and Cameron Rich, have all sent in patches.  Thanks,
+I have not had time to do anything about them yet...
+
+Drew Scott Daniels has been a good advocate for Squashfs.
+
+Erik Andersen has made some nice suggestions, unfortunately, I have
+not had time to implement anything.
+
+Artemiy I. Pavlov has written a useful LDP mini-howto for Squashfs
+(http://linuxdoc.artemio.net/squashfs).
+
+Yves Combe reported the Apple G5 bug, when using Squashfs for
+his PPC Knoppix-mib livecd project.
+
+Jaco Greeff (mklivecd project, and maintainer of the Mandrake
+squashfs-tools package) suggested the new mksquashfs -ef option, and the
+standalone build for mksquashfs.
+
+Mike Schaudies made a donation.
+
+Arkadiusz Patyk from the Polish Linux Distribution reported that Squashfs
+didn't work on amd64 machines. He gave me an account on a PLD amd64 machine
+which allowed myself to track down these bugs.
+
+Miles Roper, Peter Kjellerstedt and Willy Tarreau reported that release 2.1 did
+not compile with gcc < 3.x.
+
+Marcel J.E. Mol reported lack of kernel memory issues when using Squashfs
+on small memory embedded systems.  This prompted the addition of the embedded
+system kernel configuration options.
+
+Era Scarecrow noticed that Mksquashfs had not been updated to reflect that
+smaller than 4K blocks are no longer supported.
+
+Kenichi Shima reported the Kconfig file had not been updated to 2.2.
+
+Aaron Ten Clay made a donation!
+
+Tomas Matejicek (SLAX) made a donation!
diff --git a/squashfs-tools/Android.mk b/squashfs-tools/Android.mk
new file mode 100644
index 0000000..d81662e
--- /dev/null
+++ b/squashfs-tools/Android.mk
@@ -0,0 +1,5 @@
+# Copyright (C) 2015 The Android Open Source Project
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/squashfs-tools/CHANGES b/squashfs-tools/CHANGES
new file mode 100644
index 0000000..30a06df
--- /dev/null
+++ b/squashfs-tools/CHANGES
@@ -0,0 +1,613 @@
+			SQUASHFS CHANGE LOG
+
+4.3	12 MAY 2014	New compressor options, new Mksquashfs/Unsquashfs
+			functionality, duplicate checking optimisations,
+			stability improvements (option/file parsing,
+			buffer/memory overflow checks, filesystem hardening
+			on corrupted filesystems), CVE fixes.
+
+	Too many changes to do the traditional custom changelog.  But, this
+	is now unnecessary, so instead list most significant 15% of commits
+	from git changelog in chronological order.
+
+	- unsquashfs: add checks for corrupted data in opendir functions
+	- unsquashfs: completely empty filesystems incorrectly generate an error
+	- unsquashfs: fix open file limit
+	- mksquashfs: Use linked list to store directory entries rather
+	- mksquashfs: Remove qsort and add a bottom up linked list merge sort
+	- mksquashfs: optimise lookup_inode2() for dirs
+	- pseudo: fix handling of modify pseudo files
+	- pseudo: fix handling of directory pseudo files
+	- xattr: Fix ERROR() so that it is synchronised with the progress bar
+	- mksquashfs/sort: Fix INFO() so that it is synced with the progress bar
+	- mksquashfs: Add -progress to force progress bar when using -info
+	- error.h: consolidate the various error macros into one header file
+	- mksquashfs: fix stack overflow in write_fragment_table()
+	- mksquashfs: move list allocation from off the stack
+	- unsquashfs: fix oversight in directory permission setting
+	- mksquashfs: dynamically allocate recovery_file
+	- mksquashfs: dynamically allocate buffer in subpathname()
+	- mksquashfs: dynamically allocate buffer in pathname()
+	- unsquashfs: fix CVE-2012-4024
+	- unsquashfs: fix CVE-2012-4025
+	- mksquashfs: fix potential stack overflow in get_component()
+	- mksquashfs: add parse_number() helper for numeric command line options
+	- mksquasfs: check return value of fstat() in reader_read_file()
+	- mksquashfs: dynamically allocate filename in old_add_exclude()
+	- unsquashfs: dynamically allocate pathname in dir_scan()
+	- unsquashfs: dynamically allocate pathname in pre_scan()
+	- sort: dynamically allocate filename in add_sort_list()
+	- mksquashfs: fix dir_scan() exit if lstat of source directory fails
+	- pseudo: fix memory leak in read_pseudo_def() if exec_file() fails
+	- pseudo: dynamically allocate path in dump_pseudo()
+	- mksquashfs: dynamically allocate path in display_path2()
+	- mksquashfs: dynamically allocate b_buffer in getbase()
+	- pseudo: fix potential stack overflow in get_component()
+	- pseudo: avoid buffer overflow in read_pseudo_def() using sscanf()
+	- pseudo: dynamically allocate filename in exec_file()
+	- pseudo: avoid buffer overflow in read_sort_file() using fscanf()
+	- sort: tighten up sort file parsing
+	- unsquashfs: fix name under-allocation in process_extract_files()
+	- unsquashfs: avoid buffer overflow in print_filename() using sprintf()
+	- Fix some limits in the file parsing routines
+	- pseudo: Rewrite pseudo file processing
+	- read_fs: fix small memory leaks in read_filesystem()
+	- mksquashfs: fix fclose leak in reader_read_file() on I/O error
+	- mksquashfs: fix frag struct leak in write_file_{process|blocks|frag}
+	- unsquashfs_xattr: fix memory leak in write_xattr()
+	- read_xattrs: fix xattr free in get_xattr() in error path
+	- unsquashfs: add -user-xattrs option to only extract user.xxx xattrs
+	- unsquashfs: add code to only print "not superuser" error message once
+	- unsquashfs: check for integer overflow in user input
+	- mksquashfs: check for integer overflow in user input
+	- mksquashfs: fix "new" variable leak in dir_scan1()
+	- read_fs: prevent buffer {over|under}flow in read_block() with
+	  corrupted filesystems
+	- read_fs: check metadata blocks are expected size in scan_inode_table()
+	- read_fs: check the root inode block is found in scan_inode_table()
+	- read_fs: Further harden scan_inode_table() against corrupted
+	  filesystems
+	- unsquashfs: prevent buffer {over|under}flow in read_block() with
+	  corrupted filesystems
+	- read_xattrs: harden xattr data reading against corrupted filesystems
+	- unsquash-[23]: harden frag table reading against corrupted filesystems
+	- unsquash-4.c: harden uid/gid & frag table reading against corruption
+	- unsquashfs: harden inode/directory table reading against corruption
+	- mksquashfs: improve out of space in output filesystem handling
+	- mksquashfs: flag lseek error in writer as probable out of space
+	- mksquashfs: flag lseek error in write_destination as probable out of
+	  space
+	- mksquashfs: print file being squashed when ^\ (SIGQUIT) typed
+	- mksquashfs: make EXIT_MKSQUASHFS() etc restore via new restore thread
+	- mksquashfs: fix recursive restore failure check
+	- info: dump queue and cache status if ^\ hit twice within one second
+	- mksquashfs: fix rare race condition in "locked fragment" queueing
+	- lz4: add experimental support for lz4 compression
+	- lz4: add support for lz4 "high compression"
+	- lzo_wrapper: new implementation with compression options
+	- gzip_wrapper: add compression options
+	- mksquashfs: redo -comp <compressor> parsing
+	- mksquashfs: display compressor options when -X option isn't recognised
+	- mksquashfs: add -Xhelp option
+	- mksquashfs/unsquashfs: fix mtime signedness
+	- Mksquashfs: optimise duplicate checking when appending
+	- Mksquashfs: introduce additional per CPU fragment process threads
+	- Mksquashfs: significantly optimise fragment duplicate checking
+	- read_fs: scan_inode_table(), fix memory leak on filesystem corruption
+	- pseudo: add_pseudo(), fix use of freed variable
+	- mksquashfs/unsquashfs: exclude/extract/pseudo files, fix handling of
+	  leaf name
+	- mksquashfs: rewrite default queue size so it's based on physical mem
+	- mksquashfs: add a new -mem <mbytes> option
+	- mksquashfs: fix limit on the number of dynamic pseudo files
+	- mksquashfs: make -mem take a normal byte value, optionally with a
+	  K, M or G 
+
+4.2	28 FEB 2011	XZ compression, and compression options support
+
+	1. Filesystem improvements:
+
+	    1.1 Added XZ compression
+	    1.2 Added compression options support
+
+	2. Miscellaneous improvements/bug fixes
+
+	    1.1 Add missing NO_XATTR filesystem flag to indicate no-xattrs
+		option was specified and no xattrs should be stored when
+		appending.
+	    1.2 Add suppport in Unquashfs -stat option for displaying
+		NO_XATTR flag.
+	    1.3 Remove checkdata entry from Unsquashfs -stat option if a 4.0
+		filesystem - checkdata is no longer supported.
+	    1.4 Fix appending bug when appending to an empty filesystem - this
+		would be incorrectly treated as an error.
+	    1.5 Use glibc sys/xattr.h include rather than using attr/xattr.h
+		which isn't present by default on some distributions.
+	    1.6 Unsquashfs, fix block calculation error with regular files when
+		file size is between 2^32-block_size+1 and 2^32-1.
+	    1.7 Unsquashfs, fix sparse file writing when holes are larger than
+		2^31-1.
+	    1.8 Add external CFLAGS and LDFLAGS support to Makefile, and allow
+		build options to be specified on command line.  Also don't
+		over-write passed in CFLAGS definition.
+	
+
+4.1	19 SEPT 2010	Major filesystem and tools improvements
+
+	1. Filesystem improvements:
+
+	    1.1 Extended attribute support
+	    1.2 New compression framework
+	    1.3 Support for LZO compression
+	    1.4 Support for LZMA compression (not yet in mainline)
+
+	2. Mksquashfs improvements:
+
+	    1.1 Enhanced pseudo file support
+	    1.2 New options for choosing compression algorithm used
+	    1.3 New options for controlling extended attributes
+	    1.4 Fix misalignment issues with memcpy etc. seen on ARM
+	    1.5 Fix floating point error in progress_bar when max == 0
+	    1.6 Removed use of get_nproc() call unavailable in ulibc
+	    1.7 Reorganised help text
+	  
+	3. Unsquashfs improvements:
+
+	    1.1 New options for controlling extended attributes
+	    1.2 Fix misalignment issues with memcpy etc. seen on ARM
+	    1.3 Fix floating point error in progress_bar when max == 0
+	    1.4 Removed use of get_nproc() call unavailable in ulibc
+
+	  
+4.0	5 APR 2009	Major filesystems improvements
+
+	1. Kernel code improvements:
+
+	    1.1 Fixed little endian layout adopted.  All swapping macros
+		removed, and in-line swapping added for big-endian
+		architectures.
+	    1.2 Kernel code substantially improved and restructured.
+	    1.3 Kernel code split into separate files along functional lines.
+	    1.4 Vmalloc usage removed, and code changed to use separately
+		allocated 4K buffers
+
+	2. Unsquashfs improvements:
+
+	    2.1 Support for 4.0 filesystems added.
+	    2.2 Swapping macros rewritten.
+	    2.3 Unsquashfs code restructured and split into separate files.
+
+	3. Mksquashfs improvements:
+
+	    3.1 Swapping macros rewritten.  Fixed little-endian layout allows
+		code to be optimised and only added at compile time for
+		big endian systems.
+	    3.2 Support for pseudo files added.
+	 
+3.4	26 AUG 2008	Performance improvements to Unsquashfs, Mksquashfs
+			and the kernel code.  Plus many small bug fixes.
+
+	1. Kernel code improvements:
+
+	    1.1 Internal Squashfs kernel metadata and fragment cache
+		implementations have been merged and optimised.  Spinlocks are
+		now used, locks are held for smaller periods and wakeups have
+		been minimised.  Small race condition fixed where if two or
+		more processes tried to read the same cache block
+		simultaneously they would both read and decompress it.  10-20%+
+		speed improvement has been seen on tests.
+	    1.2 NFS export code rewritten following VFS changes in
+		linux-2.6.24.
+	    1.3 New patches for linux-2.6.25, linux-2.6.26, and linux-2.6.27.
+		Fixed patch for linux-2.6.24.
+	    1.4 Fixed small buffer_head leak in squashfs_read_data when
+		handling badly corrupted filesystems.
+	    1.5 Fixed bug in get_dir_index_using_offset.
+
+	2. Unsquashfs improvements:
+
+	    2.1 Unsquashfs has been parallelised.  Filesystem reading, writing
+		and decompression is now multi-threaded.  Up to 40% speed
+		improvement seen on tests.
+	    2.2 Unsquashfs now has a progress bar.  Use -no-progress to
+		disable it.
+	    2.3 Fixed small bug where unistd.h wasn't being included on
+		some distributions, leading to lseek being used rather than
+		lseek64 - which meant on these distributions Unsquashfs
+		couldn't unsquash filesystems larger than 4GB.
+
+       3. Mksquashfs improvements:
+
+	    3.1 Removed some small remaining parallelisation bottlenecks.
+		Depending on source filesystem, up to 10%+ speed improvement.
+	    3.2 Progress bar improved, and moved to separate thread.
+	    3.3 Sparse file handling bug in Mksquashfs 3.3 fixed.
+	    3.4 Two rare appending restore bugs fixed (when ^C hit twice).
+
+
+3.3	1 NOV 2007	Increase in block size, sparse file support,
+			Mksquashfs and Unsquashfs extended to use
+			pattern matching in exclude/extract files, plus
+			many more improvements and bug fixes.
+
+	1. Filesystem improvements:
+
+	     1.1. Maximum block size has been increased to 1Mbyte, and the
+	    	  default block size has been increased to 128 Kbytes.
+		  This improves compression.
+
+	     1.2. Sparse files are now supported.  Sparse files are files
+		  which have large areas of unallocated data commonly called
+		  holes.  These files are now detected by Squashfs and stored
+		  more efficiently.  This improves compression and read
+		  performance for sparse files.
+
+	2. Mksquashfs improvements:
+
+	   2.1.  Exclude files have been extended to use wildcard pattern
+		 matching and regular expressions.  Support has also been
+		 added for non-anchored excludes, which means it is
+		 now possible to specify excludes which match anywhere
+		 in the filesystem (i.e. leaf files), rather than always
+		 having to specify exclude files starting from the root
+		 directory (anchored excludes).
+
+	   2.2.  Recovery files are now created when appending to existing
+		 Squashfs filesystems.  This allows the original filesystem
+		 to be recovered if Mksquashfs aborts unexpectedly
+		 (i.e. power failure).
+
+	3. Unsquashfs improvements:
+
+	    3.1. Multiple extract files can now be specified on the
+		 command line, and the files/directories to be extracted can
+		 now also be given in a file.
+
+	    3.2. Extract files have been extended to use wildcard pattern
+		 matching and regular expressions.
+
+	    3.3. Filename printing has been enhanced and Unquashfs can
+		 now display filenames with file attributes
+		 ('ls -l' style output).
+
+	    3.4. A -stat option has been added which displays the filesystem
+		 superblock information.
+
+	    3.5. Unsquashfs now supports 1.x filesystems.
+
+	4. Miscellaneous improvements/bug fixes:
+
+	    4.1. Squashfs kernel code improved to use SetPageError in
+		 squashfs_readpage() if I/O error occurs.
+
+	    4.2. Fixed Squashfs kernel code bug preventing file
+		 seeking beyond 2GB.
+
+	    4.3. Mksquashfs now detects file size changes between
+		 first phase directory scan and second phase filesystem create.
+		 It also deals better with file I/O errors.
+
+
+3.2-r2	15 JAN 2007	Kernel patch update and progress bar bug fix
+
+	1. Kernel patches 2.6.19/2.6.20 have been updated to use
+	   const structures and mutexes rather than older semaphores.
+	2. Minor SMP bug fixes.
+	3. Progress bar broken on x86-64.  Fixed.
+
+3.2	2 JAN 2007	NFS support, improvements to the Squashfs-tools, major
+			bug fixes, lots of small improvements/bug fixes, and new
+			kernel patches.
+
+	Improvements:
+
+	1. Squashfs filesystems can now be exported via NFS.
+	2. Unsquashfs now supports 2.x filesystems.
+	3. Mksquashfs now displays a progress bar.
+	4. Squashfs kernel code has been hardened against accidently or
+	   maliciously corrupted Squashfs filesystems.
+
+	Bug fixes:
+
+	5. Race condition occurring on S390 in readpage() fixed.
+	6. Odd behaviour of MIPS memcpy in read_data() routine worked-around.
+	7. Missing cache_flush in Squashfs symlink_readpage() added.
+	
+
+3.1-r2	30 AUG 2006	Mksquashfs -sort bug fix
+
+			A code optimisation after testing unfortunately
+			broke sorting in Mksquashfs.  This has been fixed.
+
+3.1	19 AUG 2006	This release has some major improvements to
+			the squashfs-tools, a couple of major bug
+			fixes, lots of small improvements/bug fixes,
+			and new kernel patches.
+
+			
+	1. Mksquashfs has been rewritten to be multi-threaded.  It
+	   has the following improvements
+
+	   1.1. Parallel compression.  By default as many compression and
+		fragment compression threads are created as there are available
+		processors.  This significantly speeds up performance on SMP
+		systems.
+	   1.2. File input and filesystem output is peformed in parallel on
+		separate threads to maximise I/O performance.  Even on single
+		processor systems this speeds up performance by at least 10%.
+	   1.3. Appending has been significantly improved, and files within the
+		filesystem being appended to are no longer scanned and
+		checksummed.  This significantly improves append time for large
+		filesystems.
+	   1.4. File duplicate checking has been optimised, and split into two
+		separate phases.  Only files which are considered possible
+		duplicates after the first phase are checksummed and cached in
+		memory.
+	   1.5	The use of swap memory was found to significantly impact
+		performance. The amount of memory used to cache files is now a
+		command line option, by default this is 512 Mbytes.
+ 
+	2. Unsquashfs has the following improvements
+
+	   2.1  Unsquashfs now allows you to specify the filename or the
+		directory within the Squashfs filesystem that is to be
+		extracted, rather than always extracting the entire filesystem.
+	   2.2  A new -force option has been added which forces Unsquashfs to
+		output to the destination directory even if files and directories
+		already exist in the destination directory.  This allows you to
+		update an already existing directory tree, or to Unsquashfs to
+		a partially filled directory tree.  Without the -force option
+		Unsquashfs will refuse to output.
+
+	3.  The following major bug fixes have been made
+
+	  3.1  	A fragment table rounding bug has been fixed in Mksquashfs.
+		Previously if the number of fragments in the filesystem
+		were a multiple of 512, Mksquashfs would generate an
+		incorrect filesystem.
+	  3.2  	A rare SMP bug which occurred when simultaneously acccessing
+		multiply mounted Squashfs filesystems has been fixed.
+
+	4. Miscellaneous improvements/bug fixes
+
+	  4.1	Kernel code stack usage has been reduced.  This is to ensure
+		Squashfs works with 4K stacks.
+	  4.2   Readdir (Squashfs kernel code) has been fixed to always
+		return 0, rather than the number of directories read.  Squashfs
+		should now interact better with NFS.
+	  4.3	Lseek bug in Mksquashfs when appending to larger than 4GB
+		filesystems fixed.
+	  4.4	Squashfs 2.x initrds can now been mounted.
+	  4.5	Unsquashfs exit status fixed.
+	  4.6	New patches for linux-2.6.18 and linux-2.4.33.
+
+	
+3.0	15 MAR 2006	Major filesystem improvements
+
+	1. Filesystems are no longer limited to 4 GB.  In
+	   theory 2^64 or 4 exabytes is now supported.
+	2. Files are no longer limited to 4 GB.  In theory the maximum
+	   file size is 4 exabytes.
+	3. Metadata (inode table and directory tables) are no longer
+	   restricted to 16 Mbytes.
+	4. Hardlinks are now suppported.
+	5. Nlink counts are now supported.
+	6. Readdir now returns '.' and '..' entries.
+	7. Special support for files larger than 256 MB has been added to
+	   the Squashfs kernel code for faster read access.
+	8. Inode numbers are now stored within the inode rather than being
+	   computed from inode location on disk (this is not so much an
+	   improvement, but a change forced by the previously listed
+	   improvements).
+
+2.2-r2	8 SEPT 2005	Second release of 2.2, this release fixes a couple
+			of small bugs, a couple of small documentation
+			mistakes, and adds a patch for kernel 2.6.13. 
+
+	1. Mksquashfs now deletes the output filesystem image file if an
+	   error occurs whilst generating the filesystem.  Previously on
+	   error the image file was left empty or partially written.
+	2. Updated mksquashfs so that it doesn't allow you to generate
+	   filesystems with block sizes smaller than 4K.  Squashfs hasn't
+	   supported block sizes less than 4K since 2.0-alpha.
+	3. Mksquashfs now ignores missing files/directories in sort files.
+	   This was the original behaviour before 2.2.
+	4. Fixed small mistake in fs/Kconfig where the version was still
+	   listed as 2.0.
+	5. Updated ACKNOWLEDGEMENTS file.
+
+
+2.2	3 JUL 2005	This release has some small improvements, bug fixes
+			and patches for new kernels.
+
+	1. Sort routine re-worked and debugged from release 2.1.  It now allows
+	   you to give Mkisofs style sort files and checks for filenames that
+	   don't match anything.  Sort priority has also been changed to
+	   conform to Mkisofs usage, highest priority files are now placed
+	   at the start of the filesystem (this means they will be on the
+	   inside of a CD or DVD).
+	2. New Configure options for embedded systems (memory constrained
+	   systems).  See INSTALL file for further details.
+	3. Directory index bug fixed where chars were treated as signed on
+           some architectures.  A file would not be found in the rare case
+	   that the filename started with a chracter greater than 127.
+	4. Bug introduced into the read_data() routine when sped up to use data
+	   block queueing fixed.  If the second or later block resulted in an
+	   I/O error this was not checked.
+	5. Append bug introduced in 2.1 fixed.  The code to compute the new
+	   compressed and uncompressed directory parts after appending was
+	   wrong.
+	6. Metadata block length read routine altered to not perform a
+	   misaligned short read.  This was to fix reading on an ARM7 running
+	   uCLinux without a misaligned read interrupt handler.
+	7. Checkdata bug introduced in 2.1 fixed.
+	
+
+2.1-r2  15 DEC 2004	Code changed so it can be compiled with gcc 2.x
+
+	1.  In some of the code added for release 2.1 I unknowingly used some
+	    gcc extensions only supported by 3.x compilers.  I have received
+	    a couple of reports that the 2.1 release doesn't build on 2.x and so
+	    people are clearly still using gcc 2.x.  The code has been
+	    rewritten to remove these extensions.
+
+2.1	10 DEC 2004	Significantly improved directory handling plus numerous
+			other smaller improvements
+
+	1.  Fast indexed directories implemented.  These speed up directory
+	    operations (ls, file lookup etc.) significantly for directories
+	    larger than 8 KB.
+	2.  All directories are now sorted in alphabetical order.  This again
+	    speeds up directory operations, and in some cases it also results in
+	    a small compression improvement (greater data similarity between
+	    files with alphabetically similar names).
+	3.  Maximum directory size increased from 512 KB to 128 MB.
+	4.  Duplicate fragment checking and appending optimised in mksquashfs,
+	    depending on filesystem, this is now up to 25% faster.
+	5.  Mksquashfs help information reformatted and reorganised.
+	6.  The Squashfs version and release date is now printed at kernel
+	    boot-time or module insertion.  This addition will hopefully help
+	    to reduce the growing problem where the Squashfs version supported
+	    by a kernel is unknown and the kernel source is unavailable.
+        7.  New PERFORMANCE.README file.
+	8.  New -2.0 mksquashfs option.
+	9.  CHANGES file reorganised.
+	10. README file reorganised, clarified and updated to include the 2.0
+	    mksquashfs options.
+	11. New patch for Linux 2.6.9.
+	12. New patch for Linux 2.4.28.
+
+2.0r2	29 AUG 2004	Workaround for kernel bug in kernels 2.6.8 and newer
+			added
+
+	1. New patch for kernel 2.6.8.1.  This includes a workaround for a
+	   kernel bug introduced in 2.6.7bk14, which is present in all later
+	   versions of the kernel.  
+
+	   If you're using a 2.6.8 kernel or later then you must use this
+	   2.6.8.1 patch.  If you've experienced hangs or oopses using Squashfs
+	   with a 2.6.8 or later kernel then you've hit this bug, and this
+	   patch will fix it.
+
+	   It is worth mentioning that this kernel bug potentially affects
+	   other filesystems.  If you receive odd results with other
+	   filesystems you may be experiencing this bug with that filesystem.
+	   I submitted a patch but this has not yet gone into the
+	   kernel, hopefully the bug will be fixed in later kernels. 
+
+2.0	13 JULY 2004	A couple of new options, and some bug fixes
+
+	1. New mksquashfs -all-root, -root-owned, -force-uid, and -force-gid
+	   options.  These allow the uids/gids of files in the generated
+	   filesystem to be specified, overriding the uids/gids in the
+	   source filesystem.
+	2. Initrds are now supported for kernels 2.6.x.
+	3. amd64 bug fixes.  If you use an amd64, please read the README-AMD64
+	   file.
+	4. Check-data and gid bug fixes.  With 2.0-alpha when mounting 1.x
+	   filesystems in certain cases file gids were corrupted.
+	5. New patch for Linux 2.6.7.
+
+2.0-ALPHA	21 MAY 2004	Filesystem changes and compression improvements
+
+	1. Squashfs 2.0 has added the concept of fragment blocks.
+           Files smaller than the file block size and optionally the
+	   remainder of files that do not fit fully into a block (i.e. the
+	   last 32K in a 96K file) are packed into shared fragments and
+	   compressed together.  This achieves on average 5 - 20% better
+	   compression than Squashfs 1.x.
+	2. The maximum block size has been increased to 64K (in the ALPHA
+	   version of Squashfs 2.0).
+	3. The maximum number of UIDs has been increased to 256 (from 48 in
+	   1.x).
+	4. The maximum number of GIDs has been increased to 256 (from 15 in
+	   1.x).
+	5. Removal of sleep_on() function call in 2.6.x patch, to allow Squashfs
+	   to work on the Fedora rc2 kernel.
+	6. Numerous small bug fixes have been made.
+
+1.3r3	18 JAN 2004	Third release of 1.3, this adds a new mksquashfs option,
+			some bug fixes, and extra patches for new kernels
+
+	1. New mksquashfs -ef exclude option.  This option reads the exclude
+	   dirs/files from an exclude file, one exclude dir/file per line.  This
+	   avoids the command line size limit when using the -e exclude option,
+	2. When appending to existing filesystems, if mksquashfs experiences a
+	   fatal error (e.g. out of space when adding to the destination), the
+	   original filesystem is restored,
+	3. Mksquashfs now builds standalone, without the kernel needing to be
+	   patched.
+	4. Bug fix in the kernel squashfs filesystem, where the pages being
+	   filled were not kmapped.  This seems to only have caused problems
+	   on an Apple G5,
+	5. New patch for Linux 2.4.24,
+
+	6. New patch for Linux 2.6.1, this replaces the patch for 2.6.0-test7.
+
+1.3r2	14 OCT 2003	Second release of 1.3, bug fixes and extra patches for
+		        new kernels
+
+	1. Bug fix in routine that adds files to the filesystem being
+	   generated in mksquashfs.  This bug was introduced in 1.3
+	   (not enough testing...) when I rewrote it to handle files larger
+	   than available memory.  This bug caused a SEGV, so if you've ever
+	   got that, it is now fixed,
+	2. Long running bug where ls -s and du reported wrong block size
+	   fixed.  I'm pretty sure this used to work many kernel versions ago
+	   (2.4.7) but it broke somewhere along the line since then,
+	3. New patch for Linux 2.4.22,
+	4. New patch for 2.6.0-test7, this replaces the patch for 2.6.0-test1.
+
+1.3	29 JUL 2003	FIFO/Socket support added plus optimisations and
+		        improvements
+
+	1. FIFOs and Socket inodes are now supported,
+	2. Mksquashfs can now compress files larger than available
+	   memory,
+	3. File duplicate check routine optimised,
+	4. Exit codes fixed in Mksquashfs,
+	5. Patch for Linux 2.4.21,
+	6. Patch for Linux 2.6.0-test1.  Hopefully, this will work for
+	   the next few releases of 2.6.0-testx, otherwise, I'll be
+	   releasing a lot of updates to the 2.6.0 patch...
+
+1.2	13 MAR 2003	Append feature and new mksquashfs options added
+
+	Mksquashfs can now add to existing squashfs filesystems.  Three extra
+	options "-noappend", "-keep-as-directory", and "root-becomes"
+	have been added.
+
+	The append option with file duplicate detection, means squashfs can be
+	used as a simple versioning archiving filesystem. A squashfs
+	filesystem can be created with for example the linux-2.4.19 source.
+	Appending the linux-2.4.20 source will create a filesystem with the
+	two source trees, but only the changed files will take extra room,
+	the unchanged files will be detected as duplicates.
+
+	See the README file for usage changes.
+
+1.1b	16 JAN 2003	Bug fix release
+
+	Fixed readpage deadlock bug.  This was a rare deadlock bug that
+	happened when pushing pages into the page cache when using greater
+	than 4K blocks.  I never got this bug when I tested the filesystem,
+	but two people emailed me on the same day about the problem!
+	I fixed it by using a page cache function that wasn't there when
+	I originally did the work, which was nice :-)
+
+1.1	8 JAN 2003	Added features
+
+	1. Kernel squashfs can now mount different byte order filesystems.
+	2. Additional features added to mksquashfs.  Mksquashfs now supports
+	   exclude files and multiple source files/directories can be
+	   specified.  A nopad option has also been added, which
+	   informs mksquashfs not to pad filesystems to a multiple of 4K.
+	   See README for mksquashfs usage changes.
+	3. Greater than 2GB filesystems bug fix.  Filesystems greater than 2GB
+	   can now be created.
+
+1.0c 	14 NOV 2002	Bug fix release
+
+	Fixed bugs with initrds and device nodes
+
+1.0 	23 OCT 2002	Initial release
diff --git a/squashfs-tools/COPYING b/squashfs-tools/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/squashfs-tools/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 of the License, 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, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/squashfs-tools/INSTALL b/squashfs-tools/INSTALL
new file mode 100644
index 0000000..5212d54
--- /dev/null
+++ b/squashfs-tools/INSTALL
@@ -0,0 +1,30 @@
+			INSTALLING SQUASHFS
+
+The squashfs4.3.tar.gz file contains the squashfs-tools directory containing
+mksquashfs and unsquashfs.
+
+1. Kernel support
+-----------------
+
+This release is for 2.6.29 and newer kernels.  Kernel patching is not necessary.
+
+Extended attribute support requires 2.6.35 or newer.  File systems with
+extended attributes can be mounted on 2.6.29 and newer kernels (the
+extended attributes will be ignored with a warning).
+
+LZO compression support requires 2.6.36 or newer kernels.
+
+XZ compression support requires 2.6.38 or newer kernels.
+
+LZ4 support is not yet in any mainline kernel.
+
+2. Building squashfs tools
+--------------------------
+
+The squashfs-tools directory contains the mksquashfs and unsquashfs programs.
+These can be made by typing make (or make install to install in /usr/local/bin).
+
+By default the tools are built with GZIP compression and extended attribute
+support.  Read the Makefile in squashfs-tools/ for instructions on building
+LZO, LZ4 and XZ compression support, and for instructions on disabling GZIP
+and extended attribute support if desired.
diff --git a/squashfs-tools/MODULE_LICENSE_GPL b/squashfs-tools/MODULE_LICENSE_GPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/squashfs-tools/MODULE_LICENSE_GPL
diff --git a/squashfs-tools/NOTICE b/squashfs-tools/NOTICE
new file mode 120000
index 0000000..d24842f
--- /dev/null
+++ b/squashfs-tools/NOTICE
@@ -0,0 +1 @@
+COPYING
\ No newline at end of file
diff --git a/squashfs-tools/README b/squashfs-tools/README
new file mode 100644
index 0000000..3ba308d
--- /dev/null
+++ b/squashfs-tools/README
@@ -0,0 +1,21 @@
+Git status:
+
+This is the squashfs.sourceforge.net CVS repository imported into this
+new git repository.  All new development will be under git.
+
+Squashfs-tools: The new Squashfs 4.3 release (2014/05/05).
+
+kernel: obsolete, the kernel code is now in mainline at www.kernel.org.
+
+All Squashfs kernel development trees are stored on kernel.org,
+under git.kernel.org:/linux/kernel/git/pkl/...
+
+kernel-2.4: prehistoric, not updated since the 3.1 release.  If you still need
+Squashfs support in the 2.4 kernel then use the squashfs 3.1 release.  This
+is the last release that supported 2.4 kernels.
+
+The kernel and kernel-2.4 directories (imported from CVS) are not really
+relevant anymore, but are here temporarily while I decide where to put
+them (I don't want to delete them and have all the pre-mainlining
+kernel commit history disappear from public repositories).
+
diff --git a/squashfs-tools/RELEASE-README b/squashfs-tools/RELEASE-README
new file mode 100644
index 0000000..a033a4b
--- /dev/null
+++ b/squashfs-tools/RELEASE-README
@@ -0,0 +1,1010 @@
+	SQUASHFS 4.3 - A squashed read-only filesystem for Linux
+
+	Copyright 2002-2014 Phillip Lougher <phillip@lougher.demon.co.uk>
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to Squashfs version 4.3.  Please read the README-4.3 and CHANGES files
+for details of changes.
+
+Squashfs is a highly compressed read-only filesystem for Linux.
+It uses either gzip/xz/lzo/lz4 compression to compress both files, inodes
+and directories.  Inodes in the system are very small and all blocks are
+packed to minimise data overhead. Block sizes greater than 4K are supported
+up to a maximum of 1Mbytes (default block size 128K).
+
+Squashfs is intended for general read-only filesystem use, for archival
+use (i.e. in cases where a .tar.gz file may be used), and in constrained
+block device/memory systems (e.g. embedded systems) where low overhead is
+needed.
+
+1. SQUASHFS OVERVIEW
+--------------------
+
+1. Data, inodes and directories are compressed.
+
+2. Squashfs stores full uid/gids (32 bits), and file creation time.
+
+3. In theory files up to 2^64 bytes are supported.  In theory filesystems can
+   be up to 2^64 bytes.
+
+4. Inode and directory data are highly compacted, and packed on byte
+   boundaries.  Each compressed inode is on average 8 bytes in length
+   (the exact length varies on file type, i.e. regular file, directory,
+   symbolic link, and block/char device inodes have different sizes).
+
+5. Squashfs can use block sizes up to 1Mbyte (the default size is 128K).
+   Using 128K blocks achieves greater compression ratios than the normal
+   4K block size.
+
+6. File duplicates are detected and removed.
+
+7. Filesystems can be compressed with gzip, xz (lzma2), lzo or lz4
+   compression algorithms.
+
+1.1 Extended attributes (xattrs)
+--------------------------------
+
+Squashfs filesystems now have extended attribute support.  The
+extended attribute implementation has the following features:
+
+1. Layout can store up to 2^48 bytes of compressed xattr data.
+2. Number of xattrs per inode unlimited.
+3. Total size of xattr data per inode 2^48 bytes of compressed data.
+4. Up to 4 Gbytes of data per xattr value.
+5. Inline and out-of-line xattr values supported for higher performance
+   in xattr scanning (listxattr & getxattr), and to allow xattr value
+   de-duplication.
+6. Both whole inode xattr duplicate detection and individual xattr value
+   duplicate detection supported.  These can obviously nest, file C's
+   xattrs can be a complete duplicate of file B, and file B's xattrs
+   can be a partial duplicate of file A.
+7. Xattr name prefix types stored, allowing the redundant "user.", "trusted."
+   etc. characters to be eliminated and more concisely stored.
+8. Support for files, directories, symbolic links, device nodes, fifos
+   and sockets.
+
+Extended attribute support is in 2.6.35 and later kernels.  Filesystems
+with extended attributes can be mounted on 2.6.29 and later kernels, the
+extended attributes will be ignored with a warning.
+
+2. USING SQUASHFS
+-----------------
+
+Squashfs filesystems should be mounted with 'mount' with the filesystem type
+'squashfs'.  If the filesystem is on a block device, the filesystem can be
+mounted directly, e.g.
+
+%mount -t squashfs /dev/sda1 /mnt
+
+Will mount the squashfs filesystem on "/dev/sda1" under the directory "/mnt".
+
+If the squashfs filesystem has been written to a file, the loopback device
+can be used to mount it (loopback support must be in the kernel), e.g.
+
+%mount -t squashfs image /mnt -o loop
+
+Will mount the squashfs filesystem in the file "image" under
+the directory "/mnt".
+
+3. MKSQUASHFS
+-------------
+
+3.1 Mksquashfs options and overview
+-----------------------------------
+
+As squashfs is a read-only filesystem, the mksquashfs program must be used to
+create populated squashfs filesystems.
+
+SYNTAX:./mksquashfs source1 source2 ...  dest [options] [-e list of exclude
+dirs/files]
+
+Filesystem build options:
+-comp <comp>		select <comp> compression
+			Compressors available:
+				gzip (default)
+				lzo
+				lz4
+				xz
+-b <block_size>		set data block to <block_size>.  Default 128 Kbytes
+			Optionally a suffix of K or M can be given to specify
+			Kbytes or Mbytes respectively
+-no-exports		don't make the filesystem exportable via NFS
+-no-sparse		don't detect sparse files
+-no-xattrs		don't store extended attributes
+-xattrs			store extended attributes (default)
+-noI			do not compress inode table
+-noD			do not compress data blocks
+-noF			do not compress fragment blocks
+-noX			do not compress extended attributes
+-no-fragments		do not use fragments
+-always-use-fragments	use fragment blocks for files larger than block size
+-no-duplicates		do not perform duplicate checking
+-all-root		make all files owned by root
+-force-uid uid		set all file uids to uid
+-force-gid gid		set all file gids to gid
+-nopad			do not pad filesystem to a multiple of 4K
+-keep-as-directory	if one source directory is specified, create a root
+			directory containing that directory, rather than the
+			contents of the directory
+
+Filesystem filter options:
+-p <pseudo-definition>	Add pseudo file definition
+-pf <pseudo-file>	Add list of pseudo file definitions
+-sort <sort_file>	sort files according to priorities in <sort_file>.  One
+			file or dir with priority per line.  Priority -32768 to
+			32767, default priority 0
+-ef <exclude_file>	list of exclude dirs/files.  One per line
+-wildcards		Allow extended shell wildcards (globbing) to be used in
+			exclude dirs/files
+-regex			Allow POSIX regular expressions to be used in exclude
+			dirs/files
+
+Filesystem append options:
+-noappend		do not append to existing filesystem
+-root-becomes <name>	when appending source files/directories, make the
+			original root become a subdirectory in the new root
+			called <name>, rather than adding the new source items
+			to the original root
+
+Mksquashfs runtime options:
+-version		print version, licence and copyright message
+-exit-on-error		treat normally ignored errors as fatal
+-recover <name>		recover filesystem data using recovery file <name>
+-no-recovery		don't generate a recovery file
+-info			print files written to filesystem
+-no-progress		don't display the progress bar
+-progress		display progress bar when using the -info option
+-processors <number>	Use <number> processors.  By default will use number of
+			processors available
+-mem <size>		Use <size> physical memory.  Currently set to 1922M
+			Optionally a suffix of K, M or G can be given to specify
+			Kbytes, Mbytes or Gbytes respectively
+
+Miscellaneous options:
+-root-owned		alternative name for -all-root
+-noInodeCompression	alternative name for -noI
+-noDataCompression	alternative name for -noD
+-noFragmentCompression	alternative name for -noF
+-noXattrCompression	alternative name for -noX
+
+-Xhelp			print compressor options for selected compressor
+
+Compressors available and compressor specific options:
+	gzip (default)
+	  -Xcompression-level <compression-level>
+		<compression-level> should be 1 .. 9 (default 9)
+	  -Xwindow-size <window-size>
+		<window-size> should be 8 .. 15 (default 15)
+	  -Xstrategy strategy1,strategy2,...,strategyN
+		Compress using strategy1,strategy2,...,strategyN in turn
+		and choose the best compression.
+		Available strategies: default, filtered, huffman_only,
+		run_length_encoded and fixed
+	lzo
+	  -Xalgorithm <algorithm>
+		Where <algorithm> is one of:
+			lzo1x_1
+			lzo1x_1_11
+			lzo1x_1_12
+			lzo1x_1_15
+			lzo1x_999 (default)
+	  -Xcompression-level <compression-level>
+		<compression-level> should be 1 .. 9 (default 8)
+		Only applies to lzo1x_999 algorithm
+	lz4
+	  -Xhc
+		Compress using LZ4 High Compression
+	xz
+	  -Xbcj filter1,filter2,...,filterN
+		Compress using filter1,filter2,...,filterN in turn
+		(in addition to no filter), and choose the best compression.
+		Available filters: x86, arm, armthumb, powerpc, sparc, ia64
+	  -Xdict-size <dict-size>
+		Use <dict-size> as the XZ dictionary size.  The dictionary size
+		can be specified as a percentage of the block size, or as an
+		absolute value.  The dictionary size must be less than or equal
+		to the block size and 8192 bytes or larger.  It must also be
+		storable in the xz header as either 2^n or as 2^n+2^(n+1).
+		Example dict-sizes are 75%, 50%, 37.5%, 25%, or 32K, 16K, 8K
+		etc.
+
+Source1 source2 ... are the source directories/files containing the
+files/directories that will form the squashfs filesystem.  If a single
+directory is specified (i.e. mksquashfs source output_fs) the squashfs
+filesystem will consist of that directory, with the top-level root
+directory corresponding to the source directory.
+
+If multiple source directories or files are specified, mksquashfs will merge
+the specified sources into a single filesystem, with the root directory
+containing each of the source files/directories.  The name of each directory
+entry will be the basename of the source path.   If more than one source
+entry maps to the same name, the conflicts are named xxx_1, xxx_2, etc. where
+xxx is the original name.
+
+To make this clear, take two example directories.  Source directory
+"/home/phillip/test" contains  "file1", "file2" and "dir1".
+Source directory "goodies" contains "goodies1", "goodies2" and "goodies3".
+
+usage example 1:
+
+%mksquashfs /home/phillip/test output_fs
+
+This will generate a squashfs filesystem with root entries
+"file1", "file2" and "dir1".
+
+example 2:
+
+%mksquashfs /home/phillip/test goodies output_fs
+
+This will create a squashfs filesystem with the root containing
+entries "test" and "goodies" corresponding to the source
+directories "/home/phillip/test" and "goodies".
+
+example 3:
+
+%mksquashfs /home/phillip/test goodies test output_fs
+
+This is the same as the previous example, except a third
+source directory "test" has been specified.  This conflicts
+with the first directory named "test" and will be renamed "test_1".
+
+Multiple sources allow filesystems to be generated without needing to
+copy all source files into a common directory.  This simplifies creating
+filesystems.
+
+The -keep-as-directory option can be used when only one source directory
+is specified, and you wish the root to contain that directory, rather than
+the contents of the directory.  For example:
+
+example 4:
+
+%mksquashfs /home/phillip/test output_fs -keep-as-directory
+
+This is the same as example 1, except for -keep-as-directory.
+This will generate a root directory containing directory "test",
+rather than the "test" directory contents "file1", "file2" and "dir1".
+
+The Dest argument is the destination where the squashfs filesystem will be
+written.  This can either be a conventional file or a block device.  If the file
+doesn't exist it will be created, if it does exist and a squashfs
+filesystem exists on it, mksquashfs will append.  The -noappend option will
+write a new filesystem irrespective of whether an existing filesystem is
+present.
+
+3.2 Changing compression algorithm and compression specific options
+-------------------------------------------------------------------
+
+By default Mksquashfs will compress using the gzip compression
+algorithm.  This algorithm offers a good trade-off between compression
+ratio, and memory and time taken to decompress.
+
+Squashfs also supports LZ4, LZO and XZ (LZMA2) compression.  LZO offers worse
+compression ratio than gzip, but is faster to decompress.  XZ offers better
+compression ratio than gzip, but at the expense of greater memory and time
+to decompress (and significantly more time to compress).  LZ4 is similar
+to LZO, but, support for it is not yet in the mainline kernel, and so
+its usefulness is currently limited to using Squashfs with Mksquashfs/Unsquashfs
+as an archival system like tar.
+
+If you're not building the squashfs-tools and kernel from source, then
+the tools and kernel may or may not have been built with support for LZ4, LZO or
+XZ compression.  The compression algorithms supported by the build of
+Mksquashfs can be found by typing mksquashfs without any arguments.  The
+compressors available are displayed at the end of the help message, e.g. 
+
+Compressors available and compressor specific options:
+	gzip (default)
+	  -Xcompression-level <compression-level>
+		<compression-level> should be 1 .. 9 (default 9)
+	  -Xwindow-size <window-size>
+		<window-size> should be 8 .. 15 (default 15)
+	  -Xstrategy strategy1,strategy2,...,strategyN
+		Compress using strategy1,strategy2,...,strategyN in turn
+		and choose the best compression.
+		Available strategies: default, filtered, huffman_only,
+		run_length_encoded and fixed
+	lzo
+	  -Xalgorithm <algorithm>
+		Where <algorithm> is one of:
+			lzo1x_1
+			lzo1x_1_11
+			lzo1x_1_12
+			lzo1x_1_15
+			lzo1x_999 (default)
+	  -Xcompression-level <compression-level>
+		<compression-level> should be 1 .. 9 (default 8)
+		Only applies to lzo1x_999 algorithm
+	lz4
+	  -Xhc
+		Compress using LZ4 High Compression
+	xz
+	  -Xbcj filter1,filter2,...,filterN
+		Compress using filter1,filter2,...,filterN in turn
+		(in addition to no filter), and choose the best compression.
+		Available filters: x86, arm, armthumb, powerpc, sparc, ia64
+	  -Xdict-size <dict-size>
+		Use <dict-size> as the XZ dictionary size.  The dictionary size
+		can be specified as a percentage of the block size, or as an
+		absolute value.  The dictionary size must be less than or equal
+		to the block size and 8192 bytes or larger.  It must also be
+		storable in the xz header as either 2^n or as 2^n+2^(n+1).
+		Example dict-sizes are 75%, 50%, 37.5%, 25%, or 32K, 16K, 8K
+		etc.
+
+If the compressor offers compression specific options (all the compressors now
+have compression specific options except the deprecated lzma1 compressor)
+then these options are also displayed (.i.e. in the above XZ is shown with two
+compression specific options).  The compression specific options are, obviously,
+specific to the compressor in question, and the compressor documentation and
+web sites should be consulted to understand their behaviour.  In general
+the Mksquashfs compression defaults for each compressor are optimised to
+give the best performance for each compressor, where what constitutes
+best depends on the compressor.  For gzip/xz best means highest compression,
+for LZO/LZ4 best means a tradeoff between compression and (de)-compression
+overhead (LZO/LZ4 by definition are intended for weaker processors).
+
+3.3 Changing global compression defaults used in mksquashfs
+-----------------------------------------------------------
+
+There are a large number of options that can be used to control the 
+compression in mksquashfs.  By and large the defaults are the most
+optimum settings and should only be changed in exceptional circumstances!
+Note, this does not apply to the block size, increasing the block size
+from the default of 128Kbytes will increase compression (especially
+for the xz compressor) and should increase I/O performance too.  However,
+a block size of greater than 128Kbytes may increase latency in certain
+cases (where the filesystem contains lots of fragments, and no locality
+of reference is observed).  For this reason the block size default is
+configured to the less optimal 128Kbytes.  Users should experiment
+with 256Kbyte sizes or above.
+
+The -noI, -noD and -noF options (also -noInodeCompression, -noDataCompression
+and -noFragmentCompression) can be used to force mksquashfs to not compress
+inodes/directories, data and fragments respectively.  Giving all options
+generates an uncompressed filesystem.
+
+The -no-fragments tells mksquashfs to not generate fragment blocks, and rather
+generate a filesystem similar to a Squashfs 1.x filesystem.  It will of course
+still be a Squashfs 4.0 filesystem but without fragments, and so it won't be
+mountable on a Squashfs 1.x system.
+
+The -always-use-fragments option tells mksquashfs to always generate
+fragments for files irrespective of the file length.  By default only small
+files less than the block size are packed into fragment blocks.  The ends of
+files which do not fit fully into a block, are NOT by default packed into
+fragments.  To illustrate this, a 100K file has an initial 64K block and a 36K
+remainder.  This 36K remainder is not packed into a fragment by default.  This
+is because to do so leads to a 10 - 20% drop in sequential I/O performance, as a
+disk head seek is needed to seek to the initial file data and another disk seek
+is need to seek to the fragment block.  Specify this option if you want file
+remainders to be packed into fragment blocks.  Doing so may increase the
+compression obtained BUT at the expense of I/O speed.
+
+The -no-duplicates option tells mksquashfs to not check the files being
+added to the filesystem for duplicates.  This can result in quicker filesystem
+generation and appending although obviously compression will suffer badly if
+there is a lot of duplicate files.
+
+The -b option allows the block size to be selected, both "K" and "M" postfixes
+are supported, this can be either 4K, 8K, 16K, 32K, 64K, 128K, 256K, 512K or
+1M bytes.
+
+3.4 Specifying the UIDs/GIDs used in the filesystem
+---------------------------------------------------
+
+By default files in the generated filesystem inherit the UID and GID ownership
+of the original file.  However,  mksquashfs provides a number of options which
+can be used to override the ownership.
+
+The options -all-root and -root-owned (both do exactly the same thing) force all
+file uids/gids in the generated Squashfs filesystem to be root.  This allows
+root owned filesystems to be built without root access on the host machine.
+
+The "-force-uid uid"  option forces all files in the generated Squashfs
+filesystem to be owned by the specified uid.  The uid can be specified either by
+name (i.e. "root") or by number.
+
+The "-force-gid gid" option forces all files in the generated Squashfs
+filesystem to be group owned by the specified gid.  The gid can be specified
+either by name (i.e. "root") or by number.
+
+3.5 Excluding files from the filesystem
+---------------------------------------
+
+The -e and -ef options allow files/directories to be specified which are
+excluded from the output filesystem.  The -e option takes the exclude
+files/directories from the command line, the -ef option takes the
+exlude files/directories from the specified exclude file, one file/directory
+per line.
+
+Two styles of exclude file matching are supported: basic exclude matching, and
+extended wildcard matching.  Basic exclude matching is a legacy feature
+retained for backwards compatibility with earlier versions of Mksquashfs.
+Extended wildcard matching should be used in preference.
+
+3.5.1 Basic exclude matching
+----------------------------
+
+Each exclude file is treated as an exact match of a file/directory in
+the source directories.  If an exclude file/directory is absolute (i.e.
+prefixed with /, ../, or ./) the entry is treated as absolute, however, if an
+exclude file/directory is relative, it is treated as being relative to each of
+the sources in turn, i.e.
+
+%mksquashfs /tmp/source1 source2  output_fs -e ex1 /tmp/source1/ex2 out/ex3
+
+Will generate exclude files /tmp/source1/ex2, /tmp/source1/ex1, source2/ex1,
+/tmp/source1/out/ex3 and source2/out/ex3.
+
+3.5.2 Extended exclude file handling
+------------------------------------
+
+Extended exclude file matching treats each exclude file as a wildcard or
+regex expression.  To enable wildcard matching specify the -wildcards
+option, and to enable regex matching specify the -regex option.  In most
+cases the -wildcards option should be used rather than -regex because wildcard
+matching behaviour is significantly easier to understand!
+
+In addition to wildcards/regex expressions, exclude files can be "anchored" or
+"non-anchored".  An anchored exclude is one which matches from the root of the
+directory and nowhere else, a non-anchored exclude matches anywhere.  For
+example given the directory hierarchy "a/b/c/a/b", the anchored exclude
+"a/b" will match "a/b" at the root of the directory hierarchy, but
+it will not match the "/a/b" sub-directory within directory "c", whereas a
+non-anchored exclude would.
+
+A couple of examples should make this clearer.
+ 
+Anchored excludes
+
+  1. mksquashfs example image.sqsh -wildcards -e 'test/*.gz'
+
+     Exclude all files matching "*.gz" in the top level directory "test".
+
+  2. mksquashfs example image.sqsh -wildcards -e '*/[Tt]est/example*'
+
+     Exclude all files beginning with "example" inside directories called
+     "Test" or "test", that occur inside any top level directory.
+
+  Using extended wildcards, negative matching is also possible.
+
+  3. mksquashfs example image.sqsh -wildcards -e 'test/!(*data*).gz'
+
+     Exclude all files matching "*.gz" in top level directory "test",
+     except those with "data" in the name.
+
+Non-anchored excludes
+
+  By default excludes match from the top level directory, but it is
+  often useful to exclude a file matching anywhere in the source directories.
+  For this non-anchored excludes can be used, specified by pre-fixing the
+  exclude with "...".
+
+  Examples:
+
+  1. mksquashfs example image.sqsh -wildcards -e '... *.gz'
+
+     Exclude files matching "*.gz" anywhere in the source directories.
+     For example this will match "example.gz", "test/example.gz", and
+     "test/test/example.gz".
+
+  2. mksquashfs example image.sqsh -wildcards -e '... [Tt]est/*.gz'
+
+     Exclude files matching "*.gz" inside directories called "Test" or
+     "test" that occur anywhere in the source directories.
+
+  Again, using extended wildcards, negative matching is also possible.
+
+  3. mksquashfs example image.sqsh -wildcards -e '... !(*data*).gz'
+
+     Exclude all files matching "*.gz" anywhere in the source directories,
+     except those with "data" in the name.
+
+3.5.3 Exclude files summary
+---------------------------
+
+The -e and -ef exclude options are usefully used in archiving the entire
+filesystem, where it is wished to avoid archiving /proc, and the filesystem
+being generated, i.e.
+
+%mksquashfs / /tmp/root.sqsh -e proc /tmp/root.sqsh
+
+Multiple -ef options can be specified on the command line, and the -ef
+option can be used in conjuction with the -e option.
+
+3.6 Appending to squashfs filesystems
+-------------------------------------
+
+Running squashfs with the destination directory containing an existing
+filesystem will add the source items to the existing filesystem.  By default,
+the source items are added to the existing root directory.
+
+To make this clear... An existing filesystem "image" contains root entries
+"old1", and "old2".  Source directory "/home/phillip/test" contains  "file1",
+"file2" and "dir1".
+
+example 1:
+
+%mksquashfs /home/phillip/test image
+
+Will create a new "image" with root entries "old1", "old2", "file1", "file2" and
+"dir1"
+
+example 2:
+
+%mksquashfs /home/phillip/test image -keep-as-directory
+
+Will create a new "image" with root entries "old1", "old2", and "test".
+As shown in the previous section, for single source directories
+'-keep-as-directory' adds the source directory rather than the
+contents of the directory.
+
+example 3:
+
+%mksquashfs /home/phillip/test image -keep-as-directory -root-becomes
+original-root
+
+Will create a new "image" with root entries "original-root", and "test".  The
+'-root-becomes' option specifies that the original root becomes a subdirectory
+in the new root, with the specified name.
+
+The append option with file duplicate detection, means squashfs can be
+used as a simple versioning archiving filesystem. A squashfs filesystem can
+be created with for example the linux-2.4.19 source.  Appending the linux-2.4.20
+source will create a filesystem with the two source trees, but only the
+changed files will take extra room, the unchanged files will be detected as
+duplicates.
+
+3.7 Appending recovery file feature
+-----------------------------------
+
+Recovery files are created when appending to existing Squashfs
+filesystems.  This allows the original filesystem to be recovered
+if Mksquashfs aborts unexpectedly (i.e. power failure).
+
+The recovery files are called squashfs_recovery_xxx_yyy, where
+"xxx" is the name of the filesystem being appended to, and "yyy" is a
+number to guarantee filename uniqueness (the PID of the parent Mksquashfs
+process).
+
+Normally if Mksquashfs exits correctly the recovery file is deleted to
+avoid cluttering the filesystem.  If Mksquashfs aborts, the "-recover"
+option can be used to recover the filesystem, giving the previously
+created recovery file as a parameter, i.e.
+
+mksquashfs dummy image.sqsh -recover squashfs_recovery_image.sqsh_1234
+
+The writing of the recovery file can be disabled by specifying the
+"-no-recovery" option.
+
+3.8 Pseudo file support
+-----------------------
+
+Mksquashfs supports pseudo files, these allow fake files, directories, character
+and block devices to be specified and added to the Squashfs filesystem being
+built, rather than requiring them to be present in the source directories.
+This, for example, allows device nodes to be added to the filesystem without
+requiring root access.
+
+Mksquashfs 4.1 added support for "dynamic pseudo files" and a modify operation.
+Dynamic pseudo files allow files to be dynamically created when Mksquashfs
+is run, their contents being the result of running a command or piece of
+shell script.  The modifiy operation allows the mode/uid/gid of an existing
+file in the source filesystem to be modified.
+
+Two Mksquashfs options are supported, -p allows one pseudo file to be specified
+on the command line, and -pf allows a pseudo file to be specified containing a
+list of pseduo definitions, one per line.
+
+3.8.1. Creating a dynamic file
+------------------------------
+
+Pseudo definition
+
+Filename f mode uid gid command
+
+mode is the octal mode specifier, similar to that expected by chmod.
+
+uid and gid can be either specified as a decimal number, or by name.
+
+command can be an executable or a piece of shell script, and it is executed
+by running "/bin/sh -c command".   The stdout becomes the contents of
+"Filename".
+
+Examples:
+
+Running a basic command
+-----------------------
+
+/somedir/dmesg f 444 root root dmesg
+
+creates a file "/somedir/dmesg" containing the output from dmesg.
+
+Executing shell script
+----------------------
+
+RELEASE f 444 root root \
+		if [ ! -e /tmp/ver ]; then \
+			echo 0 > /tmp/ver; \
+		fi; \
+                ver=`cat /tmp/ver`; \
+                ver=$((ver +1)); \
+                echo $ver > /tmp/ver; \
+                echo -n `cat /tmp/release`; \
+                echo "-dev #"$ver `date` "Build host" `hostname`
+
+Creates a file RELEASE containing the release name, date, build host, and
+an incrementing version number.  The incrementing version is a side-effect
+of executing the shell script, and ensures every time Mksquashfs is run a
+new version number is used without requiring any other shell scripting.
+
+The above example also shows that commands can be split across multiple lines
+using "\".  Obviously as the script will be presented to the shell as a single
+line, a semicolon is need to separate individual shell commands within the
+shell script.
+
+Reading from a device (or fifo/named socket)
+--------------------------------------------
+
+input f 444 root root dd if=/dev/sda1 bs=1024 count=10
+
+Copies 10K from the device /dev/sda1 into the file input.  Ordinarily Mksquashfs
+given a device, fifo, or named socket will place that special file within the
+Squashfs filesystem, the above allows input from these special files to be
+captured and placed in the Squashfs filesystem.
+
+3.8.2. Creating a block or character device
+-------------------------------------------
+
+Pseudo definition
+
+Filename type mode uid gid major minor
+
+Where type is either
+	b - for block devices, and
+	c - for character devices
+
+mode is the octal mode specifier, similar to that expected by chmod.
+
+uid and gid can be either specified as a decimal number, or by name.
+
+For example:
+
+/dev/chr_dev c 666 root root 100 1
+/dev/blk_dev b 666 0 0 200 200
+
+creates a character device "/dev/chr_dev" with major:minor 100:1 and
+a block device "/dev/blk_dev" with major:minor 200:200, both with root
+uid/gid and a mode of rw-rw-rw.
+
+3.8.3. Creating a directory
+---------------------------
+
+Pseudo definition
+
+Filename d mode uid gid
+
+mode is the octal mode specifier, similar to that expected by chmod.
+
+uid and gid can be either specified as a decimal number, or by name.
+
+For example:
+
+/pseudo_dir d 666 root root
+
+creates a directory "/pseudo_dir" with root uid/gid and mode of rw-rw-rw.
+
+3.8.4. Modifying attributes of an existing file
+-----------------------------------------------
+
+Pseudo definition
+
+Filename m mode uid gid
+
+mode is the octal mode specifier, similar to that expected by chmod.
+
+uid and gid can be either specified as a decimal number, or by name.
+
+For example:
+
+dmesg m 666 root root
+
+Changes the attributes of the file "dmesg" in the filesystem to have
+root uid/gid and a mode of rw-rw-rw, overriding the attributes obtained
+from the source filesystem.
+
+3.9 Miscellaneous options
+-------------------------
+
+The -info option displays the files/directories as they are compressed and
+added to the filesystem.  The original uncompressed size of each file
+is printed, along with DUPLICATE if the file is a duplicate of a
+file in the filesystem.
+
+The -nopad option informs mksquashfs to not pad the filesystem to a 4K multiple.
+This is performed by default to enable the output filesystem file to be mounted
+by loopback, which requires files to be a 4K multiple.  If the filesystem is
+being written to a block device, or is to be stored in a bootimage, the extra
+pad bytes are not needed.
+
+4. UNSQUASHFS
+-------------
+
+Unsquashfs allows you to decompress and extract a Squashfs filesystem without
+mounting it.  It can extract the entire filesystem, or a specific
+file or directory.
+
+The Unsquashfs usage info is:
+
+SYNTAX: ./unsquashfs [options] filesystem [directories or files to extract]
+	-v[ersion]		print version, licence and copyright information
+	-d[est] <pathname>	unsquash to <pathname>, default "squashfs-root"
+	-n[o-progress]		don't display the progress bar
+	-no[-xattrs]		don't extract xattrs in file system
+	-x[attrs]		extract xattrs in file system (default)
+	-u[ser-xattrs]		only extract user xattrs in file system.
+				Enables extracting xattrs
+	-p[rocessors] <number>	use <number> processors.  By default will use
+				number of processors available
+	-i[nfo]			print files as they are unsquashed
+	-li[nfo]		print files as they are unsquashed with file
+				attributes (like ls -l output)
+	-l[s]			list filesystem, but don't unsquash
+	-ll[s]			list filesystem with file attributes (like
+				ls -l output), but don't unsquash
+	-f[orce]		if file already exists then overwrite
+	-s[tat]			display filesystem superblock information
+	-e[f] <extract file>	list of directories or files to extract.
+				One per line
+	-da[ta-queue] <size>	Set data queue to <size> Mbytes.  Default 256
+				Mbytes
+	-fr[ag-queue] <size>	Set fragment queue to <size> Mbytes.  Default
+				256 Mbytes
+	-r[egex]		treat extract names as POSIX regular expressions
+				rather than use the default shell wildcard
+				expansion (globbing)
+
+Decompressors available:
+	gzip
+	lzo
+	lz4
+	xz
+
+To extract a subset of the filesystem, the filenames or directory
+trees that are to be extracted can be specified on the command line.  The
+files/directories should be specified using the full path to the
+files/directories as they appear within the Squashfs filesystem.  The
+files/directories will also be extracted to those positions within the specified
+destination directory.
+
+The extract files can also be given in a file using the "-e[f]" option.
+
+Similarly to Mksquashfs, wildcard matching is performed on the extract
+files.  Wildcard matching is enabled by default.
+
+Examples:
+
+  1. unsquashfs image.sqsh 'test/*.gz'
+
+     Extract all files matching "*.gz" in the top level directory "test".
+
+  2. unsquashfs image.sqsh '[Tt]est/example*'
+
+     Extract all files beginning with "example" inside top level directories
+     called "Test" or "test".
+
+  Using extended wildcards, negative matching is also possible.
+
+  3. unsquashfs image.sqsh 'test/!(*data*).gz'
+
+     Extract all files matching "*.gz" in top level directory "test",
+     except those with "data" in the name.
+
+
+4.1 Unsquashfs options
+----------------------
+
+The "-ls" option can be used to list the contents of a filesystem without
+decompressing the filesystem data itself.  The "-lls" option is similar
+but it also displays file attributes (ls -l style output).
+
+The "-info" option forces Unsquashfs to print each file as it is decompressed.
+The -"linfo" is similar but it also displays file attributes.
+
+The "-dest" option specifies the directory that is used to decompress
+the filesystem data.  If this option is not given then the filesystem is
+decompressed to the directory "squashfs-root" in the current working directory.
+
+The "-force" option forces Unsquashfs to output to the destination
+directory even if files or directories already exist.  This allows you
+to update an existing directory tree, or to Unsquashfs to a partially
+filled directory.  Without the "-force" option, Unsquashfs will
+refuse to overwrite any existing files, or to create any directories if they
+already exist.  This is done to protect data in case of mistakes, and
+so the "-force" option should be used with caution.
+
+The "-stat" option displays filesystem superblock information.  This is
+useful to discover the filesystem version, byte ordering, whether it has a NFS
+export table, and what options were used to compress the filesystem, etc.
+
+Unsquashfs can decompress all Squashfs filesystem versions, 1.x, 2.x, 3.x and
+4.0 filesystems.
+
+5. FILESYSTEM LAYOUT
+--------------------
+
+A squashfs filesystem consists of a maximum of nine parts, packed together on a
+byte alignment:
+
+	 ---------------
+	|  superblock 	|
+	|---------------|
+	|  compression  |
+	|    options    |
+	|---------------|
+	|  datablocks   |
+	|  & fragments  |
+	|---------------|
+	|  inode table	|
+	|---------------|
+	|   directory	|
+	|     table     |
+	|---------------|
+	|   fragment	|
+	|    table      |
+	|---------------|
+	|    export     |
+	|    table      |
+	|---------------|
+	|    uid/gid	|
+	|  lookup table	|
+	|---------------|
+	|     xattr     |
+	|     table	|
+	 ---------------
+
+Compressed data blocks are written to the filesystem as files are read from
+the source directory, and checked for duplicates.  Once all file data has been
+written the completed super-block, compression options, inode, directory,
+fragment, export, uid/gid lookup and xattr tables are written.
+
+5.1 Compression options
+-----------------------
+
+Compressors can optionally support compression specific options (e.g.
+dictionary size).  If non-default compression options have been used, then
+these are stored here.
+
+5.2 Inodes
+----------
+
+Metadata (inodes and directories) are compressed in 8Kbyte blocks.  Each
+compressed block is prefixed by a two byte length, the top bit is set if the
+block is uncompressed.  A block will be uncompressed if the -noI option is set,
+or if the compressed block was larger than the uncompressed block.
+
+Inodes are packed into the metadata blocks, and are not aligned to block
+boundaries, therefore inodes overlap compressed blocks.  Inodes are identified
+by a 48-bit number which encodes the location of the compressed metadata block
+containing the inode, and the byte offset into that block where the inode is
+placed (<block, offset>).
+
+To maximise compression there are different inodes for each file type
+(regular file, directory, device, etc.), the inode contents and length
+varying with the type.
+
+To further maximise compression, two types of regular file inode and
+directory inode are defined: inodes optimised for frequently occurring
+regular files and directories, and extended types where extra
+information has to be stored.
+
+5.3 Directories
+---------------
+
+Like inodes, directories are packed into compressed metadata blocks, stored
+in a directory table.  Directories are accessed using the start address of
+the metablock containing the directory and the offset into the
+decompressed block (<block, offset>).
+
+Directories are organised in a slightly complex way, and are not simply
+a list of file names.  The organisation takes advantage of the
+fact that (in most cases) the inodes of the files will be in the same
+compressed metadata block, and therefore, can share the start block.
+Directories are therefore organised in a two level list, a directory
+header containing the shared start block value, and a sequence of directory
+entries, each of which share the shared start block.  A new directory header
+is written once/if the inode start block changes.  The directory
+header/directory entry list is repeated as many times as necessary.
+
+Directories are sorted, and can contain a directory index to speed up
+file lookup.  Directory indexes store one entry per metablock, each entry
+storing the index/filename mapping to the first directory header
+in each metadata block.  Directories are sorted in alphabetical order,
+and at lookup the index is scanned linearly looking for the first filename
+alphabetically larger than the filename being looked up.  At this point the
+location of the metadata block the filename is in has been found.
+The general idea of the index is ensure only one metadata block needs to be
+decompressed to do a lookup irrespective of the length of the directory.
+This scheme has the advantage that it doesn't require extra memory overhead
+and doesn't require much extra storage on disk.
+
+5.4 File data
+-------------
+
+Regular files consist of a sequence of contiguous compressed blocks, and/or a
+compressed fragment block (tail-end packed block).   The compressed size
+of each datablock is stored in a block list contained within the
+file inode.
+
+To speed up access to datablocks when reading 'large' files (256 Mbytes or
+larger), the code implements an index cache that caches the mapping from
+block index to datablock location on disk.
+
+The index cache allows Squashfs to handle large files (up to 1.75 TiB) while
+retaining a simple and space-efficient block list on disk.  The cache
+is split into slots, caching up to eight 224 GiB files (128 KiB blocks).
+Larger files use multiple slots, with 1.75 TiB files using all 8 slots.
+The index cache is designed to be memory efficient, and by default uses
+16 KiB.
+
+5.5 Fragment lookup table
+-------------------------
+
+Regular files can contain a fragment index which is mapped to a fragment
+location on disk and compressed size using a fragment lookup table.  This
+fragment lookup table is itself stored compressed into metadata blocks.
+A second index table is used to locate these.  This second index table for
+speed of access (and because it is small) is read at mount time and cached
+in memory.
+
+5.6 Uid/gid lookup table
+------------------------
+
+For space efficiency regular files store uid and gid indexes, which are
+converted to 32-bit uids/gids using an id look up table.  This table is
+stored compressed into metadata blocks.  A second index table is used to
+locate these.  This second index table for speed of access (and because it
+is small) is read at mount time and cached in memory.
+
+5.7 Export table
+----------------
+
+To enable Squashfs filesystems to be exportable (via NFS etc.) filesystems
+can optionally (disabled with the -no-exports Mksquashfs option) contain
+an inode number to inode disk location lookup table.  This is required to
+enable Squashfs to map inode numbers passed in filehandles to the inode
+location on disk, which is necessary when the export code reinstantiates
+expired/flushed inodes.
+
+This table is stored compressed into metadata blocks.  A second index table is
+used to locate these.  This second index table for speed of access (and because
+it is small) is read at mount time and cached in memory.
+
+5.8 Xattr table
+---------------
+
+The xattr table contains extended attributes for each inode.  The xattrs
+for each inode are stored in a list, each list entry containing a type,
+name and value field.  The type field encodes the xattr prefix
+("user.", "trusted." etc) and it also encodes how the name/value fields
+should be interpreted.  Currently the type indicates whether the value
+is stored inline (in which case the value field contains the xattr value),
+or if it is stored out of line (in which case the value field stores a
+reference to where the actual value is stored).  This allows large values
+to be stored out of line improving scanning and lookup performance and it
+also allows values to be de-duplicated, the value being stored once, and
+all other occurences holding an out of line reference to that value.
+
+The xattr lists are packed into compressed 8K metadata blocks.
+To reduce overhead in inodes, rather than storing the on-disk
+location of the xattr list inside each inode, a 32-bit xattr id
+is stored.  This xattr id is mapped into the location of the xattr
+list using a second xattr id lookup table.
+
+6. AUTHOR INFO
+--------------
+
+Squashfs was written by Phillip Lougher, email phillip@lougher.demon.co.uk,
+in Chepstow, Wales, UK.   If you like the program, or have any problems,
+then please email me, as it's nice to get feedback!
diff --git a/squashfs-tools/RELEASE-READMEs/DONATIONS b/squashfs-tools/RELEASE-READMEs/DONATIONS
new file mode 100644
index 0000000..b4653bb
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/DONATIONS
@@ -0,0 +1,15 @@
+Help sponsor Squashfs development!
+
+Maintaining and improving Squashfs is a lot of work, but Squashfs is one of
+the only widely used Linux file systems that has no company backing.  Squashfs
+development is funded soley by the author, partially supported by donations
+from companies and individuals that want to improve Squashfs for themselves
+and others.
+
+There's lots of exciting new improvements to Squashfs in the pipeline, and
+if your company is a serious user of Squashfs, please consider accelerating
+development of Squashfs by donating.
+
+Donatations can be made from the Squashfs sourceforge homepage, or if you
+prefer by contacting the author privately.
+
diff --git a/squashfs-tools/RELEASE-READMEs/PERFORMANCE.README b/squashfs-tools/RELEASE-READMEs/PERFORMANCE.README
new file mode 100644
index 0000000..efb98f2
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/PERFORMANCE.README
@@ -0,0 +1,171 @@
+GENERAL INFORMATION ON PERFORMANCE TESTS
+----------------------------------------
+
+The following performance tests were based on two file sets: the
+liveCD filesystem from the Ubuntu liveCD (Warty release), and the
+liveCD filesystem from the Damn Small Linux liveCD (release 0.8.4).
+The Ubuntu liveCD filesystem was used to test filesystem performance
+from CDROM and hard disk for Zisofs, Cloop, Squashfs 2.0 and Squashfs2.1.
+CRAMFS filesystem performance could not be tested for this filesystem
+bacause it exceeds the maximum supported size of CRAMFS.  To test
+CRAMFS performance against Squashfs, the liveCD filesystem from
+Damn Small Linux was used.
+
+NOTE: the usual warnings apply to these results, they are provided for
+illustrative purposes only, and due to different hardware and/or file data, you
+may obtain different results.  As such the results are provided "as is" without
+any warranty (either express or implied) and you assume all risks as to their
+quality and accuracy.
+
+1. Ubuntu liveCD performance tests
+
+   ext3 uncompressed size      1.4 GB
+   Zisofs compressed size      589.81 MB
+   Cloop compressed size       471.89 MB
+   Squashfs2.0 compressed size 448.58 MB
+   Squashfs2.1 compressed size 448.58 MB
+
+1.1 Performance tests from CDROM
+
+1.1.1 Directory Lookup performance
+
+  Time taken to perform "ls -lR --color=alawys | cat > /dev/null" on filesystem
+  mounted from CDROM
+
+  Zisofs       49.88 seconds (User 2.60 secs, Sys 11.19 secs)
+  Cloop        20.80 seconds (User 2.71 secs, Sys 13.50 secs)
+  Squashfs2.0  16.56 seconds (User 2.42 secs, Sys 10.37 secs)
+  Squashfs2.1  10.14 seconds (User 2.48 secs, Sys 4.44 secs)
+
+1.1.2 Sequential I/O performance
+
+  Time taken to perform "tar cf - | cat > /dev/null" on filesystem mounted
+  from CDROM
+
+  Zisofs       27 minutes 28.54 seconds (User 3.00 secs, Sys 1 min 4.80 secs)
+  Cloop        5 minutes 55.72 seconds (User 2.90 secs, Sys 3 min 37.90 secs)
+  Squashfs2.0  5 minutes 20.87 seconds (User 2.33 secs, Sys 56.98 secs)
+  Squashfs2.1  5 minutes 15.46 seconds (user 2.28 secs, Sys 51.12 secs)
+
+1.1.3 Random I/O performance
+
+  Random access pattern generated by "find /mnt -type f -printf "%s %p\n" | sort
+  -g | awk '{ printf $2 }' > /tmp/sort
+
+  Time taken to perform "cpio -o --quiet -H newc < /tmp/sort > /dev/null"
+  on filesystem mounted from CDROM
+
+  Zisofs       101 minutes 29.65 seconds (User 5.33 secs, Sys  1 min 17.20 secs)
+  Cloop        35 minutes 27.51 seconds (user 5.93 secs, Sys 4 mins 30.23 secs)
+  Squashfs2.0  21 minutes 53.05 seconds (user 5.71 secs, Sys 2 mins 36.59 secs)
+  Squashfs2.1  21 minutes 46.99 seconds (User 5.80 secs, Sys 2 mins 31.88 secs)
+
+
+1.2 Performance tests from Hard disk
+
+1.2.1 Directory Lookup performance
+
+  Time taken to perform "ls -lR --color=alawys | cat > /dev/null" on filesystem
+  mounted from Hard disk 
+
+  Zisofs       17.29 seconds (User 2.62 secs, Sys 11.08 secs)
+  Cloop        16.46 seconds (User 2.63 secs, Sys 13.41 secs)
+  Squashfs2.0  13.75 seconds (User 2.44 secs, Sys 11.00 secs)
+  Squashfs2.1  6.94 seconds (User 2.44 secs, Sys 4.48 secs)
+
+1.2.2 Sequential I/O performance
+
+  Time taken to perform "tar cf - | cat > /dev/null" on filesystem mounted
+  from Hard disk
+
+  Zisofs       1 minute 21.47 seconds (User 2.73 secs, Sys 54.44 secs)
+  Cloop        1 minute 34.06 seconds (user 2.85 secs, Sys 1 min 12.13 secs)
+  Squashfs2.0  1 minute 21.22 seconds (User 2.42 secs, Sys 56.21 secs)
+  Squashfs2.1  1 minute 15.46 seconds (User 2.36 secs, Sys 49.78 secs)
+
+1.2.3 Random I/O performance
+
+  Random access pattern generated by "find /mnt -type f -printf "%s %p\n" | sort
+  -g | awk '{ printf $2 }' > /tmp/sort
+
+  Time taken to perform "cpio -o --quiet -H newc < /tmp/sort > /dev/null"
+  on filesystem mounted from Hard disk
+
+  Zisofs       11 minutes 13.64 seconds (User 5.08 secs, Sys 52.62 secs)
+  Cloop        5 minutes 37.93 seconds (user 6 secs, Sys 2 mins 22.38 secs)
+  Squashfs2.0  5 minutes 7.11 seconds (user 5.63 secs, Sys 2 mins 35.23 secs)
+  Squashfs2.1  5 minutes 1.87 seconds (User 5.71 secs, Sys 2 mins 29.98 secs)
+
+
+2. Damn Small Linux liveCD performance tests
+
+   ext3 uncompressed size      126 MB
+   CRAMFS compressed size      52.19 MB
+   Squashfs2.0 compressed size 46.52 MB
+   Squashfs2.1 compressed size 46.52 MB
+
+2.1 Performance tests from CDROM
+
+2.1.1 Directory Lookup performance
+
+  Time taken to perform "ls -lR --color=alawys | cat > /dev/null" on filesystem
+  mounted from CDROM
+
+  CRAMFS       10.85 seconds (User 0.39 secs, Sys 0.98 secs)
+  Squashfs2.0  2.97 seconds (User 0.36 secs, Sys 2.15 secs)
+  Squashfs2.1  2.43 seconds (User 0.40 secs, Sys 1.42 secs)
+
+2.1.2 Sequential I/O performance
+
+  Time taken to perform "tar cf - | cat > /dev/null" on filesystem mounted
+  from CDROM
+
+  CRAMFS       55.38 seconds (User 0.34 secs, Sys 6.98 secs)
+  Squashfs2.0  35.99 seconds (User 0.30 secs, Sys 6.35 secs)
+  Squashfs2.1  33.83 seconds (User 0.26 secs, Sys 5.56 secs)
+
+2.1.3 Random I/O performance
+
+  Random access pattern generated by "find /mnt -type f -printf "%s %p\n" | sort
+  -g | awk '{ printf $2 }' > /tmp/sort
+
+  Time taken to perform "cpio -o --quiet -H newc < /tmp/sort > /dev/null"
+  on filesystem mounted from CDROM
+
+
+  CRAMFS        3 minutes 1.68 seconds (User 0.54 secs, Sys 9.51 secs)
+  Squashfs2.0   1 minute 39.45 seconds (User 0.57 secs, Sys 13.14 secs)
+  Squashfs2.1   1 minute 38.41 seconds (User 0.58 secs, Sys 13.08 secs)
+
+2.2 Performance tests from Hard disk
+
+2.2.1 Directory Lookup performance
+
+  Time taken to perform "ls -lR --color=alawys | cat > /dev/null" on filesystem
+  mounted from Hard disk
+
+  CRAMFS       1.77 seconds (User 0.53 secs, Sys 1.21 secs)
+  Squashfs2.0  2.67 seconds (User 0.41 secs, Sys 2.25 secs)
+  Squashfs2.1  1.87 seconds (User 0.41 secs, Sys 1.46 secs)
+
+2.2.2 Sequential I/O performance
+
+  Time taken to perform "tar cf - | cat > /dev/null" on filesystem mounted
+  from Hard disk 
+
+  CRAMFS       6.80 seconds (User 0.36 secs, Sys 6.02 secs)
+  Squashfs2.0  7.23 seconds (User 0.29 secs, Sys 6.62 secs)
+  Squashfs2.1  6.53 seconds (User 0.31 secs, Sys 5.82 secs)
+
+2.2.3 Random I/O performance
+
+  Random access pattern generated by "find /mnt -type f -printf "%s %p\n" | sort
+  -g | awk '{ printf $2 }' > /tmp/sort
+
+  Time taken to perform "cpio -o --quiet -H newc < /tmp/sort > /dev/null"
+  on filesystem mounted from Hard disk 
+
+
+  CRAMFS       28.55 seconds (User 0.49 secs, Sys 6.49 secs)
+  Squashfs2.0  25.44 seconds (User 0.58 secs, Sys 13.17 secs)
+  Squashfs2.1  24.72 seconds (User 0.56 secs, Sys 13.15 secs)
diff --git a/squashfs-tools/RELEASE-READMEs/README-2.0 b/squashfs-tools/RELEASE-READMEs/README-2.0
new file mode 100644
index 0000000..41931d8
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-2.0
@@ -0,0 +1,161 @@
+NOTE:  This the original README for version 2.0.  It is retained as it
+contains information about the fragment design.  A description of the new 2.0
+mksquashfs options has been added to the main README file, and that
+file should now be consulted for these.
+
+	SQUASHFS 2.0 - A squashed read-only filesystem for Linux
+
+	Copyright 2004 Phillip Lougher (plougher@users.sourceforge.net)
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to the final release of Squashfs version 2.0!  A lot of changes to the
+filesystem have been made under the bonnet (hood).  Squashfs 2.0 uses fragment
+blocks and larger blocks (64K) to improve compression ratio by about 5 - 20%
+over Squashfs 1.0 depending on the files being compressed.  Using fragment
+blocks allows Squashfs 2.0 to achieve better compression than cloop and similar
+compression to tgz files while retaining the I/O efficiency of a compressed
+filesystem.
+
+Detailed changes:
+
+1. Squashfs 2.0 has added the concept of fragment blocks (see later discussion).
+   Files smaller than the file block size (64K in Squashfs 2.0) and optionally
+   the remainder of files that do not fit fully into a block (i.e. the last 32K
+   in a 96K file) are packed into shared fragments and compressed together.
+   This achieves on average 5 - 20% better compression than Squashfs 1.x.
+
+2. The maximum block size has been increased to 64K.
+
+3. The maximum number of UIDs has been increased to 256 (from 48 in 1.x).
+
+4. The maximum number of GIDs has been increased to 256 (from 15 in 1.x).
+
+5. New mksquashfs -all-root, -root-owned, -force-uid, and -force-gid
+   options.  These allow the uids/gids of files in the generated
+   filesystem to be specified, overriding the uids/gids in the
+   source filesystem.
+
+6. Initrds are now supported for kernels 2.6.x.
+
+7. Removal of sleep_on() function call in 2.6.x patch, to allow Squashfs
+   to work on the Fedora rc2 kernel.
+
+8. AMD64, check-data and gid bug fixes.
+
+9. Numerous small bug fixes have been made.
+
+10. New patch for Linux 2.6.7.
+
+
+New Squashfs 2.0 options
+------------------------
+
+-noF or -noFragmentCompression
+
+	Do not compress the fragments.  Added for compatibility with noI and
+	noD, probably not that useful.
+
+-no-fragments
+
+	Do not use fragment blocks, and rather generate a filesystem
+	similar to a Squashfs 1.x filesystem.  It will of course still
+	be a Squashfs 2.0 filesystem but without fragments, and so
+	it won't be mountable on a Squashfs 1.x system.
+
+-always-use-fragments
+
+	By default only small files less than the block size are packed into
+	fragment blocks.  The ends of files which do not fit fully into a block,
+	are NOT by default packed into fragments.  To illustrate this, a
+	100K file has an initial 64K block and a 36K remainder.  This
+	36K remainder is not packed into a fragment by default.  This is
+	because to do so leads to a 10 - 20% drop in sequential I/O
+	performance, as a disk head seek is needed to seek to the initial
+	file data and another disk seek is need to seek to the fragment
+	block.
+
+	Specify this option if you want file remainders to be packed into
+	fragment blocks.  Doing so may increase the compression obtained
+	BUT at the expense of I/O speed.
+
+-no-duplicates
+
+	Do not detect duplicate files.
+
+-all-root
+-root-owned
+
+	These options (both do exactly the same thing), force all file
+	uids/gids in the generated Squashfs filesystem to be root.
+	This allows root owned filesystems to be built without root access
+	on the host machine.
+
+-force-uid uid
+
+	This option forces all files in the generated Squashfs filesystem to
+	be owned by the specified uid.  The uid can be specified either by
+	name (i.e. "root") or by number.
+
+-force-gid gid
+
+	This option forces all files in the generated Squashfs filesystem to
+	be group owned by the specified gid.  The gid can be specified either by
+	name (i.e. "root") or by number.
+
+
+Compression improvements example
+--------------------------------
+
+The following is the compression results obtained compressing the 2.6.6
+linux kernel source using CRAMFS, Cloop (with iso filesystem), Squashfs 1.3 and
+Squashfs 2.0 (results generated using big-endian filesystems).
+
+In decreasing order of size:
+
+	CRAMFS		62791680 bytes (59.9M)
+	Squashfs 1.x	51351552 bytes (48.9M)
+	Cloop		46118681 bytes (44.0M)
+	Squashfs 2.0	45604854 bytes (43.5M)
+
+
+The Squashfs 1.x filesystem is 12.6% larger than the new 2.0 filesystem.
+The cloop filesystem is 1.1% larger than the Squashfs 2.0 filesystem.
+
+
+Fragment blocks in Squashfs 2.0
+-------------------------------
+
+Squashfs like all other compressed filesystems compresses files individually
+on a block by block basis.  This is performed to allow mounting and
+de-compression of files on a block by block basis without requiring the entire
+filesystem to be decompressed.  This is in contrast to data-based compression
+schemes which compress without understanding the underlying filesystem (i.e.
+cloop and tgz files) and which, therefore, do not compress files individually.
+Each approach has advantages and disadvantages, data-based systems have better
+compression because compression is always performed at the maximum block size
+(64K in cloop) irrespective of the size of each file (which could be less than
+the block size).  Compressed filesystems tend to be faster at I/O because
+they understand the filesystem and therefore employ better caching stategies
+and read less un-needed data from the filesystem.
+
+Fragment blocks in Squashfs 2.0 solves this problem by packing files (and
+optionally the ends of files) which are smaller than the block size into
+shared blocks, which are compressed together.  For example five files each of
+10K will be packed into one shared fragment of 50K and compressed together,
+rather than being compressed in five 10K blocks.
+
+This scheme produces a hybrid filesystem, retaining the I/O efficiency
+of a compressed filesystem, while obtaining the compression efficiency
+of data-based schemes by compressing small files together.
+
+
+Squashfs 1.x and Squashfs 2.0 compatibility
+-------------------------------------------
+
+Appending to Squashfs 1.x filesystems is not supported.  If you wish to append
+to 1.x filesystems, then either use the original mksquashfs, or convert them
+to Squashfs 2.0 by mounting the filesystem and running the 2.0 mksquashfs
+on the mounted filesystem.
+
+Mounting Squashfs 1.x filesystems IS supported by the 2.0 kernel patch.
diff --git a/squashfs-tools/RELEASE-READMEs/README-2.0-AMD64 b/squashfs-tools/RELEASE-READMEs/README-2.0-AMD64
new file mode 100644
index 0000000..5569617
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-2.0-AMD64
@@ -0,0 +1,18 @@
+Information for amd64 users
+---------------------------
+
+All previous releases of Squashfs (2.0-alpha and older) generate incorrect
+filesystems on amd64 machines.  These filesystems work correctly on amd64
+machines, but cannot be mounted on non-amd64 machines.  Likewise, filesystems
+generated on non amd64 machines could not be mounted on amd64 machines.
+This bug was caused by the different size of the "time_t" definition used in
+SquashFS filesystem structures.
+
+This bug is now fixed in this release.  However, all amd64 filesystems
+generated by previous releases will not be mountable on amd64 machines
+with this release.  If you have pre-existing amd64 generated filesystems,
+it is important that you recreate the filesystem.  This can be performed
+by mounting the filesystem using a kernel with the original patch
+(i.e. a 2.0-alpha or older patch) and running the SquashFS 2.0
+(i.e. this release) mksquashfs tool to create a new SquashFS filesystem.
+
diff --git a/squashfs-tools/RELEASE-READMEs/README-2.1 b/squashfs-tools/RELEASE-READMEs/README-2.1
new file mode 100644
index 0000000..e70167e
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-2.1
@@ -0,0 +1,87 @@
+	SQUASHFS 2.1 - A squashed read-only filesystem for Linux
+
+	Copyright 2004 Phillip Lougher (plougher@users.sourceforge.net)
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to Squashfs version 2.1-r2.  Squashfs 2.1 introduces indexed
+directories which considerably speed up directory lookup (ls, find etc.) for
+directories which are greater than 8K in size.  All directories are now also
+sorted alphabetically which further speeds up directory lookup.  Many smaller
+improvements have also been made to this release, please see the CHANGES file
+entry for detailed changes.
+
+1. DIRECTORY SPEED IMPROVEMENT EXAMPLES
+---------------------------------------
+
+To give an indication of the directory speed improvements a number of test
+results are shown here.  There is in addition a new PERFORMANCE.README file
+which gives details of I/O and lookup performance for Squashfs 2.1 against
+the Zisofs, Cloop and CRAMFS filesystems.
+
+example 1:
+
+Filesystems generated from a single directory of 72,784 files (2.6 MB
+directory size).  Each file is 10 bytes in size (the test is directory
+lookup and so the file size isn't an issue).  The ext3 uncompressed
+directory size is 288 MB (presumably because of one file per block).
+
+Zisofs compressed size        153.50 MB
+Cloop (isofs) compressed size 1.74 MB
+Squashfs2.1 compressed size   612 KB (0.60 MB)
+
+Time taken to perform "ls -lR --color=always | cat > /dev/null" on
+filesystems mounted on hard disk.
+
+Zisofs       35 minutes 7.895 seconds (User 7.868 secs, Sys 34 mins 5.621 secs)
+Cloop        35 minutes 12.765 seconds (User 7.771 secs, Sys 34 mins 3.869 secs)
+Squashfs2.1  19 seconds (User 5.119 secs, Sys 14.547 secs)
+
+example 2:
+
+Filesystems were generated from the Ubuntu Warty livecd (original uncompressed
+size on ext3 is 1.4 GB).
+
+Zisofs compressed size        589.81 MB
+Cloop (isofs) compressed size 471.19 MB
+Squashfs2.0 compressed size   448.58 MB
+Squashfs2.1 compressed size   448.58 MB
+
+Time taken to perform "ls -lR --color=always | cat > /dev/null" on
+filesystems mounted on hard disk.
+
+Zisofs        49.875 seconds (User time 2.589 secs, Sys 11.194 secs)
+Cloop         20.797 seconds (User time 2.706 secs, Sys 13.496 secs)
+Squashfs2.0   16.556 seconds (User time 2.424 secs, Sys 10.371 secs)
+Squashfs2.1   10.143 seconds (User time 2.475 secs, Sys 4.440 secs)
+
+
+NOTE: the usual warnings apply to these results, they are provided for
+illustrative purposes only, and due to different hardware and/or file data, you
+may obtain different results.  As such the results are provided "as is" without
+any warranty (either express or implied) and you assume all risks as to their
+quality and accuracy.
+
+2. NEW MKSQUASHFS OPTIONS
+-------------------------
+
+There is only one extra option "-2.0".  This tells mksquashfs to generate
+a filesystem which is mountable with Squashfs version 2.0.
+
+3. APPENDING AND MOUNTING SQUASHFS 2.0 FILESYSTEMS
+--------------------------------------------------
+
+Mounting 2.0 filesystems is supported by Squashfs 2.1.  In addition
+mksquashfs v2.1 can append to 2.0 filesystems, although the generated
+filesystem will still be a 2.0 filesystem.
+
+4. DONATIONS
+------------
+
+If you find Squashfs useful then please consider making a donation,
+particularly if you use Squashfs in a commercial product.  Please consider
+giving something back especially if you're making money from it.
+
+Off the Squashfs subject somewhat I'm currently looking for another
+job doing Linux kernel or filesystems work.  If you know of any such
+work that can be performed from the UK then please get in touch.  Thanks.
diff --git a/squashfs-tools/RELEASE-READMEs/README-3.0 b/squashfs-tools/RELEASE-READMEs/README-3.0
new file mode 100644
index 0000000..fd16dd3
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-3.0
@@ -0,0 +1,60 @@
+	SQUASHFS 3.0 - A squashed read-only filesystem for Linux
+
+	Copyright 2002-2006 Phillip Lougher <phillip@lougher.org.uk>
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to the first release of Squashfs version 3.0.  Squashfs 3.0 has the
+the following improvements to 2.x.
+
+	1. Filesystems are no longer limited to 4 GB.  In
+	   theory 2^64 or 4 exabytes is now supported.
+
+	2. Files are no longer limited to 4 GB.  In theory the maximum
+	   file size is 4 exabytes.
+
+	3. Metadata (inode table and directory tables) are no longer
+	   restricted to 16 Mbytes.
+
+	4. Hardlinks are now suppported.
+
+	5. Nlink counts are now supported.
+
+	6. Readdir now returns '.' and '..' entries.
+
+	7. Special support for files larger than 256 MB has been added to
+	   the Squashfs kernel code for faster read access.
+
+	8. Inode numbers are now stored within the inode rather than being
+	   computed from inode location on disk (this is not so much an
+	   improvement, but a change forced by the previously listed
+	   improvements).
+
+There is a new Unsquashfs utility (in squashfs-tools) than can be used to
+decompress a filesystem without mounting it.
+
+Squashfs 3.0 supports 2.x filesystems.  Support for 1.x filesystems
+will be added in the future.
+
+1. UNSQUASHFS
+-------------
+
+Unsquashfs has the following options:
+
+SYNTAX: unsquashfs [-ls | -dest] filesystem
+	-version		print version, licence and copyright information
+	-info			print files as they are unsquashed
+	-ls			list filesystem only
+	-dest <pathname>	unsquash to <pathname>, default "squashfs-root"
+
+The "-ls" option can be used to list the contents of a filesystem without
+decompressing the filesystem data itself.
+
+The "-info" option forces Unsquashfs to print each file as it is decompressed.
+
+The "-dest" option specifies the directory that is used to decompress
+the filesystem data.  If this option is not given then the filesystem is
+decompressed to the directory "squashfs-root" in the current working directory.
+
+Unsquashfs can decompress 3.0 filesystems.  Support for 2.x and 1.x
+filesystems will be added in the future.
diff --git a/squashfs-tools/RELEASE-READMEs/README-3.1 b/squashfs-tools/RELEASE-READMEs/README-3.1
new file mode 100755
index 0000000..0e1ee79
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-3.1
@@ -0,0 +1,158 @@
+	SQUASHFS 3.1 - A squashed read-only filesystem for Linux
+
+	Copyright 2002-2006 Phillip Lougher <phillip@lougher.org.uk>
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to Squashfs version 3.1-r2.  Squashfs 3.1 has major improvements to
+the Squashfs tools (Mksquashfs and Unsquashfs), some major bug fixes, new
+kernel patches, and various other smaller improvements and bug fixes.
+Please see the CHANGES file for a detailed list.
+
+1. MKSQUASHFS
+-------------
+
+Mksquashfs has been rewritten and it is now multi-threaded.  It offers
+the following improvements:
+
+1. Parallel compression.  By default as many compression and fragment
+compression threads are created as there are available processors.
+This significantly speeds up performance on SMP systems.
+
+2. File input and filesystem output is peformed in parallel on separate
+threads to maximise I/O performance.  Even on single processor systems
+this speeds up performance by at least 10%.
+
+3. Appending has been significantly improved, and files within the
+filesystem being appended to are no longer scanned and checksummed.  This
+significantly improves append time for large filesystems.
+
+4. File duplicate checking has been optimised, and split into two separate
+phases.  Only files which are considered possible duplicates after the
+first phase are checksummed and cached in memory.
+
+5. The use of swap memory was found to significantly impact performance. The
+amount of memory used to cache the file is now a command line option, by default
+this is 512 Mbytes.
+
+1.1 NEW COMMAND LINE OPTIONS
+----------------------------
+
+The new Mksquashfs program has a couple of extra command line options
+which can be used to control the new features:
+
+-processors <processors>
+
+This specifies the number of processors used by Mksquashfs.
+By default this is the number of available processors.
+
+-read_queue <size in Mbytes>
+
+This specifies the size of the file input queue used by the reader thread.
+This defaults to 64 Mbytes.
+
+-write_queue <size in Mbytes>
+
+This specifies the size of the filesystem output queue used by the
+writer thread.  It also specifies the maximum cache used in file
+duplicate detection (the output queue is shared between these tasks).
+This defaults to 512 Mbytes.
+
+1.2 PERFORMANCE RESULTS
+-----------------------
+
+The following results give an indication of the speed improvements.  Two
+example filesystems were tested, a liveCD filesystem (about 1.8 Gbytes
+uncompressed), and my home directory consisting largely of text files
+(about 1.3 Gbytes uncompressed).  Tests were run on a single core
+and a dual core system.
+
+Dual Core (AMDx2 3800+) system:
+Source directories on ext3.
+
+LiveCD, old mksquashfs:
+
+real    11m48.401s
+user    9m27.056s
+sys     0m15.281s
+
+LiveCD, new par_mksquashfs:
+
+real    4m8.736s
+user    7m11.771s
+sys     0m27.749s
+
+"Home", old mksquashfs:
+
+real    4m34.360s
+user    3m54.007s
+sys     0m32.155s
+
+"Home", new par_mksquashfs:
+
+real    1m27.381s
+user    2m7.304s
+sys     0m17.234s
+
+Single Core PowerBook (PowerPC G4 1.5 GHz Ubuntu Linux)
+Source directories on ext3.
+
+LiveCD, old mksquashs:
+
+real    11m38.472s
+user    9m6.137s
+sys     0m23.799s
+
+LiveCD,  par_mksquashfs:
+
+real    10m5.572s
+user    8m59.921s
+sys     0m16.145s
+
+"Home", old mksquashfs:
+
+real    3m42.298s
+user    2m49.478s
+sys     0m13.675s
+
+"Home", new par_mksquashfs:
+
+real    3m9.178s
+user    2m50.699s
+sys     0m9.069s
+
+I'll be interested in any performance results obtained, especially from SMP
+machines larger than my dual-core AMD box, as this will give an indication of
+the scalability of the code.  Obviously, I'm also interested in any problems,
+deadlocks, low performance etc.
+
+2. UNSQUASHFS
+-------------
+
+Unsquashfs now allows you to specify the filename or directory that is to be
+extracted from the Squashfs filesystem, rather than always extracting the
+entire filesystem.  It also has a new "-force" option, and all options can be
+specified in a short form (-i rather than -info).
+
+The Unsquashfs usage info is now:
+
+SYNTAX: ./unsquashfs [options] filesystem [directory or file to extract]
+	-v[ersion]		print version, licence and copyright information
+	-i[nfo]			print files as they are unsquashed
+	-l[s]			list filesystem only
+	-d[est] <pathname>	unsquash to <pathname>, default "squashfs-root"
+	-f[orce]		if file already exists then overwrite
+
+To extract a subset of the filesystem, the filename or directory
+tree that is to be extracted can now be specified on the command line.  The
+file/directory should be specified using the full path to the file/directory
+as it appears within the Squashfs filesystem.  The file/directory will also be
+extracted to that position within the specified destination directory.
+
+The new "-force" option forces Unsquashfs to output to the destination
+directory even if files or directories already exist.  This allows you
+to update an existing directory tree, or to Unsquashfs to a partially
+filled directory.  Without the "-force" option, Unsquashfs will
+refuse to overwrite any existing files, or to create any directories if they
+already exist.  This is done to protect data in case of mistakes, and
+so the "-force" option should be used with caution.
diff --git a/squashfs-tools/RELEASE-READMEs/README-3.2 b/squashfs-tools/RELEASE-READMEs/README-3.2
new file mode 100755
index 0000000..e38286f
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-3.2
@@ -0,0 +1,33 @@
+	SQUASHFS 3.2 - A squashed read-only filesystem for Linux
+
+	Copyright 2002-2007 Phillip Lougher <phillip@lougher.org.uk>
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to Squashfs version 3.2.  Squashfs 3.2 has support for NFS exporting,
+some improvements to the Squashfs tools (Mksquashfs and Unsquashfs), some
+major bug fixes, new kernel patches, and various other smaller improvements
+and bug fixes.  Please see the CHANGES file for a detailed list.
+
+1. MKSQUASHFS
+-------------
+
+New command line options:
+
+-no-exports
+
+	Squashfs now supports NFS exports.  By default the additional
+	information necessary is added to the filesystem by Mksquashfs.  If you
+	do not wish this extra information, then this option can be specified.
+	This will save a couple of bytes per file, and the filesystem
+	will be identical to Squashfs 3.1.
+
+-no-progress
+
+	Mksquashfs by default now displays a progress bar. This option disables
+	it.
+
+2. UNSQUASHFS
+-------------
+
+	Unsquashfs now supports Squashfs 2.x filesystems.
diff --git a/squashfs-tools/RELEASE-READMEs/README-3.3 b/squashfs-tools/RELEASE-READMEs/README-3.3
new file mode 100644
index 0000000..a38a39e
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-3.3
@@ -0,0 +1,169 @@
+	SQUASHFS 3.3 - A squashed read-only filesystem for Linux
+
+	Copyright 2002-2007 Phillip Lougher <phillip@lougher.demon.co.uk>
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to another release of Squashfs.  This is the 22nd release in just
+over five years of work.  Squashfs 3.3 has lots of nice improvements,
+both to the filesystem itself (bigger blocks, and sparse files), but
+also to the Squashfs-tools Mksquashfs and Unsquashfs.  As usual the
+CHANGES file has a detailed list of all the improvements.
+
+Following is a description of the changes to  the Squashfs tools, usage
+guides to the new options, and a summary of the new options.
+
+1. MKSQUASHFS - EXTENDED EXCLUDE FILE HANDLING
+----------------------------------------------
+
+1. Extended wildcard pattern matching now supported in exclude files
+
+  Enabled by specifying -wildcards option
+
+  Supports both anchored and non-anchored exclude files.
+
+1.1 Anchored excludes
+
+  Similar to existing exclude files except with wildcards.  Exclude
+  file matches from root of source directories.
+
+  Examples:
+
+  1. mksquashfs example image.sqsh -wildcards -e 'test/*.gz'
+
+     Exclude all files matching "*.gz" in the top level directory "test".
+
+  2. mksquashfs example image.sqsh -wildcards -e '*/[Tt]est/example*'
+
+     Exclude all files beginning with "example" inside directories called
+     "Test" or "test", that occur inside any top level directory.
+
+  Using extended wildcards, negative matching is also possible.
+
+  3. mksquashfs example image.sqsh -wildcards -e 'test/!(*data*).gz'
+
+     Exclude all files matching "*.gz" in top level directory "test",
+     except those with "data" in the name.
+
+1.2 Non-anchored excludes
+
+  By default excludes match from the top level directory, but it is
+  often useful to exclude a file matching anywhere in the source directories.
+  For this non-anchored excludes can be used, specified by pre-fixing the
+  exclude with "...".
+
+  Examples:
+
+  1. mksquashfs example image.sqsh -wildcards -e '... *.gz'
+
+     Exclude files matching "*.gz" anywhere in the source directories.
+     For example this will match "example.gz", "test/example.gz", and
+     "test/test/example.gz".
+
+  2. mksquashfs example image.sqsh -wildcards -e '... [Tt]est/*.gz'
+
+     Exclude files matching "*.gz" inside directories called "Test" or
+     "test" that occur anywhere in the source directories.
+
+  Again, using extended wildcards, negative matching is also possible.
+
+  3. mksquashfs example image.sqsh -wildcards -e '... !(*data*).gz'
+
+     Exclude all files matching "*.gz" anywhere in the source directories,
+     except those with "data" in the name.
+
+2. Regular expression pattern matching now supported in exclude files
+
+  Enabled by specifying -regex option.  Identical behaviour to wild
+card pattern matching, except patterns are considered to be regular
+expressions.
+
+  Supports both anchored and non-anchored exclude files.
+
+
+2. MKSQUASHFS - NEW RECOVERY FILE FEATURE
+-----------------------------------------
+
+Recovery files are now created when appending to existing Squashfs
+filesystems.  This allows the original filesystem to be recovered
+if Mksquashfs aborts unexpectedly (i.e. power failure).
+
+The recovery files are called squashfs_recovery_xxx_yyy, where
+"xxx" is the name of the filesystem being appended to, and "yyy" is a
+number to guarantee filename uniqueness (the PID of the parent Mksquashfs
+process).
+
+Normally if Mksquashfs exits correctly the recovery file is deleted to
+avoid cluttering the filesystem.  If Mksquashfs aborts, the "-recover"
+option can be used to recover the filesystem, giving the previously
+created recovery file as a parameter, i.e.
+
+mksquashfs dummy image.sqsh -recover squashfs_recovery_image.sqsh_1234
+
+The writing of the recovery file can be disabled by specifying the
+"-no-recovery" option.
+
+
+3. UNSQUASHFS - EXTENDED EXTRACT FILE HANDLING
+----------------------------------------------
+
+1. Multiple extract files can now be specified on the command line, and the
+files/directories to be extracted can now also be given in a file.
+
+To specify a file containing the extract files use the "-e[f]" option.
+
+2. Extended wildcard pattern matching now supported in extract files
+
+  Enabled by default.  Similar to existing extract files except with
+wildcards.
+
+  Examples:
+
+  1. unsquashfs image.sqsh 'test/*.gz'
+
+     Extract all files matching "*.gz" in the top level directory "test".
+
+  2. unsquashfs image.sqsh '[Tt]est/example*'
+
+     Extract all files beginning with "example" inside top level directories
+     called "Test" or "test".
+
+  Using extended wildcards, negative matching is also possible.
+
+  3. unsquashfs image.sqsh 'test/!(*data*).gz'
+
+     Extract all files matching "*.gz" in top level directory "test",
+     except those with "data" in the name.
+
+3. Regular expression pattern matching now supported in extract files
+
+  Enabled by specifying -r[egex] option.  Identical behaviour to wild
+card pattern matching, except patterns are considered to be regular
+expressions.
+
+4. UNSQUASHFS - EXTENDED FILENAME PRINTING
+------------------------------------------
+
+Filename printing has been enhanced and Unquashfs can now display filenames
+with file attributes ('ls -l' style output).
+
+New options:
+
+  -ll[s]
+
+   list filesystem with file attributes, but don't unsquash
+
+  -li[nfo]
+
+   print files as they are unsquashed with file attributes
+
+
+5. UNSQUASHFS - MISCELLANEOUS OPTIONS
+-------------------------------------
+
+   -s[tat]
+
+   Display the filesystem superblock information.  This is useful to
+   discover the filesystem version, byte ordering, whether it has an
+   NFS export table, and what options were used to compress
+   the filesystem.
diff --git a/squashfs-tools/RELEASE-READMEs/README-4.0 b/squashfs-tools/RELEASE-READMEs/README-4.0
new file mode 100644
index 0000000..8cc9514
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-4.0
@@ -0,0 +1,48 @@
+	SQUASHFS 4.0 - A squashed read-only filesystem for Linux
+
+	Copyright 2002-2009 Phillip Lougher <phillip@lougher.demon.co.uk>
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to Squashfs 4.0.  This is an initial tools only release to
+support users of the 2.6.29 kernel, following the mainlining of Squashfs
+earlier this year.
+
+Later releases will probably contain kernel patches supporting 4.0
+layouts for earlier kernels.
+
+New Mksquashfs options
+----------------------
+
+Mksquashfs now supports pseudo files, these allow fake directories, character
+and block devices to be specified and added to the Squashfs filesystem being
+built, rather than requiring them to be present in the source directories.
+This, for example, allows device nodes to be added to the filesystem without
+requiring root access.
+
+Two options are supported, -p allows one pseudo file to be specified on the
+command line, and -pf allows a pseudo file to be specified containing a
+list of pseduo definitions, one per line.
+
+Pseudo device nodes are specified using 7 arguments
+
+Filename type mode uid gid major minor
+
+Where type is either
+	b - for block devices, and
+	c - for character devices
+
+mode is the octal mode specifier, similar to that expected by chmod.
+
+Uid and gid can be either specified as a decimal number, or by name.
+
+For example:
+
+/dev/chr_dev c 666 root root 100 1
+/dev/blk_dev b 444 0 0 200 200
+
+Directories are specified using 5 arguments
+
+Filename type mode uid gid
+
+Where type is d.
diff --git a/squashfs-tools/RELEASE-READMEs/README-4.1 b/squashfs-tools/RELEASE-READMEs/README-4.1
new file mode 100644
index 0000000..d2712f9
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-4.1
@@ -0,0 +1,265 @@
+	SQUASHFS 4.1 - A squashed read-only filesystem for Linux
+
+	Copyright 2002-2010 Phillip Lougher <phillip@lougher.demon.co.uk>
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to Squashfs 4.1.  This is a tools only release, support for Squashfs
+file systems is in mainline (2.6.29 and later).
+
+New features in Squashfs-tools 4.1
+----------------------------------
+
+  1. Support for extended attributes
+  2. Support for LZMA and LZO compression
+  3. New pseudo file features
+
+Compatiblity
+------------
+
+Mksquashfs 4.1 generates 4.0 filesystems.  These filesystems are fully
+compatible/interchangable with filesystems generated by Mksquashfs 4.0 and are
+mountable on 2.6.29 and later kernels.
+
+Extended attributes (xattrs)
+----------------------------
+
+Squashfs file systems now have extended attribute support.  The
+extended attribute implementation has the following features:
+
+1. Layout can store up to 2^48 bytes of compressed xattr data.
+2. Number of xattrs per inode unlimited.
+3. Total size of xattr data per inode 2^48 bytes of compressed data.
+4. Up to 4 Gbytes of data per xattr value.
+5. Inline and out-of-line xattr values supported for higher performance
+   in xattr scanning (listxattr & getxattr), and to allow xattr value
+   de-duplication.
+6. Both whole inode xattr duplicate detection and individual xattr value
+   duplicate detection supported.  These can obviously nest, file C's
+   xattrs can be a complete duplicate of file B, and file B's xattrs
+   can be a partial duplicate of file A.
+7. Xattr name prefix types stored, allowing the redundant "user.", "trusted."
+   etc. characters to be eliminated and more concisely stored.
+8. Support for files, directories, symbolic links, device nodes, fifos
+   and sockets.
+
+Extended attribute support is in 2.6.35 and later kernels.  File systems
+with extended attributes can be mounted on 2.6.29 and later kernels, the
+extended attributes will be ignored with a warning.
+
+LZMA and LZO compression
+------------------------
+
+Squashfs now supports LZMA and LZO compression.
+
+LZO support is in 2.6.36 and newer kernels.  LZMA is not yet in mainline.
+
+New Mksquashfs options
+----------------------
+
+-comp <comp>
+
+    Select <comp> compression.  
+
+    The compression algorithms supported by the build of Mksquashfs can be
+    found by typing mksquashfs without any arguments.  The compressors available
+    are displayed at the end of the help message, e.g.
+
+    Compressors available:
+	gzip (default)
+	lzma
+	lzo
+
+    The default compression used when -comp isn't specified on the command line
+    is indicated by "(default)".
+
+-no-xattrs
+    Don't store extended attributes
+
+-xattrs
+    Store extended attributes
+
+    The default behaviour of Mksquashfs with respect to extended attribute
+    storage is build time selectable.  The Mksquashfs help message indicates
+    whether extended attributes are stored or not, e.g.
+
+	-no-xattrs		don't store extended attributes
+	-xattrs			store extended attributes (default)
+
+    shows that extended attributes are stored by default, and can be disabled
+    by the -no-xattrs option.
+
+	-no-xattrs		don't store extended attributes (default)
+	-xattrs			store extended attributes 
+
+    shows that extended attributes are not stored by default, storage can be
+    enabled by the -xattrs option.
+
+
+-noX
+-noXattrCompression
+    Don't compress extended attributes
+
+
+New Unsquashfs options
+----------------------
+
+-n[o-xattrs]
+    Don't extract xattrs in filesystem
+
+-x[attrs]
+    Extract xattrs in filesystem
+
+    The default behaviour of Unsquashfs with respect to extended attributes
+    is build time selectable.  The Unsquashfs help message indicates whether
+    extended attributes are stored or not, e.g.
+
+	-no[-xattrs]		don't extract xattrs in file system
+	-x[attrs]		extract xattrs in file system (default)
+
+    shows that xattrs are extracted by default.
+
+	-no[-xattrs]		don't extract xattrs in file system (default)
+	-x[attrs]		extract xattrs in file system
+
+    shows that xattrs are not extracted by default.
+
+
+New pseudo file support
+-----------------------
+
+Mksquashfs supports pseudo files, these allow fake files, directories, character
+and block devices to be specified and added to the Squashfs filesystem being
+built, rather than requiring them to be present in the source directories.
+This, for example, allows device nodes to be added to the filesystem without
+requiring root access.
+
+Mksquashfs 4.1 adds support for "dynamic pseudo files" and a modify operation.
+Dynamic pseudo files allow files to be dynamically created when Mksquashfs
+is run, their contents being the result of running a command or piece of
+shell script.  The modifiy operation allows the mode/uid/gid of an existing
+file in the source filesystem to be modified.
+
+Two Mksquashfs options are supported, -p allows one pseudo file to be specified
+on the command line, and -pf allows a pseudo file to be specified containing a
+list of pseduo definitions, one per line.
+
+Pseudo operations
+-----------------
+
+1. Creating a dynamic file
+--------------------------
+
+Pseudo definition
+
+Filename f mode uid gid command
+
+mode is the octal mode specifier, similar to that expected by chmod.
+
+uid and gid can be either specified as a decimal number, or by name.
+
+command can be an executable or a piece of shell script, and it is executed
+by running "/bin/sh -c command".   The stdout becomes the contents of
+"Filename".
+
+Examples:
+
+Running a basic command
+-----------------------
+
+/somedir/dmesg f 444 root root dmesg
+
+creates a file "/somedir/dmesg" containing the output from dmesg.
+
+Executing shell script
+----------------------
+
+RELEASE f 444 root root \
+		if [ ! -e /tmp/ver ]; then \
+			echo 0 > /tmp/ver; \
+		fi; \
+                ver=`cat /tmp/ver`; \
+                ver=$((ver +1)); \
+                echo $ver > /tmp/ver; \
+                echo -n `cat /tmp/release`; \
+                echo "-dev #"$ver `date` "Build host" `hostname`
+
+Creates a file RELEASE containing the release name, date, build host, and
+an incrementing version number.  The incrementing version is a side-effect
+of executing the shell script, and ensures every time Mksquashfs is run a
+new version number is used without requiring any other shell scripting.
+
+The above example also shows that commands can be split across multiple lines
+using "\".  Obviously as the script will be presented to the shell as a single
+line, a semicolon is need to separate individual shell commands within the
+shell script.
+
+Reading from a device (or fifo/named socket)
+--------------------------------------------
+
+input f 444 root root dd if=/dev/sda1 bs=1024 count=10
+
+Copies 10K from the device /dev/sda1 into the file input.  Ordinarily Mksquashfs
+given a device, fifo, or named socket will place that special file within the
+Squashfs filesystem, the above allows input from these special files to be
+captured and placed in the Squashfs filesystem.
+
+2. Creating a block or character device
+---------------------------------------
+
+Pseudo definition
+
+Filename type mode uid gid major minor
+
+Where type is either
+	b - for block devices, and
+	c - for character devices
+
+mode is the octal mode specifier, similar to that expected by chmod.
+
+uid and gid can be either specified as a decimal number, or by name.
+
+For example:
+
+/dev/chr_dev c 666 root root 100 1
+/dev/blk_dev b 666 0 0 200 200
+
+creates a character device "/dev/chr_dev" with major:minor 100:1 and
+a block device "/dev/blk_dev" with major:minor 200:200, both with root
+uid/gid and a mode of rw-rw-rw.
+
+3. Creating a directory
+-----------------------
+
+Pseudo definition
+
+Filename d mode uid gid
+
+mode is the octal mode specifier, similar to that expected by chmod.
+
+uid and gid can be either specified as a decimal number, or by name.
+
+For example:
+
+/pseudo_dir d 666 root root
+
+creates a directory "/pseudo_dir" with root uid/gid and mode of rw-rw-rw.
+
+4. Modifying attributes of an existing file
+-------------------------------------------
+
+Pseudo definition
+
+Filename m mode uid gid
+
+mode is the octal mode specifier, similar to that expected by chmod.
+
+uid and gid can be either specified as a decimal number, or by name.
+
+For example:
+
+dmesg m 666 root root
+
+Changes the attributes of the file "dmesg" in the filesystem to have
+root uid/gid and a mode of rw-rw-rw, overriding the attributes obtained
+from the source filesystem.
diff --git a/squashfs-tools/RELEASE-READMEs/README-4.2 b/squashfs-tools/RELEASE-READMEs/README-4.2
new file mode 100644
index 0000000..db28f53
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-4.2
@@ -0,0 +1,57 @@
+	SQUASHFS 4.2 - A squashed read-only filesystem for Linux
+
+	Copyright 2002-2011 Phillip Lougher <phillip@lougher.demon.co.uk>
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to Squashfs 4.2.  This is a tools only release, support for Squashfs
+filesystems is in mainline (2.6.29 and later).
+
+New features in Squashfs-tools 4.2
+----------------------------------
+
+  1. Support for XZ compression
+  2. Support for compressor specific options
+
+Compatiblity
+------------
+
+Mksquashfs 4.2 generates 4.0 filesystems.  These filesystems are fully
+compatible/interchangable with filesystems generated by Mksquashfs 4.0 and are
+mountable on 2.6.29 and later kernels.
+
+XZ compression
+--------------
+
+Squashfs now supports XZ compression.  
+
+XZ support is in 2.6.38 and newer kernels.
+
+New Mksquashfs options
+----------------------
+
+-X<compressor-option>
+
+  Compression algorithms can now support compression specific options.  These
+options are prefixed by -X, and are passed to the compressor for handling.
+
+  The compression specific options supported by each compressor can be
+found by typing mksquashfs without any arguments.  They are displayed at the
+end of the help message, e.g. 
+
+Compressors available and compressor specific options:
+	gzip (no options) (default)
+	lzo (no options)
+	xz
+	  -Xbcj filter1,filter2,...,filterN
+		Compress using filter1,filter2,...,filterN in turn
+		(in addition to no filter), and choose the best compression.
+		Available filters: x86, arm, armthumb, powerpc, sparc, ia64
+	  -Xdict-size <dict-size>
+		Use <dict-size> as the XZ dictionary size.  The dictionary size
+		can be specified as a percentage of the block size, or as an
+		absolute value.  The dictionary size must be less than or equal
+		to the block size and 8192 bytes or larger.  It must also be
+		storable in the xz header as either 2^n or as 2^n+2^(n+1).
+		Example dict-sizes are 75%, 50%, 37.5%, 25%, or 32K, 16K, 8K
+		etc.
diff --git a/squashfs-tools/RELEASE-READMEs/README-4.3 b/squashfs-tools/RELEASE-READMEs/README-4.3
new file mode 100644
index 0000000..d2370a0
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/README-4.3
@@ -0,0 +1,182 @@
+	SQUASHFS 4.3 - A squashed read-only filesystem for Linux
+
+	Copyright 2002-2014 Phillip Lougher <phillip@lougher.demon.co.uk>
+
+	Released under the GPL licence (version 2 or later).
+
+Welcome to Squashfs 4.3.  This is the first release in over 3 years, and
+there are substantial improvements to stability, new compression options
+and compressors, speed optimisations, and new options for Mksquashfs/Unsquashfs.
+
+This is a tools only release, support for Squashfs filesystems is
+in mainline (2.6.29 and later).
+
+Changes in Squashfs-tools 4.3
+-----------------------------
+
+1. Stability improvements.  Better checking of user input for out of
+   range/invalid values.  Better handling of corrupted Squashfs filesystems
+   (Mksquashfs append mode, and Unsquashfs).  Better handling of buffer
+   overflow/underflow.
+
+2. GZIP compressor now supports compression options, allowing different
+   compression levels to be used.
+
+3. Rewritten LZO compressor with compression options, allowing different
+   LZO algorithms and different compression levels to be used.
+
+4. New LZ4 compressor (note not yet in mainline kernel)
+
+5. Better default memory usage for Mksquashfs.  Mksquashfs by default now
+   uses 25% of physical memory.
+
+6. Duplicate checking in Mksquashfs further optimised.  With certain
+   "problem filesystems" greater than 2x performance improvement.
+   Filesystems with a lot of duplicates should see at least 10-20% speed
+   improvement.
+
+7. The -stat option in Unsquashfs now displays the compression options
+   used to generate the original filesystem.  Previously -stat only displayed
+   the compression algorithm used.
+
+8. The file being compressed/uncompressed in Mksquashfs/Unsquashfs is now
+   displayed if CTRL-\ (SIGQUIT from keyboard) typed.
+
+9. The status of the internal queues/caches in Mksquashfs/Unsquashfs is
+   now displayed if CTRL-\ (SIGQUIT from keyboard) is typed twice within
+   one second.  Normally only useful for "power users", but it can be
+   used to discover if there's any bottlenecks affecting performance
+   (the bottleneck will normally be the compressors/fragment compressors).
+
+10. Miscellaneous new options for Mksquashfs/Unsquashfs to fine tune behaviour.
+
+11. Fixes for CVE-2012-4024 and CVE-2012-4025.
+
+Compatiblity
+------------
+
+Mksquashfs 4.3 generates 4.0 filesystems.  These filesystems are fully
+compatible/interchangable with filesystems generated by Mksquashfs 4.0 and are
+mountable on 2.6.29 and later kernels.
+
+Compressors
+-----------
+
+New compression options and compressors are now supported.
+
+The new options and compressors are:
+
+1. gzip
+	  -Xcompression-level <compression-level>
+		<compression-level> should be 1 .. 9 (default 9)
+	  -Xwindow-size <window-size>
+		<window-size> should be 8 .. 15 (default 15)
+	  -Xstrategy strategy1,strategy2,...,strategyN
+		Compress using strategy1,strategy2,...,strategyN in turn
+		and choose the best compression.
+		Available strategies: default, filtered, huffman_only,
+		run_length_encoded and fixed
+
+2. lzo
+	  -Xalgorithm <algorithm>
+		Where <algorithm> is one of:
+			lzo1x_1
+			lzo1x_1_11
+			lzo1x_1_12
+			lzo1x_1_15
+			lzo1x_999 (default)
+	  -Xcompression-level <compression-level>
+		<compression-level> should be 1 .. 9 (default 8)
+		Only applies to lzo1x_999 algorithm
+
+3. lz4
+	  -Xhc
+		Compress using LZ4 High Compression
+
+The compression specific options are, obviously, specific to the compressor
+in question, and you should read the compressor documentation and check
+their web sites to understand their behaviour.
+
+In general the defaults used by Mksquashfs for each compressor are optimised
+to give the best performance for each compressor, where what constitutes
+best depends on the compressor.  For gzip/xz best means highest compression
+(trying multiple filters/strategies can improve compression, but this is
+extremely expensive computationally, and hence, not suitable for the defaults),
+for LZO/LZ4 best means a tradeoff between compression and (de)-compression
+overhead (LZO/LZ4 by definition are intended for weaker processors).
+
+New Mksquashfs options
+----------------------
+
+1. -mem <size>
+
+  Set the amount of memory used by Mksquashfs to <size> bytes.  G/M and K
+  post-fixes are supported.
+
+  By default Mksquashfs uses 25% of the physical memory.  Increasing
+  this with the -mem option can increase performance (note it does not have
+  any effect on compression).  Reducing it can prevent thrashing if the
+  system is busy and there is not 25% of physical memory free (again, note
+  it does not have any effect on compression).
+
+2. -exit-on-error
+
+  By default Mksquashfs treats certain errors as benign, if these
+  errors occur Mksquashfs prints the error on the console but continues.
+  These errors are typically failure to read a file from the source filesystem.
+  This is deliberate, in many cases users prefer Mksquashfs to flag
+  the error but continue rather than abort what may be hours of compression.
+
+  But there are times where failure to read any file is considered critical,
+  and users (especially in the case of automated scripts where the
+  errors output to the console may be missed) prefer Mksquashfs to exit.
+
+  The new -exit-on-error option can be used in this scenario.  This option
+  makes Mksquashfs treat all benign errors as fatal.
+
+3. -progress
+
+  By default if -info is specified, the progress bar is disabled as it gets
+  in the way.  Occasionally you might want the progress bar enabled whilst
+  -info is enabled.  This option forces Mksquashfs to output the progress
+  bar when -info is specified.
+
+4. -Xhelp
+
+  Display the usage text for the currently selected compressor.
+
+New Unsquashfs options
+----------------------
+
+1. -u[ser-xattrs]
+
+  Only write user xattrs.  This forces Unsquashfs to ignore system xattrs.
+  This is useful when Unsquashing a filesystem as a non-root user, and the
+  filesystem contains system xattrs which are only writable by root.
+
+Major bugs fixed
+----------------
+
+1. If Mksquashfs ran out of space in the destination filesystem, this
+   would not cause Mksquashfs to immediately abort, and Mksquashfs would
+   continue to process the source filesystem.  Mksquashfs now immediately
+   aborts on out of space in the destination filesystem.
+
+2. Unsquashfs ignored the maximum number of open files limit, and if that
+   was lower than the default limit for Linux, it would run out of file
+   descriptors.  Unsquashfs now limits the number of open files to the
+   limit currently in force (e.g. specified by setrlimit).
+
+3. If huge numbers of dynamic pseudo files were specified, Mksquashfs
+   could exceed the maximum number of open files limit.  This was because
+   Mksquashfs created all the dynamic file processes up front before
+   commencing source filesystem reading and compression.  Mksquashfs
+   now creates the dynamic file processes on demand whilst reading
+   and compressing the source filesystem, thus limiting the number of
+   dynamic pseudo file processes in existence at any one time.
+
+4. When outputting Unsquashfs used to set the permissions of directories
+   as it recursively descended.  This in hindsight had an obvious oversight,
+   if a directory had only read permission (or was otherwise restricted), then
+   Unsquashfs would fail to write its contents when descending into it.  Fixed
+   by setting directory permissions as Unsquashfs recursively unwinds.
diff --git a/squashfs-tools/RELEASE-READMEs/pseudo-file.example b/squashfs-tools/RELEASE-READMEs/pseudo-file.example
new file mode 100644
index 0000000..f866d90
--- /dev/null
+++ b/squashfs-tools/RELEASE-READMEs/pseudo-file.example
@@ -0,0 +1,74 @@
+# Pseudo file example
+
+# Mksquashfs supports pseudo files, these allow fake files, directories,
+# character and block devices to be specified and added to the Squashfs
+# filesystem being built, rather than requiring them to be present in the
+# source directories.
+#
+# This, for example, allows device nodes to be added to the filesystem without
+# requiring root access.
+
+# Mksquashfs 4.1 adds support for "dynamic pseudo files" and a modify operation.
+# Dynamic pseudo files allow files to be dynamically created when Mksquashfs
+# is run, their contents being the result of running a command or piece of
+# shell script.  The modifiy operation allows the mode/uid/gid of an existing
+# file in the source filesystem to be modified.
+
+# Two Mksquashfs options are supported, -p allows one pseudo file to be
+# specified #on the command line, and -pf allows a pseudo file to be specified
+# containing a list of pseduo definitions, one per line.
+
+# Pseudo file examples
+# Run mkquashfs . /tmp/img -pf pseudo-file.examples
+# to see their effect
+
+# Creating dynamic file examples
+
+# Create a file "dmesg" containing the output from dmesg.
+dmesg f 444 root root dmesg
+
+
+# Create a file RELEASE containing the release name, date, build host, and
+# an incrementing version number.  The incrementing version is a side-effect
+# of executing the shell script, and ensures every time Mksquashfs is run a
+# new version number is used without requiring any other shell scripting.
+RELEASE f 444 root root \
+		if [ ! -e /tmp/ver ]; then \
+			echo 0 > /tmp/ver; \
+		fi; \
+                ver=`cat /tmp/ver`; \
+                ver=$((ver +1)); \
+                echo $ver > /tmp/ver; \
+                echo -n "release x.x"; \
+                echo "-dev #"$ver `date` "Build host" `hostname`
+
+
+# Copy 10K from the device /dev/sda1 into the file input.  Ordinarily
+# Mksquashfs given a device, fifo, or named socket will place that special file
+# within the Squashfs filesystem, this allows input from these special
+# files to be captured and placed in the Squashfs filesystem.
+input f 444 root root dd if=/dev/sda1 bs=1024 count=10
+
+
+# Creating a block or character device examples
+
+# Create a character device "chr_dev" with major:minor 100:1 and
+# a block device "blk_dev" with major:minor 200:200, both with root
+# uid/gid and a mode of rw-rw-rw.
+chr_dev c 666 root root 100 1
+blk_dev b 666 0 0 200 200
+
+
+# Creating a directory example
+
+# create a directory "pseudo_dir" with root uid/gid and mode of r--r--r--.
+pseudo_dir d 444 root root
+
+
+# Modifying attributes of an existing file exmaple
+
+# Change the attributes of the file "INSTALL" in the filesystem to have
+# root uid/gid and a mode of rw-rw-rw, overriding the attributes obtained
+# from the source filesystem.
+INSTALL m 666 root root
+
diff --git a/squashfs-tools/kernel-2.4/fs/squashfs/Makefile b/squashfs-tools/kernel-2.4/fs/squashfs/Makefile
new file mode 100644
index 0000000..0965287
--- /dev/null
+++ b/squashfs-tools/kernel-2.4/fs/squashfs/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for the linux squashfs routines.
+#
+
+O_TARGET := squashfs.o
+
+obj-y  := inode.o squashfs2_0.o
+
+obj-m := $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff --git a/squashfs-tools/kernel-2.4/fs/squashfs/inode.c b/squashfs-tools/kernel-2.4/fs/squashfs/inode.c
new file mode 100755
index 0000000..54061f6
--- /dev/null
+++ b/squashfs-tools/kernel-2.4/fs/squashfs/inode.c
@@ -0,0 +1,2029 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006
+ * Phillip Lougher <phillip@lougher.demon.co.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.
+ *
+ * inode.c
+ */
+
+#include <linux/types.h>
+#include <linux/squashfs_fs.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/zlib.h>
+#include <linux/fs.h>
+#include <linux/smp_lock.h>
+#include <linux/locks.h>
+#include <linux/init.h>
+#include <linux/dcache.h>
+#include <linux/wait.h>
+#include <linux/blkdev.h>
+#include <linux/vmalloc.h>
+#include <asm/uaccess.h>
+#include <asm/semaphore.h>
+
+#include "squashfs.h"
+
+static struct super_block *squashfs_read_super(struct super_block *, void *, int);
+static void squashfs_put_super(struct super_block *);
+static int squashfs_statfs(struct super_block *, struct statfs *);
+static int squashfs_symlink_readpage(struct file *file, struct page *page);
+static int squashfs_readpage(struct file *file, struct page *page);
+static int squashfs_readpage4K(struct file *file, struct page *page);
+static int squashfs_readdir(struct file *, void *, filldir_t);
+static struct dentry *squashfs_lookup(struct inode *, struct dentry *);
+static struct inode *squashfs_iget(struct super_block *s, squashfs_inode_t inode);
+static long long read_blocklist(struct inode *inode, int index,
+				int readahead_blks, char *block_list,
+				unsigned short **block_p, unsigned int *bsize);
+
+static DECLARE_FSTYPE_DEV(squashfs_fs_type, "squashfs", squashfs_read_super);
+
+static unsigned char squashfs_filetype_table[] = {
+	DT_UNKNOWN, DT_DIR, DT_REG, DT_LNK, DT_BLK, DT_CHR, DT_FIFO, DT_SOCK
+};
+
+static struct super_operations squashfs_ops = {
+	.statfs = squashfs_statfs,
+	.put_super = squashfs_put_super,
+};
+
+SQSH_EXTERN struct address_space_operations squashfs_symlink_aops = {
+	.readpage = squashfs_symlink_readpage
+};
+
+SQSH_EXTERN struct address_space_operations squashfs_aops = {
+	.readpage = squashfs_readpage
+};
+
+SQSH_EXTERN struct address_space_operations squashfs_aops_4K = {
+	.readpage = squashfs_readpage4K
+};
+
+static struct file_operations squashfs_dir_ops = {
+	.read = generic_read_dir,
+	.readdir = squashfs_readdir
+};
+
+static struct inode_operations squashfs_dir_inode_ops = {
+	.lookup = squashfs_lookup
+};
+
+static struct buffer_head *get_block_length(struct super_block *s,
+				int *cur_index, int *offset, int *c_byte)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	unsigned short temp;
+	struct buffer_head *bh;
+
+	if (!(bh = sb_bread(s, *cur_index)))
+		goto out;
+
+	if (msblk->devblksize - *offset == 1) {
+		if (msblk->swap)
+			((unsigned char *) &temp)[1] = *((unsigned char *)
+				(bh->b_data + *offset));
+		else
+			((unsigned char *) &temp)[0] = *((unsigned char *)
+				(bh->b_data + *offset));
+		brelse(bh);
+		if (!(bh = sb_bread(s, ++(*cur_index))))
+			goto out;
+		if (msblk->swap)
+			((unsigned char *) &temp)[0] = *((unsigned char *)
+				bh->b_data); 
+		else
+			((unsigned char *) &temp)[1] = *((unsigned char *)
+				bh->b_data); 
+		*c_byte = temp;
+		*offset = 1;
+	} else {
+		if (msblk->swap) {
+			((unsigned char *) &temp)[1] = *((unsigned char *)
+				(bh->b_data + *offset));
+			((unsigned char *) &temp)[0] = *((unsigned char *)
+				(bh->b_data + *offset + 1)); 
+		} else {
+			((unsigned char *) &temp)[0] = *((unsigned char *)
+				(bh->b_data + *offset));
+			((unsigned char *) &temp)[1] = *((unsigned char *)
+				(bh->b_data + *offset + 1)); 
+		}
+		*c_byte = temp;
+		*offset += 2;
+	}
+
+	if (SQUASHFS_CHECK_DATA(msblk->sblk.flags)) {
+		if (*offset == msblk->devblksize) {
+			brelse(bh);
+			if (!(bh = sb_bread(s, ++(*cur_index))))
+				goto out;
+			*offset = 0;
+		}
+		if (*((unsigned char *) (bh->b_data + *offset)) !=
+						SQUASHFS_MARKER_BYTE) {
+			ERROR("Metadata block marker corrupt @ %x\n",
+						*cur_index);
+			brelse(bh);
+			goto out;
+		}
+		(*offset)++;
+	}
+	return bh;
+
+out:
+	return NULL;
+}
+
+
+SQSH_EXTERN unsigned int squashfs_read_data(struct super_block *s, char *buffer,
+			long long index, unsigned int length,
+			long long *next_index)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct buffer_head *bh[((SQUASHFS_FILE_MAX_SIZE - 1) >>
+			msblk->devblksize_log2) + 2];
+	unsigned int offset = index & ((1 << msblk->devblksize_log2) - 1);
+	unsigned int cur_index = index >> msblk->devblksize_log2;
+	int bytes, avail_bytes, b = 0, k;
+	char *c_buffer;
+	unsigned int compressed;
+	unsigned int c_byte = length;
+
+	if (c_byte) {
+		bytes = msblk->devblksize - offset;
+		compressed = SQUASHFS_COMPRESSED_BLOCK(c_byte);
+		c_buffer = compressed ? msblk->read_data : buffer;
+		c_byte = SQUASHFS_COMPRESSED_SIZE_BLOCK(c_byte);
+
+		TRACE("Block @ 0x%llx, %scompressed size %d\n", index, compressed
+					? "" : "un", (unsigned int) c_byte);
+
+		if (!(bh[0] = sb_getblk(s, cur_index)))
+			goto block_release;
+
+		for (b = 1; bytes < c_byte; b++) {
+			if (!(bh[b] = sb_getblk(s, ++cur_index)))
+				goto block_release;
+			bytes += msblk->devblksize;
+		}
+		ll_rw_block(READ, b, bh);
+	} else {
+		if (!(bh[0] = get_block_length(s, &cur_index, &offset,
+								&c_byte)))
+			goto read_failure;
+
+		bytes = msblk->devblksize - offset;
+		compressed = SQUASHFS_COMPRESSED(c_byte);
+		c_buffer = compressed ? msblk->read_data : buffer;
+		c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte);
+
+		TRACE("Block @ 0x%llx, %scompressed size %d\n", index, compressed
+					? "" : "un", (unsigned int) c_byte);
+
+		for (b = 1; bytes < c_byte; b++) {
+			if (!(bh[b] = sb_getblk(s, ++cur_index)))
+				goto block_release;
+			bytes += msblk->devblksize;
+		}
+		ll_rw_block(READ, b - 1, bh + 1);
+	}
+
+	if (compressed)
+		down(&msblk->read_data_mutex);
+
+	for (bytes = 0, k = 0; k < b; k++) {
+		avail_bytes = (c_byte - bytes) > (msblk->devblksize - offset) ?
+					msblk->devblksize - offset :
+					c_byte - bytes;
+		wait_on_buffer(bh[k]);
+		if (!buffer_uptodate(bh[k]))
+			goto block_release;
+		memcpy(c_buffer + bytes, bh[k]->b_data + offset, avail_bytes);
+		bytes += avail_bytes;
+		offset = 0;
+		brelse(bh[k]);
+	}
+
+	/*
+	 * uncompress block
+	 */
+	if (compressed) {
+		int zlib_err;
+
+		msblk->stream.next_in = c_buffer;
+		msblk->stream.avail_in = c_byte;
+		msblk->stream.next_out = buffer;
+		msblk->stream.avail_out = msblk->read_size;
+
+		if (((zlib_err = zlib_inflateInit(&msblk->stream)) != Z_OK) ||
+				((zlib_err = zlib_inflate(&msblk->stream, Z_FINISH))
+				 != Z_STREAM_END) || ((zlib_err =
+				zlib_inflateEnd(&msblk->stream)) != Z_OK)) {
+			ERROR("zlib_fs returned unexpected result 0x%x\n",
+				zlib_err);
+			bytes = 0;
+		} else
+			bytes = msblk->stream.total_out;
+		
+		up(&msblk->read_data_mutex);
+	}
+
+	if (next_index)
+		*next_index = index + c_byte + (length ? 0 :
+				(SQUASHFS_CHECK_DATA(msblk->sblk.flags)
+				 ? 3 : 2));
+	return bytes;
+
+block_release:
+	while (--b >= 0)
+		brelse(bh[b]);
+
+read_failure:
+	ERROR("sb_bread failed reading block 0x%x\n", cur_index);
+	return 0;
+}
+
+
+SQSH_EXTERN int squashfs_get_cached_block(struct super_block *s, char *buffer,
+				long long block, unsigned int offset,
+				int length, long long *next_block,
+				unsigned int *next_offset)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	int n, i, bytes, return_length = length;
+	long long next_index;
+
+	TRACE("Entered squashfs_get_cached_block [%llx:%x]\n", block, offset);
+
+	while ( 1 ) {
+		for (i = 0; i < SQUASHFS_CACHED_BLKS; i++) 
+			if (msblk->block_cache[i].block == block)
+				break; 
+		
+		down(&msblk->block_cache_mutex);
+
+		if (i == SQUASHFS_CACHED_BLKS) {
+			/* read inode header block */
+			for (i = msblk->next_cache, n = SQUASHFS_CACHED_BLKS;
+					n ; n --, i = (i + 1) %
+					SQUASHFS_CACHED_BLKS)
+				if (msblk->block_cache[i].block !=
+							SQUASHFS_USED_BLK)
+					break;
+
+			if (n == 0) {
+				wait_queue_t wait;
+
+				init_waitqueue_entry(&wait, current);
+				add_wait_queue(&msblk->waitq, &wait);
+				set_current_state(TASK_UNINTERRUPTIBLE);
+ 				up(&msblk->block_cache_mutex);
+				schedule();
+				set_current_state(TASK_RUNNING);
+				remove_wait_queue(&msblk->waitq, &wait);
+				continue;
+			}
+			msblk->next_cache = (i + 1) % SQUASHFS_CACHED_BLKS;
+
+			if (msblk->block_cache[i].block ==
+							SQUASHFS_INVALID_BLK) {
+				if (!(msblk->block_cache[i].data =
+						kmalloc(SQUASHFS_METADATA_SIZE,
+						GFP_KERNEL))) {
+					ERROR("Failed to allocate cache"
+							"block\n");
+					up(&msblk->block_cache_mutex);
+					goto out;
+				}
+			}
+	
+			msblk->block_cache[i].block = SQUASHFS_USED_BLK;
+			up(&msblk->block_cache_mutex);
+
+			if (!(msblk->block_cache[i].length =
+						squashfs_read_data(s,
+						msblk->block_cache[i].data,
+						block, 0, &next_index))) {
+				ERROR("Unable to read cache block [%llx:%x]\n",
+						block, offset);
+				goto out;
+			}
+
+			down(&msblk->block_cache_mutex);
+			wake_up(&msblk->waitq);
+			msblk->block_cache[i].block = block;
+			msblk->block_cache[i].next_index = next_index;
+			TRACE("Read cache block [%llx:%x]\n", block, offset);
+		}
+
+		if (msblk->block_cache[i].block != block) {
+			up(&msblk->block_cache_mutex);
+			continue;
+		}
+
+		if ((bytes = msblk->block_cache[i].length - offset) >= length) {
+			if (buffer)
+				memcpy(buffer, msblk->block_cache[i].data +
+						offset, length);
+			if (msblk->block_cache[i].length - offset == length) {
+				*next_block = msblk->block_cache[i].next_index;
+				*next_offset = 0;
+			} else {
+				*next_block = block;
+				*next_offset = offset + length;
+			}
+			up(&msblk->block_cache_mutex);
+			goto finish;
+		} else {
+			if (buffer) {
+				memcpy(buffer, msblk->block_cache[i].data +
+						offset, bytes);
+				buffer += bytes;
+			}
+			block = msblk->block_cache[i].next_index;
+			up(&msblk->block_cache_mutex);
+			length -= bytes;
+			offset = 0;
+		}
+	}
+
+finish:
+	return return_length;
+out:
+	return 0;
+}
+
+
+static int get_fragment_location(struct super_block *s, unsigned int fragment,
+				long long *fragment_start_block,
+				unsigned int *fragment_size)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	long long start_block =
+		msblk->fragment_index[SQUASHFS_FRAGMENT_INDEX(fragment)];
+	int offset = SQUASHFS_FRAGMENT_INDEX_OFFSET(fragment);
+	struct squashfs_fragment_entry fragment_entry;
+
+	if (msblk->swap) {
+		struct squashfs_fragment_entry sfragment_entry;
+
+		if (!squashfs_get_cached_block(s, (char *) &sfragment_entry,
+					start_block, offset,
+					sizeof(sfragment_entry), &start_block,
+					&offset))
+			goto out;
+		SQUASHFS_SWAP_FRAGMENT_ENTRY(&fragment_entry, &sfragment_entry);
+	} else
+		if (!squashfs_get_cached_block(s, (char *) &fragment_entry,
+					start_block, offset,
+					sizeof(fragment_entry), &start_block,
+					&offset))
+			goto out;
+
+	*fragment_start_block = fragment_entry.start_block;
+	*fragment_size = fragment_entry.size;
+
+	return 1;
+
+out:
+	return 0;
+}
+
+
+SQSH_EXTERN void release_cached_fragment(struct squashfs_sb_info *msblk, struct
+					squashfs_fragment_cache *fragment)
+{
+	down(&msblk->fragment_mutex);
+	fragment->locked --;
+	wake_up(&msblk->fragment_wait_queue);
+	up(&msblk->fragment_mutex);
+}
+
+
+SQSH_EXTERN struct squashfs_fragment_cache *get_cached_fragment(struct super_block
+					*s, long long start_block,
+					int length)
+{
+	int i, n;
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+
+	while ( 1 ) {
+		down(&msblk->fragment_mutex);
+
+		for (i = 0; i < SQUASHFS_CACHED_FRAGMENTS &&
+				msblk->fragment[i].block != start_block; i++);
+
+		if (i == SQUASHFS_CACHED_FRAGMENTS) {
+			for (i = msblk->next_fragment, n =
+				SQUASHFS_CACHED_FRAGMENTS; n &&
+				msblk->fragment[i].locked; n--, i = (i + 1) %
+				SQUASHFS_CACHED_FRAGMENTS);
+
+			if (n == 0) {
+				wait_queue_t wait;
+
+				init_waitqueue_entry(&wait, current);
+				add_wait_queue(&msblk->fragment_wait_queue,
+									&wait);
+				set_current_state(TASK_UNINTERRUPTIBLE);
+				up(&msblk->fragment_mutex);
+				schedule();
+				set_current_state(TASK_RUNNING);
+				remove_wait_queue(&msblk->fragment_wait_queue,
+									&wait);
+				continue;
+			}
+			msblk->next_fragment = (msblk->next_fragment + 1) %
+				SQUASHFS_CACHED_FRAGMENTS;
+			
+			if (msblk->fragment[i].data == NULL)
+				if (!(msblk->fragment[i].data = SQUASHFS_ALLOC
+						(SQUASHFS_FILE_MAX_SIZE))) {
+					ERROR("Failed to allocate fragment "
+							"cache block\n");
+					up(&msblk->fragment_mutex);
+					goto out;
+				}
+
+			msblk->fragment[i].block = SQUASHFS_INVALID_BLK;
+			msblk->fragment[i].locked = 1;
+			up(&msblk->fragment_mutex);
+
+			if (!(msblk->fragment[i].length = squashfs_read_data(s,
+						msblk->fragment[i].data,
+						start_block, length, NULL))) {
+				ERROR("Unable to read fragment cache block "
+							"[%llx]\n", start_block);
+				msblk->fragment[i].locked = 0;
+				goto out;
+			}
+
+			msblk->fragment[i].block = start_block;
+			TRACE("New fragment %d, start block %lld, locked %d\n",
+						i, msblk->fragment[i].block,
+						msblk->fragment[i].locked);
+			break;
+		}
+
+		msblk->fragment[i].locked++;
+		up(&msblk->fragment_mutex);
+		TRACE("Got fragment %d, start block %lld, locked %d\n", i,
+						msblk->fragment[i].block,
+						msblk->fragment[i].locked);
+		break;
+	}
+
+	return &msblk->fragment[i];
+
+out:
+	return NULL;
+}
+
+
+static struct inode *squashfs_new_inode(struct super_block *s,
+		struct squashfs_base_inode_header *inodeb)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct inode *i = new_inode(s);
+
+	if (i) {
+		i->i_ino = inodeb->inode_number;
+		i->i_mtime = inodeb->mtime;
+		i->i_atime = inodeb->mtime;
+		i->i_ctime = inodeb->mtime;
+		i->i_uid = msblk->uid[inodeb->uid];
+		i->i_mode = inodeb->mode;
+		i->i_size = 0;
+		if (inodeb->guid == SQUASHFS_GUIDS)
+			i->i_gid = i->i_uid;
+		else
+			i->i_gid = msblk->guid[inodeb->guid];
+	}
+
+	return i;
+}
+
+
+static struct inode *squashfs_iget(struct super_block *s, squashfs_inode_t inode)
+{
+	struct inode *i;
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	long long block = SQUASHFS_INODE_BLK(inode) +
+		sblk->inode_table_start;
+	unsigned int offset = SQUASHFS_INODE_OFFSET(inode);
+	long long next_block;
+	unsigned int next_offset;
+	union squashfs_inode_header id, sid;
+	struct squashfs_base_inode_header *inodeb = &id.base,
+					  *sinodeb = &sid.base;
+
+	TRACE("Entered squashfs_iget\n");
+
+	if (msblk->swap) {
+		if (!squashfs_get_cached_block(s, (char *) sinodeb, block,
+					offset, sizeof(*sinodeb), &next_block,
+					&next_offset))
+			goto failed_read;
+		SQUASHFS_SWAP_BASE_INODE_HEADER(inodeb, sinodeb,
+					sizeof(*sinodeb));
+	} else
+		if (!squashfs_get_cached_block(s, (char *) inodeb, block,
+					offset, sizeof(*inodeb), &next_block,
+					&next_offset))
+			goto failed_read;
+
+	switch(inodeb->inode_type) {
+		case SQUASHFS_FILE_TYPE: {
+			unsigned int frag_size;
+			long long frag_blk;
+			struct squashfs_reg_inode_header *inodep = &id.reg;
+			struct squashfs_reg_inode_header *sinodep = &sid.reg;
+				
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_REG_INODE_HEADER(inodep, sinodep);
+			} else
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			frag_blk = SQUASHFS_INVALID_BLK;
+			if (inodep->fragment != SQUASHFS_INVALID_FRAG &&
+					!get_fragment_location(s,
+					inodep->fragment, &frag_blk, &frag_size))
+				goto failed_read;
+				
+			if((i = squashfs_new_inode(s, inodeb)) == NULL)
+				goto failed_read1;
+
+			i->i_nlink = 1;
+			i->i_size = inodep->file_size;
+			i->i_fop = &generic_ro_fops;
+			i->i_mode |= S_IFREG;
+			i->i_blocks = ((i->i_size - 1) >> 9) + 1;
+			i->i_blksize = PAGE_CACHE_SIZE;
+			SQUASHFS_I(i)->u.s1.fragment_start_block = frag_blk;
+			SQUASHFS_I(i)->u.s1.fragment_size = frag_size;
+			SQUASHFS_I(i)->u.s1.fragment_offset = inodep->offset;
+			SQUASHFS_I(i)->start_block = inodep->start_block;
+			SQUASHFS_I(i)->u.s1.block_list_start = next_block;
+			SQUASHFS_I(i)->offset = next_offset;
+			if (sblk->block_size > 4096)
+				i->i_data.a_ops = &squashfs_aops;
+			else
+				i->i_data.a_ops = &squashfs_aops_4K;
+
+			TRACE("File inode %x:%x, start_block %llx, "
+					"block_list_start %llx, offset %x\n",
+					SQUASHFS_INODE_BLK(inode), offset,
+					inodep->start_block, next_block,
+					next_offset);
+			break;
+		}
+		case SQUASHFS_LREG_TYPE: {
+			unsigned int frag_size;
+			long long frag_blk;
+			struct squashfs_lreg_inode_header *inodep = &id.lreg;
+			struct squashfs_lreg_inode_header *sinodep = &sid.lreg;
+				
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_LREG_INODE_HEADER(inodep, sinodep);
+			} else
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			frag_blk = SQUASHFS_INVALID_BLK;
+			if (inodep->fragment != SQUASHFS_INVALID_FRAG &&
+					!get_fragment_location(s,
+					inodep->fragment, &frag_blk, &frag_size))
+				goto failed_read;
+				
+			if((i = squashfs_new_inode(s, inodeb)) == NULL)
+				goto failed_read1;
+
+			i->i_nlink = inodep->nlink;
+			i->i_size = inodep->file_size;
+			i->i_fop = &generic_ro_fops;
+			i->i_mode |= S_IFREG;
+			i->i_blocks = ((i->i_size - 1) >> 9) + 1;
+			i->i_blksize = PAGE_CACHE_SIZE;
+			SQUASHFS_I(i)->u.s1.fragment_start_block = frag_blk;
+			SQUASHFS_I(i)->u.s1.fragment_size = frag_size;
+			SQUASHFS_I(i)->u.s1.fragment_offset = inodep->offset;
+			SQUASHFS_I(i)->start_block = inodep->start_block;
+			SQUASHFS_I(i)->u.s1.block_list_start = next_block;
+			SQUASHFS_I(i)->offset = next_offset;
+			if (sblk->block_size > 4096)
+				i->i_data.a_ops = &squashfs_aops;
+			else
+				i->i_data.a_ops = &squashfs_aops_4K;
+
+			TRACE("File inode %x:%x, start_block %llx, "
+					"block_list_start %llx, offset %x\n",
+					SQUASHFS_INODE_BLK(inode), offset,
+					inodep->start_block, next_block,
+					next_offset);
+			break;
+		}
+		case SQUASHFS_DIR_TYPE: {
+			struct squashfs_dir_inode_header *inodep = &id.dir;
+			struct squashfs_dir_inode_header *sinodep = &sid.dir;
+
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_DIR_INODE_HEADER(inodep, sinodep);
+			} else
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			if((i = squashfs_new_inode(s, inodeb)) == NULL)
+				goto failed_read1;
+
+			i->i_nlink = inodep->nlink;
+			i->i_size = inodep->file_size;
+			i->i_op = &squashfs_dir_inode_ops;
+			i->i_fop = &squashfs_dir_ops;
+			i->i_mode |= S_IFDIR;
+			SQUASHFS_I(i)->start_block = inodep->start_block;
+			SQUASHFS_I(i)->offset = inodep->offset;
+			SQUASHFS_I(i)->u.s2.directory_index_count = 0;
+			SQUASHFS_I(i)->u.s2.parent_inode = inodep->parent_inode;
+
+			TRACE("Directory inode %x:%x, start_block %x, offset "
+					"%x\n", SQUASHFS_INODE_BLK(inode),
+					offset, inodep->start_block,
+					inodep->offset);
+			break;
+		}
+		case SQUASHFS_LDIR_TYPE: {
+			struct squashfs_ldir_inode_header *inodep = &id.ldir;
+			struct squashfs_ldir_inode_header *sinodep = &sid.ldir;
+
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_LDIR_INODE_HEADER(inodep,
+						sinodep);
+			} else
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			if((i = squashfs_new_inode(s, inodeb)) == NULL)
+				goto failed_read1;
+
+			i->i_nlink = inodep->nlink;
+			i->i_size = inodep->file_size;
+			i->i_op = &squashfs_dir_inode_ops;
+			i->i_fop = &squashfs_dir_ops;
+			i->i_mode |= S_IFDIR;
+			SQUASHFS_I(i)->start_block = inodep->start_block;
+			SQUASHFS_I(i)->offset = inodep->offset;
+			SQUASHFS_I(i)->u.s2.directory_index_start = next_block;
+			SQUASHFS_I(i)->u.s2.directory_index_offset =
+								next_offset;
+			SQUASHFS_I(i)->u.s2.directory_index_count =
+								inodep->i_count;
+			SQUASHFS_I(i)->u.s2.parent_inode = inodep->parent_inode;
+
+			TRACE("Long directory inode %x:%x, start_block %x, "
+					"offset %x\n",
+					SQUASHFS_INODE_BLK(inode), offset,
+					inodep->start_block, inodep->offset);
+			break;
+		}
+		case SQUASHFS_SYMLINK_TYPE: {
+			struct squashfs_symlink_inode_header *inodep =
+								&id.symlink;
+			struct squashfs_symlink_inode_header *sinodep =
+								&sid.symlink;
+	
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_SYMLINK_INODE_HEADER(inodep,
+								sinodep);
+			} else
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			if((i = squashfs_new_inode(s, inodeb)) == NULL)
+				goto failed_read1;
+
+			i->i_nlink = inodep->nlink;
+			i->i_size = inodep->symlink_size;
+			i->i_op = &page_symlink_inode_operations;
+			i->i_data.a_ops = &squashfs_symlink_aops;
+			i->i_mode |= S_IFLNK;
+			SQUASHFS_I(i)->start_block = next_block;
+			SQUASHFS_I(i)->offset = next_offset;
+
+			TRACE("Symbolic link inode %x:%x, start_block %llx, "
+					"offset %x\n",
+					SQUASHFS_INODE_BLK(inode), offset,
+					next_block, next_offset);
+			break;
+		 }
+		 case SQUASHFS_BLKDEV_TYPE:
+		 case SQUASHFS_CHRDEV_TYPE: {
+			struct squashfs_dev_inode_header *inodep = &id.dev;
+			struct squashfs_dev_inode_header *sinodep = &sid.dev;
+
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_DEV_INODE_HEADER(inodep, sinodep);
+			} else	
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			if ((i = squashfs_new_inode(s, inodeb)) == NULL)
+				goto failed_read1;
+
+			i->i_nlink = inodep->nlink;
+			i->i_mode |= (inodeb->inode_type ==
+					SQUASHFS_CHRDEV_TYPE) ?  S_IFCHR :
+					S_IFBLK;
+			init_special_inode(i, i->i_mode, inodep->rdev);
+
+			TRACE("Device inode %x:%x, rdev %x\n",
+					SQUASHFS_INODE_BLK(inode), offset,
+					inodep->rdev);
+			break;
+		 }
+		 case SQUASHFS_FIFO_TYPE:
+		 case SQUASHFS_SOCKET_TYPE: {
+			struct squashfs_ipc_inode_header *inodep = &id.ipc;
+			struct squashfs_ipc_inode_header *sinodep = &sid.ipc;
+
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_IPC_INODE_HEADER(inodep, sinodep);
+			} else	
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			if ((i = squashfs_new_inode(s, inodeb)) == NULL)
+				goto failed_read1;
+
+			i->i_nlink = inodep->nlink;
+			i->i_mode |= (inodeb->inode_type == SQUASHFS_FIFO_TYPE)
+							? S_IFIFO : S_IFSOCK;
+			init_special_inode(i, i->i_mode, 0);
+			break;
+		 }
+		 default:
+			ERROR("Unknown inode type %d in squashfs_iget!\n",
+					inodeb->inode_type);
+			goto failed_read1;
+	}
+	
+	insert_inode_hash(i);
+	return i;
+
+failed_read:
+	ERROR("Unable to read inode [%llx:%x]\n", block, offset);
+
+failed_read1:
+	return NULL;
+}
+
+
+int read_fragment_index_table(struct super_block *s)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+
+	if (!(msblk->fragment_index = kmalloc(SQUASHFS_FRAGMENT_INDEX_BYTES
+					(sblk->fragments), GFP_KERNEL))) {
+		ERROR("Failed to allocate uid/gid table\n");
+		return 0;
+	}
+   
+	if (SQUASHFS_FRAGMENT_INDEX_BYTES(sblk->fragments) &&
+					!squashfs_read_data(s, (char *)
+					msblk->fragment_index,
+					sblk->fragment_table_start,
+					SQUASHFS_FRAGMENT_INDEX_BYTES
+					(sblk->fragments) |
+					SQUASHFS_COMPRESSED_BIT_BLOCK, NULL)) {
+		ERROR("unable to read fragment index table\n");
+		return 0;
+	}
+
+	if (msblk->swap) {
+		int i;
+		long long fragment;
+
+		for (i = 0; i < SQUASHFS_FRAGMENT_INDEXES(sblk->fragments);
+									i++) {
+			SQUASHFS_SWAP_FRAGMENT_INDEXES((&fragment),
+						&msblk->fragment_index[i], 1);
+			msblk->fragment_index[i] = fragment;
+		}
+	}
+
+	return 1;
+}
+
+
+static int supported_squashfs_filesystem(struct squashfs_sb_info *msblk, int silent)
+{
+	struct squashfs_super_block *sblk = &msblk->sblk;
+
+	msblk->iget = squashfs_iget;
+	msblk->read_blocklist = read_blocklist;
+	msblk->read_fragment_index_table = read_fragment_index_table;
+
+	if (sblk->s_major == 1) {
+		if (!squashfs_1_0_supported(msblk)) {
+			SERROR("Major/Minor mismatch, Squashfs 1.0 filesystems "
+				"are unsupported\n");
+			SERROR("Please recompile with "
+				"Squashfs 1.0 support enabled\n");
+			return 0;
+		}
+	} else if (sblk->s_major == 2) {
+		if (!squashfs_2_0_supported(msblk)) {
+			SERROR("Major/Minor mismatch, Squashfs 2.0 filesystems "
+				"are unsupported\n");
+			SERROR("Please recompile with "
+				"Squashfs 2.0 support enabled\n");
+			return 0;
+		}
+	} else if(sblk->s_major != SQUASHFS_MAJOR || sblk->s_minor >
+			SQUASHFS_MINOR) {
+		SERROR("Major/Minor mismatch, trying to mount newer %d.%d "
+				"filesystem\n", sblk->s_major, sblk->s_minor);
+		SERROR("Please update your kernel\n");
+		return 0;
+	}
+
+	return 1;
+}
+
+
+static struct super_block *squashfs_read_super(struct super_block *s,
+		void *data, int silent)
+{
+	kdev_t dev = s->s_dev;
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	int i;
+	struct inode *root;
+
+	if (!(msblk->stream.workspace = vmalloc(zlib_inflate_workspacesize()))) {
+		ERROR("Failed to allocate zlib workspace\n");
+		goto failed_mount;
+	}
+
+	msblk->devblksize = get_hardsect_size(dev);
+	if(msblk->devblksize < BLOCK_SIZE)
+		msblk->devblksize = BLOCK_SIZE;
+	msblk->devblksize_log2 = ffz(~msblk->devblksize);
+        set_blocksize(dev, msblk->devblksize);
+	s->s_blocksize = msblk->devblksize;
+	s->s_blocksize_bits = msblk->devblksize_log2;
+	
+	init_MUTEX(&msblk->read_data_mutex);
+	init_MUTEX(&msblk->read_page_mutex);
+	init_MUTEX(&msblk->block_cache_mutex);
+	init_MUTEX(&msblk->fragment_mutex);
+	
+	init_waitqueue_head(&msblk->waitq);
+	init_waitqueue_head(&msblk->fragment_wait_queue);
+
+	if (!squashfs_read_data(s, (char *) sblk, SQUASHFS_START,
+					sizeof(struct squashfs_super_block) |
+					SQUASHFS_COMPRESSED_BIT_BLOCK, NULL)) {
+		SERROR("unable to read superblock\n");
+		goto failed_mount;
+	}
+
+	/* Check it is a SQUASHFS superblock */
+	msblk->swap = 0;
+	if ((s->s_magic = sblk->s_magic) != SQUASHFS_MAGIC) {
+		if (sblk->s_magic == SQUASHFS_MAGIC_SWAP) {
+			struct squashfs_super_block ssblk;
+
+			WARNING("Mounting a different endian SQUASHFS "
+				"filesystem on %s\n", bdevname(dev));
+
+			SQUASHFS_SWAP_SUPER_BLOCK(&ssblk, sblk);
+			memcpy(sblk, &ssblk, sizeof(struct squashfs_super_block));
+			msblk->swap = 1;
+		} else  {
+			SERROR("Can't find a SQUASHFS superblock on %s\n",
+							bdevname(dev));
+			goto failed_mount;
+		}
+	}
+
+	/* Check the MAJOR & MINOR versions */
+	if(!supported_squashfs_filesystem(msblk, silent))
+		goto failed_mount;
+
+	TRACE("Found valid superblock on %s\n", bdevname(dev));
+	TRACE("Inodes are %scompressed\n",
+					SQUASHFS_UNCOMPRESSED_INODES
+					(sblk->flags) ? "un" : "");
+	TRACE("Data is %scompressed\n",
+					SQUASHFS_UNCOMPRESSED_DATA(sblk->flags)
+					? "un" : "");
+	TRACE("Check data is %s present in the filesystem\n",
+					SQUASHFS_CHECK_DATA(sblk->flags) ?
+					"" : "not");
+	TRACE("Filesystem size %lld bytes\n", sblk->bytes_used);
+	TRACE("Block size %d\n", sblk->block_size);
+	TRACE("Number of inodes %d\n", sblk->inodes);
+	if (sblk->s_major > 1)
+		TRACE("Number of fragments %d\n", sblk->fragments);
+	TRACE("Number of uids %d\n", sblk->no_uids);
+	TRACE("Number of gids %d\n", sblk->no_guids);
+	TRACE("sblk->inode_table_start %llx\n", sblk->inode_table_start);
+	TRACE("sblk->directory_table_start %llx\n", sblk->directory_table_start);
+	if (sblk->s_major > 1)
+		TRACE("sblk->fragment_table_start %llx\n",
+					sblk->fragment_table_start);
+	TRACE("sblk->uid_start %llx\n", sblk->uid_start);
+
+	s->s_flags |= MS_RDONLY;
+	s->s_op = &squashfs_ops;
+
+	/* Init inode_table block pointer array */
+	if (!(msblk->block_cache = kmalloc(sizeof(struct squashfs_cache) *
+					SQUASHFS_CACHED_BLKS, GFP_KERNEL))) {
+		ERROR("Failed to allocate block cache\n");
+		goto failed_mount;
+	}
+
+	for (i = 0; i < SQUASHFS_CACHED_BLKS; i++)
+		msblk->block_cache[i].block = SQUASHFS_INVALID_BLK;
+
+	msblk->next_cache = 0;
+
+	/* Allocate read_data block */
+	msblk->read_size = (sblk->block_size < SQUASHFS_METADATA_SIZE) ?
+					SQUASHFS_METADATA_SIZE :
+					sblk->block_size;
+
+	if (!(msblk->read_data = kmalloc(msblk->read_size, GFP_KERNEL))) {
+		ERROR("Failed to allocate read_data block\n");
+		goto failed_mount;
+	}
+
+	/* Allocate read_page block */
+	if (!(msblk->read_page = kmalloc(sblk->block_size, GFP_KERNEL))) {
+		ERROR("Failed to allocate read_page block\n");
+		goto failed_mount;
+	}
+
+	/* Allocate uid and gid tables */
+	if (!(msblk->uid = kmalloc((sblk->no_uids + sblk->no_guids) *
+					sizeof(unsigned int), GFP_KERNEL))) {
+		ERROR("Failed to allocate uid/gid table\n");
+		goto failed_mount;
+	}
+	msblk->guid = msblk->uid + sblk->no_uids;
+   
+	if (msblk->swap) {
+		unsigned int suid[sblk->no_uids + sblk->no_guids];
+
+		if (!squashfs_read_data(s, (char *) &suid, sblk->uid_start,
+					((sblk->no_uids + sblk->no_guids) *
+					 sizeof(unsigned int)) |
+					SQUASHFS_COMPRESSED_BIT_BLOCK, NULL)) {
+			ERROR("unable to read uid/gid table\n");
+			goto failed_mount;
+		}
+
+		SQUASHFS_SWAP_DATA(msblk->uid, suid, (sblk->no_uids +
+			sblk->no_guids), (sizeof(unsigned int) * 8));
+	} else
+		if (!squashfs_read_data(s, (char *) msblk->uid, sblk->uid_start,
+					((sblk->no_uids + sblk->no_guids) *
+					 sizeof(unsigned int)) |
+					SQUASHFS_COMPRESSED_BIT_BLOCK, NULL)) {
+			ERROR("unable to read uid/gid table\n");
+			goto failed_mount;
+		}
+
+
+	if (sblk->s_major == 1 && squashfs_1_0_supported(msblk))
+		goto allocate_root;
+
+	if (!(msblk->fragment = kmalloc(sizeof(struct squashfs_fragment_cache) *
+				SQUASHFS_CACHED_FRAGMENTS, GFP_KERNEL))) {
+		ERROR("Failed to allocate fragment block cache\n");
+		goto failed_mount;
+	}
+
+	for (i = 0; i < SQUASHFS_CACHED_FRAGMENTS; i++) {
+		msblk->fragment[i].locked = 0;
+		msblk->fragment[i].block = SQUASHFS_INVALID_BLK;
+		msblk->fragment[i].data = NULL;
+	}
+
+	msblk->next_fragment = 0;
+
+	/* Allocate fragment index table */
+	if(msblk->read_fragment_index_table(s) == 0)
+		goto failed_mount;
+
+allocate_root:
+	if ((root = (msblk->iget)(s, sblk->root_inode)) == NULL)
+		goto failed_mount;
+
+	if ((s->s_root = d_alloc_root(root)) == NULL) {
+		ERROR("Root inode create failed\n");
+		iput(root);
+		goto failed_mount;
+	}
+
+	TRACE("Leaving squashfs_read_super\n");
+	return s;
+
+failed_mount:
+	kfree(msblk->fragment_index);
+	kfree(msblk->fragment);
+	kfree(msblk->uid);
+	kfree(msblk->read_page);
+	kfree(msblk->read_data);
+	kfree(msblk->block_cache);
+	kfree(msblk->fragment_index_2);
+	vfree(msblk->stream.workspace);
+	return NULL;
+}
+
+
+static int squashfs_statfs(struct super_block *s, struct statfs *buf)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+
+	TRACE("Entered squashfs_statfs\n");
+
+	buf->f_type = SQUASHFS_MAGIC;
+	buf->f_bsize = sblk->block_size;
+	buf->f_blocks = ((sblk->bytes_used - 1) >> sblk->block_log) + 1;
+	buf->f_bfree = buf->f_bavail = 0;
+	buf->f_files = sblk->inodes;
+	buf->f_ffree = 0;
+	buf->f_namelen = SQUASHFS_NAME_LEN;
+
+	return 0;
+}
+
+
+static int squashfs_symlink_readpage(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	int index = page->index << PAGE_CACHE_SHIFT, length, bytes;
+	long long block = SQUASHFS_I(inode)->start_block;
+	int offset = SQUASHFS_I(inode)->offset;
+	void *pageaddr = kmap(page);
+
+	TRACE("Entered squashfs_symlink_readpage, page index %ld, start block "
+				"%llx, offset %x\n", page->index,
+				SQUASHFS_I(inode)->start_block,
+				SQUASHFS_I(inode)->offset);
+
+	for (length = 0; length < index; length += bytes) {
+		if (!(bytes = squashfs_get_cached_block(inode->i_sb, NULL,
+				block, offset, PAGE_CACHE_SIZE, &block,
+				&offset))) {
+			ERROR("Unable to read symbolic link [%llx:%x]\n", block,
+					offset);
+			goto skip_read;
+		}
+	}
+
+	if (length != index) {
+		ERROR("(squashfs_symlink_readpage) length != index\n");
+		bytes = 0;
+		goto skip_read;
+	}
+
+	bytes = (i_size_read(inode) - length) > PAGE_CACHE_SIZE ? PAGE_CACHE_SIZE :
+					i_size_read(inode) - length;
+
+	if (!(bytes = squashfs_get_cached_block(inode->i_sb, pageaddr, block,
+					offset, bytes, &block, &offset)))
+		ERROR("Unable to read symbolic link [%llx:%x]\n", block, offset);
+
+skip_read:
+	memset(pageaddr + bytes, 0, PAGE_CACHE_SIZE - bytes);
+	kunmap(page);
+	SetPageUptodate(page);
+	UnlockPage(page);
+
+	return 0;
+}
+
+
+struct meta_index *locate_meta_index(struct inode *inode, int index, int offset)
+{
+	struct meta_index *meta = NULL;
+	struct squashfs_sb_info *msblk = &inode->i_sb->u.squashfs_sb;
+	int i;
+
+	down(&msblk->meta_index_mutex);
+
+	TRACE("locate_meta_index: index %d, offset %d\n", index, offset);
+
+	if(msblk->meta_index == NULL)
+		goto not_allocated;
+
+	for (i = 0; i < SQUASHFS_META_NUMBER; i ++)
+		if (msblk->meta_index[i].inode_number == inode->i_ino &&
+				msblk->meta_index[i].offset >= offset &&
+				msblk->meta_index[i].offset <= index &&
+				msblk->meta_index[i].locked == 0) {
+			TRACE("locate_meta_index: entry %d, offset %d\n", i,
+					msblk->meta_index[i].offset);
+			meta = &msblk->meta_index[i];
+			offset = meta->offset;
+		}
+
+	if (meta)
+		meta->locked = 1;
+
+not_allocated:
+	up(&msblk->meta_index_mutex);
+
+	return meta;
+}
+
+
+struct meta_index *empty_meta_index(struct inode *inode, int offset, int skip)
+{
+	struct squashfs_sb_info *msblk = &inode->i_sb->u.squashfs_sb;
+	struct meta_index *meta = NULL;
+	int i;
+
+	down(&msblk->meta_index_mutex);
+
+	TRACE("empty_meta_index: offset %d, skip %d\n", offset, skip);
+
+	if(msblk->meta_index == NULL) {
+		if (!(msblk->meta_index = kmalloc(sizeof(struct meta_index) *
+					SQUASHFS_META_NUMBER, GFP_KERNEL))) {
+			ERROR("Failed to allocate meta_index\n");
+			goto failed;
+		}
+		for(i = 0; i < SQUASHFS_META_NUMBER; i++) {
+			msblk->meta_index[i].inode_number = 0;
+			msblk->meta_index[i].locked = 0;
+		}
+		msblk->next_meta_index = 0;
+	}
+
+	for(i = SQUASHFS_META_NUMBER; i &&
+			msblk->meta_index[msblk->next_meta_index].locked; i --)
+		msblk->next_meta_index = (msblk->next_meta_index + 1) %
+			SQUASHFS_META_NUMBER;
+
+	if(i == 0) {
+		TRACE("empty_meta_index: failed!\n");
+		goto failed;
+	}
+
+	TRACE("empty_meta_index: returned meta entry %d, %p\n",
+			msblk->next_meta_index,
+			&msblk->meta_index[msblk->next_meta_index]);
+
+	meta = &msblk->meta_index[msblk->next_meta_index];
+	msblk->next_meta_index = (msblk->next_meta_index + 1) %
+			SQUASHFS_META_NUMBER;
+
+	meta->inode_number = inode->i_ino;
+	meta->offset = offset;
+	meta->skip = skip;
+	meta->entries = 0;
+	meta->locked = 1;
+
+failed:
+	up(&msblk->meta_index_mutex);
+	return meta;
+}
+
+
+void release_meta_index(struct inode *inode, struct meta_index *meta)
+{
+	meta->locked = 0;
+}
+
+
+static int read_block_index(struct super_block *s, int blocks, char *block_list,
+		long long *start_block, int *offset)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	unsigned int *block_listp;
+	int block = 0;
+	
+	if (msblk->swap) {
+		char sblock_list[blocks << 2];
+
+		if (!squashfs_get_cached_block(s, sblock_list, *start_block,
+				*offset, blocks << 2, start_block, offset)) {
+			ERROR("Unable to read block list [%llx:%x]\n",
+				*start_block, *offset);
+			goto failure;
+		}
+		SQUASHFS_SWAP_INTS(((unsigned int *)block_list),
+				((unsigned int *)sblock_list), blocks);
+	} else
+		if (!squashfs_get_cached_block(s, block_list, *start_block,
+				*offset, blocks << 2, start_block, offset)) {
+			ERROR("Unable to read block list [%llx:%x]\n",
+				*start_block, *offset);
+			goto failure;
+		}
+
+	for (block_listp = (unsigned int *) block_list; blocks;
+				block_listp++, blocks --)
+		block += SQUASHFS_COMPRESSED_SIZE_BLOCK(*block_listp);
+
+	return block;
+
+failure:
+	return -1;
+}
+
+
+#define SIZE 256
+
+static inline int calculate_skip(int blocks) {
+	int skip = (blocks - 1) / ((SQUASHFS_SLOTS * SQUASHFS_META_ENTRIES + 1) * SQUASHFS_META_INDEXES);
+	return skip >= 7 ? 7 : skip + 1;
+}
+
+
+static int get_meta_index(struct inode *inode, int index,
+		long long *index_block, int *index_offset,
+		long long *data_block, char *block_list)
+{
+	struct squashfs_sb_info *msblk = &inode->i_sb->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	int skip = calculate_skip(i_size_read(inode) >> sblk->block_log);
+	int offset = 0;
+	struct meta_index *meta;
+	struct meta_entry *meta_entry;
+	long long cur_index_block = SQUASHFS_I(inode)->u.s1.block_list_start;
+	int cur_offset = SQUASHFS_I(inode)->offset;
+	long long cur_data_block = SQUASHFS_I(inode)->start_block;
+	int i;
+ 
+	index /= SQUASHFS_META_INDEXES * skip;
+
+	while ( offset < index ) {
+		meta = locate_meta_index(inode, index, offset + 1);
+
+		if (meta == NULL) {
+			if ((meta = empty_meta_index(inode, offset + 1,
+							skip)) == NULL)
+				goto all_done;
+		} else {
+			offset = index < meta->offset + meta->entries ? index :
+				meta->offset + meta->entries - 1;
+			meta_entry = &meta->meta_entry[offset - meta->offset];
+			cur_index_block = meta_entry->index_block + sblk->inode_table_start;
+			cur_offset = meta_entry->offset;
+			cur_data_block = meta_entry->data_block;
+			TRACE("get_meta_index: offset %d, meta->offset %d, "
+				"meta->entries %d\n", offset, meta->offset,
+				meta->entries);
+			TRACE("get_meta_index: index_block 0x%llx, offset 0x%x"
+				" data_block 0x%llx\n", cur_index_block,
+				cur_offset, cur_data_block);
+		}
+
+		for (i = meta->offset + meta->entries; i <= index &&
+				i < meta->offset + SQUASHFS_META_ENTRIES; i++) {
+			int blocks = skip * SQUASHFS_META_INDEXES;
+
+			while (blocks) {
+				int block = blocks > (SIZE >> 2) ? (SIZE >> 2) :
+					blocks;
+				int res = read_block_index(inode->i_sb, block,
+					block_list, &cur_index_block,
+					&cur_offset);
+
+				if (res == -1)
+					goto failed;
+
+				cur_data_block += res;
+				blocks -= block;
+			}
+
+			meta_entry = &meta->meta_entry[i - meta->offset];
+			meta_entry->index_block = cur_index_block - sblk->inode_table_start;
+			meta_entry->offset = cur_offset;
+			meta_entry->data_block = cur_data_block;
+			meta->entries ++;
+			offset ++;
+		}
+
+		TRACE("get_meta_index: meta->offset %d, meta->entries %d\n",
+				meta->offset, meta->entries);
+
+		release_meta_index(inode, meta);
+	}
+
+all_done:
+	*index_block = cur_index_block;
+	*index_offset = cur_offset;
+	*data_block = cur_data_block;
+
+	return offset * SQUASHFS_META_INDEXES * skip;
+
+failed:
+	release_meta_index(inode, meta);
+	return -1;
+}
+
+
+static long long read_blocklist(struct inode *inode, int index,
+				int readahead_blks, char *block_list,
+				unsigned short **block_p, unsigned int *bsize)
+{
+	long long block_ptr;
+	int offset;
+	long long block;
+	int res = get_meta_index(inode, index, &block_ptr, &offset, &block,
+		block_list);
+
+	TRACE("read_blocklist: res %d, index %d, block_ptr 0x%llx, offset"
+		       " 0x%x, block 0x%llx\n", res, index, block_ptr, offset,
+		       block);
+
+	if(res == -1)
+		goto failure;
+
+	index -= res;
+
+	while ( index ) {
+		int blocks = index > (SIZE >> 2) ? (SIZE >> 2) : index;
+		int res = read_block_index(inode->i_sb, blocks, block_list,
+			&block_ptr, &offset);
+		if (res == -1)
+			goto failure;
+		block += res;
+		index -= blocks;
+	}
+
+	if (read_block_index(inode->i_sb, 1, block_list,
+			&block_ptr, &offset) == -1)
+		goto failure;
+	*bsize = *((unsigned int *) block_list);
+
+	return block;
+
+failure:
+	return 0;
+}
+
+
+static int squashfs_readpage(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	struct squashfs_sb_info *msblk = &inode->i_sb->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	unsigned char block_list[SIZE];
+	long long block;
+	unsigned int bsize, i = 0, bytes = 0, byte_offset = 0;
+	int index = page->index >> (sblk->block_log - PAGE_CACHE_SHIFT);
+ 	void *pageaddr;
+	struct squashfs_fragment_cache *fragment = NULL;
+	char *data_ptr = msblk->read_page;
+	
+	int mask = (1 << (sblk->block_log - PAGE_CACHE_SHIFT)) - 1;
+	int start_index = page->index & ~mask;
+	int end_index = start_index | mask;
+
+	TRACE("Entered squashfs_readpage, page index %lx, start block %llx\n",
+					page->index,
+					SQUASHFS_I(inode)->start_block);
+
+	if (page->index >= ((i_size_read(inode) + PAGE_CACHE_SIZE - 1) >>
+					PAGE_CACHE_SHIFT))
+		goto skip_read;
+
+	if (SQUASHFS_I(inode)->u.s1.fragment_start_block == SQUASHFS_INVALID_BLK
+					|| index < (i_size_read(inode) >>
+					sblk->block_log)) {
+		if ((block = (msblk->read_blocklist)(inode, index, 1,
+					block_list, NULL, &bsize)) == 0)
+			goto skip_read;
+
+		down(&msblk->read_page_mutex);
+		
+		if (!(bytes = squashfs_read_data(inode->i_sb, msblk->read_page,
+					block, bsize, NULL))) {
+			ERROR("Unable to read page, block %llx, size %x\n", block,
+					bsize);
+			up(&msblk->read_page_mutex);
+			goto skip_read;
+		}
+	} else {
+		if ((fragment = get_cached_fragment(inode->i_sb,
+					SQUASHFS_I(inode)->
+					u.s1.fragment_start_block,
+					SQUASHFS_I(inode)->u.s1.fragment_size))
+					== NULL) {
+			ERROR("Unable to read page, block %llx, size %x\n",
+					SQUASHFS_I(inode)->
+					u.s1.fragment_start_block,
+					(int) SQUASHFS_I(inode)->
+					u.s1.fragment_size);
+			goto skip_read;
+		}
+		bytes = SQUASHFS_I(inode)->u.s1.fragment_offset +
+					(i_size_read(inode) & (sblk->block_size
+					- 1));
+		byte_offset = SQUASHFS_I(inode)->u.s1.fragment_offset;
+		data_ptr = fragment->data;
+	}
+
+	for (i = start_index; i <= end_index && byte_offset < bytes;
+					i++, byte_offset += PAGE_CACHE_SIZE) {
+		struct page *push_page;
+		int available_bytes = (bytes - byte_offset) > PAGE_CACHE_SIZE ?
+					PAGE_CACHE_SIZE : bytes - byte_offset;
+
+		TRACE("bytes %d, i %d, byte_offset %d, available_bytes %d\n",
+					bytes, i, byte_offset, available_bytes);
+
+		if (i == page->index)  {
+			pageaddr = kmap_atomic(page, KM_USER0);
+			memcpy(pageaddr, data_ptr + byte_offset,
+					available_bytes);
+			memset(pageaddr + available_bytes, 0,
+					PAGE_CACHE_SIZE - available_bytes);
+			kunmap_atomic(pageaddr, KM_USER0);
+			flush_dcache_page(page);
+			SetPageUptodate(page);
+			UnlockPage(page);
+		} else if ((push_page =
+				grab_cache_page_nowait(page->mapping, i))) {
+ 			pageaddr = kmap_atomic(push_page, KM_USER0);
+
+			memcpy(pageaddr, data_ptr + byte_offset,
+					available_bytes);
+			memset(pageaddr + available_bytes, 0,
+					PAGE_CACHE_SIZE - available_bytes);
+			kunmap_atomic(pageaddr, KM_USER0);
+			flush_dcache_page(push_page);
+			SetPageUptodate(push_page);
+			UnlockPage(push_page);
+			page_cache_release(push_page);
+		}
+	}
+
+	if (SQUASHFS_I(inode)->u.s1.fragment_start_block == SQUASHFS_INVALID_BLK
+					|| index < (i_size_read(inode) >>
+					sblk->block_log))
+		up(&msblk->read_page_mutex);
+	else
+		release_cached_fragment(msblk, fragment);
+
+	return 0;
+
+skip_read:
+	pageaddr = kmap_atomic(page, KM_USER0);
+	memset(pageaddr + bytes, 0, PAGE_CACHE_SIZE - bytes);
+	kunmap_atomic(pageaddr, KM_USER0);
+	flush_dcache_page(page);
+	SetPageUptodate(page);
+	UnlockPage(page);
+
+	return 0;
+}
+
+
+static int squashfs_readpage4K(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	struct squashfs_sb_info *msblk = &inode->i_sb->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	unsigned char block_list[SIZE];
+	long long block;
+	unsigned int bsize, bytes = 0;
+ 	void *pageaddr;
+	
+	TRACE("Entered squashfs_readpage4K, page index %lx, start block %llx\n",
+					page->index,
+					SQUASHFS_I(inode)->start_block);
+
+	if (page->index >= ((i_size_read(inode) + PAGE_CACHE_SIZE - 1) >>
+					PAGE_CACHE_SHIFT)) {
+		pageaddr = kmap_atomic(page, KM_USER0);
+		goto skip_read;
+	}
+
+	if (SQUASHFS_I(inode)->u.s1.fragment_start_block == SQUASHFS_INVALID_BLK
+					|| page->index < (i_size_read(inode) >>
+					sblk->block_log)) {
+		block = (msblk->read_blocklist)(inode, page->index, 1,
+					block_list, NULL, &bsize);
+
+		down(&msblk->read_page_mutex);
+		bytes = squashfs_read_data(inode->i_sb, msblk->read_page, block,
+					bsize, NULL);
+		pageaddr = kmap_atomic(page, KM_USER0);
+		if (bytes)
+			memcpy(pageaddr, msblk->read_page, bytes);
+		else
+			ERROR("Unable to read page, block %llx, size %x\n",
+					block, bsize);
+		up(&msblk->read_page_mutex);
+	} else {
+		struct squashfs_fragment_cache *fragment =
+			get_cached_fragment(inode->i_sb,
+					SQUASHFS_I(inode)->
+					u.s1.fragment_start_block,
+					SQUASHFS_I(inode)-> u.s1.fragment_size);
+		pageaddr = kmap_atomic(page, KM_USER0);
+		if (fragment) {
+			bytes = i_size_read(inode) & (sblk->block_size - 1);
+			memcpy(pageaddr, fragment->data + SQUASHFS_I(inode)->
+					u.s1.fragment_offset, bytes);
+			release_cached_fragment(msblk, fragment);
+		} else
+			ERROR("Unable to read page, block %llx, size %x\n",
+					SQUASHFS_I(inode)->
+					u.s1.fragment_start_block, (int)
+					SQUASHFS_I(inode)-> u.s1.fragment_size);
+	}
+
+skip_read:
+	memset(pageaddr + bytes, 0, PAGE_CACHE_SIZE - bytes);
+	kunmap_atomic(pageaddr, KM_USER0);
+	flush_dcache_page(page);
+	SetPageUptodate(page);
+	UnlockPage(page);
+
+	return 0;
+}
+
+
+static int get_dir_index_using_offset(struct super_block *s, long long 
+				*next_block, unsigned int *next_offset,
+				long long index_start,
+				unsigned int index_offset, int i_count,
+				long long f_pos)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	int i, length = 0;
+	struct squashfs_dir_index index;
+
+	TRACE("Entered get_dir_index_using_offset, i_count %d, f_pos %d\n",
+					i_count, (unsigned int) f_pos);
+
+	f_pos -= 3;
+	if (f_pos == 0)
+		goto finish;
+
+	for (i = 0; i < i_count; i++) {
+		if (msblk->swap) {
+			struct squashfs_dir_index sindex;
+			squashfs_get_cached_block(s, (char *) &sindex,
+					index_start, index_offset,
+					sizeof(sindex), &index_start,
+					&index_offset);
+			SQUASHFS_SWAP_DIR_INDEX(&index, &sindex);
+		} else
+			squashfs_get_cached_block(s, (char *) &index,
+					index_start, index_offset,
+					sizeof(index), &index_start,
+					&index_offset);
+
+		if (index.index > f_pos)
+			break;
+
+		squashfs_get_cached_block(s, NULL, index_start, index_offset,
+					index.size + 1, &index_start,
+					&index_offset);
+
+		length = index.index;
+		*next_block = index.start_block + sblk->directory_table_start;
+	}
+
+	*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
+
+finish:
+	return length + 3;
+}
+
+
+static int get_dir_index_using_name(struct super_block *s, long long
+				*next_block, unsigned int *next_offset,
+				long long index_start,
+				unsigned int index_offset, int i_count,
+				const char *name, int size)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	int i, length = 0;
+	char buffer[sizeof(struct squashfs_dir_index) + SQUASHFS_NAME_LEN + 1];
+	struct squashfs_dir_index *index = (struct squashfs_dir_index *) buffer;
+	char str[SQUASHFS_NAME_LEN + 1];
+
+	TRACE("Entered get_dir_index_using_name, i_count %d\n", i_count);
+
+	strncpy(str, name, size);
+	str[size] = '\0';
+
+	for (i = 0; i < i_count; i++) {
+		if (msblk->swap) {
+			struct squashfs_dir_index sindex;
+			squashfs_get_cached_block(s, (char *) &sindex,
+					index_start, index_offset,
+					sizeof(sindex), &index_start,
+					&index_offset);
+			SQUASHFS_SWAP_DIR_INDEX(index, &sindex);
+		} else
+			squashfs_get_cached_block(s, (char *) index,
+					index_start, index_offset,
+					sizeof(struct squashfs_dir_index),
+					&index_start, &index_offset);
+
+		squashfs_get_cached_block(s, index->name, index_start,
+					index_offset, index->size + 1,
+					&index_start, &index_offset);
+
+		index->name[index->size + 1] = '\0';
+
+		if (strcmp(index->name, str) > 0)
+			break;
+
+		length = index->index;
+		*next_block = index->start_block + sblk->directory_table_start;
+	}
+
+	*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
+	return length + 3;
+}
+
+		
+static int squashfs_readdir(struct file *file, void *dirent, filldir_t filldir)
+{
+	struct inode *i = file->f_dentry->d_inode;
+	struct squashfs_sb_info *msblk = &i->i_sb->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	long long next_block = SQUASHFS_I(i)->start_block +
+		sblk->directory_table_start;
+	int next_offset = SQUASHFS_I(i)->offset, length = 0,
+		dir_count;
+	struct squashfs_dir_header dirh;
+	char buffer[sizeof(struct squashfs_dir_entry) + SQUASHFS_NAME_LEN + 1];
+	struct squashfs_dir_entry *dire = (struct squashfs_dir_entry *) buffer;
+
+	TRACE("Entered squashfs_readdir [%llx:%x]\n", next_block, next_offset);
+
+	while(file->f_pos < 3) {
+		char *name;
+		int size, i_ino;
+
+		if(file->f_pos == 0) {
+			name = ".";
+			size = 1;
+			i_ino = i->i_ino;
+		} else {
+			name = "..";
+			size = 2;
+			i_ino = SQUASHFS_I(i)->u.s2.parent_inode;
+		}
+		TRACE("Calling filldir(%x, %s, %d, %d, %d, %d)\n",
+				(unsigned int) dirent, name, size, (int)
+				file->f_pos, i_ino,
+				squashfs_filetype_table[1]);
+
+		if (filldir(dirent, name, size,
+				file->f_pos, i_ino,
+				squashfs_filetype_table[1]) < 0) {
+				TRACE("Filldir returned less than 0\n");
+				goto finish;
+		}
+		file->f_pos += size;
+	}
+
+	length = get_dir_index_using_offset(i->i_sb, &next_block, &next_offset,
+				SQUASHFS_I(i)->u.s2.directory_index_start,
+				SQUASHFS_I(i)->u.s2.directory_index_offset,
+				SQUASHFS_I(i)->u.s2.directory_index_count,
+				file->f_pos);
+
+	while (length < i_size_read(i)) {
+		/* read directory header */
+		if (msblk->swap) {
+			struct squashfs_dir_header sdirh;
+			
+			if (!squashfs_get_cached_block(i->i_sb, (char *) &sdirh,
+					next_block, next_offset, sizeof(sdirh),
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += sizeof(sdirh);
+			SQUASHFS_SWAP_DIR_HEADER(&dirh, &sdirh);
+		} else {
+			if (!squashfs_get_cached_block(i->i_sb, (char *) &dirh,
+					next_block, next_offset, sizeof(dirh),
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += sizeof(dirh);
+		}
+
+		dir_count = dirh.count + 1;
+		while (dir_count--) {
+			if (msblk->swap) {
+				struct squashfs_dir_entry sdire;
+				if (!squashfs_get_cached_block(i->i_sb, (char *)
+						&sdire, next_block, next_offset,
+						sizeof(sdire), &next_block,
+						&next_offset))
+					goto failed_read;
+				
+				length += sizeof(sdire);
+				SQUASHFS_SWAP_DIR_ENTRY(dire, &sdire);
+			} else {
+				if (!squashfs_get_cached_block(i->i_sb, (char *)
+						dire, next_block, next_offset,
+						sizeof(*dire), &next_block,
+						&next_offset))
+					goto failed_read;
+
+				length += sizeof(*dire);
+			}
+
+			if (!squashfs_get_cached_block(i->i_sb, dire->name,
+						next_block, next_offset,
+						dire->size + 1, &next_block,
+						&next_offset))
+				goto failed_read;
+
+			length += dire->size + 1;
+
+			if (file->f_pos >= length)
+				continue;
+
+			dire->name[dire->size + 1] = '\0';
+
+			TRACE("Calling filldir(%x, %s, %d, %d, %x:%x, %d, %d)\n",
+					(unsigned int) dirent, dire->name,
+					dire->size + 1, (int) file->f_pos,
+					dirh.start_block, dire->offset,
+					dirh.inode_number + dire->inode_number,
+					squashfs_filetype_table[dire->type]);
+
+			if (filldir(dirent, dire->name, dire->size + 1,
+					file->f_pos,
+					dirh.inode_number + dire->inode_number,
+					squashfs_filetype_table[dire->type])
+					< 0) {
+				TRACE("Filldir returned less than 0\n");
+				goto finish;
+			}
+			file->f_pos = length;
+		}
+	}
+
+finish:
+	return 0;
+
+failed_read:
+	ERROR("Unable to read directory block [%llx:%x]\n", next_block,
+		next_offset);
+	return 0;
+}
+
+
+static struct dentry *squashfs_lookup(struct inode *i, struct dentry *dentry)
+{
+	const unsigned char *name = dentry->d_name.name;
+	int len = dentry->d_name.len;
+	struct inode *inode = NULL;
+	struct squashfs_sb_info *msblk = &i->i_sb->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	long long next_block = SQUASHFS_I(i)->start_block +
+				sblk->directory_table_start;
+	int next_offset = SQUASHFS_I(i)->offset, length = 0,
+				dir_count;
+	struct squashfs_dir_header dirh;
+	char buffer[sizeof(struct squashfs_dir_entry) + SQUASHFS_NAME_LEN];
+	struct squashfs_dir_entry *dire = (struct squashfs_dir_entry *) buffer;
+
+	TRACE("Entered squashfs_lookup [%llx:%x]\n", next_block, next_offset);
+
+	if (len > SQUASHFS_NAME_LEN)
+		goto exit_loop;
+
+	length = get_dir_index_using_name(i->i_sb, &next_block, &next_offset,
+				SQUASHFS_I(i)->u.s2.directory_index_start,
+				SQUASHFS_I(i)->u.s2.directory_index_offset,
+				SQUASHFS_I(i)->u.s2.directory_index_count, name,
+				len);
+
+	while (length < i_size_read(i)) {
+		/* read directory header */
+		if (msblk->swap) {
+			struct squashfs_dir_header sdirh;
+			if (!squashfs_get_cached_block(i->i_sb, (char *) &sdirh,
+					next_block, next_offset, sizeof(sdirh),
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += sizeof(sdirh);
+			SQUASHFS_SWAP_DIR_HEADER(&dirh, &sdirh);
+		} else {
+			if (!squashfs_get_cached_block(i->i_sb, (char *) &dirh,
+					next_block, next_offset, sizeof(dirh),
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += sizeof(dirh);
+		}
+
+		dir_count = dirh.count + 1;
+		while (dir_count--) {
+			if (msblk->swap) {
+				struct squashfs_dir_entry sdire;
+				if (!squashfs_get_cached_block(i->i_sb, (char *)
+						&sdire, next_block,next_offset,
+						sizeof(sdire), &next_block,
+						&next_offset))
+					goto failed_read;
+				
+				length += sizeof(sdire);
+				SQUASHFS_SWAP_DIR_ENTRY(dire, &sdire);
+			} else {
+				if (!squashfs_get_cached_block(i->i_sb, (char *)
+						dire, next_block,next_offset,
+						sizeof(*dire), &next_block,
+						&next_offset))
+					goto failed_read;
+
+				length += sizeof(*dire);
+			}
+
+			if (!squashfs_get_cached_block(i->i_sb, dire->name,
+					next_block, next_offset, dire->size + 1,
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += dire->size + 1;
+
+			if (name[0] < dire->name[0])
+				goto exit_loop;
+
+			if ((len == dire->size + 1) && !strncmp(name,
+						dire->name, len)) {
+				squashfs_inode_t ino =
+					SQUASHFS_MKINODE(dirh.start_block,
+					dire->offset);
+
+				TRACE("calling squashfs_iget for directory "
+					"entry %s, inode %x:%x, %d\n", name,
+					dirh.start_block, dire->offset,
+					dirh.inode_number + dire->inode_number);
+
+				inode = (msblk->iget)(i->i_sb, ino);
+
+				goto exit_loop;
+			}
+		}
+	}
+
+exit_loop:
+	d_add(dentry, inode);
+	return ERR_PTR(0);
+
+failed_read:
+	ERROR("Unable to read directory block [%llx:%x]\n", next_block,
+		next_offset);
+	goto exit_loop;
+}
+
+
+static void squashfs_put_super(struct super_block *s)
+{
+	int i;
+
+		struct squashfs_sb_info *sbi = &s->u.squashfs_sb;
+		if (sbi->block_cache)
+			for (i = 0; i < SQUASHFS_CACHED_BLKS; i++)
+				if (sbi->block_cache[i].block !=
+							SQUASHFS_INVALID_BLK)
+					kfree(sbi->block_cache[i].data);
+		if (sbi->fragment)
+			for (i = 0; i < SQUASHFS_CACHED_FRAGMENTS; i++) 
+				SQUASHFS_FREE(sbi->fragment[i].data);
+		kfree(sbi->fragment);
+		kfree(sbi->block_cache);
+		kfree(sbi->read_data);
+		kfree(sbi->read_page);
+		kfree(sbi->uid);
+		kfree(sbi->fragment_index);
+		kfree(sbi->fragment_index_2);
+		kfree(sbi->meta_index);
+		vfree(sbi->stream.workspace);
+		sbi->block_cache = NULL;
+		sbi->uid = NULL;
+		sbi->read_data = NULL;
+		sbi->read_page = NULL;
+		sbi->fragment = NULL;
+		sbi->fragment_index = NULL;
+		sbi->fragment_index_2 = NULL;
+		sbi->meta_index = NULL;
+		sbi->stream.workspace = NULL;
+}
+
+
+static int __init init_squashfs_fs(void)
+{
+
+	printk(KERN_INFO "squashfs: version 3.1 (2006/08/15) "
+		"Phillip Lougher\n");
+
+	return register_filesystem(&squashfs_fs_type);
+}
+
+
+static void __exit exit_squashfs_fs(void)
+{
+	unregister_filesystem(&squashfs_fs_type);
+}
+
+
+EXPORT_NO_SYMBOLS;
+
+module_init(init_squashfs_fs);
+module_exit(exit_squashfs_fs);
+MODULE_DESCRIPTION("squashfs 3.1, a compressed read-only filesystem");
+MODULE_AUTHOR("Phillip Lougher <phillip@lougher.demon.co.uk>");
+MODULE_LICENSE("GPL");
diff --git a/squashfs-tools/kernel-2.4/fs/squashfs/squashfs.h b/squashfs-tools/kernel-2.4/fs/squashfs/squashfs.h
new file mode 100755
index 0000000..b5f93fb
--- /dev/null
+++ b/squashfs-tools/kernel-2.4/fs/squashfs/squashfs.h
@@ -0,0 +1,85 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006
+ * Phillip Lougher <phillip@lougher.demon.co.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.
+ *
+ * squashfs.h
+ */
+
+#ifdef CONFIG_SQUASHFS_1_0_COMPATIBILITY
+#undef CONFIG_SQUASHFS_1_0_COMPATIBILITY
+#endif
+#ifdef SQUASHFS_TRACE
+#define TRACE(s, args...)	printk(KERN_NOTICE "SQUASHFS: "s, ## args)
+#else
+#define TRACE(s, args...)	{}
+#endif
+
+#define ERROR(s, args...)	printk(KERN_ERR "SQUASHFS error: "s, ## args)
+
+#define SERROR(s, args...)	do { \
+				if (!silent) \
+				printk(KERN_ERR "SQUASHFS error: "s, ## args);\
+				} while(0)
+
+#define WARNING(s, args...)	printk(KERN_WARNING "SQUASHFS: "s, ## args)
+
+#define SQUASHFS_I(INO)			(&INO->u.squashfs_i)
+
+#define i_size_read(INO)		(INO->i_size)
+
+#if defined(CONFIG_SQUASHFS_1_0_COMPATIBILITY ) || defined(CONFIG_SQUASHFS_2_0_COMPATIBILITY)
+#define SQSH_EXTERN
+extern unsigned int squashfs_read_data(struct super_block *s, char *buffer,
+				long long index, unsigned int length,
+				long long *next_index);
+extern int squashfs_get_cached_block(struct super_block *s, char *buffer,
+				long long block, unsigned int offset,
+				int length, long long *next_block,
+				unsigned int *next_offset);
+extern void release_cached_fragment(struct squashfs_sb_info *msblk, struct
+					squashfs_fragment_cache *fragment);
+extern struct squashfs_fragment_cache *get_cached_fragment(struct super_block
+					*s, long long start_block,
+					int length);
+extern struct address_space_operations squashfs_symlink_aops;
+extern struct address_space_operations squashfs_aops;
+extern struct address_space_operations squashfs_aops_4K;
+extern struct file_operations squashfs_dir_ops;
+extern struct inode_operations squashfs_dir_inode_ops;
+#else
+#define SQSH_EXTERN static
+#endif
+
+#ifdef CONFIG_SQUASHFS_1_0_COMPATIBILITY
+extern int squashfs_1_0_supported(struct squashfs_sb_info *msblk);
+#else
+static inline int squashfs_1_0_supported(struct squashfs_sb_info *msblk)
+{
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_SQUASHFS_2_0_COMPATIBILITY
+extern int squashfs_2_0_supported(struct squashfs_sb_info *msblk);
+#else
+static inline int squashfs_2_0_supported(struct squashfs_sb_info *msblk)
+{
+	return 0;
+}
+#endif
diff --git a/squashfs-tools/kernel-2.4/fs/squashfs/squashfs2_0.c b/squashfs-tools/kernel-2.4/fs/squashfs/squashfs2_0.c
new file mode 100755
index 0000000..6411a04
--- /dev/null
+++ b/squashfs-tools/kernel-2.4/fs/squashfs/squashfs2_0.c
@@ -0,0 +1,751 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006
+ * Phillip Lougher <phillip@lougher.demon.co.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.
+ *
+ * squashfs2_0.c
+ */
+
+#include <linux/types.h>
+#include <linux/squashfs_fs.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/zlib.h>
+#include <linux/fs.h>
+#include <linux/smp_lock.h>
+#include <linux/locks.h>
+#include <linux/init.h>
+#include <linux/dcache.h>
+#include <linux/wait.h>
+#include <linux/zlib.h>
+#include <linux/blkdev.h>
+#include <linux/vmalloc.h>
+#include <asm/uaccess.h>
+#include <asm/semaphore.h>
+#include "squashfs.h"
+
+static int squashfs_readdir_2(struct file *file, void *dirent, filldir_t filldir);
+static struct dentry *squashfs_lookup_2(struct inode *i, struct dentry *dentry);
+
+static struct file_operations squashfs_dir_ops_2 = {
+	.read = generic_read_dir,
+	.readdir = squashfs_readdir_2
+};
+
+static struct inode_operations squashfs_dir_inode_ops_2 = {
+	.lookup = squashfs_lookup_2
+};
+
+static unsigned char squashfs_filetype_table[] = {
+	DT_UNKNOWN, DT_DIR, DT_REG, DT_LNK, DT_BLK, DT_CHR, DT_FIFO, DT_SOCK
+};
+
+static int read_fragment_index_table_2(struct super_block *s)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+
+	if (!(msblk->fragment_index_2 = kmalloc(SQUASHFS_FRAGMENT_INDEX_BYTES_2
+					(sblk->fragments), GFP_KERNEL))) {
+		ERROR("Failed to allocate uid/gid table\n");
+		return 0;
+	}
+   
+	if (SQUASHFS_FRAGMENT_INDEX_BYTES_2(sblk->fragments) &&
+					!squashfs_read_data(s, (char *)
+					msblk->fragment_index_2,
+					sblk->fragment_table_start,
+					SQUASHFS_FRAGMENT_INDEX_BYTES_2
+					(sblk->fragments) |
+					SQUASHFS_COMPRESSED_BIT_BLOCK, NULL)) {
+		ERROR("unable to read fragment index table\n");
+		return 0;
+	}
+
+	if (msblk->swap) {
+		int i;
+		unsigned int fragment;
+
+		for (i = 0; i < SQUASHFS_FRAGMENT_INDEXES_2(sblk->fragments);
+									i++) {
+			SQUASHFS_SWAP_FRAGMENT_INDEXES_2((&fragment),
+						&msblk->fragment_index_2[i], 1);
+			msblk->fragment_index_2[i] = fragment;
+		}
+	}
+
+	return 1;
+}
+
+
+static int get_fragment_location_2(struct super_block *s, unsigned int fragment,
+				long long *fragment_start_block,
+				unsigned int *fragment_size)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	long long start_block =
+		msblk->fragment_index_2[SQUASHFS_FRAGMENT_INDEX_2(fragment)];
+	int offset = SQUASHFS_FRAGMENT_INDEX_OFFSET_2(fragment);
+	struct squashfs_fragment_entry_2 fragment_entry;
+
+	if (msblk->swap) {
+		struct squashfs_fragment_entry_2 sfragment_entry;
+
+		if (!squashfs_get_cached_block(s, (char *) &sfragment_entry,
+					start_block, offset,
+					sizeof(sfragment_entry), &start_block,
+					&offset))
+			goto out;
+		SQUASHFS_SWAP_FRAGMENT_ENTRY_2(&fragment_entry, &sfragment_entry);
+	} else
+		if (!squashfs_get_cached_block(s, (char *) &fragment_entry,
+					start_block, offset,
+					sizeof(fragment_entry), &start_block,
+					&offset))
+			goto out;
+
+	*fragment_start_block = fragment_entry.start_block;
+	*fragment_size = fragment_entry.size;
+
+	return 1;
+
+out:
+	return 0;
+}
+
+
+static struct inode *squashfs_new_inode(struct super_block *s,
+		struct squashfs_base_inode_header_2 *inodeb, unsigned int ino)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	struct inode *i = new_inode(s);
+
+	if (i) {
+		i->i_ino = ino;
+		i->i_mtime = sblk->mkfs_time;
+		i->i_atime = sblk->mkfs_time;
+		i->i_ctime = sblk->mkfs_time;
+		i->i_uid = msblk->uid[inodeb->uid];
+		i->i_mode = inodeb->mode;
+		i->i_nlink = 1;
+		i->i_size = 0;
+		if (inodeb->guid == SQUASHFS_GUIDS)
+			i->i_gid = i->i_uid;
+		else
+			i->i_gid = msblk->guid[inodeb->guid];
+	}
+
+	return i;
+}
+
+
+static struct inode *squashfs_iget_2(struct super_block *s, squashfs_inode_t inode)
+{
+	struct inode *i;
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	unsigned int block = SQUASHFS_INODE_BLK(inode) +
+		sblk->inode_table_start;
+	unsigned int offset = SQUASHFS_INODE_OFFSET(inode);
+	unsigned int ino = SQUASHFS_MK_VFS_INODE(block
+		- sblk->inode_table_start, offset);
+	long long next_block;
+	unsigned int next_offset;
+	union squashfs_inode_header_2 id, sid;
+	struct squashfs_base_inode_header_2 *inodeb = &id.base,
+					  *sinodeb = &sid.base;
+
+	TRACE("Entered squashfs_iget\n");
+
+	if (msblk->swap) {
+		if (!squashfs_get_cached_block(s, (char *) sinodeb, block,
+					offset, sizeof(*sinodeb), &next_block,
+					&next_offset))
+			goto failed_read;
+		SQUASHFS_SWAP_BASE_INODE_HEADER_2(inodeb, sinodeb,
+					sizeof(*sinodeb));
+	} else
+		if (!squashfs_get_cached_block(s, (char *) inodeb, block,
+					offset, sizeof(*inodeb), &next_block,
+					&next_offset))
+			goto failed_read;
+
+	switch(inodeb->inode_type) {
+		case SQUASHFS_FILE_TYPE: {
+			struct squashfs_reg_inode_header_2 *inodep = &id.reg;
+			struct squashfs_reg_inode_header_2 *sinodep = &sid.reg;
+			long long frag_blk;
+			unsigned int frag_size;
+				
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_REG_INODE_HEADER_2(inodep, sinodep);
+			} else
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			frag_blk = SQUASHFS_INVALID_BLK;
+			if (inodep->fragment != SQUASHFS_INVALID_FRAG &&
+					!get_fragment_location_2(s,
+					inodep->fragment, &frag_blk, &frag_size))
+				goto failed_read;
+				
+			if((i = squashfs_new_inode(s, inodeb, ino)) == NULL)
+				goto failed_read1;
+
+			i->i_size = inodep->file_size;
+			i->i_fop = &generic_ro_fops;
+			i->i_mode |= S_IFREG;
+			i->i_mtime = inodep->mtime;
+			i->i_atime = inodep->mtime;
+			i->i_ctime = inodep->mtime;
+			i->i_blocks = ((i->i_size - 1) >> 9) + 1;
+			i->i_blksize = PAGE_CACHE_SIZE;
+			SQUASHFS_I(i)->u.s1.fragment_start_block = frag_blk;
+			SQUASHFS_I(i)->u.s1.fragment_size = frag_size;
+			SQUASHFS_I(i)->u.s1.fragment_offset = inodep->offset;
+			SQUASHFS_I(i)->start_block = inodep->start_block;
+			SQUASHFS_I(i)->u.s1.block_list_start = next_block;
+			SQUASHFS_I(i)->offset = next_offset;
+			if (sblk->block_size > 4096)
+				i->i_data.a_ops = &squashfs_aops;
+			else
+				i->i_data.a_ops = &squashfs_aops_4K;
+
+			TRACE("File inode %x:%x, start_block %x, "
+					"block_list_start %llx, offset %x\n",
+					SQUASHFS_INODE_BLK(inode), offset,
+					inodep->start_block, next_block,
+					next_offset);
+			break;
+		}
+		case SQUASHFS_DIR_TYPE: {
+			struct squashfs_dir_inode_header_2 *inodep = &id.dir;
+			struct squashfs_dir_inode_header_2 *sinodep = &sid.dir;
+
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_DIR_INODE_HEADER_2(inodep, sinodep);
+			} else
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			if((i = squashfs_new_inode(s, inodeb, ino)) == NULL)
+				goto failed_read1;
+
+			i->i_size = inodep->file_size;
+			i->i_op = &squashfs_dir_inode_ops_2;
+			i->i_fop = &squashfs_dir_ops_2;
+			i->i_mode |= S_IFDIR;
+			i->i_mtime = inodep->mtime;
+			i->i_atime = inodep->mtime;
+			i->i_ctime = inodep->mtime;
+			SQUASHFS_I(i)->start_block = inodep->start_block;
+			SQUASHFS_I(i)->offset = inodep->offset;
+			SQUASHFS_I(i)->u.s2.directory_index_count = 0;
+			SQUASHFS_I(i)->u.s2.parent_inode = 0;
+
+			TRACE("Directory inode %x:%x, start_block %x, offset "
+					"%x\n", SQUASHFS_INODE_BLK(inode),
+					offset, inodep->start_block,
+					inodep->offset);
+			break;
+		}
+		case SQUASHFS_LDIR_TYPE: {
+			struct squashfs_ldir_inode_header_2 *inodep = &id.ldir;
+			struct squashfs_ldir_inode_header_2 *sinodep = &sid.ldir;
+
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_LDIR_INODE_HEADER_2(inodep,
+						sinodep);
+			} else
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			if((i = squashfs_new_inode(s, inodeb, ino)) == NULL)
+				goto failed_read1;
+
+			i->i_size = inodep->file_size;
+			i->i_op = &squashfs_dir_inode_ops_2;
+			i->i_fop = &squashfs_dir_ops_2;
+			i->i_mode |= S_IFDIR;
+			i->i_mtime = inodep->mtime;
+			i->i_atime = inodep->mtime;
+			i->i_ctime = inodep->mtime;
+			SQUASHFS_I(i)->start_block = inodep->start_block;
+			SQUASHFS_I(i)->offset = inodep->offset;
+			SQUASHFS_I(i)->u.s2.directory_index_start = next_block;
+			SQUASHFS_I(i)->u.s2.directory_index_offset =
+								next_offset;
+			SQUASHFS_I(i)->u.s2.directory_index_count =
+								inodep->i_count;
+			SQUASHFS_I(i)->u.s2.parent_inode = 0;
+
+			TRACE("Long directory inode %x:%x, start_block %x, "
+					"offset %x\n",
+					SQUASHFS_INODE_BLK(inode), offset,
+					inodep->start_block, inodep->offset);
+			break;
+		}
+		case SQUASHFS_SYMLINK_TYPE: {
+			struct squashfs_symlink_inode_header_2 *inodep =
+								&id.symlink;
+			struct squashfs_symlink_inode_header_2 *sinodep =
+								&sid.symlink;
+	
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_SYMLINK_INODE_HEADER_2(inodep,
+								sinodep);
+			} else
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			if((i = squashfs_new_inode(s, inodeb, ino)) == NULL)
+				goto failed_read1;
+
+			i->i_size = inodep->symlink_size;
+			i->i_op = &page_symlink_inode_operations;
+			i->i_data.a_ops = &squashfs_symlink_aops;
+			i->i_mode |= S_IFLNK;
+			SQUASHFS_I(i)->start_block = next_block;
+			SQUASHFS_I(i)->offset = next_offset;
+
+			TRACE("Symbolic link inode %x:%x, start_block %llx, "
+					"offset %x\n",
+					SQUASHFS_INODE_BLK(inode), offset,
+					next_block, next_offset);
+			break;
+		 }
+		 case SQUASHFS_BLKDEV_TYPE:
+		 case SQUASHFS_CHRDEV_TYPE: {
+			struct squashfs_dev_inode_header_2 *inodep = &id.dev;
+			struct squashfs_dev_inode_header_2 *sinodep = &sid.dev;
+
+			if (msblk->swap) {
+				if (!squashfs_get_cached_block(s, (char *)
+						sinodep, block, offset,
+						sizeof(*sinodep), &next_block,
+						&next_offset))
+					goto failed_read;
+				SQUASHFS_SWAP_DEV_INODE_HEADER_2(inodep, sinodep);
+			} else	
+				if (!squashfs_get_cached_block(s, (char *)
+						inodep, block, offset,
+						sizeof(*inodep), &next_block,
+						&next_offset))
+					goto failed_read;
+
+			if ((i = squashfs_new_inode(s, inodeb, ino)) == NULL)
+				goto failed_read1;
+
+			i->i_mode |= (inodeb->inode_type ==
+					SQUASHFS_CHRDEV_TYPE) ?  S_IFCHR :
+					S_IFBLK;
+			init_special_inode(i, i->i_mode, inodep->rdev);
+
+			TRACE("Device inode %x:%x, rdev %x\n",
+					SQUASHFS_INODE_BLK(inode), offset,
+					inodep->rdev);
+			break;
+		 }
+		 case SQUASHFS_FIFO_TYPE:
+		 case SQUASHFS_SOCKET_TYPE: {
+			if ((i = squashfs_new_inode(s, inodeb, ino)) == NULL)
+				goto failed_read1;
+
+			i->i_mode |= (inodeb->inode_type == SQUASHFS_FIFO_TYPE)
+							? S_IFIFO : S_IFSOCK;
+			init_special_inode(i, i->i_mode, 0);
+			break;
+		 }
+		 default:
+			ERROR("Unknown inode type %d in squashfs_iget!\n",
+					inodeb->inode_type);
+			goto failed_read1;
+	}
+	
+	insert_inode_hash(i);
+	return i;
+
+failed_read:
+	ERROR("Unable to read inode [%x:%x]\n", block, offset);
+
+failed_read1:
+	return NULL;
+}
+
+
+static int get_dir_index_using_offset(struct super_block *s, long long 
+				*next_block, unsigned int *next_offset,
+				long long index_start,
+				unsigned int index_offset, int i_count,
+				long long f_pos)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	int i, length = 0;
+	struct squashfs_dir_index_2 index;
+
+	TRACE("Entered get_dir_index_using_offset, i_count %d, f_pos %d\n",
+					i_count, (unsigned int) f_pos);
+
+	if (f_pos == 0)
+		goto finish;
+
+	for (i = 0; i < i_count; i++) {
+		if (msblk->swap) {
+			struct squashfs_dir_index_2 sindex;
+			squashfs_get_cached_block(s, (char *) &sindex,
+					index_start, index_offset,
+					sizeof(sindex), &index_start,
+					&index_offset);
+			SQUASHFS_SWAP_DIR_INDEX_2(&index, &sindex);
+		} else
+			squashfs_get_cached_block(s, (char *) &index,
+					index_start, index_offset,
+					sizeof(index), &index_start,
+					&index_offset);
+
+		if (index.index > f_pos)
+			break;
+
+		squashfs_get_cached_block(s, NULL, index_start, index_offset,
+					index.size + 1, &index_start,
+					&index_offset);
+
+		length = index.index;
+		*next_block = index.start_block + sblk->directory_table_start;
+	}
+
+	*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
+
+finish:
+	return length;
+}
+
+
+static int get_dir_index_using_name(struct super_block *s, long long
+				*next_block, unsigned int *next_offset,
+				long long index_start,
+				unsigned int index_offset, int i_count,
+				const char *name, int size)
+{
+	struct squashfs_sb_info *msblk = &s->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	int i, length = 0;
+	char buffer[sizeof(struct squashfs_dir_index_2) + SQUASHFS_NAME_LEN + 1];
+	struct squashfs_dir_index_2 *index = (struct squashfs_dir_index_2 *) buffer;
+	char str[SQUASHFS_NAME_LEN + 1];
+
+	TRACE("Entered get_dir_index_using_name, i_count %d\n", i_count);
+
+	strncpy(str, name, size);
+	str[size] = '\0';
+
+	for (i = 0; i < i_count; i++) {
+		if (msblk->swap) {
+			struct squashfs_dir_index_2 sindex;
+			squashfs_get_cached_block(s, (char *) &sindex,
+					index_start, index_offset,
+					sizeof(sindex), &index_start,
+					&index_offset);
+			SQUASHFS_SWAP_DIR_INDEX_2(index, &sindex);
+		} else
+			squashfs_get_cached_block(s, (char *) index,
+					index_start, index_offset,
+					sizeof(struct squashfs_dir_index_2),
+					&index_start, &index_offset);
+
+		squashfs_get_cached_block(s, index->name, index_start,
+					index_offset, index->size + 1,
+					&index_start, &index_offset);
+
+		index->name[index->size + 1] = '\0';
+
+		if (strcmp(index->name, str) > 0)
+			break;
+
+		length = index->index;
+		*next_block = index->start_block + sblk->directory_table_start;
+	}
+
+	*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
+	return length;
+}
+
+		
+static int squashfs_readdir_2(struct file *file, void *dirent, filldir_t filldir)
+{
+	struct inode *i = file->f_dentry->d_inode;
+	struct squashfs_sb_info *msblk = &i->i_sb->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	long long next_block = SQUASHFS_I(i)->start_block +
+		sblk->directory_table_start;
+	int next_offset = SQUASHFS_I(i)->offset, length = 0,
+		dir_count;
+	struct squashfs_dir_header_2 dirh;
+	char buffer[sizeof(struct squashfs_dir_entry_2) + SQUASHFS_NAME_LEN + 1];
+	struct squashfs_dir_entry_2 *dire = (struct squashfs_dir_entry_2 *) buffer;
+
+	TRACE("Entered squashfs_readdir_2 [%llx:%x]\n", next_block, next_offset);
+
+	length = get_dir_index_using_offset(i->i_sb, &next_block, &next_offset,
+				SQUASHFS_I(i)->u.s2.directory_index_start,
+				SQUASHFS_I(i)->u.s2.directory_index_offset,
+				SQUASHFS_I(i)->u.s2.directory_index_count,
+				file->f_pos);
+
+	while (length < i_size_read(i)) {
+		/* read directory header */
+		if (msblk->swap) {
+			struct squashfs_dir_header_2 sdirh;
+			
+			if (!squashfs_get_cached_block(i->i_sb, (char *) &sdirh,
+					next_block, next_offset, sizeof(sdirh),
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += sizeof(sdirh);
+			SQUASHFS_SWAP_DIR_HEADER_2(&dirh, &sdirh);
+		} else {
+			if (!squashfs_get_cached_block(i->i_sb, (char *) &dirh,
+					next_block, next_offset, sizeof(dirh),
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += sizeof(dirh);
+		}
+
+		dir_count = dirh.count + 1;
+		while (dir_count--) {
+			if (msblk->swap) {
+				struct squashfs_dir_entry_2 sdire;
+				if (!squashfs_get_cached_block(i->i_sb, (char *)
+						&sdire, next_block, next_offset,
+						sizeof(sdire), &next_block,
+						&next_offset))
+					goto failed_read;
+				
+				length += sizeof(sdire);
+				SQUASHFS_SWAP_DIR_ENTRY_2(dire, &sdire);
+			} else {
+				if (!squashfs_get_cached_block(i->i_sb, (char *)
+						dire, next_block, next_offset,
+						sizeof(*dire), &next_block,
+						&next_offset))
+					goto failed_read;
+
+				length += sizeof(*dire);
+			}
+
+			if (!squashfs_get_cached_block(i->i_sb, dire->name,
+						next_block, next_offset,
+						dire->size + 1, &next_block,
+						&next_offset))
+				goto failed_read;
+
+			length += dire->size + 1;
+
+			if (file->f_pos >= length)
+				continue;
+
+			dire->name[dire->size + 1] = '\0';
+
+			TRACE("Calling filldir(%x, %s, %d, %d, %x:%x, %d)\n",
+					(unsigned int) dirent, dire->name,
+					dire->size + 1, (int) file->f_pos,
+					dirh.start_block, dire->offset,
+					squashfs_filetype_table[dire->type]);
+
+			if (filldir(dirent, dire->name, dire->size + 1,
+					file->f_pos, SQUASHFS_MK_VFS_INODE(
+					dirh.start_block, dire->offset),
+					squashfs_filetype_table[dire->type])
+					< 0) {
+				TRACE("Filldir returned less than 0\n");
+				goto finish;
+			}
+			file->f_pos = length;
+		}
+	}
+
+finish:
+	return 0;
+
+failed_read:
+	ERROR("Unable to read directory block [%llx:%x]\n", next_block,
+		next_offset);
+	return 0;
+}
+
+
+static struct dentry *squashfs_lookup_2(struct inode *i, struct dentry *dentry)
+{
+	const unsigned char *name = dentry->d_name.name;
+	int len = dentry->d_name.len;
+	struct inode *inode = NULL;
+	struct squashfs_sb_info *msblk = &i->i_sb->u.squashfs_sb;
+	struct squashfs_super_block *sblk = &msblk->sblk;
+	long long next_block = SQUASHFS_I(i)->start_block +
+				sblk->directory_table_start;
+	int next_offset = SQUASHFS_I(i)->offset, length = 0,
+				dir_count;
+	struct squashfs_dir_header_2 dirh;
+	char buffer[sizeof(struct squashfs_dir_entry_2) + SQUASHFS_NAME_LEN];
+	struct squashfs_dir_entry_2 *dire = (struct squashfs_dir_entry_2 *) buffer;
+	int sorted = sblk->s_major == 2 && sblk->s_minor >= 1;
+
+	TRACE("Entered squashfs_lookup [%llx:%x]\n", next_block, next_offset);
+
+	if (len > SQUASHFS_NAME_LEN)
+		goto exit_loop;
+
+	length = get_dir_index_using_name(i->i_sb, &next_block, &next_offset,
+				SQUASHFS_I(i)->u.s2.directory_index_start,
+				SQUASHFS_I(i)->u.s2.directory_index_offset,
+				SQUASHFS_I(i)->u.s2.directory_index_count, name,
+				len);
+
+	while (length < i_size_read(i)) {
+		/* read directory header */
+		if (msblk->swap) {
+			struct squashfs_dir_header_2 sdirh;
+			if (!squashfs_get_cached_block(i->i_sb, (char *) &sdirh,
+					next_block, next_offset, sizeof(sdirh),
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += sizeof(sdirh);
+			SQUASHFS_SWAP_DIR_HEADER_2(&dirh, &sdirh);
+		} else {
+			if (!squashfs_get_cached_block(i->i_sb, (char *) &dirh,
+					next_block, next_offset, sizeof(dirh),
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += sizeof(dirh);
+		}
+
+		dir_count = dirh.count + 1;
+		while (dir_count--) {
+			if (msblk->swap) {
+				struct squashfs_dir_entry_2 sdire;
+				if (!squashfs_get_cached_block(i->i_sb, (char *)
+						&sdire, next_block,next_offset,
+						sizeof(sdire), &next_block,
+						&next_offset))
+					goto failed_read;
+				
+				length += sizeof(sdire);
+				SQUASHFS_SWAP_DIR_ENTRY_2(dire, &sdire);
+			} else {
+				if (!squashfs_get_cached_block(i->i_sb, (char *)
+						dire, next_block,next_offset,
+						sizeof(*dire), &next_block,
+						&next_offset))
+					goto failed_read;
+
+				length += sizeof(*dire);
+			}
+
+			if (!squashfs_get_cached_block(i->i_sb, dire->name,
+					next_block, next_offset, dire->size + 1,
+					&next_block, &next_offset))
+				goto failed_read;
+
+			length += dire->size + 1;
+
+			if (sorted && name[0] < dire->name[0])
+				goto exit_loop;
+
+			if ((len == dire->size + 1) && !strncmp(name,
+						dire->name, len)) {
+				squashfs_inode_t ino =
+					SQUASHFS_MKINODE(dirh.start_block,
+					dire->offset);
+
+				TRACE("calling squashfs_iget for directory "
+					"entry %s, inode %x:%x, %d\n", name,
+					dirh.start_block, dire->offset, ino);
+
+				inode = (msblk->iget)(i->i_sb, ino);
+
+				goto exit_loop;
+			}
+		}
+	}
+
+exit_loop:
+	d_add(dentry, inode);
+	return ERR_PTR(0);
+
+failed_read:
+	ERROR("Unable to read directory block [%llx:%x]\n", next_block,
+		next_offset);
+	goto exit_loop;
+}
+
+
+int squashfs_2_0_supported(struct squashfs_sb_info *msblk)
+{
+	struct squashfs_super_block *sblk = &msblk->sblk;
+
+	msblk->iget = squashfs_iget_2;
+	msblk->read_fragment_index_table = read_fragment_index_table_2;
+
+	sblk->bytes_used = sblk->bytes_used_2;
+	sblk->uid_start = sblk->uid_start_2;
+	sblk->guid_start = sblk->guid_start_2;
+	sblk->inode_table_start = sblk->inode_table_start_2;
+	sblk->directory_table_start = sblk->directory_table_start_2;
+	sblk->fragment_table_start = sblk->fragment_table_start_2;
+
+	return 1;
+}
diff --git a/squashfs-tools/kernel-2.4/include/linux/squashfs_fs.h b/squashfs-tools/kernel-2.4/include/linux/squashfs_fs.h
new file mode 100755
index 0000000..eed48c3
--- /dev/null
+++ b/squashfs-tools/kernel-2.4/include/linux/squashfs_fs.h
@@ -0,0 +1,915 @@
+#ifndef SQUASHFS_FS
+#define SQUASHFS_FS
+
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006
+ * Phillip Lougher <phillip@lougher.demon.co.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.
+ *
+ * squashfs_fs.h
+ */
+
+#ifndef CONFIG_SQUASHFS_2_0_COMPATIBILITY
+#define CONFIG_SQUASHFS_2_0_COMPATIBILITY
+#endif
+
+#ifdef CONFIG_SQUASHFS_VMALLOC
+#define SQUASHFS_ALLOC(a)		vmalloc(a)
+#define SQUASHFS_FREE(a)		vfree(a)
+#else
+#define SQUASHFS_ALLOC(a)		kmalloc(a, GFP_KERNEL)
+#define SQUASHFS_FREE(a)		kfree(a)
+#endif
+#ifdef CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE
+#define SQUASHFS_CACHED_FRAGMENTS	CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE
+#else
+#define SQUASHFS_CACHED_FRAGMENTS	3
+#endif
+#define SQUASHFS_MAJOR			3
+#define SQUASHFS_MINOR			0
+#define SQUASHFS_MAGIC			0x73717368
+#define SQUASHFS_MAGIC_SWAP		0x68737173
+#define SQUASHFS_START			0
+
+/* size of metadata (inode and directory) blocks */
+#define SQUASHFS_METADATA_SIZE		8192
+#define SQUASHFS_METADATA_LOG		13
+
+/* default size of data blocks */
+#define SQUASHFS_FILE_SIZE		65536
+#define SQUASHFS_FILE_LOG		16
+
+#define SQUASHFS_FILE_MAX_SIZE		65536
+
+/* Max number of uids and gids */
+#define SQUASHFS_UIDS			256
+#define SQUASHFS_GUIDS			255
+
+/* Max length of filename (not 255) */
+#define SQUASHFS_NAME_LEN		256
+
+#define SQUASHFS_INVALID		((long long) 0xffffffffffff)
+#define SQUASHFS_INVALID_FRAG		((unsigned int) 0xffffffff)
+#define SQUASHFS_INVALID_BLK		((long long) -1)
+#define SQUASHFS_USED_BLK		((long long) -2)
+
+/* Filesystem flags */
+#define SQUASHFS_NOI			0
+#define SQUASHFS_NOD			1
+#define SQUASHFS_CHECK			2
+#define SQUASHFS_NOF			3
+#define SQUASHFS_NO_FRAG		4
+#define SQUASHFS_ALWAYS_FRAG		5
+#define SQUASHFS_DUPLICATE		6
+
+#define SQUASHFS_BIT(flag, bit)		((flag >> bit) & 1)
+
+#define SQUASHFS_UNCOMPRESSED_INODES(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOI)
+
+#define SQUASHFS_UNCOMPRESSED_DATA(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOD)
+
+#define SQUASHFS_UNCOMPRESSED_FRAGMENTS(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOF)
+
+#define SQUASHFS_NO_FRAGMENTS(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_NO_FRAG)
+
+#define SQUASHFS_ALWAYS_FRAGMENTS(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_ALWAYS_FRAG)
+
+#define SQUASHFS_DUPLICATES(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_DUPLICATE)
+
+#define SQUASHFS_CHECK_DATA(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_CHECK)
+
+#define SQUASHFS_MKFLAGS(noi, nod, check_data, nof, no_frag, always_frag, \
+		duplicate_checking)	(noi | (nod << 1) | (check_data << 2) \
+		| (nof << 3) | (no_frag << 4) | (always_frag << 5) | \
+		(duplicate_checking << 6))
+
+/* Max number of types and file types */
+#define SQUASHFS_DIR_TYPE		1
+#define SQUASHFS_FILE_TYPE		2
+#define SQUASHFS_SYMLINK_TYPE		3
+#define SQUASHFS_BLKDEV_TYPE		4
+#define SQUASHFS_CHRDEV_TYPE		5
+#define SQUASHFS_FIFO_TYPE		6
+#define SQUASHFS_SOCKET_TYPE		7
+#define SQUASHFS_LDIR_TYPE		8
+#define SQUASHFS_LREG_TYPE		9
+
+/* 1.0 filesystem type definitions */
+#define SQUASHFS_TYPES			5
+#define SQUASHFS_IPC_TYPE		0
+
+/* Flag whether block is compressed or uncompressed, bit is set if block is
+ * uncompressed */
+#define SQUASHFS_COMPRESSED_BIT		(1 << 15)
+
+#define SQUASHFS_COMPRESSED_SIZE(B)	(((B) & ~SQUASHFS_COMPRESSED_BIT) ? \
+		(B) & ~SQUASHFS_COMPRESSED_BIT :  SQUASHFS_COMPRESSED_BIT)
+
+#define SQUASHFS_COMPRESSED(B)		(!((B) & SQUASHFS_COMPRESSED_BIT))
+
+#define SQUASHFS_COMPRESSED_BIT_BLOCK		(1 << 24)
+
+#define SQUASHFS_COMPRESSED_SIZE_BLOCK(B)	(((B) & \
+	~SQUASHFS_COMPRESSED_BIT_BLOCK) ? (B) & \
+	~SQUASHFS_COMPRESSED_BIT_BLOCK : SQUASHFS_COMPRESSED_BIT_BLOCK)
+
+#define SQUASHFS_COMPRESSED_BLOCK(B)	(!((B) & SQUASHFS_COMPRESSED_BIT_BLOCK))
+
+/*
+ * Inode number ops.  Inodes consist of a compressed block number, and an
+ * uncompressed  offset within that block
+ */
+#define SQUASHFS_INODE_BLK(a)		((unsigned int) ((a) >> 16))
+
+#define SQUASHFS_INODE_OFFSET(a)	((unsigned int) ((a) & 0xffff))
+
+#define SQUASHFS_MKINODE(A, B)		((squashfs_inode_t)(((squashfs_inode_t) (A)\
+					<< 16) + (B)))
+
+/* Compute 32 bit VFS inode number from squashfs inode number */
+#define SQUASHFS_MK_VFS_INODE(a, b)	((unsigned int) (((a) << 8) + \
+					((b) >> 2) + 1))
+/* XXX */
+
+/* Translate between VFS mode and squashfs mode */
+#define SQUASHFS_MODE(a)		((a) & 0xfff)
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES(A)	(A * sizeof(struct squashfs_fragment_entry))
+
+#define SQUASHFS_FRAGMENT_INDEX(A)	(SQUASHFS_FRAGMENT_BYTES(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET(A)	(SQUASHFS_FRAGMENT_BYTES(A) % \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES(A)	((SQUASHFS_FRAGMENT_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES(A)	(SQUASHFS_FRAGMENT_INDEXES(A) *\
+						sizeof(long long))
+
+/* cached data constants for filesystem */
+#define SQUASHFS_CACHED_BLKS		8
+
+#define SQUASHFS_MAX_FILE_SIZE_LOG	64
+
+#define SQUASHFS_MAX_FILE_SIZE		((long long) 1 << \
+					(SQUASHFS_MAX_FILE_SIZE_LOG - 2))
+
+#define SQUASHFS_MARKER_BYTE		0xff
+
+/* meta index cache */
+#define SQUASHFS_META_INDEXES	(SQUASHFS_METADATA_SIZE / sizeof(unsigned int))
+#define SQUASHFS_META_ENTRIES	31
+#define SQUASHFS_META_NUMBER	8
+#define SQUASHFS_SLOTS		4
+
+struct meta_entry {
+	long long		data_block;
+	unsigned int		index_block;
+	unsigned short		offset;
+	unsigned short		pad;
+};
+
+struct meta_index {
+	unsigned int		inode_number;
+	unsigned int		offset;
+	unsigned short		entries;
+	unsigned short		skip;
+	unsigned short		locked;
+	unsigned short		pad;
+	struct meta_entry	meta_entry[SQUASHFS_META_ENTRIES];
+};
+
+
+/*
+ * definitions for structures on disk
+ */
+
+typedef long long		squashfs_block_t;
+typedef long long		squashfs_inode_t;
+
+struct squashfs_super_block {
+	unsigned int		s_magic;
+	unsigned int		inodes;
+	unsigned int		bytes_used_2;
+	unsigned int		uid_start_2;
+	unsigned int		guid_start_2;
+	unsigned int		inode_table_start_2;
+	unsigned int		directory_table_start_2;
+	unsigned int		s_major:16;
+	unsigned int		s_minor:16;
+	unsigned int		block_size_1:16;
+	unsigned int		block_log:16;
+	unsigned int		flags:8;
+	unsigned int		no_uids:8;
+	unsigned int		no_guids:8;
+	unsigned int		mkfs_time /* time of filesystem creation */;
+	squashfs_inode_t	root_inode;
+	unsigned int		block_size;
+	unsigned int		fragments;
+	unsigned int		fragment_table_start_2;
+	long long		bytes_used;
+	long long		uid_start;
+	long long		guid_start;
+	long long		inode_table_start;
+	long long		directory_table_start;
+	long long		fragment_table_start;
+	long long		unused;
+} __attribute__ ((packed));
+
+struct squashfs_dir_index {
+	unsigned int		index;
+	unsigned int		start_block;
+	unsigned char		size;
+	unsigned char		name[0];
+} __attribute__ ((packed));
+
+#define SQUASHFS_BASE_INODE_HEADER		\
+	unsigned int		inode_type:4;	\
+	unsigned int		mode:12;	\
+	unsigned int		uid:8;		\
+	unsigned int		guid:8;		\
+	unsigned int		mtime;		\
+	unsigned int 		inode_number;
+
+struct squashfs_base_inode_header {
+	SQUASHFS_BASE_INODE_HEADER;
+} __attribute__ ((packed));
+
+struct squashfs_ipc_inode_header {
+	SQUASHFS_BASE_INODE_HEADER;
+	unsigned int		nlink;
+} __attribute__ ((packed));
+
+struct squashfs_dev_inode_header {
+	SQUASHFS_BASE_INODE_HEADER;
+	unsigned int		nlink;
+	unsigned short		rdev;
+} __attribute__ ((packed));
+	
+struct squashfs_symlink_inode_header {
+	SQUASHFS_BASE_INODE_HEADER;
+	unsigned int		nlink;
+	unsigned short		symlink_size;
+	char			symlink[0];
+} __attribute__ ((packed));
+
+struct squashfs_reg_inode_header {
+	SQUASHFS_BASE_INODE_HEADER;
+	squashfs_block_t	start_block;
+	unsigned int		fragment;
+	unsigned int		offset;
+	unsigned int		file_size;
+	unsigned short		block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_lreg_inode_header {
+	SQUASHFS_BASE_INODE_HEADER;
+	unsigned int		nlink;
+	squashfs_block_t	start_block;
+	unsigned int		fragment;
+	unsigned int		offset;
+	long long		file_size;
+	unsigned short		block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_inode_header {
+	SQUASHFS_BASE_INODE_HEADER;
+	unsigned int		nlink;
+	unsigned int		file_size:19;
+	unsigned int		offset:13;
+	unsigned int		start_block;
+	unsigned int		parent_inode;
+} __attribute__  ((packed));
+
+struct squashfs_ldir_inode_header {
+	SQUASHFS_BASE_INODE_HEADER;
+	unsigned int		nlink;
+	unsigned int		file_size:27;
+	unsigned int		offset:13;
+	unsigned int		start_block;
+	unsigned int		i_count:16;
+	unsigned int		parent_inode;
+	struct squashfs_dir_index	index[0];
+} __attribute__  ((packed));
+
+union squashfs_inode_header {
+	struct squashfs_base_inode_header	base;
+	struct squashfs_dev_inode_header	dev;
+	struct squashfs_symlink_inode_header	symlink;
+	struct squashfs_reg_inode_header	reg;
+	struct squashfs_lreg_inode_header	lreg;
+	struct squashfs_dir_inode_header	dir;
+	struct squashfs_ldir_inode_header	ldir;
+	struct squashfs_ipc_inode_header	ipc;
+};
+	
+struct squashfs_dir_entry {
+	unsigned int		offset:13;
+	unsigned int		type:3;
+	unsigned int		size:8;
+	int			inode_number:16;
+	char			name[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_header {
+	unsigned int		count:8;
+	unsigned int		start_block;
+	unsigned int		inode_number;
+} __attribute__ ((packed));
+
+struct squashfs_fragment_entry {
+	long long		start_block;
+	unsigned int		size;
+	unsigned int		unused;
+} __attribute__ ((packed));
+
+extern int squashfs_uncompress_block(void *d, int dstlen, void *s, int srclen);
+extern int squashfs_uncompress_init(void);
+extern int squashfs_uncompress_exit(void);
+
+/*
+ * macros to convert each packed bitfield structure from little endian to big
+ * endian and vice versa.  These are needed when creating or using a filesystem
+ * on a machine with different byte ordering to the target architecture.
+ *
+ */
+
+#define SQUASHFS_SWAP_START \
+	int bits;\
+	int b_pos;\
+	unsigned long long val;\
+	unsigned char *s;\
+	unsigned char *d;
+
+#define SQUASHFS_SWAP_SUPER_BLOCK(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_super_block));\
+	SQUASHFS_SWAP((s)->s_magic, d, 0, 32);\
+	SQUASHFS_SWAP((s)->inodes, d, 32, 32);\
+	SQUASHFS_SWAP((s)->bytes_used_2, d, 64, 32);\
+	SQUASHFS_SWAP((s)->uid_start_2, d, 96, 32);\
+	SQUASHFS_SWAP((s)->guid_start_2, d, 128, 32);\
+	SQUASHFS_SWAP((s)->inode_table_start_2, d, 160, 32);\
+	SQUASHFS_SWAP((s)->directory_table_start_2, d, 192, 32);\
+	SQUASHFS_SWAP((s)->s_major, d, 224, 16);\
+	SQUASHFS_SWAP((s)->s_minor, d, 240, 16);\
+	SQUASHFS_SWAP((s)->block_size_1, d, 256, 16);\
+	SQUASHFS_SWAP((s)->block_log, d, 272, 16);\
+	SQUASHFS_SWAP((s)->flags, d, 288, 8);\
+	SQUASHFS_SWAP((s)->no_uids, d, 296, 8);\
+	SQUASHFS_SWAP((s)->no_guids, d, 304, 8);\
+	SQUASHFS_SWAP((s)->mkfs_time, d, 312, 32);\
+	SQUASHFS_SWAP((s)->root_inode, d, 344, 64);\
+	SQUASHFS_SWAP((s)->block_size, d, 408, 32);\
+	SQUASHFS_SWAP((s)->fragments, d, 440, 32);\
+	SQUASHFS_SWAP((s)->fragment_table_start_2, d, 472, 32);\
+	SQUASHFS_SWAP((s)->bytes_used, d, 504, 64);\
+	SQUASHFS_SWAP((s)->uid_start, d, 568, 64);\
+	SQUASHFS_SWAP((s)->guid_start, d, 632, 64);\
+	SQUASHFS_SWAP((s)->inode_table_start, d, 696, 64);\
+	SQUASHFS_SWAP((s)->directory_table_start, d, 760, 64);\
+	SQUASHFS_SWAP((s)->fragment_table_start, d, 824, 64);\
+	SQUASHFS_SWAP((s)->unused, d, 888, 64);\
+}
+
+#define SQUASHFS_SWAP_BASE_INODE_CORE(s, d, n)\
+	SQUASHFS_MEMSET(s, d, n);\
+	SQUASHFS_SWAP((s)->inode_type, d, 0, 4);\
+	SQUASHFS_SWAP((s)->mode, d, 4, 12);\
+	SQUASHFS_SWAP((s)->uid, d, 16, 8);\
+	SQUASHFS_SWAP((s)->guid, d, 24, 8);\
+	SQUASHFS_SWAP((s)->mtime, d, 32, 32);\
+	SQUASHFS_SWAP((s)->inode_number, d, 64, 32);
+
+#define SQUASHFS_SWAP_BASE_INODE_HEADER(s, d, n) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE(s, d, n)\
+}
+
+#define SQUASHFS_SWAP_IPC_INODE_HEADER(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE(s, d, \
+			sizeof(struct squashfs_ipc_inode_header))\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+}
+
+#define SQUASHFS_SWAP_DEV_INODE_HEADER(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE(s, d, \
+			sizeof(struct squashfs_dev_inode_header)); \
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->rdev, d, 128, 16);\
+}
+
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE(s, d, \
+			sizeof(struct squashfs_symlink_inode_header));\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->symlink_size, d, 128, 16);\
+}
+
+#define SQUASHFS_SWAP_REG_INODE_HEADER(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE(s, d, \
+			sizeof(struct squashfs_reg_inode_header));\
+	SQUASHFS_SWAP((s)->start_block, d, 96, 64);\
+	SQUASHFS_SWAP((s)->fragment, d, 160, 32);\
+	SQUASHFS_SWAP((s)->offset, d, 192, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 224, 32);\
+}
+
+#define SQUASHFS_SWAP_LREG_INODE_HEADER(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE(s, d, \
+			sizeof(struct squashfs_lreg_inode_header));\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 128, 64);\
+	SQUASHFS_SWAP((s)->fragment, d, 192, 32);\
+	SQUASHFS_SWAP((s)->offset, d, 224, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 256, 64);\
+}
+
+#define SQUASHFS_SWAP_DIR_INODE_HEADER(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE(s, d, \
+			sizeof(struct squashfs_dir_inode_header));\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 128, 19);\
+	SQUASHFS_SWAP((s)->offset, d, 147, 13);\
+	SQUASHFS_SWAP((s)->start_block, d, 160, 32);\
+	SQUASHFS_SWAP((s)->parent_inode, d, 192, 32);\
+}
+
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE(s, d, \
+			sizeof(struct squashfs_ldir_inode_header));\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 128, 27);\
+	SQUASHFS_SWAP((s)->offset, d, 155, 13);\
+	SQUASHFS_SWAP((s)->start_block, d, 168, 32);\
+	SQUASHFS_SWAP((s)->i_count, d, 200, 16);\
+	SQUASHFS_SWAP((s)->parent_inode, d, 216, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_INDEX(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_index));\
+	SQUASHFS_SWAP((s)->index, d, 0, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 32, 32);\
+	SQUASHFS_SWAP((s)->size, d, 64, 8);\
+}
+
+#define SQUASHFS_SWAP_DIR_HEADER(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_header));\
+	SQUASHFS_SWAP((s)->count, d, 0, 8);\
+	SQUASHFS_SWAP((s)->start_block, d, 8, 32);\
+	SQUASHFS_SWAP((s)->inode_number, d, 40, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_ENTRY(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_entry));\
+	SQUASHFS_SWAP((s)->offset, d, 0, 13);\
+	SQUASHFS_SWAP((s)->type, d, 13, 3);\
+	SQUASHFS_SWAP((s)->size, d, 16, 8);\
+	SQUASHFS_SWAP((s)->inode_number, d, 24, 16);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_fragment_entry));\
+	SQUASHFS_SWAP((s)->start_block, d, 0, 64);\
+	SQUASHFS_SWAP((s)->size, d, 64, 32);\
+}
+
+#define SQUASHFS_SWAP_SHORTS(s, d, n) {\
+	int entry;\
+	int bit_position;\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, n * 2);\
+	for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+			16)\
+		SQUASHFS_SWAP(s[entry], d, bit_position, 16);\
+}
+
+#define SQUASHFS_SWAP_INTS(s, d, n) {\
+	int entry;\
+	int bit_position;\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, n * 4);\
+	for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+			32)\
+		SQUASHFS_SWAP(s[entry], d, bit_position, 32);\
+}
+
+#define SQUASHFS_SWAP_LONG_LONGS(s, d, n) {\
+	int entry;\
+	int bit_position;\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, n * 8);\
+	for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+			64)\
+		SQUASHFS_SWAP(s[entry], d, bit_position, 64);\
+}
+
+#define SQUASHFS_SWAP_DATA(s, d, n, bits) {\
+	int entry;\
+	int bit_position;\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, n * bits / 8);\
+	for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+			bits)\
+		SQUASHFS_SWAP(s[entry], d, bit_position, bits);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES(s, d, n) SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+
+#ifdef CONFIG_SQUASHFS_1_0_COMPATIBILITY
+
+struct squashfs_base_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+} __attribute__ ((packed));
+
+struct squashfs_ipc_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	unsigned int		type:4;
+	unsigned int		offset:4;
+} __attribute__ ((packed));
+
+struct squashfs_dev_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	unsigned short		rdev;
+} __attribute__ ((packed));
+	
+struct squashfs_symlink_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	unsigned short		symlink_size;
+	char			symlink[0];
+} __attribute__ ((packed));
+
+struct squashfs_reg_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	unsigned int		mtime;
+	unsigned int		start_block;
+	unsigned int		file_size:32;
+	unsigned short		block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	unsigned int		file_size:19;
+	unsigned int		offset:13;
+	unsigned int		mtime;
+	unsigned int		start_block:24;
+} __attribute__  ((packed));
+
+#define SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, n) \
+	SQUASHFS_MEMSET(s, d, n);\
+	SQUASHFS_SWAP((s)->inode_type, d, 0, 4);\
+	SQUASHFS_SWAP((s)->mode, d, 4, 12);\
+	SQUASHFS_SWAP((s)->uid, d, 16, 4);\
+	SQUASHFS_SWAP((s)->guid, d, 20, 4);
+
+#define SQUASHFS_SWAP_BASE_INODE_HEADER_1(s, d, n) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, n)\
+}
+
+#define SQUASHFS_SWAP_IPC_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_ipc_inode_header_1));\
+	SQUASHFS_SWAP((s)->type, d, 24, 4);\
+	SQUASHFS_SWAP((s)->offset, d, 28, 4);\
+}
+
+#define SQUASHFS_SWAP_DEV_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_dev_inode_header_1));\
+	SQUASHFS_SWAP((s)->rdev, d, 24, 16);\
+}
+
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_symlink_inode_header_1));\
+	SQUASHFS_SWAP((s)->symlink_size, d, 24, 16);\
+}
+
+#define SQUASHFS_SWAP_REG_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_reg_inode_header_1));\
+	SQUASHFS_SWAP((s)->mtime, d, 24, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 56, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 88, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_dir_inode_header_1));\
+	SQUASHFS_SWAP((s)->file_size, d, 24, 19);\
+	SQUASHFS_SWAP((s)->offset, d, 43, 13);\
+	SQUASHFS_SWAP((s)->mtime, d, 56, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 88, 24);\
+}
+
+#endif
+
+#ifdef CONFIG_SQUASHFS_2_0_COMPATIBILITY
+
+struct squashfs_dir_index_2 {
+	unsigned int		index:27;
+	unsigned int		start_block:29;
+	unsigned char		size;
+	unsigned char		name[0];
+} __attribute__ ((packed));
+
+struct squashfs_base_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+} __attribute__ ((packed));
+
+struct squashfs_ipc_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+} __attribute__ ((packed));
+
+struct squashfs_dev_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	unsigned short		rdev;
+} __attribute__ ((packed));
+	
+struct squashfs_symlink_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	unsigned short		symlink_size;
+	char			symlink[0];
+} __attribute__ ((packed));
+
+struct squashfs_reg_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	unsigned int		mtime;
+	unsigned int		start_block;
+	unsigned int		fragment;
+	unsigned int		offset;
+	unsigned int		file_size:32;
+	unsigned short		block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	unsigned int		file_size:19;
+	unsigned int		offset:13;
+	unsigned int		mtime;
+	unsigned int		start_block:24;
+} __attribute__  ((packed));
+
+struct squashfs_ldir_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	unsigned int		file_size:27;
+	unsigned int		offset:13;
+	unsigned int		mtime;
+	unsigned int		start_block:24;
+	unsigned int		i_count:16;
+	struct squashfs_dir_index_2	index[0];
+} __attribute__  ((packed));
+
+union squashfs_inode_header_2 {
+	struct squashfs_base_inode_header_2	base;
+	struct squashfs_dev_inode_header_2	dev;
+	struct squashfs_symlink_inode_header_2	symlink;
+	struct squashfs_reg_inode_header_2	reg;
+	struct squashfs_dir_inode_header_2	dir;
+	struct squashfs_ldir_inode_header_2	ldir;
+	struct squashfs_ipc_inode_header_2	ipc;
+};
+	
+struct squashfs_dir_header_2 {
+	unsigned int		count:8;
+	unsigned int		start_block:24;
+} __attribute__ ((packed));
+
+struct squashfs_dir_entry_2 {
+	unsigned int		offset:13;
+	unsigned int		type:3;
+	unsigned int		size:8;
+	char			name[0];
+} __attribute__ ((packed));
+
+struct squashfs_fragment_entry_2 {
+	unsigned int		start_block;
+	unsigned int		size;
+} __attribute__ ((packed));
+
+#define SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, n)\
+	SQUASHFS_MEMSET(s, d, n);\
+	SQUASHFS_SWAP((s)->inode_type, d, 0, 4);\
+	SQUASHFS_SWAP((s)->mode, d, 4, 12);\
+	SQUASHFS_SWAP((s)->uid, d, 16, 8);\
+	SQUASHFS_SWAP((s)->guid, d, 24, 8);\
+
+#define SQUASHFS_SWAP_BASE_INODE_HEADER_2(s, d, n) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, n)\
+}
+
+#define SQUASHFS_SWAP_IPC_INODE_HEADER_2(s, d) \
+	SQUASHFS_SWAP_BASE_INODE_HEADER_2(s, d, sizeof(struct squashfs_ipc_inode_header_2))
+
+#define SQUASHFS_SWAP_DEV_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_dev_inode_header_2)); \
+	SQUASHFS_SWAP((s)->rdev, d, 32, 16);\
+}
+
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_symlink_inode_header_2));\
+	SQUASHFS_SWAP((s)->symlink_size, d, 32, 16);\
+}
+
+#define SQUASHFS_SWAP_REG_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_reg_inode_header_2));\
+	SQUASHFS_SWAP((s)->mtime, d, 32, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 64, 32);\
+	SQUASHFS_SWAP((s)->fragment, d, 96, 32);\
+	SQUASHFS_SWAP((s)->offset, d, 128, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 160, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_dir_inode_header_2));\
+	SQUASHFS_SWAP((s)->file_size, d, 32, 19);\
+	SQUASHFS_SWAP((s)->offset, d, 51, 13);\
+	SQUASHFS_SWAP((s)->mtime, d, 64, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 96, 24);\
+}
+
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_ldir_inode_header_2));\
+	SQUASHFS_SWAP((s)->file_size, d, 32, 27);\
+	SQUASHFS_SWAP((s)->offset, d, 59, 13);\
+	SQUASHFS_SWAP((s)->mtime, d, 72, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 104, 24);\
+	SQUASHFS_SWAP((s)->i_count, d, 128, 16);\
+}
+
+#define SQUASHFS_SWAP_DIR_INDEX_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_index_2));\
+	SQUASHFS_SWAP((s)->index, d, 0, 27);\
+	SQUASHFS_SWAP((s)->start_block, d, 27, 29);\
+	SQUASHFS_SWAP((s)->size, d, 56, 8);\
+}
+#define SQUASHFS_SWAP_DIR_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_header_2));\
+	SQUASHFS_SWAP((s)->count, d, 0, 8);\
+	SQUASHFS_SWAP((s)->start_block, d, 8, 24);\
+}
+
+#define SQUASHFS_SWAP_DIR_ENTRY_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_entry_2));\
+	SQUASHFS_SWAP((s)->offset, d, 0, 13);\
+	SQUASHFS_SWAP((s)->type, d, 13, 3);\
+	SQUASHFS_SWAP((s)->size, d, 16, 8);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_fragment_entry_2));\
+	SQUASHFS_SWAP((s)->start_block, d, 0, 32);\
+	SQUASHFS_SWAP((s)->size, d, 32, 32);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES_2(s, d, n) SQUASHFS_SWAP_INTS(s, d, n)
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES_2(A)	(A * sizeof(struct squashfs_fragment_entry_2))
+
+#define SQUASHFS_FRAGMENT_INDEX_2(A)	(SQUASHFS_FRAGMENT_BYTES_2(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET_2(A)	(SQUASHFS_FRAGMENT_BYTES_2(A) % \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES_2(A)	((SQUASHFS_FRAGMENT_BYTES_2(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES_2(A)	(SQUASHFS_FRAGMENT_INDEXES_2(A) *\
+						sizeof(int))
+
+#endif
+
+#ifdef __KERNEL__
+
+/*
+ * macros used to swap each structure entry, taking into account
+ * bitfields and different bitfield placing conventions on differing
+ * architectures
+ */
+
+#include <asm/byteorder.h>
+
+#ifdef __BIG_ENDIAN
+	/* convert from little endian to big endian */
+#define SQUASHFS_SWAP(value, p, pos, tbits) _SQUASHFS_SWAP(value, p, pos, \
+		tbits, b_pos)
+#else
+	/* convert from big endian to little endian */ 
+#define SQUASHFS_SWAP(value, p, pos, tbits) _SQUASHFS_SWAP(value, p, pos, \
+		tbits, 64 - tbits - b_pos)
+#endif
+
+#define _SQUASHFS_SWAP(value, p, pos, tbits, SHIFT) {\
+	b_pos = pos % 8;\
+	val = 0;\
+	s = (unsigned char *)p + (pos / 8);\
+	d = ((unsigned char *) &val) + 7;\
+	for(bits = 0; bits < (tbits + b_pos); bits += 8) \
+		*d-- = *s++;\
+	value = (val >> (SHIFT))/* & ((1 << tbits) - 1)*/;\
+}
+
+#define SQUASHFS_MEMSET(s, d, n)	memset(s, 0, n);
+
+#endif
+#endif
diff --git a/squashfs-tools/kernel-2.4/include/linux/squashfs_fs_i.h b/squashfs-tools/kernel-2.4/include/linux/squashfs_fs_i.h
new file mode 100755
index 0000000..1d7720c
--- /dev/null
+++ b/squashfs-tools/kernel-2.4/include/linux/squashfs_fs_i.h
@@ -0,0 +1,44 @@
+#ifndef SQUASHFS_FS_I
+#define SQUASHFS_FS_I
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006
+ * Phillip Lougher <phillip@lougher.demon.co.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.
+ *
+ * squashfs_fs_i.h
+ */
+
+struct squashfs_inode_info {
+	long long	start_block;
+	unsigned int	offset;
+	union {
+		struct {
+			long long	fragment_start_block;
+			unsigned int	fragment_size;
+			unsigned int	fragment_offset;
+			long long	block_list_start;
+		} s1;
+		struct {
+			long long	directory_index_start;
+			unsigned int	directory_index_offset;
+			unsigned int	directory_index_count;
+			unsigned int	parent_inode;
+		} s2;
+	} u;
+};
+#endif
diff --git a/squashfs-tools/kernel-2.4/include/linux/squashfs_fs_sb.h b/squashfs-tools/kernel-2.4/include/linux/squashfs_fs_sb.h
new file mode 100755
index 0000000..ba08d7f
--- /dev/null
+++ b/squashfs-tools/kernel-2.4/include/linux/squashfs_fs_sb.h
@@ -0,0 +1,76 @@
+#ifndef SQUASHFS_FS_SB
+#define SQUASHFS_FS_SB
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006
+ * Phillip Lougher <phillip@lougher.demon.co.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.
+ *
+ * squashfs_fs_sb.h
+ */
+
+#include <linux/squashfs_fs.h>
+#include <linux/zlib.h>
+
+struct squashfs_cache {
+	long long	block;
+	int		length;
+	long long	next_index;
+	char		*data;
+};
+
+struct squashfs_fragment_cache {
+	long long	block;
+	int		length;
+	unsigned int	locked;
+	char		*data;
+};
+
+struct squashfs_sb_info {
+	struct squashfs_super_block	sblk;
+	int			devblksize;
+	int			devblksize_log2;
+	int			swap;
+	struct squashfs_cache	*block_cache;
+	struct squashfs_fragment_cache	*fragment;
+	int			next_cache;
+	int			next_fragment;
+	int			next_meta_index;
+	unsigned int		*uid;
+	unsigned int		*guid;
+	long long		*fragment_index;
+	unsigned int		*fragment_index_2;
+	unsigned int		read_size;
+	char			*read_data;
+	char			*read_page;
+	struct semaphore	read_data_mutex;
+	struct semaphore	read_page_mutex;
+	struct semaphore	block_cache_mutex;
+	struct semaphore	fragment_mutex;
+	struct semaphore	meta_index_mutex;
+	wait_queue_head_t	waitq;
+	wait_queue_head_t	fragment_wait_queue;
+	struct meta_index	*meta_index;
+	z_stream		stream;
+	struct inode		*(*iget)(struct super_block *s,  squashfs_inode_t
+				inode);
+	long long		(*read_blocklist)(struct inode *inode, int
+				index, int readahead_blks, char *block_list,
+				unsigned short **block_p, unsigned int *bsize);
+	int			(*read_fragment_index_table)(struct super_block *s);
+};
+#endif
diff --git a/squashfs-tools/kernel/Documentation/filesystems/squashfs.txt b/squashfs-tools/kernel/Documentation/filesystems/squashfs.txt
new file mode 100644
index 0000000..3e79e4a
--- /dev/null
+++ b/squashfs-tools/kernel/Documentation/filesystems/squashfs.txt
@@ -0,0 +1,225 @@
+SQUASHFS 4.0 FILESYSTEM
+=======================
+
+Squashfs is a compressed read-only filesystem for Linux.
+It uses zlib compression to compress files, inodes and directories.
+Inodes in the system are very small and all blocks are packed to minimise
+data overhead. Block sizes greater than 4K are supported up to a maximum
+of 1Mbytes (default block size 128K).
+
+Squashfs is intended for general read-only filesystem use, for archival
+use (i.e. in cases where a .tar.gz file may be used), and in constrained
+block device/memory systems (e.g. embedded systems) where low overhead is
+needed.
+
+Mailing list: squashfs-devel@lists.sourceforge.net
+Web site: www.squashfs.org
+
+1. FILESYSTEM FEATURES
+----------------------
+
+Squashfs filesystem features versus Cramfs:
+
+				Squashfs		Cramfs
+
+Max filesystem size:		2^64			16 MiB
+Max file size:			~ 2 TiB			16 MiB
+Max files:			unlimited		unlimited
+Max directories:		unlimited		unlimited
+Max entries per directory:	unlimited		unlimited
+Max block size:			1 MiB			4 KiB
+Metadata compression:		yes			no
+Directory indexes:		yes			no
+Sparse file support:		yes			no
+Tail-end packing (fragments):	yes			no
+Exportable (NFS etc.):		yes			no
+Hard link support:		yes			no
+"." and ".." in readdir:	yes			no
+Real inode numbers:		yes			no
+32-bit uids/gids:		yes			no
+File creation time:		yes			no
+Xattr and ACL support:		no			no
+
+Squashfs compresses data, inodes and directories.  In addition, inode and
+directory data are highly compacted, and packed on byte boundaries.  Each
+compressed inode is on average 8 bytes in length (the exact length varies on
+file type, i.e. regular file, directory, symbolic link, and block/char device
+inodes have different sizes).
+
+2. USING SQUASHFS
+-----------------
+
+As squashfs is a read-only filesystem, the mksquashfs program must be used to
+create populated squashfs filesystems.  This and other squashfs utilities
+can be obtained from http://www.squashfs.org.  Usage instructions can be
+obtained from this site also.
+
+
+3. SQUASHFS FILESYSTEM DESIGN
+-----------------------------
+
+A squashfs filesystem consists of seven parts, packed together on a byte
+alignment:
+
+	 ---------------
+	|  superblock 	|
+	|---------------|
+	|  datablocks   |
+	|  & fragments  |
+	|---------------|
+	|  inode table	|
+	|---------------|
+	|   directory	|
+	|     table     |
+	|---------------|
+	|   fragment	|
+	|    table      |
+	|---------------|
+	|    export     |
+	|    table      |
+	|---------------|
+	|    uid/gid	|
+	|  lookup table	|
+	 ---------------
+
+Compressed data blocks are written to the filesystem as files are read from
+the source directory, and checked for duplicates.  Once all file data has been
+written the completed inode, directory, fragment, export and uid/gid lookup
+tables are written.
+
+3.1 Inodes
+----------
+
+Metadata (inodes and directories) are compressed in 8Kbyte blocks.  Each
+compressed block is prefixed by a two byte length, the top bit is set if the
+block is uncompressed.  A block will be uncompressed if the -noI option is set,
+or if the compressed block was larger than the uncompressed block.
+
+Inodes are packed into the metadata blocks, and are not aligned to block
+boundaries, therefore inodes overlap compressed blocks.  Inodes are identified
+by a 48-bit number which encodes the location of the compressed metadata block
+containing the inode, and the byte offset into that block where the inode is
+placed (<block, offset>).
+
+To maximise compression there are different inodes for each file type
+(regular file, directory, device, etc.), the inode contents and length
+varying with the type.
+
+To further maximise compression, two types of regular file inode and
+directory inode are defined: inodes optimised for frequently occurring
+regular files and directories, and extended types where extra
+information has to be stored.
+
+3.2 Directories
+---------------
+
+Like inodes, directories are packed into compressed metadata blocks, stored
+in a directory table.  Directories are accessed using the start address of
+the metablock containing the directory and the offset into the
+decompressed block (<block, offset>).
+
+Directories are organised in a slightly complex way, and are not simply
+a list of file names.  The organisation takes advantage of the
+fact that (in most cases) the inodes of the files will be in the same
+compressed metadata block, and therefore, can share the start block.
+Directories are therefore organised in a two level list, a directory
+header containing the shared start block value, and a sequence of directory
+entries, each of which share the shared start block.  A new directory header
+is written once/if the inode start block changes.  The directory
+header/directory entry list is repeated as many times as necessary.
+
+Directories are sorted, and can contain a directory index to speed up
+file lookup.  Directory indexes store one entry per metablock, each entry
+storing the index/filename mapping to the first directory header
+in each metadata block.  Directories are sorted in alphabetical order,
+and at lookup the index is scanned linearly looking for the first filename
+alphabetically larger than the filename being looked up.  At this point the
+location of the metadata block the filename is in has been found.
+The general idea of the index is ensure only one metadata block needs to be
+decompressed to do a lookup irrespective of the length of the directory.
+This scheme has the advantage that it doesn't require extra memory overhead
+and doesn't require much extra storage on disk.
+
+3.3 File data
+-------------
+
+Regular files consist of a sequence of contiguous compressed blocks, and/or a
+compressed fragment block (tail-end packed block).   The compressed size
+of each datablock is stored in a block list contained within the
+file inode.
+
+To speed up access to datablocks when reading 'large' files (256 Mbytes or
+larger), the code implements an index cache that caches the mapping from
+block index to datablock location on disk.
+
+The index cache allows Squashfs to handle large files (up to 1.75 TiB) while
+retaining a simple and space-efficient block list on disk.  The cache
+is split into slots, caching up to eight 224 GiB files (128 KiB blocks).
+Larger files use multiple slots, with 1.75 TiB files using all 8 slots.
+The index cache is designed to be memory efficient, and by default uses
+16 KiB.
+
+3.4 Fragment lookup table
+-------------------------
+
+Regular files can contain a fragment index which is mapped to a fragment
+location on disk and compressed size using a fragment lookup table.  This
+fragment lookup table is itself stored compressed into metadata blocks.
+A second index table is used to locate these.  This second index table for
+speed of access (and because it is small) is read at mount time and cached
+in memory.
+
+3.5 Uid/gid lookup table
+------------------------
+
+For space efficiency regular files store uid and gid indexes, which are
+converted to 32-bit uids/gids using an id look up table.  This table is
+stored compressed into metadata blocks.  A second index table is used to
+locate these.  This second index table for speed of access (and because it
+is small) is read at mount time and cached in memory.
+
+3.6 Export table
+----------------
+
+To enable Squashfs filesystems to be exportable (via NFS etc.) filesystems
+can optionally (disabled with the -no-exports Mksquashfs option) contain
+an inode number to inode disk location lookup table.  This is required to
+enable Squashfs to map inode numbers passed in filehandles to the inode
+location on disk, which is necessary when the export code reinstantiates
+expired/flushed inodes.
+
+This table is stored compressed into metadata blocks.  A second index table is
+used to locate these.  This second index table for speed of access (and because
+it is small) is read at mount time and cached in memory.
+
+
+4. TODOS AND OUTSTANDING ISSUES
+-------------------------------
+
+4.1 Todo list
+-------------
+
+Implement Xattr and ACL support.  The Squashfs 4.0 filesystem layout has hooks
+for these but the code has not been written.  Once the code has been written
+the existing layout should not require modification.
+
+4.2 Squashfs internal cache
+---------------------------
+
+Blocks in Squashfs are compressed.  To avoid repeatedly decompressing
+recently accessed data Squashfs uses two small metadata and fragment caches.
+
+The cache is not used for file datablocks, these are decompressed and cached in
+the page-cache in the normal way.  The cache is used to temporarily cache
+fragment and metadata blocks which have been read as a result of a metadata
+(i.e. inode or directory) or fragment access.  Because metadata and fragments
+are packed together into blocks (to gain greater compression) the read of a
+particular piece of metadata or fragment will retrieve other metadata/fragments
+which have been packed with it, these because of locality-of-reference may be
+read in the near future. Temporarily caching them ensures they are available
+for near future access without requiring an additional read and decompress.
+
+In the future this internal cache may be replaced with an implementation which
+uses the kernel page cache.  Because the page cache operates on page sized
+units this may introduce additional complexity in terms of locking and
+associated race conditions.
diff --git a/squashfs-tools/kernel/README b/squashfs-tools/kernel/README
new file mode 100644
index 0000000..54ab958
--- /dev/null
+++ b/squashfs-tools/kernel/README
@@ -0,0 +1,3 @@
+Squashfs is now in mainline at www.kernel.org.
+
+These files are obsolete and not updated.
diff --git a/squashfs-tools/kernel/fs/squashfs/Makefile b/squashfs-tools/kernel/fs/squashfs/Makefile
new file mode 100644
index 0000000..8258cf9
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for the linux squashfs routines.
+#
+
+obj-$(CONFIG_SQUASHFS) += squashfs.o
+squashfs-y += block.o cache.o dir.o export.o file.o fragment.o id.o inode.o
+squashfs-y += namei.o super.o symlink.o
+#squashfs-y += squashfs2_0.o
diff --git a/squashfs-tools/kernel/fs/squashfs/block.c b/squashfs-tools/kernel/fs/squashfs/block.c
new file mode 100644
index 0000000..c837dfc
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/block.c
@@ -0,0 +1,274 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 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/mutex.h>
+#include <linux/string.h>
+#include <linux/buffer_head.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+/*
+ * 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;
+	}
+
+	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
+ * generated a larger block - this does occasionally happen with zlib).
+ */
+int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
+			int length, u64 *next_index, int srclength)
+{
+	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;
+	int bytes, compressed, b = 0, k = 0, page = 0, avail;
+
+
+	bh = kcalloc((msblk->block_size >> msblk->devblksize_log2) + 1,
+				sizeof(*bh), GFP_KERNEL);
+	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",
+			index, compressed ? "" : "un", length, srclength);
+
+		if (length < 0 || length > srclength ||
+				(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);
+
+		if (length < 0 || length > srclength ||
+					(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);
+	}
+
+	if (compressed) {
+		int zlib_err = 0, zlib_init = 0;
+
+		/*
+		 * Uncompress block.
+		 */
+
+		mutex_lock(&msblk->read_data_mutex);
+
+		msblk->stream.avail_out = 0;
+		msblk->stream.avail_in = 0;
+
+		bytes = length;
+		do {
+			if (msblk->stream.avail_in == 0 && k < b) {
+				avail = min(bytes, msblk->devblksize - offset);
+				bytes -= avail;
+				wait_on_buffer(bh[k]);
+				if (!buffer_uptodate(bh[k]))
+					goto release_mutex;
+
+				if (avail == 0) {
+					offset = 0;
+					put_bh(bh[k++]);
+					continue;
+				}
+
+				msblk->stream.next_in = bh[k]->b_data + offset;
+				msblk->stream.avail_in = avail;
+				offset = 0;
+			}
+
+			if (msblk->stream.avail_out == 0) {
+				msblk->stream.next_out = buffer[page++];
+				msblk->stream.avail_out = PAGE_CACHE_SIZE;
+			}
+
+			if (!zlib_init) {
+				zlib_err = zlib_inflateInit(&msblk->stream);
+				if (zlib_err != Z_OK) {
+					ERROR("zlib_inflateInit returned"
+						" unexpected result 0x%x,"
+						" srclength %d\n", zlib_err,
+						srclength);
+					goto release_mutex;
+				}
+				zlib_init = 1;
+			}
+
+			zlib_err = zlib_inflate(&msblk->stream, Z_NO_FLUSH);
+
+			if (msblk->stream.avail_in == 0 && k < b)
+				put_bh(bh[k++]);
+		} while (zlib_err == Z_OK);
+
+		if (zlib_err != Z_STREAM_END) {
+			ERROR("zlib_inflate returned unexpected result"
+				" 0x%x, srclength %d, avail_in %d,"
+				" avail_out %d\n", zlib_err, srclength,
+				msblk->stream.avail_in,
+				msblk->stream.avail_out);
+			goto release_mutex;
+		}
+
+		zlib_err = zlib_inflateEnd(&msblk->stream);
+		if (zlib_err != Z_OK) {
+			ERROR("zlib_inflateEnd returned unexpected result 0x%x,"
+				" srclength %d\n", zlib_err, srclength);
+			goto release_mutex;
+		}
+		length = msblk->stream.total_out;
+		mutex_unlock(&msblk->read_data_mutex);
+	} else {
+		/*
+		 * Block is uncompressed.
+		 */
+		int i, in, pg_offset = 0;
+
+		for (i = 0; i < b; i++) {
+			wait_on_buffer(bh[i]);
+			if (!buffer_uptodate(bh[i]))
+				goto block_release;
+		}
+
+		for (bytes = length; k < b; k++) {
+			in = min(bytes, msblk->devblksize - offset);
+			bytes -= in;
+			while (in) {
+				if (pg_offset == PAGE_CACHE_SIZE) {
+					page++;
+					pg_offset = 0;
+				}
+				avail = min_t(int, in, PAGE_CACHE_SIZE -
+						pg_offset);
+				memcpy(buffer[page] + pg_offset,
+						bh[k]->b_data + offset, avail);
+				in -= avail;
+				pg_offset += avail;
+				offset += avail;
+			}
+			offset = 0;
+			put_bh(bh[k]);
+		}
+	}
+
+	kfree(bh);
+	return length;
+
+release_mutex:
+	mutex_unlock(&msblk->read_data_mutex);
+
+block_release:
+	for (; k < b; k++)
+		put_bh(bh[k]);
+
+read_failure:
+	ERROR("sb_bread failed reading block 0x%llx\n", cur_index);
+	kfree(bh);
+	return -EIO;
+}
diff --git a/squashfs-tools/kernel/fs/squashfs/cache.c b/squashfs-tools/kernel/fs/squashfs/cache.c
new file mode 100644
index 0000000..f29eda1
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/cache.c
@@ -0,0 +1,412 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * cache.c
+ */
+
+/*
+ * Blocks in Squashfs are compressed.  To avoid repeatedly decompressing
+ * recently accessed data Squashfs uses two small metadata and fragment caches.
+ *
+ * This file implements a generic cache implementation used for both caches,
+ * plus functions layered ontop of the generic cache implementation to
+ * access the metadata and fragment caches.
+ *
+ * To avoid out of memory and fragmentation isssues with vmalloc the cache
+ * uses sequences of kmalloced PAGE_CACHE_SIZE buffers.
+ *
+ * It should be noted that the cache is not used for file datablocks, these
+ * are decompressed and cached in the page-cache in the normal way.  The
+ * cache is only used to temporarily cache fragment and metadata blocks
+ * which have been read as as a result of a metadata (i.e. inode or
+ * directory) or fragment access.  Because metadata and fragments are packed
+ * together into blocks (to gain greater compression) the read of a particular
+ * piece of metadata or fragment will retrieve other metadata/fragments which
+ * have been packed with it, these because of locality-of-reference may be read
+ * in the near future. Temporarily caching them ensures they are available for
+ * near future access without requiring an additional read and decompress.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/zlib.h>
+#include <linux/pagemap.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+/*
+ * Look-up block in cache, and increment usage count.  If not in cache, read
+ * and decompress it from disk.
+ */
+struct squashfs_cache_entry *squashfs_cache_get(struct super_block *sb,
+	struct squashfs_cache *cache, u64 block, int length)
+{
+	int i, n;
+	struct squashfs_cache_entry *entry;
+
+	spin_lock(&cache->lock);
+
+	while (1) {
+		for (i = 0; i < cache->entries; i++)
+			if (cache->entry[i].block == block)
+				break;
+
+		if (i == cache->entries) {
+			/*
+			 * Block not in cache, if all cache entries are used
+			 * go to sleep waiting for one to become available.
+			 */
+			if (cache->unused == 0) {
+				cache->num_waiters++;
+				spin_unlock(&cache->lock);
+				wait_event(cache->wait_queue, cache->unused);
+				spin_lock(&cache->lock);
+				cache->num_waiters--;
+				continue;
+			}
+
+			/*
+			 * At least one unused cache entry.  A simple
+			 * round-robin strategy is used to choose the entry to
+			 * be evicted from the cache.
+			 */
+			i = cache->next_blk;
+			for (n = 0; n < cache->entries; n++) {
+				if (cache->entry[i].refcount == 0)
+					break;
+				i = (i + 1) % cache->entries;
+			}
+
+			cache->next_blk = (i + 1) % cache->entries;
+			entry = &cache->entry[i];
+
+			/*
+			 * Initialise choosen cache entry, and fill it in from
+			 * disk.
+			 */
+			cache->unused--;
+			entry->block = block;
+			entry->refcount = 1;
+			entry->pending = 1;
+			entry->num_waiters = 0;
+			entry->error = 0;
+			spin_unlock(&cache->lock);
+
+			entry->length = squashfs_read_data(sb, entry->data,
+				block, length, &entry->next_index,
+				cache->block_size);
+
+			spin_lock(&cache->lock);
+
+			if (entry->length < 0)
+				entry->error = entry->length;
+
+			entry->pending = 0;
+
+			/*
+			 * While filling this entry one or more other processes
+			 * have looked it up in the cache, and have slept
+			 * waiting for it to become available.
+			 */
+			if (entry->num_waiters) {
+				spin_unlock(&cache->lock);
+				wake_up_all(&entry->wait_queue);
+			} else
+				spin_unlock(&cache->lock);
+
+			goto out;
+		}
+
+		/*
+		 * Block already in cache.  Increment refcount so it doesn't
+		 * get reused until we're finished with it, if it was
+		 * previously unused there's one less cache entry available
+		 * for reuse.
+		 */
+		entry = &cache->entry[i];
+		if (entry->refcount == 0)
+			cache->unused--;
+		entry->refcount++;
+
+		/*
+		 * If the entry is currently being filled in by another process
+		 * go to sleep waiting for it to become available.
+		 */
+		if (entry->pending) {
+			entry->num_waiters++;
+			spin_unlock(&cache->lock);
+			wait_event(entry->wait_queue, !entry->pending);
+		} else
+			spin_unlock(&cache->lock);
+
+		goto out;
+	}
+
+out:
+	TRACE("Got %s %d, start block %lld, refcount %d, error %d\n",
+		cache->name, i, entry->block, entry->refcount, entry->error);
+
+	if (entry->error)
+		ERROR("Unable to read %s cache entry [%llx]\n", cache->name,
+							block);
+	return entry;
+}
+
+
+/*
+ * Release cache entry, once usage count is zero it can be reused.
+ */
+void squashfs_cache_put(struct squashfs_cache_entry *entry)
+{
+	struct squashfs_cache *cache = entry->cache;
+
+	spin_lock(&cache->lock);
+	entry->refcount--;
+	if (entry->refcount == 0) {
+		cache->unused++;
+		/*
+		 * If there's any processes waiting for a block to become
+		 * available, wake one up.
+		 */
+		if (cache->num_waiters) {
+			spin_unlock(&cache->lock);
+			wake_up(&cache->wait_queue);
+			return;
+		}
+	}
+	spin_unlock(&cache->lock);
+}
+
+/*
+ * Delete cache reclaiming all kmalloced buffers.
+ */
+void squashfs_cache_delete(struct squashfs_cache *cache)
+{
+	int i, j;
+
+	if (cache == NULL)
+		return;
+
+	for (i = 0; i < cache->entries; i++) {
+		if (cache->entry[i].data) {
+			for (j = 0; j < cache->pages; j++)
+				kfree(cache->entry[i].data[j]);
+			kfree(cache->entry[i].data);
+		}
+	}
+
+	kfree(cache->entry);
+	kfree(cache);
+}
+
+
+/*
+ * Initialise cache allocating the specified number of entries, each of
+ * size block_size.  To avoid vmalloc fragmentation issues each entry
+ * is allocated as a sequence of kmalloced PAGE_CACHE_SIZE buffers.
+ */
+struct squashfs_cache *squashfs_cache_init(char *name, int entries,
+	int block_size)
+{
+	int i, j;
+	struct squashfs_cache *cache = kzalloc(sizeof(*cache), GFP_KERNEL);
+
+	if (cache == NULL) {
+		ERROR("Failed to allocate %s cache\n", name);
+		return NULL;
+	}
+
+	cache->entry = kcalloc(entries, sizeof(*(cache->entry)), GFP_KERNEL);
+	if (cache->entry == NULL) {
+		ERROR("Failed to allocate %s cache\n", name);
+		goto cleanup;
+	}
+
+	cache->next_blk = 0;
+	cache->unused = entries;
+	cache->entries = entries;
+	cache->block_size = block_size;
+	cache->pages = block_size >> PAGE_CACHE_SHIFT;
+	cache->name = name;
+	cache->num_waiters = 0;
+	spin_lock_init(&cache->lock);
+	init_waitqueue_head(&cache->wait_queue);
+
+	for (i = 0; i < entries; i++) {
+		struct squashfs_cache_entry *entry = &cache->entry[i];
+
+		init_waitqueue_head(&cache->entry[i].wait_queue);
+		entry->cache = cache;
+		entry->block = SQUASHFS_INVALID_BLK;
+		entry->data = kcalloc(cache->pages, sizeof(void *), GFP_KERNEL);
+		if (entry->data == NULL) {
+			ERROR("Failed to allocate %s cache entry\n", name);
+			goto cleanup;
+		}
+
+		for (j = 0; j < cache->pages; j++) {
+			entry->data[j] = kmalloc(PAGE_CACHE_SIZE, GFP_KERNEL);
+			if (entry->data[j] == NULL) {
+				ERROR("Failed to allocate %s buffer\n", name);
+				goto cleanup;
+			}
+		}
+	}
+
+	return cache;
+
+cleanup:
+	squashfs_cache_delete(cache);
+	return NULL;
+}
+
+
+/*
+ * Copy upto length bytes from cache entry to buffer starting at offset bytes
+ * into the cache entry.  If there's not length bytes then copy the number of
+ * bytes available.  In all cases return the number of bytes copied.
+ */
+int squashfs_copy_data(void *buffer, struct squashfs_cache_entry *entry,
+		int offset, int length)
+{
+	int remaining = length;
+
+	if (length == 0)
+		return 0;
+	else if (buffer == NULL)
+		return min(length, entry->length - offset);
+
+	while (offset < entry->length) {
+		void *buff = entry->data[offset / PAGE_CACHE_SIZE]
+				+ (offset % PAGE_CACHE_SIZE);
+		int bytes = min_t(int, entry->length - offset,
+				PAGE_CACHE_SIZE - (offset % PAGE_CACHE_SIZE));
+
+		if (bytes >= remaining) {
+			memcpy(buffer, buff, remaining);
+			remaining = 0;
+			break;
+		}
+
+		memcpy(buffer, buff, bytes);
+		buffer += bytes;
+		remaining -= bytes;
+		offset += bytes;
+	}
+
+	return length - remaining;
+}
+
+
+/*
+ * Read length bytes from metadata position <block, offset> (block is the
+ * start of the compressed block on disk, and offset is the offset into
+ * the block once decompressed).  Data is packed into consecutive blocks,
+ * and length bytes may require reading more than one block.
+ */
+int squashfs_read_metadata(struct super_block *sb, void *buffer,
+		u64 *block, int *offset, int length)
+{
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+	int bytes, copied = length;
+	struct squashfs_cache_entry *entry;
+
+	TRACE("Entered squashfs_read_metadata [%llx:%x]\n", *block, *offset);
+
+	while (length) {
+		entry = squashfs_cache_get(sb, msblk->block_cache, *block, 0);
+		if (entry->error)
+			return entry->error;
+		else if (*offset >= entry->length)
+			return -EIO;
+
+		bytes = squashfs_copy_data(buffer, entry, *offset, length);
+		if (buffer)
+			buffer += bytes;
+		length -= bytes;
+		*offset += bytes;
+
+		if (*offset == entry->length) {
+			*block = entry->next_index;
+			*offset = 0;
+		}
+
+		squashfs_cache_put(entry);
+	}
+
+	return copied;
+}
+
+
+/*
+ * Look-up in the fragmment cache the fragment located at <start_block> in the
+ * filesystem.  If necessary read and decompress it from disk.
+ */
+struct squashfs_cache_entry *squashfs_get_fragment(struct super_block *sb,
+				u64 start_block, int length)
+{
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+
+	return squashfs_cache_get(sb, msblk->fragment_cache, start_block,
+		length);
+}
+
+
+/*
+ * Read and decompress the datablock located at <start_block> in the
+ * filesystem.  The cache is used here to avoid duplicating locking and
+ * read/decompress code.
+ */
+struct squashfs_cache_entry *squashfs_get_datablock(struct super_block *sb,
+				u64 start_block, int length)
+{
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+
+	return squashfs_cache_get(sb, msblk->read_page, start_block, length);
+}
+
+
+/*
+ * Read a filesystem table (uncompressed sequence of bytes) from disk
+ */
+int squashfs_read_table(struct super_block *sb, void *buffer, u64 block,
+	int length)
+{
+	int pages = (length + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
+	int i, res;
+	void **data = kcalloc(pages, sizeof(void *), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < pages; i++, buffer += PAGE_CACHE_SIZE)
+		data[i] = buffer;
+	res = squashfs_read_data(sb, data, block, length |
+		SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, length);
+	kfree(data);
+	return res;
+}
diff --git a/squashfs-tools/kernel/fs/squashfs/dir.c b/squashfs-tools/kernel/fs/squashfs/dir.c
new file mode 100644
index 0000000..566b0ea
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/dir.c
@@ -0,0 +1,235 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * dir.c
+ */
+
+/*
+ * This file implements code to read directories from disk.
+ *
+ * See namei.c for a description of directory organisation on disk.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/slab.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+static const unsigned char squashfs_filetype_table[] = {
+	DT_UNKNOWN, DT_DIR, DT_REG, DT_LNK, DT_BLK, DT_CHR, DT_FIFO, DT_SOCK
+};
+
+/*
+ * Lookup offset (f_pos) in the directory index, returning the
+ * metadata block containing it.
+ *
+ * If we get an error reading the index then return the part of the index
+ * (if any) we have managed to read - the index isn't essential, just
+ * quicker.
+ */
+static int get_dir_index_using_offset(struct super_block *sb,
+	u64 *next_block, int *next_offset, u64 index_start, int index_offset,
+	int i_count, u64 f_pos)
+{
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+	int err, i, index, length = 0;
+	struct squashfs_dir_index dir_index;
+
+	TRACE("Entered get_dir_index_using_offset, i_count %d, f_pos %lld\n",
+					i_count, f_pos);
+
+	/*
+	 * Translate from external f_pos to the internal f_pos.  This
+	 * is offset by 3 because we invent "." and ".." entries which are
+	 * not actually stored in the directory.
+	 */
+	if (f_pos < 3)
+		return f_pos;
+	f_pos -= 3;
+
+	for (i = 0; i < i_count; i++) {
+		err = squashfs_read_metadata(sb, &dir_index, &index_start,
+				&index_offset, sizeof(dir_index));
+		if (err < 0)
+			break;
+
+		index = le32_to_cpu(dir_index.index);
+		if (index > f_pos)
+			/*
+			 * Found the index we're looking for.
+			 */
+			break;
+
+		err = squashfs_read_metadata(sb, NULL, &index_start,
+				&index_offset, le32_to_cpu(dir_index.size) + 1);
+		if (err < 0)
+			break;
+
+		length = index;
+		*next_block = le32_to_cpu(dir_index.start_block) +
+					msblk->directory_table;
+	}
+
+	*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
+
+	/*
+	 * Translate back from internal f_pos to external f_pos.
+	 */
+	return length + 3;
+}
+
+
+static int squashfs_readdir(struct file *file, void *dirent, filldir_t filldir)
+{
+	struct inode *inode = file->f_dentry->d_inode;
+	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
+	u64 block = squashfs_i(inode)->start + msblk->directory_table;
+	int offset = squashfs_i(inode)->offset, length = 0, dir_count, size,
+				type, err;
+	unsigned int inode_number;
+	struct squashfs_dir_header dirh;
+	struct squashfs_dir_entry *dire;
+
+	TRACE("Entered squashfs_readdir [%llx:%x]\n", block, offset);
+
+	dire = kmalloc(sizeof(*dire) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL);
+	if (dire == NULL) {
+		ERROR("Failed to allocate squashfs_dir_entry\n");
+		goto finish;
+	}
+
+	/*
+	 * Return "." and  ".." entries as the first two filenames in the
+	 * directory.  To maximise compression these two entries are not
+	 * stored in the directory, and so we invent them here.
+	 *
+	 * It also means that the external f_pos is offset by 3 from the
+	 * on-disk directory f_pos.
+	 */
+	while (file->f_pos < 3) {
+		char *name;
+		int i_ino;
+
+		if (file->f_pos == 0) {
+			name = ".";
+			size = 1;
+			i_ino = inode->i_ino;
+		} else {
+			name = "..";
+			size = 2;
+			i_ino = squashfs_i(inode)->parent;
+		}
+
+		TRACE("Calling filldir(%p, %s, %d, %lld, %d, %d)\n",
+				dirent, name, size, file->f_pos, i_ino,
+				squashfs_filetype_table[1]);
+
+		if (filldir(dirent, name, size, file->f_pos, i_ino,
+				squashfs_filetype_table[1]) < 0) {
+				TRACE("Filldir returned less than 0\n");
+			goto finish;
+		}
+
+		file->f_pos += size;
+	}
+
+	length = get_dir_index_using_offset(inode->i_sb, &block, &offset,
+				squashfs_i(inode)->dir_idx_start,
+				squashfs_i(inode)->dir_idx_offset,
+				squashfs_i(inode)->dir_idx_cnt,
+				file->f_pos);
+
+	while (length < i_size_read(inode)) {
+		/*
+		 * Read directory header
+		 */
+		err = squashfs_read_metadata(inode->i_sb, &dirh, &block,
+					&offset, sizeof(dirh));
+		if (err < 0)
+			goto failed_read;
+
+		length += sizeof(dirh);
+
+		dir_count = le32_to_cpu(dirh.count) + 1;
+		while (dir_count--) {
+			/*
+			 * Read directory entry.
+			 */
+			err = squashfs_read_metadata(inode->i_sb, dire, &block,
+					&offset, sizeof(*dire));
+			if (err < 0)
+				goto failed_read;
+
+			size = le16_to_cpu(dire->size) + 1;
+
+			err = squashfs_read_metadata(inode->i_sb, dire->name,
+					&block, &offset, size);
+			if (err < 0)
+				goto failed_read;
+
+			length += sizeof(*dire) + size;
+
+			if (file->f_pos >= length)
+				continue;
+
+			dire->name[size] = '\0';
+			inode_number = le32_to_cpu(dirh.inode_number) +
+				((short) le16_to_cpu(dire->inode_number));
+			type = le16_to_cpu(dire->type);
+
+			TRACE("Calling filldir(%p, %s, %d, %lld, %x:%x, %d, %d)"
+					"\n", dirent, dire->name, size,
+					file->f_pos,
+					le32_to_cpu(dirh.start_block),
+					le16_to_cpu(dire->offset),
+					inode_number,
+					squashfs_filetype_table[type]);
+
+			if (filldir(dirent, dire->name, size, file->f_pos,
+					inode_number,
+					squashfs_filetype_table[type]) < 0) {
+				TRACE("Filldir returned less than 0\n");
+				goto finish;
+			}
+
+			file->f_pos = length;
+		}
+	}
+
+finish:
+	kfree(dire);
+	return 0;
+
+failed_read:
+	ERROR("Unable to read directory block [%llx:%x]\n", block, offset);
+	kfree(dire);
+	return 0;
+}
+
+
+const struct file_operations squashfs_dir_ops = {
+	.read = generic_read_dir,
+	.readdir = squashfs_readdir
+};
diff --git a/squashfs-tools/kernel/fs/squashfs/export.c b/squashfs-tools/kernel/fs/squashfs/export.c
new file mode 100644
index 0000000..69e971d
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/export.c
@@ -0,0 +1,155 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * export.c
+ */
+
+/*
+ * This file implements code to make Squashfs filesystems exportable (NFS etc.)
+ *
+ * The export code uses an inode lookup table to map inode numbers passed in
+ * filehandles to an inode location on disk.  This table is stored compressed
+ * into metadata blocks.  A second index table is used to locate these.  This
+ * second index table for speed of access (and because it is small) is read at
+ * mount time and cached in memory.
+ *
+ * The inode lookup table is used only by the export code, inode disk
+ * locations are directly encoded in directories, enabling direct access
+ * without an intermediate lookup for all operations except the export ops.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/dcache.h>
+#include <linux/exportfs.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+/*
+ * Look-up inode number (ino) in table, returning the inode location.
+ */
+static long long squashfs_inode_lookup(struct super_block *sb, int ino_num)
+{
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+	int blk = SQUASHFS_LOOKUP_BLOCK(ino_num - 1);
+	int offset = SQUASHFS_LOOKUP_BLOCK_OFFSET(ino_num - 1);
+	u64 start = le64_to_cpu(msblk->inode_lookup_table[blk]);
+	__le64 ino;
+	int err;
+
+	TRACE("Entered squashfs_inode_lookup, inode_number = %d\n", ino_num);
+
+	err = squashfs_read_metadata(sb, &ino, &start, &offset, sizeof(ino));
+	if (err < 0)
+		return err;
+
+	TRACE("squashfs_inode_lookup, inode = 0x%llx\n",
+		(u64) le64_to_cpu(ino));
+
+	return le64_to_cpu(ino);
+}
+
+
+static struct dentry *squashfs_export_iget(struct super_block *sb,
+	unsigned int ino_num)
+{
+	long long ino;
+	struct dentry *dentry = ERR_PTR(-ENOENT);
+
+	TRACE("Entered squashfs_export_iget\n");
+
+	ino = squashfs_inode_lookup(sb, ino_num);
+	if (ino >= 0)
+		dentry = d_obtain_alias(squashfs_iget(sb, ino, ino_num));
+
+	return dentry;
+}
+
+
+static struct dentry *squashfs_fh_to_dentry(struct super_block *sb,
+		struct fid *fid, int fh_len, int fh_type)
+{
+	if ((fh_type != FILEID_INO32_GEN && fh_type != FILEID_INO32_GEN_PARENT)
+			|| fh_len < 2)
+		return NULL;
+
+	return squashfs_export_iget(sb, fid->i32.ino);
+}
+
+
+static struct dentry *squashfs_fh_to_parent(struct super_block *sb,
+		struct fid *fid, int fh_len, int fh_type)
+{
+	if (fh_type != FILEID_INO32_GEN_PARENT || fh_len < 4)
+		return NULL;
+
+	return squashfs_export_iget(sb, fid->i32.parent_ino);
+}
+
+
+static struct dentry *squashfs_get_parent(struct dentry *child)
+{
+	struct inode *inode = child->d_inode;
+	unsigned int parent_ino = squashfs_i(inode)->parent;
+
+	return squashfs_export_iget(inode->i_sb, parent_ino);
+}
+
+
+/*
+ * Read uncompressed inode lookup table indexes off disk into memory
+ */
+__le64 *squashfs_read_inode_lookup_table(struct super_block *sb,
+		u64 lookup_table_start, unsigned int inodes)
+{
+	unsigned int length = SQUASHFS_LOOKUP_BLOCK_BYTES(inodes);
+	__le64 *inode_lookup_table;
+	int err;
+
+	TRACE("In read_inode_lookup_table, length %d\n", length);
+
+	/* Allocate inode lookup table indexes */
+	inode_lookup_table = kmalloc(length, GFP_KERNEL);
+	if (inode_lookup_table == NULL) {
+		ERROR("Failed to allocate inode lookup table\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	err = squashfs_read_table(sb, inode_lookup_table, lookup_table_start,
+			length);
+	if (err < 0) {
+		ERROR("unable to read inode lookup table\n");
+		kfree(inode_lookup_table);
+		return ERR_PTR(err);
+	}
+
+	return inode_lookup_table;
+}
+
+
+const struct export_operations squashfs_export_ops = {
+	.fh_to_dentry = squashfs_fh_to_dentry,
+	.fh_to_parent = squashfs_fh_to_parent,
+	.get_parent = squashfs_get_parent
+};
diff --git a/squashfs-tools/kernel/fs/squashfs/file.c b/squashfs-tools/kernel/fs/squashfs/file.c
new file mode 100644
index 0000000..717767d
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/file.c
@@ -0,0 +1,502 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * file.c
+ */
+
+/*
+ * This file contains code for handling regular files.  A regular file
+ * consists of a sequence of contiguous compressed blocks, and/or a
+ * compressed fragment block (tail-end packed block).   The compressed size
+ * of each datablock is stored in a block list contained within the
+ * file inode (itself stored in one or more compressed metadata blocks).
+ *
+ * To speed up access to datablocks when reading 'large' files (256 Mbytes or
+ * larger), the code implements an index cache that caches the mapping from
+ * block index to datablock location on disk.
+ *
+ * The index cache allows Squashfs to handle large files (up to 1.75 TiB) while
+ * retaining a simple and space-efficient block list on disk.  The cache
+ * is split into slots, caching up to eight 224 GiB files (128 KiB blocks).
+ * Larger files use multiple slots, with 1.75 TiB files using all 8 slots.
+ * The index cache is designed to be memory efficient, and by default uses
+ * 16 KiB.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/pagemap.h>
+#include <linux/mutex.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+/*
+ * Locate cache slot in range [offset, index] for specified inode.  If
+ * there's more than one return the slot closest to index.
+ */
+static struct meta_index *locate_meta_index(struct inode *inode, int offset,
+				int index)
+{
+	struct meta_index *meta = NULL;
+	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
+	int i;
+
+	mutex_lock(&msblk->meta_index_mutex);
+
+	TRACE("locate_meta_index: index %d, offset %d\n", index, offset);
+
+	if (msblk->meta_index == NULL)
+		goto not_allocated;
+
+	for (i = 0; i < SQUASHFS_META_SLOTS; i++) {
+		if (msblk->meta_index[i].inode_number == inode->i_ino &&
+				msblk->meta_index[i].offset >= offset &&
+				msblk->meta_index[i].offset <= index &&
+				msblk->meta_index[i].locked == 0) {
+			TRACE("locate_meta_index: entry %d, offset %d\n", i,
+					msblk->meta_index[i].offset);
+			meta = &msblk->meta_index[i];
+			offset = meta->offset;
+		}
+	}
+
+	if (meta)
+		meta->locked = 1;
+
+not_allocated:
+	mutex_unlock(&msblk->meta_index_mutex);
+
+	return meta;
+}
+
+
+/*
+ * Find and initialise an empty cache slot for index offset.
+ */
+static struct meta_index *empty_meta_index(struct inode *inode, int offset,
+				int skip)
+{
+	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
+	struct meta_index *meta = NULL;
+	int i;
+
+	mutex_lock(&msblk->meta_index_mutex);
+
+	TRACE("empty_meta_index: offset %d, skip %d\n", offset, skip);
+
+	if (msblk->meta_index == NULL) {
+		/*
+		 * First time cache index has been used, allocate and
+		 * initialise.  The cache index could be allocated at
+		 * mount time but doing it here means it is allocated only
+		 * if a 'large' file is read.
+		 */
+		msblk->meta_index = kcalloc(SQUASHFS_META_SLOTS,
+			sizeof(*(msblk->meta_index)), GFP_KERNEL);
+		if (msblk->meta_index == NULL) {
+			ERROR("Failed to allocate meta_index\n");
+			goto failed;
+		}
+		for (i = 0; i < SQUASHFS_META_SLOTS; i++) {
+			msblk->meta_index[i].inode_number = 0;
+			msblk->meta_index[i].locked = 0;
+		}
+		msblk->next_meta_index = 0;
+	}
+
+	for (i = SQUASHFS_META_SLOTS; i &&
+			msblk->meta_index[msblk->next_meta_index].locked; i--)
+		msblk->next_meta_index = (msblk->next_meta_index + 1) %
+			SQUASHFS_META_SLOTS;
+
+	if (i == 0) {
+		TRACE("empty_meta_index: failed!\n");
+		goto failed;
+	}
+
+	TRACE("empty_meta_index: returned meta entry %d, %p\n",
+			msblk->next_meta_index,
+			&msblk->meta_index[msblk->next_meta_index]);
+
+	meta = &msblk->meta_index[msblk->next_meta_index];
+	msblk->next_meta_index = (msblk->next_meta_index + 1) %
+			SQUASHFS_META_SLOTS;
+
+	meta->inode_number = inode->i_ino;
+	meta->offset = offset;
+	meta->skip = skip;
+	meta->entries = 0;
+	meta->locked = 1;
+
+failed:
+	mutex_unlock(&msblk->meta_index_mutex);
+	return meta;
+}
+
+
+static void release_meta_index(struct inode *inode, struct meta_index *meta)
+{
+	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
+	mutex_lock(&msblk->meta_index_mutex);
+	meta->locked = 0;
+	mutex_unlock(&msblk->meta_index_mutex);
+}
+
+
+/*
+ * Read the next n blocks from the block list, starting from
+ * metadata block <start_block, offset>.
+ */
+static long long read_indexes(struct super_block *sb, int n,
+				u64 *start_block, int *offset)
+{
+	int err, i;
+	long long block = 0;
+	__le32 *blist = kmalloc(PAGE_CACHE_SIZE, GFP_KERNEL);
+
+	if (blist == NULL) {
+		ERROR("read_indexes: Failed to allocate block_list\n");
+		return -ENOMEM;
+	}
+
+	while (n) {
+		int blocks = min_t(int, n, PAGE_CACHE_SIZE >> 2);
+
+		err = squashfs_read_metadata(sb, blist, start_block,
+				offset, blocks << 2);
+		if (err < 0) {
+			ERROR("read_indexes: reading block [%llx:%x]\n",
+				*start_block, *offset);
+			goto failure;
+		}
+
+		for (i = 0; i < blocks; i++) {
+			int size = le32_to_cpu(blist[i]);
+			block += SQUASHFS_COMPRESSED_SIZE_BLOCK(size);
+		}
+		n -= blocks;
+	}
+
+	kfree(blist);
+	return block;
+
+failure:
+	kfree(blist);
+	return err;
+}
+
+
+/*
+ * Each cache index slot has SQUASHFS_META_ENTRIES, each of which
+ * can cache one index -> datablock/blocklist-block mapping.  We wish
+ * to distribute these over the length of the file, entry[0] maps index x,
+ * entry[1] maps index x + skip, entry[2] maps index x + 2 * skip, and so on.
+ * The larger the file, the greater the skip factor.  The skip factor is
+ * limited to the size of the metadata cache (SQUASHFS_CACHED_BLKS) to ensure
+ * the number of metadata blocks that need to be read fits into the cache.
+ * If the skip factor is limited in this way then the file will use multiple
+ * slots.
+ */
+static inline int calculate_skip(int blocks)
+{
+	int skip = blocks / ((SQUASHFS_META_ENTRIES + 1)
+		 * SQUASHFS_META_INDEXES);
+	return min(SQUASHFS_CACHED_BLKS - 1, skip + 1);
+}
+
+
+/*
+ * Search and grow the index cache for the specified inode, returning the
+ * on-disk locations of the datablock and block list metadata block
+ * <index_block, index_offset> for index (scaled to nearest cache index).
+ */
+static int fill_meta_index(struct inode *inode, int index,
+		u64 *index_block, int *index_offset, u64 *data_block)
+{
+	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
+	int skip = calculate_skip(i_size_read(inode) >> msblk->block_log);
+	int offset = 0;
+	struct meta_index *meta;
+	struct meta_entry *meta_entry;
+	u64 cur_index_block = squashfs_i(inode)->block_list_start;
+	int cur_offset = squashfs_i(inode)->offset;
+	u64 cur_data_block = squashfs_i(inode)->start;
+	int err, i;
+
+	/*
+	 * Scale index to cache index (cache slot entry)
+	 */
+	index /= SQUASHFS_META_INDEXES * skip;
+
+	while (offset < index) {
+		meta = locate_meta_index(inode, offset + 1, index);
+
+		if (meta == NULL) {
+			meta = empty_meta_index(inode, offset + 1, skip);
+			if (meta == NULL)
+				goto all_done;
+		} else {
+			offset = index < meta->offset + meta->entries ? index :
+				meta->offset + meta->entries - 1;
+			meta_entry = &meta->meta_entry[offset - meta->offset];
+			cur_index_block = meta_entry->index_block +
+				msblk->inode_table;
+			cur_offset = meta_entry->offset;
+			cur_data_block = meta_entry->data_block;
+			TRACE("get_meta_index: offset %d, meta->offset %d, "
+				"meta->entries %d\n", offset, meta->offset,
+				meta->entries);
+			TRACE("get_meta_index: index_block 0x%llx, offset 0x%x"
+				" data_block 0x%llx\n", cur_index_block,
+				cur_offset, cur_data_block);
+		}
+
+		/*
+		 * If necessary grow cache slot by reading block list.  Cache
+		 * slot is extended up to index or to the end of the slot, in
+		 * which case further slots will be used.
+		 */
+		for (i = meta->offset + meta->entries; i <= index &&
+				i < meta->offset + SQUASHFS_META_ENTRIES; i++) {
+			int blocks = skip * SQUASHFS_META_INDEXES;
+			long long res = read_indexes(inode->i_sb, blocks,
+					&cur_index_block, &cur_offset);
+
+			if (res < 0) {
+				if (meta->entries == 0)
+					/*
+					 * Don't leave an empty slot on read
+					 * error allocated to this inode...
+					 */
+					meta->inode_number = 0;
+				err = res;
+				goto failed;
+			}
+
+			cur_data_block += res;
+			meta_entry = &meta->meta_entry[i - meta->offset];
+			meta_entry->index_block = cur_index_block -
+				msblk->inode_table;
+			meta_entry->offset = cur_offset;
+			meta_entry->data_block = cur_data_block;
+			meta->entries++;
+			offset++;
+		}
+
+		TRACE("get_meta_index: meta->offset %d, meta->entries %d\n",
+				meta->offset, meta->entries);
+
+		release_meta_index(inode, meta);
+	}
+
+all_done:
+	*index_block = cur_index_block;
+	*index_offset = cur_offset;
+	*data_block = cur_data_block;
+
+	/*
+	 * Scale cache index (cache slot entry) to index
+	 */
+	return offset * SQUASHFS_META_INDEXES * skip;
+
+failed:
+	release_meta_index(inode, meta);
+	return err;
+}
+
+
+/*
+ * Get the on-disk location and compressed size of the datablock
+ * specified by index.  Fill_meta_index() does most of the work.
+ */
+static int read_blocklist(struct inode *inode, int index, u64 *block)
+{
+	u64 start;
+	long long blks;
+	int offset;
+	__le32 size;
+	int res = fill_meta_index(inode, index, &start, &offset, block);
+
+	TRACE("read_blocklist: res %d, index %d, start 0x%llx, offset"
+		       " 0x%x, block 0x%llx\n", res, index, start, offset,
+			*block);
+
+	if (res < 0)
+		return res;
+
+	/*
+	 * res contains the index of the mapping returned by fill_meta_index(),
+	 * this will likely be less than the desired index (because the
+	 * meta_index cache works at a higher granularity).  Read any
+	 * extra block indexes needed.
+	 */
+	if (res < index) {
+		blks = read_indexes(inode->i_sb, index - res, &start, &offset);
+		if (blks < 0)
+			return (int) blks;
+		*block += blks;
+	}
+
+	/*
+	 * Read length of block specified by index.
+	 */
+	res = squashfs_read_metadata(inode->i_sb, &size, &start, &offset,
+			sizeof(size));
+	if (res < 0)
+		return res;
+	return le32_to_cpu(size);
+}
+
+
+static int squashfs_readpage(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
+	int bytes, i, offset = 0, sparse = 0;
+	struct squashfs_cache_entry *buffer = NULL;
+	void *pageaddr;
+
+	int mask = (1 << (msblk->block_log - PAGE_CACHE_SHIFT)) - 1;
+	int index = page->index >> (msblk->block_log - PAGE_CACHE_SHIFT);
+	int start_index = page->index & ~mask;
+	int end_index = start_index | mask;
+	int file_end = i_size_read(inode) >> msblk->block_log;
+
+	TRACE("Entered squashfs_readpage, page index %lx, start block %llx\n",
+				page->index, squashfs_i(inode)->start);
+
+	if (page->index >= ((i_size_read(inode) + PAGE_CACHE_SIZE - 1) >>
+					PAGE_CACHE_SHIFT))
+		goto out;
+
+	if (index < file_end || squashfs_i(inode)->fragment_block ==
+					SQUASHFS_INVALID_BLK) {
+		/*
+		 * Reading a datablock from disk.  Need to read block list
+		 * to get location and block size.
+		 */
+		u64 block = 0;
+		int bsize = read_blocklist(inode, index, &block);
+		if (bsize < 0)
+			goto error_out;
+
+		if (bsize == 0) { /* hole */
+			bytes = index == file_end ?
+				(i_size_read(inode) & (msblk->block_size - 1)) :
+				 msblk->block_size;
+			sparse = 1;
+		} else {
+			/*
+			 * Read and decompress datablock.
+			 */
+			buffer = squashfs_get_datablock(inode->i_sb,
+								block, bsize);
+			if (buffer->error) {
+				ERROR("Unable to read page, block %llx, size %x"
+					"\n", block, bsize);
+				squashfs_cache_put(buffer);
+				goto error_out;
+			}
+			bytes = buffer->length;
+		}
+	} else {
+		/*
+		 * Datablock is stored inside a fragment (tail-end packed
+		 * block).
+		 */
+		buffer = squashfs_get_fragment(inode->i_sb,
+				squashfs_i(inode)->fragment_block,
+				squashfs_i(inode)->fragment_size);
+
+		if (buffer->error) {
+			ERROR("Unable to read page, block %llx, size %x\n",
+				squashfs_i(inode)->fragment_block,
+				squashfs_i(inode)->fragment_size);
+			squashfs_cache_put(buffer);
+			goto error_out;
+		}
+		bytes = i_size_read(inode) & (msblk->block_size - 1);
+		offset = squashfs_i(inode)->fragment_offset;
+	}
+
+	/*
+	 * Loop copying datablock into pages.  As the datablock likely covers
+	 * many PAGE_CACHE_SIZE pages (default block size is 128 KiB) explicitly
+	 * grab the pages from the page cache, except for the page that we've
+	 * been called to fill.
+	 */
+	for (i = start_index; i <= end_index && bytes > 0; i++,
+			bytes -= PAGE_CACHE_SIZE, offset += PAGE_CACHE_SIZE) {
+		struct page *push_page;
+		int avail = sparse ? 0 : min_t(int, bytes, PAGE_CACHE_SIZE);
+
+		TRACE("bytes %d, i %d, available_bytes %d\n", bytes, i, avail);
+
+		push_page = (i == page->index) ? page :
+			grab_cache_page_nowait(page->mapping, i);
+
+		if (!push_page)
+			continue;
+
+		if (PageUptodate(push_page))
+			goto skip_page;
+
+		pageaddr = kmap_atomic(push_page, KM_USER0);
+		squashfs_copy_data(pageaddr, buffer, offset, avail);
+		memset(pageaddr + avail, 0, PAGE_CACHE_SIZE - avail);
+		kunmap_atomic(pageaddr, KM_USER0);
+		flush_dcache_page(push_page);
+		SetPageUptodate(push_page);
+skip_page:
+		unlock_page(push_page);
+		if (i != page->index)
+			page_cache_release(push_page);
+	}
+
+	if (!sparse)
+		squashfs_cache_put(buffer);
+
+	return 0;
+
+error_out:
+	SetPageError(page);
+out:
+	pageaddr = kmap_atomic(page, KM_USER0);
+	memset(pageaddr, 0, PAGE_CACHE_SIZE);
+	kunmap_atomic(pageaddr, KM_USER0);
+	flush_dcache_page(page);
+	if (!PageError(page))
+		SetPageUptodate(page);
+	unlock_page(page);
+
+	return 0;
+}
+
+
+const struct address_space_operations squashfs_aops = {
+	.readpage = squashfs_readpage
+};
diff --git a/squashfs-tools/kernel/fs/squashfs/fragment.c b/squashfs-tools/kernel/fs/squashfs/fragment.c
new file mode 100644
index 0000000..b5a2c15
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/fragment.c
@@ -0,0 +1,98 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * fragment.c
+ */
+
+/*
+ * This file implements code to handle compressed fragments (tail-end packed
+ * datablocks).
+ *
+ * Regular files contain a fragment index which is mapped to a fragment
+ * location on disk and compressed size using a fragment lookup table.
+ * Like everything in Squashfs this fragment lookup table is itself stored
+ * compressed into metadata blocks.  A second index table is used to locate
+ * these.  This second index table for speed of access (and because it
+ * is small) is read at mount time and cached in memory.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/slab.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+/*
+ * Look-up fragment using the fragment index table.  Return the on disk
+ * location of the fragment and its compressed size
+ */
+int squashfs_frag_lookup(struct super_block *sb, unsigned int fragment,
+				u64 *fragment_block)
+{
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+	int block = SQUASHFS_FRAGMENT_INDEX(fragment);
+	int offset = SQUASHFS_FRAGMENT_INDEX_OFFSET(fragment);
+	u64 start_block = le64_to_cpu(msblk->fragment_index[block]);
+	struct squashfs_fragment_entry fragment_entry;
+	int size;
+
+	size = squashfs_read_metadata(sb, &fragment_entry, &start_block,
+					&offset, sizeof(fragment_entry));
+	if (size < 0)
+		return size;
+
+	*fragment_block = le64_to_cpu(fragment_entry.start_block);
+	size = le32_to_cpu(fragment_entry.size);
+
+	return size;
+}
+
+
+/*
+ * Read the uncompressed fragment lookup table indexes off disk into memory
+ */
+__le64 *squashfs_read_fragment_index_table(struct super_block *sb,
+	u64 fragment_table_start, unsigned int fragments)
+{
+	unsigned int length = SQUASHFS_FRAGMENT_INDEX_BYTES(fragments);
+	__le64 *fragment_index;
+	int err;
+
+	/* Allocate fragment lookup table indexes */
+	fragment_index = kmalloc(length, GFP_KERNEL);
+	if (fragment_index == NULL) {
+		ERROR("Failed to allocate fragment index table\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	err = squashfs_read_table(sb, fragment_index, fragment_table_start,
+			length);
+	if (err < 0) {
+		ERROR("unable to read fragment index table\n");
+		kfree(fragment_index);
+		return ERR_PTR(err);
+	}
+
+	return fragment_index;
+}
diff --git a/squashfs-tools/kernel/fs/squashfs/id.c b/squashfs-tools/kernel/fs/squashfs/id.c
new file mode 100644
index 0000000..3795b83
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/id.c
@@ -0,0 +1,94 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * id.c
+ */
+
+/*
+ * This file implements code to handle uids and gids.
+ *
+ * For space efficiency regular files store uid and gid indexes, which are
+ * converted to 32-bit uids/gids using an id look up table.  This table is
+ * stored compressed into metadata blocks.  A second index table is used to
+ * locate these.  This second index table for speed of access (and because it
+ * is small) is read at mount time and cached in memory.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/slab.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+/*
+ * Map uid/gid index into real 32-bit uid/gid using the id look up table
+ */
+int squashfs_get_id(struct super_block *sb, unsigned int index,
+					unsigned int *id)
+{
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+	int block = SQUASHFS_ID_BLOCK(index);
+	int offset = SQUASHFS_ID_BLOCK_OFFSET(index);
+	u64 start_block = le64_to_cpu(msblk->id_table[block]);
+	__le32 disk_id;
+	int err;
+
+	err = squashfs_read_metadata(sb, &disk_id, &start_block, &offset,
+							sizeof(disk_id));
+	if (err < 0)
+		return err;
+
+	*id = le32_to_cpu(disk_id);
+	return 0;
+}
+
+
+/*
+ * Read uncompressed id lookup table indexes from disk into memory
+ */
+__le64 *squashfs_read_id_index_table(struct super_block *sb,
+			u64 id_table_start, unsigned short no_ids)
+{
+	unsigned int length = SQUASHFS_ID_BLOCK_BYTES(no_ids);
+	__le64 *id_table;
+	int err;
+
+	TRACE("In read_id_index_table, length %d\n", length);
+
+	/* Allocate id lookup table indexes */
+	id_table = kmalloc(length, GFP_KERNEL);
+	if (id_table == NULL) {
+		ERROR("Failed to allocate id index table\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	err = squashfs_read_table(sb, id_table, id_table_start, length);
+	if (err < 0) {
+		ERROR("unable to read id index table\n");
+		kfree(id_table);
+		return ERR_PTR(err);
+	}
+
+	return id_table;
+}
diff --git a/squashfs-tools/kernel/fs/squashfs/inode.c b/squashfs-tools/kernel/fs/squashfs/inode.c
new file mode 100644
index 0000000..7a63398
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/inode.c
@@ -0,0 +1,346 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * inode.c
+ */
+
+/*
+ * This file implements code to create and read inodes from disk.
+ *
+ * Inodes in Squashfs are identified by a 48-bit inode which encodes the
+ * location of the compressed metadata block containing the inode, and the byte
+ * offset into that block where the inode is placed (<block, offset>).
+ *
+ * To maximise compression there are different inodes for each file type
+ * (regular file, directory, device, etc.), the inode contents and length
+ * varying with the type.
+ *
+ * To further maximise compression, two types of regular file inode and
+ * directory inode are defined: inodes optimised for frequently occurring
+ * regular files and directories, and extended types where extra
+ * information has to be stored.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+/*
+ * Initialise VFS inode with the base inode information common to all
+ * Squashfs inode types.  Sqsh_ino contains the unswapped base inode
+ * off disk.
+ */
+static int squashfs_new_inode(struct super_block *sb, struct inode *inode,
+				struct squashfs_base_inode *sqsh_ino)
+{
+	int err;
+
+	err = squashfs_get_id(sb, le16_to_cpu(sqsh_ino->uid), &inode->i_uid);
+	if (err)
+		return err;
+
+	err = squashfs_get_id(sb, le16_to_cpu(sqsh_ino->guid), &inode->i_gid);
+	if (err)
+		return err;
+
+	inode->i_ino = le32_to_cpu(sqsh_ino->inode_number);
+	inode->i_mtime.tv_sec = le32_to_cpu(sqsh_ino->mtime);
+	inode->i_atime.tv_sec = inode->i_mtime.tv_sec;
+	inode->i_ctime.tv_sec = inode->i_mtime.tv_sec;
+	inode->i_mode = le16_to_cpu(sqsh_ino->mode);
+	inode->i_size = 0;
+
+	return err;
+}
+
+
+struct inode *squashfs_iget(struct super_block *sb, long long ino,
+				unsigned int ino_number)
+{
+	struct inode *inode = iget_locked(sb, ino_number);
+	int err;
+
+	TRACE("Entered squashfs_iget\n");
+
+	if (!inode)
+		return ERR_PTR(-ENOMEM);
+	if (!(inode->i_state & I_NEW))
+		return inode;
+
+	err = squashfs_read_inode(inode, ino);
+	if (err) {
+		iget_failed(inode);
+		return ERR_PTR(err);
+	}
+
+	unlock_new_inode(inode);
+	return inode;
+}
+
+
+/*
+ * Initialise VFS inode by reading inode from inode table (compressed
+ * metadata).  The format and amount of data read depends on type.
+ */
+int squashfs_read_inode(struct inode *inode, long long ino)
+{
+	struct super_block *sb = inode->i_sb;
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+	u64 block = SQUASHFS_INODE_BLK(ino) + msblk->inode_table;
+	int err, type, offset = SQUASHFS_INODE_OFFSET(ino);
+	union squashfs_inode squashfs_ino;
+	struct squashfs_base_inode *sqshb_ino = &squashfs_ino.base;
+
+	TRACE("Entered squashfs_read_inode\n");
+
+	/*
+	 * Read inode base common to all inode types.
+	 */
+	err = squashfs_read_metadata(sb, sqshb_ino, &block,
+				&offset, sizeof(*sqshb_ino));
+	if (err < 0)
+		goto failed_read;
+
+	err = squashfs_new_inode(sb, inode, sqshb_ino);
+	if (err)
+		goto failed_read;
+
+	block = SQUASHFS_INODE_BLK(ino) + msblk->inode_table;
+	offset = SQUASHFS_INODE_OFFSET(ino);
+
+	type = le16_to_cpu(sqshb_ino->inode_type);
+	switch (type) {
+	case SQUASHFS_REG_TYPE: {
+		unsigned int frag_offset, frag_size, frag;
+		u64 frag_blk;
+		struct squashfs_reg_inode *sqsh_ino = &squashfs_ino.reg;
+
+		err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
+							sizeof(*sqsh_ino));
+		if (err < 0)
+			goto failed_read;
+
+		frag = le32_to_cpu(sqsh_ino->fragment);
+		if (frag != SQUASHFS_INVALID_FRAG) {
+			frag_offset = le32_to_cpu(sqsh_ino->offset);
+			frag_size = squashfs_frag_lookup(sb, frag, &frag_blk);
+			if (frag_size < 0) {
+				err = frag_size;
+				goto failed_read;
+			}
+		} else {
+			frag_blk = SQUASHFS_INVALID_BLK;
+			frag_size = 0;
+			frag_offset = 0;
+		}
+
+		inode->i_nlink = 1;
+		inode->i_size = le32_to_cpu(sqsh_ino->file_size);
+		inode->i_fop = &generic_ro_fops;
+		inode->i_mode |= S_IFREG;
+		inode->i_blocks = ((inode->i_size - 1) >> 9) + 1;
+		squashfs_i(inode)->fragment_block = frag_blk;
+		squashfs_i(inode)->fragment_size = frag_size;
+		squashfs_i(inode)->fragment_offset = frag_offset;
+		squashfs_i(inode)->start = le32_to_cpu(sqsh_ino->start_block);
+		squashfs_i(inode)->block_list_start = block;
+		squashfs_i(inode)->offset = offset;
+		inode->i_data.a_ops = &squashfs_aops;
+
+		TRACE("File inode %x:%x, start_block %llx, block_list_start "
+			"%llx, offset %x\n", SQUASHFS_INODE_BLK(ino),
+			offset, squashfs_i(inode)->start, block, offset);
+		break;
+	}
+	case SQUASHFS_LREG_TYPE: {
+		unsigned int frag_offset, frag_size, frag;
+		u64 frag_blk;
+		struct squashfs_lreg_inode *sqsh_ino = &squashfs_ino.lreg;
+
+		err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
+							sizeof(*sqsh_ino));
+		if (err < 0)
+			goto failed_read;
+
+		frag = le32_to_cpu(sqsh_ino->fragment);
+		if (frag != SQUASHFS_INVALID_FRAG) {
+			frag_offset = le32_to_cpu(sqsh_ino->offset);
+			frag_size = squashfs_frag_lookup(sb, frag, &frag_blk);
+			if (frag_size < 0) {
+				err = frag_size;
+				goto failed_read;
+			}
+		} else {
+			frag_blk = SQUASHFS_INVALID_BLK;
+			frag_size = 0;
+			frag_offset = 0;
+		}
+
+		inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
+		inode->i_size = le64_to_cpu(sqsh_ino->file_size);
+		inode->i_fop = &generic_ro_fops;
+		inode->i_mode |= S_IFREG;
+		inode->i_blocks = ((inode->i_size -
+				le64_to_cpu(sqsh_ino->sparse) - 1) >> 9) + 1;
+
+		squashfs_i(inode)->fragment_block = frag_blk;
+		squashfs_i(inode)->fragment_size = frag_size;
+		squashfs_i(inode)->fragment_offset = frag_offset;
+		squashfs_i(inode)->start = le64_to_cpu(sqsh_ino->start_block);
+		squashfs_i(inode)->block_list_start = block;
+		squashfs_i(inode)->offset = offset;
+		inode->i_data.a_ops = &squashfs_aops;
+
+		TRACE("File inode %x:%x, start_block %llx, block_list_start "
+			"%llx, offset %x\n", SQUASHFS_INODE_BLK(ino),
+			offset, squashfs_i(inode)->start, block, offset);
+		break;
+	}
+	case SQUASHFS_DIR_TYPE: {
+		struct squashfs_dir_inode *sqsh_ino = &squashfs_ino.dir;
+
+		err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
+				sizeof(*sqsh_ino));
+		if (err < 0)
+			goto failed_read;
+
+		inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
+		inode->i_size = le16_to_cpu(sqsh_ino->file_size);
+		inode->i_op = &squashfs_dir_inode_ops;
+		inode->i_fop = &squashfs_dir_ops;
+		inode->i_mode |= S_IFDIR;
+		squashfs_i(inode)->start = le32_to_cpu(sqsh_ino->start_block);
+		squashfs_i(inode)->offset = le16_to_cpu(sqsh_ino->offset);
+		squashfs_i(inode)->dir_idx_cnt = 0;
+		squashfs_i(inode)->parent = le32_to_cpu(sqsh_ino->parent_inode);
+
+		TRACE("Directory inode %x:%x, start_block %llx, offset %x\n",
+				SQUASHFS_INODE_BLK(ino), offset,
+				squashfs_i(inode)->start,
+				le16_to_cpu(sqsh_ino->offset));
+		break;
+	}
+	case SQUASHFS_LDIR_TYPE: {
+		struct squashfs_ldir_inode *sqsh_ino = &squashfs_ino.ldir;
+
+		err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
+				sizeof(*sqsh_ino));
+		if (err < 0)
+			goto failed_read;
+
+		inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
+		inode->i_size = le32_to_cpu(sqsh_ino->file_size);
+		inode->i_op = &squashfs_dir_inode_ops;
+		inode->i_fop = &squashfs_dir_ops;
+		inode->i_mode |= S_IFDIR;
+		squashfs_i(inode)->start = le32_to_cpu(sqsh_ino->start_block);
+		squashfs_i(inode)->offset = le16_to_cpu(sqsh_ino->offset);
+		squashfs_i(inode)->dir_idx_start = block;
+		squashfs_i(inode)->dir_idx_offset = offset;
+		squashfs_i(inode)->dir_idx_cnt = le16_to_cpu(sqsh_ino->i_count);
+		squashfs_i(inode)->parent = le32_to_cpu(sqsh_ino->parent_inode);
+
+		TRACE("Long directory inode %x:%x, start_block %llx, offset "
+				"%x\n", SQUASHFS_INODE_BLK(ino), offset,
+				squashfs_i(inode)->start,
+				le16_to_cpu(sqsh_ino->offset));
+		break;
+	}
+	case SQUASHFS_SYMLINK_TYPE:
+	case SQUASHFS_LSYMLINK_TYPE: {
+		struct squashfs_symlink_inode *sqsh_ino = &squashfs_ino.symlink;
+
+		err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
+				sizeof(*sqsh_ino));
+		if (err < 0)
+			goto failed_read;
+
+		inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
+		inode->i_size = le32_to_cpu(sqsh_ino->symlink_size);
+		inode->i_op = &page_symlink_inode_operations;
+		inode->i_data.a_ops = &squashfs_symlink_aops;
+		inode->i_mode |= S_IFLNK;
+		squashfs_i(inode)->start = block;
+		squashfs_i(inode)->offset = offset;
+
+		TRACE("Symbolic link inode %x:%x, start_block %llx, offset "
+				"%x\n", SQUASHFS_INODE_BLK(ino), offset,
+				block, offset);
+		break;
+	}
+	case SQUASHFS_BLKDEV_TYPE:
+	case SQUASHFS_CHRDEV_TYPE:
+	case SQUASHFS_LBLKDEV_TYPE:
+	case SQUASHFS_LCHRDEV_TYPE: {
+		struct squashfs_dev_inode *sqsh_ino = &squashfs_ino.dev;
+		unsigned int rdev;
+
+		err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
+				sizeof(*sqsh_ino));
+		if (err < 0)
+			goto failed_read;
+
+		if (type == SQUASHFS_CHRDEV_TYPE)
+			inode->i_mode |= S_IFCHR;
+		else
+			inode->i_mode |= S_IFBLK;
+		inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
+		rdev = le32_to_cpu(sqsh_ino->rdev);
+		init_special_inode(inode, inode->i_mode, new_decode_dev(rdev));
+
+		TRACE("Device inode %x:%x, rdev %x\n",
+				SQUASHFS_INODE_BLK(ino), offset, rdev);
+		break;
+	}
+	case SQUASHFS_FIFO_TYPE:
+	case SQUASHFS_SOCKET_TYPE:
+	case SQUASHFS_LFIFO_TYPE:
+	case SQUASHFS_LSOCKET_TYPE: {
+		struct squashfs_ipc_inode *sqsh_ino = &squashfs_ino.ipc;
+
+		err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
+				sizeof(*sqsh_ino));
+		if (err < 0)
+			goto failed_read;
+
+		if (type == SQUASHFS_FIFO_TYPE)
+			inode->i_mode |= S_IFIFO;
+		else
+			inode->i_mode |= S_IFSOCK;
+		inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
+		init_special_inode(inode, inode->i_mode, 0);
+		break;
+	}
+	default:
+		ERROR("Unknown inode type %d in squashfs_iget!\n", type);
+		return -EINVAL;
+	}
+
+	return 0;
+
+failed_read:
+	ERROR("Unable to read inode 0x%llx\n", ino);
+	return err;
+}
diff --git a/squashfs-tools/kernel/fs/squashfs/namei.c b/squashfs-tools/kernel/fs/squashfs/namei.c
new file mode 100644
index 0000000..9e39865
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/namei.c
@@ -0,0 +1,242 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * namei.c
+ */
+
+/*
+ * This file implements code to do filename lookup in directories.
+ *
+ * Like inodes, directories are packed into compressed metadata blocks, stored
+ * in a directory table.  Directories are accessed using the start address of
+ * the metablock containing the directory and the offset into the
+ * decompressed block (<block, offset>).
+ *
+ * Directories are organised in a slightly complex way, and are not simply
+ * a list of file names.  The organisation takes advantage of the
+ * fact that (in most cases) the inodes of the files will be in the same
+ * compressed metadata block, and therefore, can share the start block.
+ * Directories are therefore organised in a two level list, a directory
+ * header containing the shared start block value, and a sequence of directory
+ * entries, each of which share the shared start block.  A new directory header
+ * is written once/if the inode start block changes.  The directory
+ * header/directory entry list is repeated as many times as necessary.
+ *
+ * Directories are sorted, and can contain a directory index to speed up
+ * file lookup.  Directory indexes store one entry per metablock, each entry
+ * storing the index/filename mapping to the first directory header
+ * in each metadata block.  Directories are sorted in alphabetical order,
+ * and at lookup the index is scanned linearly looking for the first filename
+ * alphabetically larger than the filename being looked up.  At this point the
+ * location of the metadata block the filename is in has been found.
+ * The general idea of the index is ensure only one metadata block needs to be
+ * decompressed to do a lookup irrespective of the length of the directory.
+ * This scheme has the advantage that it doesn't require extra memory overhead
+ * and doesn't require much extra storage on disk.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/dcache.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+/*
+ * Lookup name in the directory index, returning the location of the metadata
+ * block containing it, and the directory index this represents.
+ *
+ * If we get an error reading the index then return the part of the index
+ * (if any) we have managed to read - the index isn't essential, just
+ * quicker.
+ */
+static int get_dir_index_using_name(struct super_block *sb,
+			u64 *next_block, int *next_offset, u64 index_start,
+			int index_offset, int i_count, const char *name,
+			int len)
+{
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+	int i, size, length = 0, err;
+	struct squashfs_dir_index *index;
+	char *str;
+
+	TRACE("Entered get_dir_index_using_name, i_count %d\n", i_count);
+
+	index = kmalloc(sizeof(*index) + SQUASHFS_NAME_LEN * 2 + 2, GFP_KERNEL);
+	if (index == NULL) {
+		ERROR("Failed to allocate squashfs_dir_index\n");
+		goto out;
+	}
+
+	str = &index->name[SQUASHFS_NAME_LEN + 1];
+	strncpy(str, name, len);
+	str[len] = '\0';
+
+	for (i = 0; i < i_count; i++) {
+		err = squashfs_read_metadata(sb, index, &index_start,
+					&index_offset, sizeof(*index));
+		if (err < 0)
+			break;
+
+
+		size = le32_to_cpu(index->size) + 1;
+
+		err = squashfs_read_metadata(sb, index->name, &index_start,
+					&index_offset, size);
+		if (err < 0)
+			break;
+
+		index->name[size] = '\0';
+
+		if (strcmp(index->name, str) > 0)
+			break;
+
+		length = le32_to_cpu(index->index);
+		*next_block = le32_to_cpu(index->start_block) +
+					msblk->directory_table;
+	}
+
+	*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
+	kfree(index);
+
+out:
+	/*
+	 * Return index (f_pos) of the looked up metadata block.  Translate
+	 * from internal f_pos to external f_pos which is offset by 3 because
+	 * we invent "." and ".." entries which are not actually stored in the
+	 * directory.
+	 */
+	return length + 3;
+}
+
+
+static struct dentry *squashfs_lookup(struct inode *dir, struct dentry *dentry,
+				 struct nameidata *nd)
+{
+	const unsigned char *name = dentry->d_name.name;
+	int len = dentry->d_name.len;
+	struct inode *inode = NULL;
+	struct squashfs_sb_info *msblk = dir->i_sb->s_fs_info;
+	struct squashfs_dir_header dirh;
+	struct squashfs_dir_entry *dire;
+	u64 block = squashfs_i(dir)->start + msblk->directory_table;
+	int offset = squashfs_i(dir)->offset;
+	int err, length = 0, dir_count, size;
+
+	TRACE("Entered squashfs_lookup [%llx:%x]\n", block, offset);
+
+	dire = kmalloc(sizeof(*dire) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL);
+	if (dire == NULL) {
+		ERROR("Failed to allocate squashfs_dir_entry\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	if (len > SQUASHFS_NAME_LEN) {
+		err = -ENAMETOOLONG;
+		goto failed;
+	}
+
+	length = get_dir_index_using_name(dir->i_sb, &block, &offset,
+				squashfs_i(dir)->dir_idx_start,
+				squashfs_i(dir)->dir_idx_offset,
+				squashfs_i(dir)->dir_idx_cnt, name, len);
+
+	while (length < i_size_read(dir)) {
+		/*
+		 * Read directory header.
+		 */
+		err = squashfs_read_metadata(dir->i_sb, &dirh, &block,
+				&offset, sizeof(dirh));
+		if (err < 0)
+			goto read_failure;
+
+		length += sizeof(dirh);
+
+		dir_count = le32_to_cpu(dirh.count) + 1;
+		while (dir_count--) {
+			/*
+			 * Read directory entry.
+			 */
+			err = squashfs_read_metadata(dir->i_sb, dire, &block,
+					&offset, sizeof(*dire));
+			if (err < 0)
+				goto read_failure;
+
+			size = le16_to_cpu(dire->size) + 1;
+
+			err = squashfs_read_metadata(dir->i_sb, dire->name,
+					&block, &offset, size);
+			if (err < 0)
+				goto read_failure;
+
+			length += sizeof(*dire) + size;
+
+			if (name[0] < dire->name[0])
+				goto exit_lookup;
+
+			if (len == size && !strncmp(name, dire->name, len)) {
+				unsigned int blk, off, ino_num;
+				long long ino;
+				blk = le32_to_cpu(dirh.start_block);
+				off = le16_to_cpu(dire->offset);
+				ino_num = le32_to_cpu(dirh.inode_number) +
+					(short) le16_to_cpu(dire->inode_number);
+				ino = SQUASHFS_MKINODE(blk, off);
+
+				TRACE("calling squashfs_iget for directory "
+					"entry %s, inode  %x:%x, %d\n", name,
+					blk, off, ino_num);
+
+				inode = squashfs_iget(dir->i_sb, ino, ino_num);
+				if (IS_ERR(inode)) {
+					err = PTR_ERR(inode);
+					goto failed;
+				}
+
+				goto exit_lookup;
+			}
+		}
+	}
+
+exit_lookup:
+	kfree(dire);
+	if (inode)
+		return d_splice_alias(inode, dentry);
+	d_add(dentry, inode);
+	return ERR_PTR(0);
+
+read_failure:
+	ERROR("Unable to read directory block [%llx:%x]\n",
+		squashfs_i(dir)->start + msblk->directory_table,
+		squashfs_i(dir)->offset);
+failed:
+	kfree(dire);
+	return ERR_PTR(err);
+}
+
+
+const struct inode_operations squashfs_dir_inode_ops = {
+	.lookup = squashfs_lookup
+};
diff --git a/squashfs-tools/kernel/fs/squashfs/squashfs.h b/squashfs-tools/kernel/fs/squashfs/squashfs.h
new file mode 100644
index 0000000..6b2515d
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/squashfs.h
@@ -0,0 +1,90 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * squashfs.h
+ */
+
+#define TRACE(s, args...)	pr_debug("SQUASHFS: "s, ## args)
+
+#define ERROR(s, args...)	pr_err("SQUASHFS error: "s, ## args)
+
+#define WARNING(s, args...)	pr_warning("SQUASHFS: "s, ## args)
+
+static inline struct squashfs_inode_info *squashfs_i(struct inode *inode)
+{
+	return list_entry(inode, struct squashfs_inode_info, vfs_inode);
+}
+
+/* block.c */
+extern int squashfs_read_data(struct super_block *, void **, u64, int, u64 *,
+				int);
+
+/* cache.c */
+extern struct squashfs_cache *squashfs_cache_init(char *, int, int);
+extern void squashfs_cache_delete(struct squashfs_cache *);
+extern struct squashfs_cache_entry *squashfs_cache_get(struct super_block *,
+				struct squashfs_cache *, u64, int);
+extern void squashfs_cache_put(struct squashfs_cache_entry *);
+extern int squashfs_copy_data(void *, struct squashfs_cache_entry *, int, int);
+extern int squashfs_read_metadata(struct super_block *, void *, u64 *,
+				int *, int);
+extern struct squashfs_cache_entry *squashfs_get_fragment(struct super_block *,
+				u64, int);
+extern struct squashfs_cache_entry *squashfs_get_datablock(struct super_block *,
+				u64, int);
+extern int squashfs_read_table(struct super_block *, void *, u64, int);
+
+/* export.c */
+extern __le64 *squashfs_read_inode_lookup_table(struct super_block *, u64,
+				unsigned int);
+
+/* fragment.c */
+extern int squashfs_frag_lookup(struct super_block *, unsigned int, u64 *);
+extern __le64 *squashfs_read_fragment_index_table(struct super_block *,
+				u64, unsigned int);
+
+/* id.c */
+extern int squashfs_get_id(struct super_block *, unsigned int, unsigned int *);
+extern __le64 *squashfs_read_id_index_table(struct super_block *, u64,
+				unsigned short);
+
+/* inode.c */
+extern struct inode *squashfs_iget(struct super_block *, long long,
+				unsigned int);
+extern int squashfs_read_inode(struct inode *, long long);
+
+/*
+ * Inodes and files operations
+ */
+
+/* dir.c */
+extern const struct file_operations squashfs_dir_ops;
+
+/* export.c */
+extern const struct export_operations squashfs_export_ops;
+
+/* file.c */
+extern const struct address_space_operations squashfs_aops;
+
+/* namei.c */
+extern const struct inode_operations squashfs_dir_inode_ops;
+
+/* symlink.c */
+extern const struct address_space_operations squashfs_symlink_aops;
diff --git a/squashfs-tools/kernel/fs/squashfs/squashfs_fs.h b/squashfs-tools/kernel/fs/squashfs/squashfs_fs.h
new file mode 100644
index 0000000..6840da1
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/squashfs_fs.h
@@ -0,0 +1,381 @@
+#ifndef SQUASHFS_FS
+#define SQUASHFS_FS
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * squashfs_fs.h
+ */
+
+#define SQUASHFS_CACHED_FRAGMENTS	CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE
+#define SQUASHFS_MAJOR			4
+#define SQUASHFS_MINOR			0
+#define SQUASHFS_MAGIC			0x73717368
+#define SQUASHFS_START			0
+
+/* size of metadata (inode and directory) blocks */
+#define SQUASHFS_METADATA_SIZE		8192
+#define SQUASHFS_METADATA_LOG		13
+
+/* default size of data blocks */
+#define SQUASHFS_FILE_SIZE		131072
+#define SQUASHFS_FILE_LOG		17
+
+#define SQUASHFS_FILE_MAX_SIZE		1048576
+#define SQUASHFS_FILE_MAX_LOG		20
+
+/* Max number of uids and gids */
+#define SQUASHFS_IDS			65536
+
+/* Max length of filename (not 255) */
+#define SQUASHFS_NAME_LEN		256
+
+#define SQUASHFS_INVALID_FRAG		(0xffffffffU)
+#define SQUASHFS_INVALID_BLK		(-1LL)
+
+/* Filesystem flags */
+#define SQUASHFS_NOI			0
+#define SQUASHFS_NOD			1
+#define SQUASHFS_NOF			3
+#define SQUASHFS_NO_FRAG		4
+#define SQUASHFS_ALWAYS_FRAG		5
+#define SQUASHFS_DUPLICATE		6
+#define SQUASHFS_EXPORT			7
+
+#define SQUASHFS_BIT(flag, bit)		((flag >> bit) & 1)
+
+#define SQUASHFS_UNCOMPRESSED_INODES(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOI)
+
+#define SQUASHFS_UNCOMPRESSED_DATA(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOD)
+
+#define SQUASHFS_UNCOMPRESSED_FRAGMENTS(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOF)
+
+#define SQUASHFS_NO_FRAGMENTS(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_NO_FRAG)
+
+#define SQUASHFS_ALWAYS_FRAGMENTS(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_ALWAYS_FRAG)
+
+#define SQUASHFS_DUPLICATES(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_DUPLICATE)
+
+#define SQUASHFS_EXPORTABLE(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_EXPORT)
+
+/* Max number of types and file types */
+#define SQUASHFS_DIR_TYPE		1
+#define SQUASHFS_REG_TYPE		2
+#define SQUASHFS_SYMLINK_TYPE		3
+#define SQUASHFS_BLKDEV_TYPE		4
+#define SQUASHFS_CHRDEV_TYPE		5
+#define SQUASHFS_FIFO_TYPE		6
+#define SQUASHFS_SOCKET_TYPE		7
+#define SQUASHFS_LDIR_TYPE		8
+#define SQUASHFS_LREG_TYPE		9
+#define SQUASHFS_LSYMLINK_TYPE		10
+#define SQUASHFS_LBLKDEV_TYPE		11
+#define SQUASHFS_LCHRDEV_TYPE		12
+#define SQUASHFS_LFIFO_TYPE		13
+#define SQUASHFS_LSOCKET_TYPE		14
+
+/* Flag whether block is compressed or uncompressed, bit is set if block is
+ * uncompressed */
+#define SQUASHFS_COMPRESSED_BIT		(1 << 15)
+
+#define SQUASHFS_COMPRESSED_SIZE(B)	(((B) & ~SQUASHFS_COMPRESSED_BIT) ? \
+		(B) & ~SQUASHFS_COMPRESSED_BIT :  SQUASHFS_COMPRESSED_BIT)
+
+#define SQUASHFS_COMPRESSED(B)		(!((B) & SQUASHFS_COMPRESSED_BIT))
+
+#define SQUASHFS_COMPRESSED_BIT_BLOCK	(1 << 24)
+
+#define SQUASHFS_COMPRESSED_SIZE_BLOCK(B)	((B) & \
+						~SQUASHFS_COMPRESSED_BIT_BLOCK)
+
+#define SQUASHFS_COMPRESSED_BLOCK(B)	(!((B) & SQUASHFS_COMPRESSED_BIT_BLOCK))
+
+/*
+ * Inode number ops.  Inodes consist of a compressed block number, and an
+ * uncompressed offset within that block
+ */
+#define SQUASHFS_INODE_BLK(A)		((unsigned int) ((A) >> 16))
+
+#define SQUASHFS_INODE_OFFSET(A)	((unsigned int) ((A) & 0xffff))
+
+#define SQUASHFS_MKINODE(A, B)		((long long)(((long long) (A)\
+					<< 16) + (B)))
+
+/* Translate between VFS mode and squashfs mode */
+#define SQUASHFS_MODE(A)		((A) & 0xfff)
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES(A)	\
+				((A) * sizeof(struct squashfs_fragment_entry))
+
+#define SQUASHFS_FRAGMENT_INDEX(A)	(SQUASHFS_FRAGMENT_BYTES(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET(A)	(SQUASHFS_FRAGMENT_BYTES(A) % \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES(A)	((SQUASHFS_FRAGMENT_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES(A)	(SQUASHFS_FRAGMENT_INDEXES(A) *\
+						sizeof(u64))
+
+/* inode lookup table defines */
+#define SQUASHFS_LOOKUP_BYTES(A)	((A) * sizeof(u64))
+
+#define SQUASHFS_LOOKUP_BLOCK(A)	(SQUASHFS_LOOKUP_BYTES(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_OFFSET(A)	(SQUASHFS_LOOKUP_BYTES(A) % \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCKS(A)	((SQUASHFS_LOOKUP_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_BYTES(A)	(SQUASHFS_LOOKUP_BLOCKS(A) *\
+					sizeof(u64))
+
+/* uid/gid lookup table defines */
+#define SQUASHFS_ID_BYTES(A)		((A) * sizeof(unsigned int))
+
+#define SQUASHFS_ID_BLOCK(A)		(SQUASHFS_ID_BYTES(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCK_OFFSET(A)	(SQUASHFS_ID_BYTES(A) % \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCKS(A)		((SQUASHFS_ID_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCK_BYTES(A)	(SQUASHFS_ID_BLOCKS(A) *\
+					sizeof(u64))
+
+/* cached data constants for filesystem */
+#define SQUASHFS_CACHED_BLKS		8
+
+#define SQUASHFS_MAX_FILE_SIZE_LOG	64
+
+#define SQUASHFS_MAX_FILE_SIZE		(1LL << \
+					(SQUASHFS_MAX_FILE_SIZE_LOG - 2))
+
+#define SQUASHFS_MARKER_BYTE		0xff
+
+/* meta index cache */
+#define SQUASHFS_META_INDEXES	(SQUASHFS_METADATA_SIZE / sizeof(unsigned int))
+#define SQUASHFS_META_ENTRIES	127
+#define SQUASHFS_META_SLOTS	8
+
+struct meta_entry {
+	u64			data_block;
+	unsigned int		index_block;
+	unsigned short		offset;
+	unsigned short		pad;
+};
+
+struct meta_index {
+	unsigned int		inode_number;
+	unsigned int		offset;
+	unsigned short		entries;
+	unsigned short		skip;
+	unsigned short		locked;
+	unsigned short		pad;
+	struct meta_entry	meta_entry[SQUASHFS_META_ENTRIES];
+};
+
+
+/*
+ * definitions for structures on disk
+ */
+#define ZLIB_COMPRESSION	 1
+
+struct squashfs_super_block {
+	__le32			s_magic;
+	__le32			inodes;
+	__le32			mkfs_time;
+	__le32			block_size;
+	__le32			fragments;
+	__le16			compression;
+	__le16			block_log;
+	__le16			flags;
+	__le16			no_ids;
+	__le16			s_major;
+	__le16			s_minor;
+	__le64			root_inode;
+	__le64			bytes_used;
+	__le64			id_table_start;
+	__le64			xattr_table_start;
+	__le64			inode_table_start;
+	__le64			directory_table_start;
+	__le64			fragment_table_start;
+	__le64			lookup_table_start;
+};
+
+struct squashfs_dir_index {
+	__le32			index;
+	__le32			start_block;
+	__le32			size;
+	unsigned char		name[0];
+};
+
+struct squashfs_base_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+};
+
+struct squashfs_ipc_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			nlink;
+};
+
+struct squashfs_dev_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			nlink;
+	__le32			rdev;
+};
+
+struct squashfs_symlink_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			nlink;
+	__le32			symlink_size;
+	char			symlink[0];
+};
+
+struct squashfs_reg_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			start_block;
+	__le32			fragment;
+	__le32			offset;
+	__le32			file_size;
+	__le16			block_list[0];
+};
+
+struct squashfs_lreg_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le64			start_block;
+	__le64			file_size;
+	__le64			sparse;
+	__le32			nlink;
+	__le32			fragment;
+	__le32			offset;
+	__le32			xattr;
+	__le16			block_list[0];
+};
+
+struct squashfs_dir_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			start_block;
+	__le32			nlink;
+	__le16			file_size;
+	__le16			offset;
+	__le32			parent_inode;
+};
+
+struct squashfs_ldir_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			nlink;
+	__le32			file_size;
+	__le32			start_block;
+	__le32			parent_inode;
+	__le16			i_count;
+	__le16			offset;
+	__le32			xattr;
+	struct squashfs_dir_index	index[0];
+};
+
+union squashfs_inode {
+	struct squashfs_base_inode		base;
+	struct squashfs_dev_inode		dev;
+	struct squashfs_symlink_inode		symlink;
+	struct squashfs_reg_inode		reg;
+	struct squashfs_lreg_inode		lreg;
+	struct squashfs_dir_inode		dir;
+	struct squashfs_ldir_inode		ldir;
+	struct squashfs_ipc_inode		ipc;
+};
+
+struct squashfs_dir_entry {
+	__le16			offset;
+	__le16			inode_number;
+	__le16			type;
+	__le16			size;
+	char			name[0];
+};
+
+struct squashfs_dir_header {
+	__le32			count;
+	__le32			start_block;
+	__le32			inode_number;
+};
+
+struct squashfs_fragment_entry {
+	__le64			start_block;
+	__le32			size;
+	unsigned int		unused;
+};
+
+#endif
diff --git a/squashfs-tools/kernel/fs/squashfs/squashfs_fs_i.h b/squashfs-tools/kernel/fs/squashfs/squashfs_fs_i.h
new file mode 100644
index 0000000..fbfca30
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/squashfs_fs_i.h
@@ -0,0 +1,45 @@
+#ifndef SQUASHFS_FS_I
+#define SQUASHFS_FS_I
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * squashfs_fs_i.h
+ */
+
+struct squashfs_inode_info {
+	u64		start;
+	int		offset;
+	union {
+		struct {
+			u64		fragment_block;
+			int		fragment_size;
+			int		fragment_offset;
+			u64		block_list_start;
+		};
+		struct {
+			u64		dir_idx_start;
+			int		dir_idx_offset;
+			int		dir_idx_cnt;
+			int		parent;
+		};
+	};
+	struct inode	vfs_inode;
+};
+#endif
diff --git a/squashfs-tools/kernel/fs/squashfs/squashfs_fs_sb.h b/squashfs-tools/kernel/fs/squashfs/squashfs_fs_sb.h
new file mode 100644
index 0000000..c8c6561
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/squashfs_fs_sb.h
@@ -0,0 +1,76 @@
+#ifndef SQUASHFS_FS_SB
+#define SQUASHFS_FS_SB
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * squashfs_fs_sb.h
+ */
+
+#include "squashfs_fs.h"
+
+struct squashfs_cache {
+	char			*name;
+	int			entries;
+	int			next_blk;
+	int			num_waiters;
+	int			unused;
+	int			block_size;
+	int			pages;
+	spinlock_t		lock;
+	wait_queue_head_t	wait_queue;
+	struct squashfs_cache_entry *entry;
+};
+
+struct squashfs_cache_entry {
+	u64			block;
+	int			length;
+	int			refcount;
+	u64			next_index;
+	int			pending;
+	int			error;
+	int			num_waiters;
+	wait_queue_head_t	wait_queue;
+	struct squashfs_cache	*cache;
+	void			**data;
+};
+
+struct squashfs_sb_info {
+	int			devblksize;
+	int			devblksize_log2;
+	struct squashfs_cache	*block_cache;
+	struct squashfs_cache	*fragment_cache;
+	struct squashfs_cache	*read_page;
+	int			next_meta_index;
+	__le64			*id_table;
+	__le64			*fragment_index;
+	unsigned int		*fragment_index_2;
+	struct mutex		read_data_mutex;
+	struct mutex		meta_index_mutex;
+	struct meta_index	*meta_index;
+	z_stream		stream;
+	__le64			*inode_lookup_table;
+	u64			inode_table;
+	u64			directory_table;
+	unsigned int		block_size;
+	unsigned short		block_log;
+	long long		bytes_used;
+	unsigned int		inodes;
+};
+#endif
diff --git a/squashfs-tools/kernel/fs/squashfs/super.c b/squashfs-tools/kernel/fs/squashfs/super.c
new file mode 100644
index 0000000..a0466d7
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/super.c
@@ -0,0 +1,440 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * super.c
+ */
+
+/*
+ * This file implements code to read the superblock, read and initialise
+ * in-memory structures at mount time, and all the VFS glue code to register
+ * the filesystem.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/pagemap.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+static struct file_system_type squashfs_fs_type;
+static struct super_operations squashfs_super_ops;
+
+static int supported_squashfs_filesystem(short major, short minor, short comp)
+{
+	if (major < SQUASHFS_MAJOR) {
+		ERROR("Major/Minor mismatch, older Squashfs %d.%d "
+			"filesystems are unsupported\n", major, minor);
+		return -EINVAL;
+	} else if (major > SQUASHFS_MAJOR || minor > SQUASHFS_MINOR) {
+		ERROR("Major/Minor mismatch, trying to mount newer "
+			"%d.%d filesystem\n", major, minor);
+		ERROR("Please update your kernel\n");
+		return -EINVAL;
+	}
+
+	if (comp != ZLIB_COMPRESSION)
+		return -EINVAL;
+
+	return 0;
+}
+
+
+static int squashfs_fill_super(struct super_block *sb, void *data, int silent)
+{
+	struct squashfs_sb_info *msblk;
+	struct squashfs_super_block *sblk = NULL;
+	char b[BDEVNAME_SIZE];
+	struct inode *root;
+	long long root_inode;
+	unsigned short flags;
+	unsigned int fragments;
+	u64 lookup_table_start;
+	int err;
+
+	TRACE("Entered squashfs_fill_superblock\n");
+
+	sb->s_fs_info = kzalloc(sizeof(*msblk), GFP_KERNEL);
+	if (sb->s_fs_info == NULL) {
+		ERROR("Failed to allocate squashfs_sb_info\n");
+		return -ENOMEM;
+	}
+	msblk = sb->s_fs_info;
+
+	msblk->stream.workspace = kmalloc(zlib_inflate_workspacesize(),
+		GFP_KERNEL);
+	if (msblk->stream.workspace == NULL) {
+		ERROR("Failed to allocate zlib workspace\n");
+		goto failure;
+	}
+
+	sblk = kzalloc(sizeof(*sblk), GFP_KERNEL);
+	if (sblk == NULL) {
+		ERROR("Failed to allocate squashfs_super_block\n");
+		goto failure;
+	}
+
+	msblk->devblksize = sb_min_blocksize(sb, BLOCK_SIZE);
+	msblk->devblksize_log2 = ffz(~msblk->devblksize);
+
+	mutex_init(&msblk->read_data_mutex);
+	mutex_init(&msblk->meta_index_mutex);
+
+	/*
+	 * msblk->bytes_used is checked in squashfs_read_table to ensure reads
+	 * are not beyond filesystem end.  But as we're using
+	 * squashfs_read_table here to read the superblock (including the value
+	 * of bytes_used) we need to set it to an initial sensible dummy value
+	 */
+	msblk->bytes_used = sizeof(*sblk);
+	err = squashfs_read_table(sb, sblk, SQUASHFS_START, sizeof(*sblk));
+
+	if (err < 0) {
+		ERROR("unable to read squashfs_super_block\n");
+		goto failed_mount;
+	}
+
+	/* Check it is a SQUASHFS superblock */
+	sb->s_magic = le32_to_cpu(sblk->s_magic);
+	if (sb->s_magic != SQUASHFS_MAGIC) {
+		if (!silent)
+			ERROR("Can't find a SQUASHFS superblock on %s\n",
+						bdevname(sb->s_bdev, b));
+		err = -EINVAL;
+		goto failed_mount;
+	}
+
+	/* Check the MAJOR & MINOR versions and compression type */
+	err = supported_squashfs_filesystem(le16_to_cpu(sblk->s_major),
+			le16_to_cpu(sblk->s_minor),
+			le16_to_cpu(sblk->compression));
+	if (err < 0)
+		goto failed_mount;
+
+	err = -EINVAL;
+
+	/*
+	 * Check if there's xattrs in the filesystem.  These are not
+	 * supported in this version, so warn that they will be ignored.
+	 */
+	if (le64_to_cpu(sblk->xattr_table_start) != SQUASHFS_INVALID_BLK)
+		ERROR("Xattrs in filesystem, these will be ignored\n");
+
+	/* Check the filesystem does not extend beyond the end of the
+	   block device */
+	msblk->bytes_used = le64_to_cpu(sblk->bytes_used);
+	if (msblk->bytes_used < 0 || msblk->bytes_used >
+			i_size_read(sb->s_bdev->bd_inode))
+		goto failed_mount;
+
+	/* Check block size for sanity */
+	msblk->block_size = le32_to_cpu(sblk->block_size);
+	if (msblk->block_size > SQUASHFS_FILE_MAX_SIZE)
+		goto failed_mount;
+
+	msblk->block_log = le16_to_cpu(sblk->block_log);
+	if (msblk->block_log > SQUASHFS_FILE_MAX_LOG)
+		goto failed_mount;
+
+	/* Check the root inode for sanity */
+	root_inode = le64_to_cpu(sblk->root_inode);
+	if (SQUASHFS_INODE_OFFSET(root_inode) > SQUASHFS_METADATA_SIZE)
+		goto failed_mount;
+
+	msblk->inode_table = le64_to_cpu(sblk->inode_table_start);
+	msblk->directory_table = le64_to_cpu(sblk->directory_table_start);
+	msblk->inodes = le32_to_cpu(sblk->inodes);
+	flags = le16_to_cpu(sblk->flags);
+
+	TRACE("Found valid superblock on %s\n", bdevname(sb->s_bdev, b));
+	TRACE("Inodes are %scompressed\n", SQUASHFS_UNCOMPRESSED_INODES(flags)
+				? "un" : "");
+	TRACE("Data is %scompressed\n", SQUASHFS_UNCOMPRESSED_DATA(flags)
+				? "un" : "");
+	TRACE("Filesystem size %lld bytes\n", msblk->bytes_used);
+	TRACE("Block size %d\n", msblk->block_size);
+	TRACE("Number of inodes %d\n", msblk->inodes);
+	TRACE("Number of fragments %d\n", le32_to_cpu(sblk->fragments));
+	TRACE("Number of ids %d\n", le16_to_cpu(sblk->no_ids));
+	TRACE("sblk->inode_table_start %llx\n", msblk->inode_table);
+	TRACE("sblk->directory_table_start %llx\n", msblk->directory_table);
+	TRACE("sblk->fragment_table_start %llx\n",
+		(u64) le64_to_cpu(sblk->fragment_table_start));
+	TRACE("sblk->id_table_start %llx\n",
+		(u64) le64_to_cpu(sblk->id_table_start));
+
+	sb->s_maxbytes = MAX_LFS_FILESIZE;
+	sb->s_flags |= MS_RDONLY;
+	sb->s_op = &squashfs_super_ops;
+
+	err = -ENOMEM;
+
+	msblk->block_cache = squashfs_cache_init("metadata",
+			SQUASHFS_CACHED_BLKS, SQUASHFS_METADATA_SIZE);
+	if (msblk->block_cache == NULL)
+		goto failed_mount;
+
+	/* Allocate read_page block */
+	msblk->read_page = squashfs_cache_init("data", 1, msblk->block_size);
+	if (msblk->read_page == NULL) {
+		ERROR("Failed to allocate read_page block\n");
+		goto failed_mount;
+	}
+
+	/* Allocate and read id index table */
+	msblk->id_table = squashfs_read_id_index_table(sb,
+		le64_to_cpu(sblk->id_table_start), le16_to_cpu(sblk->no_ids));
+	if (IS_ERR(msblk->id_table)) {
+		err = PTR_ERR(msblk->id_table);
+		msblk->id_table = NULL;
+		goto failed_mount;
+	}
+
+	fragments = le32_to_cpu(sblk->fragments);
+	if (fragments == 0)
+		goto allocate_lookup_table;
+
+	msblk->fragment_cache = squashfs_cache_init("fragment",
+		SQUASHFS_CACHED_FRAGMENTS, msblk->block_size);
+	if (msblk->fragment_cache == NULL) {
+		err = -ENOMEM;
+		goto failed_mount;
+	}
+
+	/* Allocate and read fragment index table */
+	msblk->fragment_index = squashfs_read_fragment_index_table(sb,
+		le64_to_cpu(sblk->fragment_table_start), fragments);
+	if (IS_ERR(msblk->fragment_index)) {
+		err = PTR_ERR(msblk->fragment_index);
+		msblk->fragment_index = NULL;
+		goto failed_mount;
+	}
+
+allocate_lookup_table:
+	lookup_table_start = le64_to_cpu(sblk->lookup_table_start);
+	if (lookup_table_start == SQUASHFS_INVALID_BLK)
+		goto allocate_root;
+
+	/* Allocate and read inode lookup table */
+	msblk->inode_lookup_table = squashfs_read_inode_lookup_table(sb,
+		lookup_table_start, msblk->inodes);
+	if (IS_ERR(msblk->inode_lookup_table)) {
+		err = PTR_ERR(msblk->inode_lookup_table);
+		msblk->inode_lookup_table = NULL;
+		goto failed_mount;
+	}
+
+	sb->s_export_op = &squashfs_export_ops;
+
+allocate_root:
+	root = new_inode(sb);
+	if (!root) {
+		err = -ENOMEM;
+		goto failed_mount;
+	}
+
+	err = squashfs_read_inode(root, root_inode);
+	if (err) {
+		iget_failed(root);
+		goto failed_mount;
+	}
+	insert_inode_hash(root);
+
+	sb->s_root = d_alloc_root(root);
+	if (sb->s_root == NULL) {
+		ERROR("Root inode create failed\n");
+		err = -ENOMEM;
+		iput(root);
+		goto failed_mount;
+	}
+
+	TRACE("Leaving squashfs_fill_super\n");
+	kfree(sblk);
+	return 0;
+
+failed_mount:
+	squashfs_cache_delete(msblk->block_cache);
+	squashfs_cache_delete(msblk->fragment_cache);
+	squashfs_cache_delete(msblk->read_page);
+	kfree(msblk->inode_lookup_table);
+	kfree(msblk->fragment_index);
+	kfree(msblk->id_table);
+	kfree(msblk->stream.workspace);
+	kfree(sb->s_fs_info);
+	sb->s_fs_info = NULL;
+	kfree(sblk);
+	return err;
+
+failure:
+	kfree(msblk->stream.workspace);
+	kfree(sb->s_fs_info);
+	sb->s_fs_info = NULL;
+	return -ENOMEM;
+}
+
+
+static int squashfs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+	struct squashfs_sb_info *msblk = dentry->d_sb->s_fs_info;
+
+	TRACE("Entered squashfs_statfs\n");
+
+	buf->f_type = SQUASHFS_MAGIC;
+	buf->f_bsize = msblk->block_size;
+	buf->f_blocks = ((msblk->bytes_used - 1) >> msblk->block_log) + 1;
+	buf->f_bfree = buf->f_bavail = 0;
+	buf->f_files = msblk->inodes;
+	buf->f_ffree = 0;
+	buf->f_namelen = SQUASHFS_NAME_LEN;
+
+	return 0;
+}
+
+
+static int squashfs_remount(struct super_block *sb, int *flags, char *data)
+{
+	*flags |= MS_RDONLY;
+	return 0;
+}
+
+
+static void squashfs_put_super(struct super_block *sb)
+{
+	if (sb->s_fs_info) {
+		struct squashfs_sb_info *sbi = sb->s_fs_info;
+		squashfs_cache_delete(sbi->block_cache);
+		squashfs_cache_delete(sbi->fragment_cache);
+		squashfs_cache_delete(sbi->read_page);
+		kfree(sbi->id_table);
+		kfree(sbi->fragment_index);
+		kfree(sbi->meta_index);
+		kfree(sbi->stream.workspace);
+		kfree(sb->s_fs_info);
+		sb->s_fs_info = NULL;
+	}
+}
+
+
+static int squashfs_get_sb(struct file_system_type *fs_type, int flags,
+				const char *dev_name, void *data,
+				struct vfsmount *mnt)
+{
+	return get_sb_bdev(fs_type, flags, dev_name, data, squashfs_fill_super,
+				mnt);
+}
+
+
+static struct kmem_cache *squashfs_inode_cachep;
+
+
+static void init_once(void *foo)
+{
+	struct squashfs_inode_info *ei = foo;
+
+	inode_init_once(&ei->vfs_inode);
+}
+
+
+static int __init init_inodecache(void)
+{
+	squashfs_inode_cachep = kmem_cache_create("squashfs_inode_cache",
+		sizeof(struct squashfs_inode_info), 0,
+		SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT, init_once);
+
+	return squashfs_inode_cachep ? 0 : -ENOMEM;
+}
+
+
+static void destroy_inodecache(void)
+{
+	kmem_cache_destroy(squashfs_inode_cachep);
+}
+
+
+static int __init init_squashfs_fs(void)
+{
+	int err = init_inodecache();
+
+	if (err)
+		return err;
+
+	err = register_filesystem(&squashfs_fs_type);
+	if (err) {
+		destroy_inodecache();
+		return err;
+	}
+
+	printk(KERN_INFO "squashfs: version 4.0 (2009/01/03) "
+		"Phillip Lougher\n");
+
+	return 0;
+}
+
+
+static void __exit exit_squashfs_fs(void)
+{
+	unregister_filesystem(&squashfs_fs_type);
+	destroy_inodecache();
+}
+
+
+static struct inode *squashfs_alloc_inode(struct super_block *sb)
+{
+	struct squashfs_inode_info *ei =
+		kmem_cache_alloc(squashfs_inode_cachep, GFP_KERNEL);
+
+	return ei ? &ei->vfs_inode : NULL;
+}
+
+
+static void squashfs_destroy_inode(struct inode *inode)
+{
+	kmem_cache_free(squashfs_inode_cachep, squashfs_i(inode));
+}
+
+
+static struct file_system_type squashfs_fs_type = {
+	.owner = THIS_MODULE,
+	.name = "squashfs",
+	.get_sb = squashfs_get_sb,
+	.kill_sb = kill_block_super,
+	.fs_flags = FS_REQUIRES_DEV
+};
+
+static struct super_operations squashfs_super_ops = {
+	.alloc_inode = squashfs_alloc_inode,
+	.destroy_inode = squashfs_destroy_inode,
+	.statfs = squashfs_statfs,
+	.put_super = squashfs_put_super,
+	.remount_fs = squashfs_remount
+};
+
+module_init(init_squashfs_fs);
+module_exit(exit_squashfs_fs);
+MODULE_DESCRIPTION("squashfs 4.0, a compressed read-only filesystem");
+MODULE_AUTHOR("Phillip Lougher <phillip@lougher.demon.co.uk>");
+MODULE_LICENSE("GPL");
diff --git a/squashfs-tools/kernel/fs/squashfs/symlink.c b/squashfs-tools/kernel/fs/squashfs/symlink.c
new file mode 100644
index 0000000..83d8788
--- /dev/null
+++ b/squashfs-tools/kernel/fs/squashfs/symlink.c
@@ -0,0 +1,118 @@
+/*
+ * Squashfs - a compressed read only filesystem for Linux
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * symlink.c
+ */
+
+/*
+ * This file implements code to handle symbolic links.
+ *
+ * The data contents of symbolic links are stored inside the symbolic
+ * link inode within the inode table.  This allows the normally small symbolic
+ * link to be compressed as part of the inode table, achieving much greater
+ * compression than if the symbolic link was compressed individually.
+ */
+
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/pagemap.h>
+#include <linux/zlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_fs_sb.h"
+#include "squashfs_fs_i.h"
+#include "squashfs.h"
+
+static int squashfs_symlink_readpage(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	struct super_block *sb = inode->i_sb;
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+	int index = page->index << PAGE_CACHE_SHIFT;
+	u64 block = squashfs_i(inode)->start;
+	int offset = squashfs_i(inode)->offset;
+	int length = min_t(int, i_size_read(inode) - index, PAGE_CACHE_SIZE);
+	int bytes, copied;
+	void *pageaddr;
+	struct squashfs_cache_entry *entry;
+
+	TRACE("Entered squashfs_symlink_readpage, page index %ld, start block "
+			"%llx, offset %x\n", page->index, block, offset);
+
+	/*
+	 * Skip index bytes into symlink metadata.
+	 */
+	if (index) {
+		bytes = squashfs_read_metadata(sb, NULL, &block, &offset,
+								index);
+		if (bytes < 0) {
+			ERROR("Unable to read symlink [%llx:%x]\n",
+				squashfs_i(inode)->start,
+				squashfs_i(inode)->offset);
+			goto error_out;
+		}
+	}
+
+	/*
+	 * Read length bytes from symlink metadata.  Squashfs_read_metadata
+	 * is not used here because it can sleep and we want to use
+	 * kmap_atomic to map the page.  Instead call the underlying
+	 * squashfs_cache_get routine.  As length bytes may overlap metadata
+	 * blocks, we may need to call squashfs_cache_get multiple times.
+	 */
+	for (bytes = 0; bytes < length; offset = 0, bytes += copied) {
+		entry = squashfs_cache_get(sb, msblk->block_cache, block, 0);
+		if (entry->error) {
+			ERROR("Unable to read symlink [%llx:%x]\n",
+				squashfs_i(inode)->start,
+				squashfs_i(inode)->offset);
+			squashfs_cache_put(entry);
+			goto error_out;
+		}
+
+		pageaddr = kmap_atomic(page, KM_USER0);
+		copied = squashfs_copy_data(pageaddr + bytes, entry, offset,
+								length - bytes);
+		if (copied == length - bytes)
+			memset(pageaddr + length, 0, PAGE_CACHE_SIZE - length);
+		else
+			block = entry->next_index;
+		kunmap_atomic(pageaddr, KM_USER0);
+		squashfs_cache_put(entry);
+	}
+
+	flush_dcache_page(page);
+	SetPageUptodate(page);
+	unlock_page(page);
+	return 0;
+
+error_out:
+	SetPageError(page);
+	unlock_page(page);
+	return 0;
+}
+
+
+const struct address_space_operations squashfs_symlink_aops = {
+	.readpage = squashfs_symlink_readpage
+};
diff --git a/squashfs-tools/kernel/include/linux/squashfs_fs.h b/squashfs-tools/kernel/include/linux/squashfs_fs.h
new file mode 100644
index 0000000..eef85b1
--- /dev/null
+++ b/squashfs-tools/kernel/include/linux/squashfs_fs.h
@@ -0,0 +1,380 @@
+#ifndef SQUASHFS_FS
+#define SQUASHFS_FS
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * squashfs_fs.h
+ */
+
+#define SQUASHFS_CACHED_FRAGMENTS	CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE
+#define SQUASHFS_MAJOR			4
+#define SQUASHFS_MINOR			0
+#define SQUASHFS_MAGIC			0x73717368
+#define SQUASHFS_START			0
+
+/* size of metadata (inode and directory) blocks */
+#define SQUASHFS_METADATA_SIZE		8192
+#define SQUASHFS_METADATA_LOG		13
+
+/* default size of data blocks */
+#define SQUASHFS_FILE_SIZE		131072
+#define SQUASHFS_FILE_LOG		17
+
+#define SQUASHFS_FILE_MAX_SIZE		1048576
+
+/* Max number of uids and gids */
+#define SQUASHFS_IDS			65536
+
+/* Max length of filename (not 255) */
+#define SQUASHFS_NAME_LEN		256
+
+#define SQUASHFS_INVALID_FRAG		(0xffffffffU)
+#define SQUASHFS_INVALID_BLK		(-1LL)
+
+/* Filesystem flags */
+#define SQUASHFS_NOI			0
+#define SQUASHFS_NOD			1
+#define SQUASHFS_NOF			3
+#define SQUASHFS_NO_FRAG		4
+#define SQUASHFS_ALWAYS_FRAG		5
+#define SQUASHFS_DUPLICATE		6
+#define SQUASHFS_EXPORT			7
+
+#define SQUASHFS_BIT(flag, bit)		((flag >> bit) & 1)
+
+#define SQUASHFS_UNCOMPRESSED_INODES(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOI)
+
+#define SQUASHFS_UNCOMPRESSED_DATA(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOD)
+
+#define SQUASHFS_UNCOMPRESSED_FRAGMENTS(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOF)
+
+#define SQUASHFS_NO_FRAGMENTS(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_NO_FRAG)
+
+#define SQUASHFS_ALWAYS_FRAGMENTS(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_ALWAYS_FRAG)
+
+#define SQUASHFS_DUPLICATES(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_DUPLICATE)
+
+#define SQUASHFS_EXPORTABLE(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_EXPORT)
+
+/* Max number of types and file types */
+#define SQUASHFS_DIR_TYPE		1
+#define SQUASHFS_REG_TYPE		2
+#define SQUASHFS_SYMLINK_TYPE		3
+#define SQUASHFS_BLKDEV_TYPE		4
+#define SQUASHFS_CHRDEV_TYPE		5
+#define SQUASHFS_FIFO_TYPE		6
+#define SQUASHFS_SOCKET_TYPE		7
+#define SQUASHFS_LDIR_TYPE		8
+#define SQUASHFS_LREG_TYPE		9
+#define SQUASHFS_LSYMLINK_TYPE		10
+#define SQUASHFS_LBLKDEV_TYPE		11
+#define SQUASHFS_LCHRDEV_TYPE		12
+#define SQUASHFS_LFIFO_TYPE		13
+#define SQUASHFS_LSOCKET_TYPE		14
+
+/* Flag whether block is compressed or uncompressed, bit is set if block is
+ * uncompressed */
+#define SQUASHFS_COMPRESSED_BIT		(1 << 15)
+
+#define SQUASHFS_COMPRESSED_SIZE(B)	(((B) & ~SQUASHFS_COMPRESSED_BIT) ? \
+		(B) & ~SQUASHFS_COMPRESSED_BIT :  SQUASHFS_COMPRESSED_BIT)
+
+#define SQUASHFS_COMPRESSED(B)		(!((B) & SQUASHFS_COMPRESSED_BIT))
+
+#define SQUASHFS_COMPRESSED_BIT_BLOCK	(1 << 24)
+
+#define SQUASHFS_COMPRESSED_SIZE_BLOCK(B)	((B) & \
+						~SQUASHFS_COMPRESSED_BIT_BLOCK)
+
+#define SQUASHFS_COMPRESSED_BLOCK(B)	(!((B) & SQUASHFS_COMPRESSED_BIT_BLOCK))
+
+/*
+ * Inode number ops.  Inodes consist of a compressed block number, and an
+ * uncompressed offset within that block
+ */
+#define SQUASHFS_INODE_BLK(A)		((unsigned int) ((A) >> 16))
+
+#define SQUASHFS_INODE_OFFSET(A)	((unsigned int) ((A) & 0xffff))
+
+#define SQUASHFS_MKINODE(A, B)		((long long)(((long long) (A)\
+					<< 16) + (B)))
+
+/* Translate between VFS mode and squashfs mode */
+#define SQUASHFS_MODE(A)		((A) & 0xfff)
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES(A)	\
+				((A) * sizeof(struct squashfs_fragment_entry))
+
+#define SQUASHFS_FRAGMENT_INDEX(A)	(SQUASHFS_FRAGMENT_BYTES(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET(A)	(SQUASHFS_FRAGMENT_BYTES(A) % \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES(A)	((SQUASHFS_FRAGMENT_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES(A)	(SQUASHFS_FRAGMENT_INDEXES(A) *\
+						sizeof(long long))
+
+/* inode lookup table defines */
+#define SQUASHFS_LOOKUP_BYTES(A)	((A) * sizeof(long long))
+
+#define SQUASHFS_LOOKUP_BLOCK(A)	(SQUASHFS_LOOKUP_BYTES(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_OFFSET(A)	(SQUASHFS_LOOKUP_BYTES(A) % \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCKS(A)	((SQUASHFS_LOOKUP_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_BYTES(A)	(SQUASHFS_LOOKUP_BLOCKS(A) *\
+					sizeof(long long))
+
+/* uid/gid lookup table defines */
+#define SQUASHFS_ID_BYTES(A)		((A) * sizeof(unsigned int))
+
+#define SQUASHFS_ID_BLOCK(A)		(SQUASHFS_ID_BYTES(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCK_OFFSET(A)	(SQUASHFS_ID_BYTES(A) % \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCKS(A)		((SQUASHFS_ID_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCK_BYTES(A)	(SQUASHFS_ID_BLOCKS(A) *\
+					sizeof(long long))
+
+/* cached data constants for filesystem */
+#define SQUASHFS_CACHED_BLKS		8
+
+#define SQUASHFS_MAX_FILE_SIZE_LOG	64
+
+#define SQUASHFS_MAX_FILE_SIZE		(1LL << \
+					(SQUASHFS_MAX_FILE_SIZE_LOG - 2))
+
+#define SQUASHFS_MARKER_BYTE		0xff
+
+/* meta index cache */
+#define SQUASHFS_META_INDEXES	(SQUASHFS_METADATA_SIZE / sizeof(unsigned int))
+#define SQUASHFS_META_ENTRIES	127
+#define SQUASHFS_META_SLOTS	8
+
+struct meta_entry {
+	long long		data_block;
+	unsigned int		index_block;
+	unsigned short		offset;
+	unsigned short		pad;
+};
+
+struct meta_index {
+	unsigned int		inode_number;
+	unsigned int		offset;
+	unsigned short		entries;
+	unsigned short		skip;
+	unsigned short		locked;
+	unsigned short		pad;
+	struct meta_entry	meta_entry[SQUASHFS_META_ENTRIES];
+};
+
+
+/*
+ * definitions for structures on disk
+ */
+#define ZLIB_COMPRESSION	 1
+
+struct squashfs_super_block {
+	__le32			s_magic;
+	__le32			inodes;
+	__le32			mkfs_time;
+	__le32			block_size;
+	__le32			fragments;
+	__le16			compression;
+	__le16			block_log;
+	__le16			flags;
+	__le16			no_ids;
+	__le16			s_major;
+	__le16			s_minor;
+	__le64			root_inode;
+	__le64			bytes_used;
+	__le64			id_table_start;
+	__le64			xattr_table_start;
+	__le64			inode_table_start;
+	__le64			directory_table_start;
+	__le64			fragment_table_start;
+	__le64			lookup_table_start;
+};
+
+struct squashfs_dir_index {
+	__le32			index;
+	__le32			start_block;
+	__le32			size;
+	unsigned char		name[0];
+};
+
+struct squashfs_base_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+};
+
+struct squashfs_ipc_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			nlink;
+};
+
+struct squashfs_dev_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			nlink;
+	__le32			rdev;
+};
+
+struct squashfs_symlink_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			nlink;
+	__le32			symlink_size;
+	char			symlink[0];
+};
+
+struct squashfs_reg_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			start_block;
+	__le32			fragment;
+	__le32			offset;
+	__le32			file_size;
+	__le16			block_list[0];
+};
+
+struct squashfs_lreg_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le64			start_block;
+	__le64			file_size;
+	__le64			sparse;
+	__le32			nlink;
+	__le32			fragment;
+	__le32			offset;
+	__le32			xattr;
+	__le16			block_list[0];
+};
+
+struct squashfs_dir_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			start_block;
+	__le32			nlink;
+	__le16			file_size;
+	__le16			offset;
+	__le32			parent_inode;
+};
+
+struct squashfs_ldir_inode {
+	__le16			inode_type;
+	__le16			mode;
+	__le16			uid;
+	__le16			guid;
+	__le32			mtime;
+	__le32	 		inode_number;
+	__le32			nlink;
+	__le32			file_size;
+	__le32			start_block;
+	__le32			parent_inode;
+	__le16			i_count;
+	__le16			offset;
+	__le32			xattr;
+	struct squashfs_dir_index	index[0];
+};
+
+union squashfs_inode {
+	struct squashfs_base_inode		base;
+	struct squashfs_dev_inode		dev;
+	struct squashfs_symlink_inode		symlink;
+	struct squashfs_reg_inode		reg;
+	struct squashfs_lreg_inode		lreg;
+	struct squashfs_dir_inode		dir;
+	struct squashfs_ldir_inode		ldir;
+	struct squashfs_ipc_inode		ipc;
+};
+
+struct squashfs_dir_entry {
+	__le16			offset;
+	__le16			inode_number;
+	__le16			type;
+	__le16			size;
+	char			name[0];
+};
+
+struct squashfs_dir_header {
+	__le32			count;
+	__le32			start_block;
+	__le32			inode_number;
+};
+
+struct squashfs_fragment_entry {
+	__le64			start_block;
+	__le32			size;
+	unsigned int		unused;
+};
+
+#endif
diff --git a/squashfs-tools/kernel/include/linux/squashfs_fs_i.h b/squashfs-tools/kernel/include/linux/squashfs_fs_i.h
new file mode 100644
index 0000000..f78e6f8
--- /dev/null
+++ b/squashfs-tools/kernel/include/linux/squashfs_fs_i.h
@@ -0,0 +1,45 @@
+#ifndef SQUASHFS_FS_I
+#define SQUASHFS_FS_I
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * squashfs_fs_i.h
+ */
+
+struct squashfs_inode_info {
+	long long	start;
+	int		offset;
+	union {
+		struct {
+			long long	fragment_block;
+			int		fragment_size;
+			int		fragment_offset;
+			long long	block_list_start;
+		};
+		struct {
+			long long	dir_idx_start;
+			int		dir_idx_offset;
+			int		dir_idx_cnt;
+			int		parent;
+		};
+	};
+	struct inode	vfs_inode;
+};
+#endif
diff --git a/squashfs-tools/kernel/include/linux/squashfs_fs_sb.h b/squashfs-tools/kernel/include/linux/squashfs_fs_sb.h
new file mode 100644
index 0000000..cc9fd5d
--- /dev/null
+++ b/squashfs-tools/kernel/include/linux/squashfs_fs_sb.h
@@ -0,0 +1,76 @@
+#ifndef SQUASHFS_FS_SB
+#define SQUASHFS_FS_SB
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+ * Phillip Lougher <phillip@lougher.demon.co.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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * squashfs_fs_sb.h
+ */
+
+#include "squashfs_fs.h"
+
+struct squashfs_cache_entry {
+	long long		block;
+	int			length;
+	int			locked;
+	long long		next_index;
+	char			pending;
+	char			error;
+	int			waiting;
+	wait_queue_head_t	wait_queue;
+	char			*data;
+};
+
+struct squashfs_cache {
+	char			*name;
+	int			entries;
+	int			block_size;
+	int			next_blk;
+	int			waiting;
+	int			unused;
+	int			use_vmalloc;
+	spinlock_t		lock;
+	wait_queue_head_t	wait_queue;
+	struct squashfs_cache_entry entry[0];
+};
+
+struct squashfs_sb_info {
+	int			devblksize;
+	int			devblksize_log2;
+	struct squashfs_cache	*block_cache;
+	struct squashfs_cache	*fragment_cache;
+	int			next_meta_index;
+	__le64			*id_table;
+	__le64			*fragment_index;
+	unsigned int		*fragment_index_2;
+	char			*read_page;
+	struct mutex		read_data_mutex;
+	struct mutex		read_page_mutex;
+	struct mutex		meta_index_mutex;
+	struct meta_index	*meta_index;
+	z_stream		stream;
+	__le64			*inode_lookup_table;
+	long long		inode_table;
+	long long		directory_table;
+	unsigned int		block_size;
+	unsigned short		block_log;
+	long long		bytes_used;
+	unsigned int		inodes;
+};
+#endif
diff --git a/squashfs-tools/squashfs-tools/Android.mk b/squashfs-tools/squashfs-tools/Android.mk
new file mode 100644
index 0000000..79b411b
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2015 The Android Open Source Project
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# squashfs-tools depends on Linux Kernel specific headers (e.g. sysinfo.h).
+LOCAL_MODULE_HOST_OS := linux darwin
+
+# The LOCAL_MODULE name is referenced by the code. Don't change it.
+LOCAL_MODULE := mksquashfs
+
+LOCAL_SRC_FILES := \
+    mksquashfs.c \
+    read_fs.c \
+    action.c \
+    swap.c \
+    pseudo.c \
+    compressor.c \
+    sort.c \
+    progressbar.c \
+    read_file.c \
+    info.c \
+    restore.c \
+    process_fragments.c \
+    caches-queues-lists.c \
+    xattr.c \
+    read_xattrs.c \
+    gzip_wrapper.c \
+    android.c \
+    lz4_wrapper.c
+
+LOCAL_CFLAGS := -I -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_GNU_SOURCE -Wall \
+                -DCOMP_DEFAULT="\"lz4\"" -DGZIP_SUPPORT -DLZ4_SUPPORT -DXATTR_SUPPORT -DXATTR_DEFAULT \
+                -Wno-unused-parameter
+
+LOCAL_LDLIBS := -lpthread -lm -lz
+
+LOCAL_SHARED_LIBRARIES := libcutils libselinux
+LOCAL_STATIC_LIBRARIES := liblz4
+
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/squashfs-tools/squashfs-tools/Makefile b/squashfs-tools/squashfs-tools/Makefile
new file mode 100644
index 0000000..52d2582
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/Makefile
@@ -0,0 +1,305 @@
+###############################################
+#          Compression build options          #
+###############################################
+#
+#
+############# Building gzip support ###########
+#
+# Gzip support is by default enabled, and the compression type default
+# (COMP_DEFAULT) is gzip.
+#
+# If you don't want/need gzip support then comment out the GZIP SUPPORT line
+# below, and change COMP_DEFAULT to one of the compression types you have
+# selected.
+#
+# Obviously, you must select at least one of the available gzip, lzma, lzo
+# compression types.
+#
+GZIP_SUPPORT = 1
+
+########### Building XZ support #############
+#
+# LZMA2 compression.
+#
+# XZ Utils liblzma (http://tukaani.org/xz/) is supported
+#
+# To build using XZ Utils liblzma - install the library and uncomment
+# the XZ_SUPPORT line below.
+#
+#XZ_SUPPORT = 1
+
+
+############ Building LZO support ##############
+#
+# The LZO library (http://www.oberhumer.com/opensource/lzo/) is supported.
+#
+# To build using the LZO library - install the library and uncomment the
+# LZO_SUPPORT line below. If needed, uncomment and set LZO_DIR to the
+# installation prefix.
+#
+#LZO_SUPPORT = 1
+#LZO_DIR = /usr/local
+
+
+########### Building LZ4 support #############
+#
+# Yann Collet's LZ4 tools are supported
+# LZ4 homepage: http://fastcompression.blogspot.com/p/lz4.html
+# LZ4 source repository: http://code.google.com/p/lz4
+#
+# To build configure the tools using cmake to build shared libraries,
+# install and uncomment
+# the LZ4_SUPPORT line below.
+#
+#LZ4_SUPPORT = 1
+
+
+########### Building LZMA support #############
+#
+# LZMA1 compression.
+#
+# LZMA1 compression is deprecated, and the newer and better XZ (LZMA2)
+# compression should be used in preference.
+#
+# Both XZ Utils liblzma (http://tukaani.org/xz/) and LZMA SDK
+# (http://www.7-zip.org/sdk.html) are supported
+#
+# To build using XZ Utils liblzma - install the library and uncomment
+# the LZMA_XZ_SUPPORT line below.
+#
+# To build using the LZMA SDK (4.65 used in development, other versions may
+# work) - download and unpack it, uncomment and set LZMA_DIR to unpacked source,
+# and uncomment the LZMA_SUPPORT line below.
+#
+#LZMA_XZ_SUPPORT = 1
+#LZMA_SUPPORT = 1
+#LZMA_DIR = ../../../../LZMA/lzma465
+
+######## Specifying default compression ########
+#
+# The next line specifies which compression algorithm is used by default
+# in Mksquashfs.  Obviously the compression algorithm must have been
+# selected to be built
+#
+COMP_DEFAULT = gzip
+
+###############################################
+#  Extended attribute (XATTRs) build options  #
+###############################################
+#
+# Building XATTR support for Mksquashfs and Unsquashfs
+#
+# If your C library or build/target environment doesn't support XATTRs then
+# comment out the next line to build Mksquashfs and Unsquashfs without XATTR
+# support
+XATTR_SUPPORT = 1
+
+# Select whether you wish xattrs to be stored by Mksquashfs and extracted
+# by Unsquashfs by default.  If selected users can disable xattr support by
+# using the -no-xattrs option
+#
+# If unselected, Mksquashfs/Unsquashfs won't store and extract xattrs by
+# default.  Users can enable xattrs by using the -xattrs option.
+XATTR_DEFAULT = 1
+
+
+###############################################
+#        End of BUILD options section         #
+###############################################
+
+INCLUDEDIR = -I.
+INSTALL_DIR = /usr/local/bin
+
+MKSQUASHFS_OBJS = mksquashfs.o read_fs.o action.o swap.o pseudo.o compressor.o \
+	sort.o progressbar.o read_file.o info.o restore.o process_fragments.o \
+	caches-queues-lists.o
+
+UNSQUASHFS_OBJS = unsquashfs.o unsquash-1.o unsquash-2.o unsquash-3.o \
+	unsquash-4.o swap.o compressor.o unsquashfs_info.o
+
+CFLAGS ?= -O2
+CFLAGS += $(EXTRA_CFLAGS) $(INCLUDEDIR) -D_FILE_OFFSET_BITS=64 \
+	-D_LARGEFILE_SOURCE -D_GNU_SOURCE -DCOMP_DEFAULT=\"$(COMP_DEFAULT)\" \
+	-Wall
+
+LIBS = -lpthread -lm
+ifeq ($(GZIP_SUPPORT),1)
+CFLAGS += -DGZIP_SUPPORT
+MKSQUASHFS_OBJS += gzip_wrapper.o
+UNSQUASHFS_OBJS += gzip_wrapper.o
+LIBS += -lz
+COMPRESSORS += gzip
+endif
+
+ifeq ($(LZMA_SUPPORT),1)
+LZMA_OBJS = $(LZMA_DIR)/C/Alloc.o $(LZMA_DIR)/C/LzFind.o \
+	$(LZMA_DIR)/C/LzmaDec.o $(LZMA_DIR)/C/LzmaEnc.o $(LZMA_DIR)/C/LzmaLib.o
+INCLUDEDIR += -I$(LZMA_DIR)/C
+CFLAGS += -DLZMA_SUPPORT
+MKSQUASHFS_OBJS += lzma_wrapper.o $(LZMA_OBJS)
+UNSQUASHFS_OBJS += lzma_wrapper.o $(LZMA_OBJS)
+COMPRESSORS += lzma
+endif
+
+ifeq ($(LZMA_XZ_SUPPORT),1)
+CFLAGS += -DLZMA_SUPPORT
+MKSQUASHFS_OBJS += lzma_xz_wrapper.o
+UNSQUASHFS_OBJS += lzma_xz_wrapper.o
+LIBS += -llzma
+COMPRESSORS += lzma
+endif
+
+ifeq ($(XZ_SUPPORT),1)
+CFLAGS += -DXZ_SUPPORT
+MKSQUASHFS_OBJS += xz_wrapper.o
+UNSQUASHFS_OBJS += xz_wrapper.o
+LIBS += -llzma
+COMPRESSORS += xz
+endif
+
+ifeq ($(LZO_SUPPORT),1)
+CFLAGS += -DLZO_SUPPORT
+ifdef LZO_DIR
+INCLUDEDIR += -I$(LZO_DIR)/include
+LZO_LIBDIR = -L$(LZO_DIR)/lib
+endif
+MKSQUASHFS_OBJS += lzo_wrapper.o
+UNSQUASHFS_OBJS += lzo_wrapper.o
+LIBS += $(LZO_LIBDIR) -llzo2
+COMPRESSORS += lzo
+endif
+
+ifeq ($(LZ4_SUPPORT),1)
+CFLAGS += -DLZ4_SUPPORT
+MKSQUASHFS_OBJS += lz4_wrapper.o
+UNSQUASHFS_OBJS += lz4_wrapper.o
+LIBS += -llz4
+COMPRESSORS += lz4
+endif
+
+ifeq ($(XATTR_SUPPORT),1)
+ifeq ($(XATTR_DEFAULT),1)
+CFLAGS += -DXATTR_SUPPORT -DXATTR_DEFAULT
+else
+CFLAGS += -DXATTR_SUPPORT
+endif
+MKSQUASHFS_OBJS += xattr.o read_xattrs.o
+UNSQUASHFS_OBJS += read_xattrs.o unsquashfs_xattr.o
+endif
+
+#
+# If LZMA_SUPPORT is specified then LZMA_DIR must be specified too
+#
+ifeq ($(LZMA_SUPPORT),1)
+ifndef LZMA_DIR
+$(error "LZMA_SUPPORT requires LZMA_DIR to be also defined")
+endif
+endif
+
+#
+# Both LZMA_XZ_SUPPORT and LZMA_SUPPORT cannot be specified
+#
+ifeq ($(LZMA_XZ_SUPPORT),1)
+ifeq ($(LZMA_SUPPORT),1)
+$(error "Both LZMA_XZ_SUPPORT and LZMA_SUPPORT cannot be specified")
+endif
+endif
+
+#
+# At least one compressor must have been selected
+#
+ifndef COMPRESSORS
+$(error "No compressor selected! Select one or more of GZIP, LZMA, XZ, LZO or \
+	LZ4!")
+endif
+
+#
+# COMP_DEFAULT must be a selected compressor
+#
+ifeq (, $(findstring $(COMP_DEFAULT), $(COMPRESSORS)))
+$(error "COMP_DEFAULT is set to ${COMP_DEFAULT}, which  isn't selected to be \
+	built!")
+endif
+
+.PHONY: all
+all: mksquashfs unsquashfs
+
+mksquashfs: $(MKSQUASHFS_OBJS)
+	$(CC) $(LDFLAGS) $(EXTRA_LDFLAGS) $(MKSQUASHFS_OBJS) $(LIBS) -o $@
+
+mksquashfs.o: Makefile mksquashfs.c squashfs_fs.h squashfs_swap.h mksquashfs.h \
+	sort.h pseudo.h compressor.h xattr.h action.h error.h progressbar.h \
+	info.h caches-queues-lists.h read_fs.h restore.h process_fragments.h 
+
+read_fs.o: read_fs.c squashfs_fs.h squashfs_swap.h compressor.h xattr.h \
+	error.h mksquashfs.h
+
+sort.o: sort.c squashfs_fs.h mksquashfs.h sort.h error.h progressbar.h
+
+swap.o: swap.c
+
+pseudo.o: pseudo.c pseudo.h error.h progressbar.h
+
+compressor.o: Makefile compressor.c compressor.h squashfs_fs.h
+
+xattr.o: xattr.c squashfs_fs.h squashfs_swap.h mksquashfs.h xattr.h error.h \
+	progressbar.h
+
+read_xattrs.o: read_xattrs.c squashfs_fs.h squashfs_swap.h xattr.h error.h
+
+action.o: action.c squashfs_fs.h mksquashfs.h action.h error.h
+
+progressbar.o: progressbar.c error.h
+
+read_file.o: read_file.c error.h
+
+info.o: info.c squashfs_fs.h mksquashfs.h error.h progressbar.h \
+	caches-queues-lists.h
+
+restore.o: restore.c caches-queues-lists.h squashfs_fs.h mksquashfs.h error.h \
+	progressbar.h info.h
+
+process_fragments.o: process_fragments.c process_fragments.h
+
+caches-queues-lists.o: caches-queues-lists.c error.h caches-queues-lists.h
+
+gzip_wrapper.o: gzip_wrapper.c squashfs_fs.h gzip_wrapper.h compressor.h
+
+lzma_wrapper.o: lzma_wrapper.c compressor.h squashfs_fs.h
+
+lzma_xz_wrapper.o: lzma_xz_wrapper.c compressor.h squashfs_fs.h
+
+lzo_wrapper.o: lzo_wrapper.c squashfs_fs.h lzo_wrapper.h compressor.h
+
+lz4_wrapper.o: lz4_wrapper.c squashfs_fs.h lz4_wrapper.h compressor.h
+
+xz_wrapper.o: xz_wrapper.c squashfs_fs.h xz_wrapper.h compressor.h
+
+unsquashfs: $(UNSQUASHFS_OBJS)
+	$(CC) $(LDFLAGS) $(EXTRA_LDFLAGS) $(UNSQUASHFS_OBJS) $(LIBS) -o $@
+
+unsquashfs.o: unsquashfs.h unsquashfs.c squashfs_fs.h squashfs_swap.h \
+	squashfs_compat.h xattr.h read_fs.h compressor.h
+
+unsquash-1.o: unsquashfs.h unsquash-1.c squashfs_fs.h squashfs_compat.h
+
+unsquash-2.o: unsquashfs.h unsquash-2.c squashfs_fs.h squashfs_compat.h
+
+unsquash-3.o: unsquashfs.h unsquash-3.c squashfs_fs.h squashfs_compat.h
+
+unsquash-4.o: unsquashfs.h unsquash-4.c squashfs_fs.h squashfs_swap.h \
+	read_fs.h
+
+unsquashfs_xattr.o: unsquashfs_xattr.c unsquashfs.h squashfs_fs.h xattr.h
+
+unsquashfs_info.o: unsquashfs.h squashfs_fs.h
+
+.PHONY: clean
+clean:
+	-rm -f *.o mksquashfs unsquashfs
+
+.PHONY: install
+install: mksquashfs unsquashfs
+	mkdir -p $(INSTALL_DIR)
+	cp mksquashfs $(INSTALL_DIR)
+	cp unsquashfs $(INSTALL_DIR)
diff --git a/squashfs-tools/squashfs-tools/action.c b/squashfs-tools/squashfs-tools/action.c
new file mode 100644
index 0000000..7e43f17
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/action.c
@@ -0,0 +1,3273 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2011, 2012, 2013, 2014
+ * 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.
+ *
+ * action.c
+ */
+
+#include <fcntl.h>
+#include <dirent.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fnmatch.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/wait.h>
+#include <regex.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifndef FNM_EXTMATCH /* glibc extension */
+    #define FNM_EXTMATCH 0
+#endif
+
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "action.h"
+#include "error.h"
+
+/*
+ * code to parse actions
+ */
+
+static char *cur_ptr, *source;
+static struct action *fragment_spec = NULL;
+static struct action *exclude_spec = NULL;
+static struct action *empty_spec = NULL;
+static struct action *move_spec = NULL;
+static struct action *prune_spec = NULL;
+static struct action *other_spec = NULL;
+static int fragment_count = 0;
+static int exclude_count = 0;
+static int empty_count = 0;
+static int move_count = 0;
+static int prune_count = 0;
+static int other_count = 0;
+static struct action_entry *parsing_action;
+
+static struct file_buffer *def_fragment = NULL;
+
+static struct token_entry token_table[] = {
+	{ "(", TOK_OPEN_BRACKET, 1, },
+	{ ")", TOK_CLOSE_BRACKET, 1 },
+	{ "&&", TOK_AND, 2 },
+	{ "||", TOK_OR, 2 },
+	{ "!", TOK_NOT, 1 },
+	{ ",", TOK_COMMA, 1 },
+	{ "@", TOK_AT, 1},
+	{ " ", 	TOK_WHITE_SPACE, 1 },
+	{ "\t ", TOK_WHITE_SPACE, 1 },
+	{ "", -1, 0 }
+};
+
+
+static struct test_entry test_table[];
+
+static struct action_entry action_table[];
+
+static struct expr *parse_expr(int subexp);
+
+extern char *pathname(struct dir_ent *);
+
+extern char *subpathname(struct dir_ent *);
+
+extern int read_file(char *filename, char *type, int (parse_line)(char *));
+
+/*
+ * Lexical analyser
+ */
+#define STR_SIZE 256
+
+static int get_token(char **string)
+{
+	/* string buffer */
+	static char *str = NULL;
+	static int size = 0;
+
+	char *str_ptr;
+	int cur_size, i, quoted;
+
+	while (1) {
+		if (*cur_ptr == '\0')
+			return TOK_EOF;
+		for (i = 0; token_table[i].token != -1; i++)
+			if (strncmp(cur_ptr, token_table[i].string,
+						token_table[i].size) == 0)
+				break;
+		if (token_table[i].token != TOK_WHITE_SPACE)
+			break;
+		cur_ptr ++;
+	}
+
+	if (token_table[i].token != -1) {
+		cur_ptr += token_table[i].size;
+		return token_table[i].token;
+	}
+
+	/* string */
+	if(str == NULL) {
+		str = malloc(STR_SIZE);
+		if(str == NULL)
+			MEM_ERROR();
+		size = STR_SIZE;
+	}
+
+	/* Initialise string being read */
+	str_ptr = str;
+	cur_size = 0;
+	quoted = 0;
+
+	while(1) {
+		while(*cur_ptr == '"') {
+			cur_ptr ++;
+			quoted = !quoted;
+		}
+
+		if(*cur_ptr == '\0') {
+			/* inside quoted string EOF, otherwise end of string */
+			if(quoted)
+				return TOK_EOF;
+			else
+				break;
+		}
+
+		if(!quoted) {
+			for(i = 0; token_table[i].token != -1; i++)
+				if (strncmp(cur_ptr, token_table[i].string,
+						token_table[i].size) == 0)
+					break;
+			if (token_table[i].token != -1)
+				break;
+		}
+
+		if(*cur_ptr == '\\') {
+			cur_ptr ++;
+			if(*cur_ptr == '\0')
+				return TOK_EOF;
+		}
+
+		if(cur_size + 2 > size) {
+			char *tmp;
+
+			size = (cur_size + 1  + STR_SIZE) & ~(STR_SIZE - 1);
+
+			tmp = realloc(str, size);
+			if(tmp == NULL)
+				MEM_ERROR();
+
+			str_ptr = str_ptr - str + tmp;
+			str = tmp;
+		}
+
+		*str_ptr ++ = *cur_ptr ++;
+		cur_size ++;
+	}
+
+	*str_ptr = '\0';
+	*string = str;
+	return TOK_STRING;
+}
+
+
+static int peek_token(char **string)
+{
+	char *saved = cur_ptr;
+	int token = get_token(string);
+
+	cur_ptr = saved;
+
+	return token;
+}
+
+
+/*
+ * Expression parser
+ */
+static void free_parse_tree(struct expr *expr)
+{
+	if(expr->type == ATOM_TYPE) {
+		int i;
+
+		for(i = 0; i < expr->atom.test->args; i++)
+			free(expr->atom.argv[i]);
+
+		free(expr->atom.argv);
+	} else if (expr->type == UNARY_TYPE)
+		free_parse_tree(expr->unary_op.expr);
+	else {
+		free_parse_tree(expr->expr_op.lhs);
+		free_parse_tree(expr->expr_op.rhs);
+	}
+
+	free(expr);
+}
+
+
+static struct expr *create_expr(struct expr *lhs, int op, struct expr *rhs)
+{
+	struct expr *expr;
+
+	if (rhs == NULL) {
+		free_parse_tree(lhs);
+		return NULL;
+	}
+
+	expr = malloc(sizeof(*expr));
+	if (expr == NULL)
+		MEM_ERROR();
+
+	expr->type = OP_TYPE;
+	expr->expr_op.lhs = lhs;
+	expr->expr_op.rhs = rhs;
+	expr->expr_op.op = op;
+
+	return expr;
+}
+
+
+static struct expr *create_unary_op(struct expr *lhs, int op)
+{
+	struct expr *expr;
+
+	if (lhs == NULL)
+		return NULL;
+
+	expr = malloc(sizeof(*expr));
+	if (expr == NULL)
+		MEM_ERROR();
+
+	expr->type = UNARY_TYPE;
+	expr->unary_op.expr = lhs;
+	expr->unary_op.op = op;
+
+	return expr;
+}
+
+
+static struct expr *parse_test(char *name)
+{
+	char *string, **argv = NULL;
+	int token, args = 0;
+	int i;
+	struct test_entry *test;
+	struct expr *expr;
+
+	for (i = 0; test_table[i].args != -1; i++)
+		if (strcmp(name, test_table[i].name) == 0)
+			break;
+
+	test = &test_table[i];
+
+	if (test->args == -1) {
+		SYNTAX_ERROR("Non-existent test \"%s\"\n", name);
+		return NULL;
+	}
+
+	if(parsing_action->type == EXCLUDE_ACTION && !test->exclude_ok) {
+		fprintf(stderr, "Failed to parse action \"%s\"\n", source);
+		fprintf(stderr, "Test \"%s\" cannot be used in exclude "
+							"actions\n", name);
+		fprintf(stderr, "Use prune action instead ...\n");
+		return NULL;
+	}
+
+	expr = malloc(sizeof(*expr));
+	if (expr == NULL)
+		MEM_ERROR();
+
+	expr->type = ATOM_TYPE;
+
+	expr->atom.test = test;
+	expr->atom.data = NULL;
+
+	/*
+	 * If the test has no arguments, then go straight to checking if there's
+	 * enough arguments
+	 */
+	token = peek_token(&string);
+
+	if (token != TOK_OPEN_BRACKET)
+			goto skip_args;
+
+	get_token(&string);
+
+	/*
+	 * speculatively read all the arguments, and then see if the
+	 * number of arguments read is the number expected, this handles
+	 * tests with a variable number of arguments
+	 */
+	token = get_token(&string);
+	if (token == TOK_CLOSE_BRACKET)
+		goto skip_args;
+
+	while(1) {
+		if (token != TOK_STRING) {
+			SYNTAX_ERROR("Unexpected token \"%s\", expected "
+				"argument\n", TOK_TO_STR(token, string));
+			goto failed;
+		}
+
+		argv = realloc(argv, (args + 1) * sizeof(char *));
+		if (argv == NULL)
+			MEM_ERROR();
+
+		argv[args ++ ] = strdup(string);
+
+		token = get_token(&string);
+
+		if (token == TOK_CLOSE_BRACKET)
+			break;
+
+		if (token != TOK_COMMA) {
+			SYNTAX_ERROR("Unexpected token \"%s\", expected "
+				"\",\" or \")\"\n", TOK_TO_STR(token, string));
+			goto failed;
+		}
+		token = get_token(&string);
+	}
+
+skip_args:
+	/*
+	 * expected number of arguments?
+	 */
+	if(test->args != -2 && args != test->args) {
+		SYNTAX_ERROR("Unexpected number of arguments, expected %d, "
+			"got %d\n", test->args, args);
+		goto failed;
+	}
+
+	expr->atom.args = args;
+	expr->atom.argv = argv;
+
+	if (test->parse_args) {
+		int res = test->parse_args(test, &expr->atom);
+
+		if (res == 0)		
+			goto failed;
+	}
+
+	return expr;
+
+failed:
+	free(argv);
+	free(expr);
+	return NULL;
+}
+
+
+static struct expr *get_atom()
+{
+	char *string;
+	int token = get_token(&string);
+
+	switch(token) {
+	case TOK_NOT:
+		return create_unary_op(get_atom(), token);
+	case TOK_OPEN_BRACKET:
+		return parse_expr(1);
+	case TOK_STRING:
+		return parse_test(string);
+	default:
+		SYNTAX_ERROR("Unexpected token \"%s\", expected test "
+					"operation, \"!\", or \"(\"\n",
+					TOK_TO_STR(token, string));
+		return NULL;
+	}
+}
+
+
+static struct expr *parse_expr(int subexp)
+{
+	struct expr *expr = get_atom();
+
+	while (expr) {
+		char *string;
+		int op = get_token(&string);
+
+		if (op == TOK_EOF) {
+			if (subexp) {
+				free_parse_tree(expr);
+				SYNTAX_ERROR("Expected \"&&\", \"||\" or "
+						"\")\", got EOF\n");
+				return NULL;
+			}
+			break;
+		}
+
+		if (op == TOK_CLOSE_BRACKET) {
+			if (!subexp) {
+				free_parse_tree(expr);
+				SYNTAX_ERROR("Unexpected \")\", expected "
+						"\"&&\", \"!!\" or EOF\n");
+				return NULL;
+			}
+			break;
+		}
+		
+		if (op != TOK_AND && op != TOK_OR) {
+			free_parse_tree(expr);
+			SYNTAX_ERROR("Unexpected token \"%s\", expected "
+				"\"&&\" or \"||\"\n", TOK_TO_STR(op, string));
+			return NULL;
+		}
+
+		expr = create_expr(expr, op, get_atom());
+	}
+
+	return expr;
+}
+
+
+/*
+ * Action parser
+ */
+int parse_action(char *s, int verbose)
+{
+	char *string, **argv = NULL;
+	int i, token, args = 0;
+	struct expr *expr;
+	struct action_entry *action;
+	void *data = NULL;
+	struct action **spec_list;
+	int spec_count;
+
+	cur_ptr = source = s;
+	token = get_token(&string);
+
+	if (token != TOK_STRING) {
+		SYNTAX_ERROR("Unexpected token \"%s\", expected name\n",
+						TOK_TO_STR(token, string));
+		return 0;
+	}
+
+	for (i = 0; action_table[i].args != -1; i++)
+		if (strcmp(string, action_table[i].name) == 0)
+			break;
+
+	if (action_table[i].args == -1) {
+		SYNTAX_ERROR("Non-existent action \"%s\"\n", string);
+		return 0;
+	}
+
+	action = &action_table[i];
+
+	token = get_token(&string);
+
+	if (token == TOK_AT)
+		goto skip_args;
+
+	if (token != TOK_OPEN_BRACKET) {
+		SYNTAX_ERROR("Unexpected token \"%s\", expected \"(\"\n",
+						TOK_TO_STR(token, string));
+		goto failed;
+	}
+
+	/*
+	 * speculatively read all the arguments, and then see if the
+	 * number of arguments read is the number expected, this handles
+	 * actions with a variable number of arguments
+	 */
+	token = get_token(&string);
+	if (token == TOK_CLOSE_BRACKET)
+		goto skip_args;
+
+	while (1) {
+		if (token != TOK_STRING) {
+			SYNTAX_ERROR("Unexpected token \"%s\", expected "
+				"argument\n", TOK_TO_STR(token, string));
+			goto failed;
+		}
+
+		argv = realloc(argv, (args + 1) * sizeof(char *));
+		if (argv == NULL)
+			MEM_ERROR();
+
+		argv[args ++] = strdup(string);
+
+		token = get_token(&string);
+
+		if (token == TOK_CLOSE_BRACKET)
+			break;
+
+		if (token != TOK_COMMA) {
+			SYNTAX_ERROR("Unexpected token \"%s\", expected "
+				"\",\" or \")\"\n", TOK_TO_STR(token, string));
+			goto failed;
+		}
+		token = get_token(&string);
+	}
+
+skip_args:
+	/*
+	 * expected number of arguments?
+	 */
+	if(action->args != -2 && args != action->args) {
+		SYNTAX_ERROR("Unexpected number of arguments, expected %d, "
+			"got %d\n", action->args, args);
+		goto failed;
+	}
+
+	if (action->parse_args) {
+		int res = action->parse_args(action, args, argv, &data);
+
+		if (res == 0)
+			goto failed;
+	}
+
+	if (token == TOK_CLOSE_BRACKET)
+		token = get_token(&string);
+
+	if (token != TOK_AT) {
+		SYNTAX_ERROR("Unexpected token \"%s\", expected \"@\"\n",
+						TOK_TO_STR(token, string));
+		goto failed;
+	}
+	
+	parsing_action = action;
+	expr = parse_expr(0);
+
+	if (expr == NULL)
+		goto failed;
+
+	/*
+	 * choose action list and increment action counter
+	 */
+	switch(action->type) {
+	case FRAGMENT_ACTION:
+		spec_count = fragment_count ++;
+		spec_list = &fragment_spec;
+		break;
+	case EXCLUDE_ACTION:
+		spec_count = exclude_count ++;
+		spec_list = &exclude_spec;
+		break;
+	case EMPTY_ACTION:
+		spec_count = empty_count ++;
+		spec_list = &empty_spec;
+		break;
+	case MOVE_ACTION:
+		spec_count = move_count ++;
+		spec_list = &move_spec;
+		break;
+	case PRUNE_ACTION:
+		spec_count = prune_count ++;
+		spec_list = &prune_spec;
+		break;
+	default:
+		spec_count = other_count ++;
+		spec_list = &other_spec;
+	}
+	
+	*spec_list = realloc(*spec_list, (spec_count + 1) *
+					sizeof(struct action));
+	if (*spec_list == NULL)
+		MEM_ERROR();
+
+	(*spec_list)[spec_count].type = action->type;
+	(*spec_list)[spec_count].action = action;
+	(*spec_list)[spec_count].args = args;
+	(*spec_list)[spec_count].argv = argv;
+	(*spec_list)[spec_count].expr = expr;
+	(*spec_list)[spec_count].data = data;
+	(*spec_list)[spec_count].verbose = verbose;
+
+	return 1;
+
+failed:
+	free(argv);
+	return 0;
+}
+
+
+/*
+ * Evaluate expressions
+ */
+
+#define ALLOC_SZ 128
+
+#define LOG_ENABLE	0
+#define LOG_DISABLE	1
+#define LOG_PRINT	2
+#define LOG_ENABLED	3
+
+char *_expr_log(char *string, int cmnd)
+{
+	static char *expr_msg = NULL;
+	static int cur_size = 0, alloc_size = 0;
+	int size;
+
+	switch(cmnd) {
+	case LOG_ENABLE:
+		expr_msg = malloc(ALLOC_SZ);
+		alloc_size = ALLOC_SZ;
+		cur_size = 0;
+		return expr_msg;
+	case LOG_DISABLE:
+		free(expr_msg);
+		alloc_size = cur_size = 0;
+		return expr_msg = NULL;
+	case LOG_ENABLED:
+		return expr_msg;
+	default:
+		if(expr_msg == NULL)
+			return NULL;
+		break;
+	}
+
+	/* if string is empty append '\0' */
+	size = strlen(string) ? : 1; 
+
+	if(alloc_size - cur_size < size) {
+		/* buffer too small, expand */
+		alloc_size = (cur_size + size + ALLOC_SZ - 1) & ~(ALLOC_SZ - 1);
+
+		expr_msg = realloc(expr_msg, alloc_size);
+		if(expr_msg == NULL)
+			MEM_ERROR();
+	}
+
+	memcpy(expr_msg + cur_size, string, size);
+	cur_size += size; 
+
+	return expr_msg;
+}
+
+
+char *expr_log_cmnd(int cmnd)
+{
+	return _expr_log(NULL, cmnd);
+}
+
+
+char *expr_log(char *string)
+{
+	return _expr_log(string, LOG_PRINT);
+}
+
+
+void expr_log_atom(struct atom *atom)
+{
+	int i;
+
+	if(atom->test->handle_logging)
+		return;
+
+	expr_log(atom->test->name);
+
+	if(atom->args) {
+		expr_log("(");
+		for(i = 0; i < atom->args; i++) {
+			expr_log(atom->argv[i]);
+			if (i + 1 < atom->args)
+				expr_log(",");
+		}
+		expr_log(")");
+	}
+}
+
+
+void expr_log_match(int match)
+{
+	if(match)
+		expr_log("=True");
+	else
+		expr_log("=False");
+}
+
+
+static int eval_expr_log(struct expr *expr, struct action_data *action_data)
+{
+	int match;
+
+	switch (expr->type) {
+	case ATOM_TYPE:
+		expr_log_atom(&expr->atom);
+		match = expr->atom.test->fn(&expr->atom, action_data);
+		expr_log_match(match);
+		break;
+	case UNARY_TYPE:
+		expr_log("!");
+		match = !eval_expr_log(expr->unary_op.expr, action_data);
+		break;
+	default:
+		expr_log("(");
+		match = eval_expr_log(expr->expr_op.lhs, action_data);
+
+		if ((expr->expr_op.op == TOK_AND && match) ||
+				(expr->expr_op.op == TOK_OR && !match)) {
+			expr_log(token_table[expr->expr_op.op].string);
+			match = eval_expr_log(expr->expr_op.rhs, action_data);
+		}
+		expr_log(")");
+		break;
+	}
+
+	return match;
+}
+
+
+static int eval_expr(struct expr *expr, struct action_data *action_data)
+{
+	int match;
+
+	switch (expr->type) {
+	case ATOM_TYPE:
+		match = expr->atom.test->fn(&expr->atom, action_data);
+		break;
+	case UNARY_TYPE:
+		match = !eval_expr(expr->unary_op.expr, action_data);
+		break;
+	default:
+		match = eval_expr(expr->expr_op.lhs, action_data);
+
+		if ((expr->expr_op.op == TOK_AND && match) ||
+					(expr->expr_op.op == TOK_OR && !match))
+			match = eval_expr(expr->expr_op.rhs, action_data);
+		break;
+	}
+
+	return match;
+}
+
+
+static int eval_expr_top(struct action *action, struct action_data *action_data)
+{
+	if(action->verbose) {
+		int match, n;
+
+		expr_log_cmnd(LOG_ENABLE);
+
+		if(action_data->subpath)
+			expr_log(action_data->subpath);
+
+		expr_log("=");
+		expr_log(action->action->name);
+
+		if(action->args) {
+			expr_log("(");
+			for (n = 0; n < action->args; n++) {
+				expr_log(action->argv[n]);
+				if(n + 1 < action->args)
+					expr_log(",");
+			}
+			expr_log(")");
+		}
+
+		expr_log("@");
+
+		match = eval_expr_log(action->expr, action_data);
+
+		/*
+		 * Print the evaluated expression log, if the
+		 * result matches the logging specified
+		 */
+		if((match && (action->verbose & ACTION_LOG_TRUE)) || (!match
+				&& (action->verbose & ACTION_LOG_FALSE)))
+			progressbar_info("%s\n", expr_log(""));
+
+		expr_log_cmnd(LOG_DISABLE);
+
+		return match;
+	} else
+		return eval_expr(action->expr, action_data);
+}
+
+
+/*
+ * Read action file, passing each line to parse_action() for
+ * parsing.
+ *
+ * One action per line, of the form
+ *	action(arg1,arg2)@expr(arg1,arg2)....
+ *
+ * Actions can be split across multiple lines using "\".
+ * 
+ * Blank lines and comment lines indicated by # are supported.
+ */
+int parse_action_true(char *s)
+{
+	return parse_action(s, ACTION_LOG_TRUE);
+}
+
+
+int parse_action_false(char *s)
+{
+	return parse_action(s, ACTION_LOG_FALSE);
+}
+
+
+int parse_action_verbose(char *s)
+{
+	return parse_action(s, ACTION_LOG_VERBOSE);
+}
+
+
+int parse_action_nonverbose(char *s)
+{
+	return parse_action(s, ACTION_LOG_NONE);
+}
+
+
+int read_action_file(char *filename, int verbose)
+{
+	switch(verbose) {
+	case ACTION_LOG_TRUE:
+		return read_file(filename, "action", parse_action_true);
+	case ACTION_LOG_FALSE:
+		return read_file(filename, "action", parse_action_false);
+	case ACTION_LOG_VERBOSE:
+		return read_file(filename, "action", parse_action_verbose);
+	default:
+		return read_file(filename, "action", parse_action_nonverbose);
+	}
+}
+
+
+/*
+ * helper to evaluate whether action/test acts on this file type
+ */
+static int file_type_match(int st_mode, int type)
+{
+	switch(type) {
+	case ACTION_DIR:
+		return S_ISDIR(st_mode);
+	case ACTION_REG:
+		return S_ISREG(st_mode);
+	case ACTION_ALL:
+		return S_ISREG(st_mode) || S_ISDIR(st_mode) ||
+			S_ISCHR(st_mode) || S_ISBLK(st_mode) ||
+			S_ISFIFO(st_mode) || S_ISSOCK(st_mode);
+	case ACTION_LNK:
+		return S_ISLNK(st_mode);
+	case ACTION_ALL_LNK:
+	default:
+		return 1;
+	}
+}
+
+
+/*
+ * General action evaluation code
+ */
+int actions()
+{
+	return other_count;
+}
+
+
+void eval_actions(struct dir_info *root, struct dir_ent *dir_ent)
+{
+	int i, match;
+	struct action_data action_data;
+	int st_mode = dir_ent->inode->buf.st_mode;
+
+	action_data.name = dir_ent->name;
+	action_data.pathname = strdup(pathname(dir_ent));
+	action_data.subpath = strdup(subpathname(dir_ent));
+	action_data.buf = &dir_ent->inode->buf;
+	action_data.depth = dir_ent->our_dir->depth;
+	action_data.dir_ent = dir_ent;
+	action_data.root = root;
+
+	for (i = 0; i < other_count; i++) {
+		struct action *action = &other_spec[i];
+
+		if (!file_type_match(st_mode, action->action->file_types))
+			/* action does not operate on this file type */
+			continue;
+
+		match = eval_expr_top(action, &action_data);
+
+		if (match)
+			action->action->run_action(action, dir_ent);
+	}
+
+	free(action_data.pathname);
+	free(action_data.subpath);
+}
+
+
+/*
+ * Fragment specific action code
+ */
+void *eval_frag_actions(struct dir_info *root, struct dir_ent *dir_ent)
+{
+	int i, match;
+	struct action_data action_data;
+
+	action_data.name = dir_ent->name;
+	action_data.pathname = strdup(pathname(dir_ent));
+	action_data.subpath = strdup(subpathname(dir_ent));
+	action_data.buf = &dir_ent->inode->buf;
+	action_data.depth = dir_ent->our_dir->depth;
+	action_data.dir_ent = dir_ent;
+	action_data.root = root;
+
+	for (i = 0; i < fragment_count; i++) {
+		match = eval_expr_top(&fragment_spec[i], &action_data);
+		if (match) {
+			free(action_data.pathname);
+			free(action_data.subpath);
+			return &fragment_spec[i].data;
+		}
+	}
+
+	free(action_data.pathname);
+	free(action_data.subpath);
+	return &def_fragment;
+}
+
+
+void *get_frag_action(void *fragment)
+{
+	struct action *spec_list_end = &fragment_spec[fragment_count];
+	struct action *action;
+
+	if (fragment == NULL)
+		return &def_fragment;
+
+	if (fragment_count == 0)
+		return NULL;
+
+	if (fragment == &def_fragment)
+		action = &fragment_spec[0] - 1;
+	else 
+		action = fragment - offsetof(struct action, data);
+
+	if (++action == spec_list_end)
+		return NULL;
+
+	return &action->data;
+}
+
+
+/*
+ * Exclude specific action code
+ */
+int exclude_actions()
+{
+	return exclude_count;
+}
+
+
+int eval_exclude_actions(char *name, char *pathname, char *subpath,
+	struct stat *buf, int depth, struct dir_ent *dir_ent)
+{
+	int i, match = 0;
+	struct action_data action_data;
+
+	action_data.name = name;
+	action_data.pathname = pathname;
+	action_data.subpath = subpath;
+	action_data.buf = buf;
+	action_data.depth = depth;
+	action_data.dir_ent = dir_ent;
+
+	for (i = 0; i < exclude_count && !match; i++)
+		match = eval_expr_top(&exclude_spec[i], &action_data);
+
+	return match;
+}
+
+
+/*
+ * Fragment specific action code
+ */
+static void frag_action(struct action *action, struct dir_ent *dir_ent)
+{
+	struct inode_info *inode = dir_ent->inode;
+
+	inode->no_fragments = 0;
+}
+
+static void no_frag_action(struct action *action, struct dir_ent *dir_ent)
+{
+	struct inode_info *inode = dir_ent->inode;
+
+	inode->no_fragments = 1;
+}
+
+static void always_frag_action(struct action *action, struct dir_ent *dir_ent)
+{
+	struct inode_info *inode = dir_ent->inode;
+
+	inode->always_use_fragments = 1;
+}
+
+static void no_always_frag_action(struct action *action, struct dir_ent *dir_ent)
+{
+	struct inode_info *inode = dir_ent->inode;
+
+	inode->always_use_fragments = 0;
+}
+
+
+/*
+ * Compression specific action code
+ */
+static void comp_action(struct action *action, struct dir_ent *dir_ent)
+{
+	struct inode_info *inode = dir_ent->inode;
+
+	inode->noD = inode->noF = 0;
+}
+
+static void uncomp_action(struct action *action, struct dir_ent *dir_ent)
+{
+	struct inode_info *inode = dir_ent->inode;
+
+	inode->noD = inode->noF = 1;
+}
+
+
+/*
+ * Uid/gid specific action code
+ */
+static long long parse_uid(char *arg) {
+	char *b;
+	long long uid = strtoll(arg, &b, 10);
+
+	if (*b == '\0') {
+		if (uid < 0 || uid >= (1LL << 32)) {
+			SYNTAX_ERROR("Uid out of range\n");
+			return -1;
+		}
+	} else {
+		struct passwd *passwd = getpwnam(arg);
+
+		if (passwd)
+			uid = passwd->pw_uid;
+		else {
+			SYNTAX_ERROR("Invalid uid or unknown user\n");
+			return -1;
+		}
+	}
+
+	return uid;
+}
+
+
+static long long parse_gid(char *arg) {
+	char *b;
+	long long gid = strtoll(arg, &b, 10);
+
+	if (*b == '\0') {
+		if (gid < 0 || gid >= (1LL << 32)) {
+			SYNTAX_ERROR("Gid out of range\n");
+			return -1;
+		}
+	} else {
+		struct group *group = getgrnam(arg);
+
+		if (group)
+			gid = group->gr_gid;
+		else {
+			SYNTAX_ERROR("Invalid gid or unknown group\n");
+			return -1;
+		}
+	}
+
+	return gid;
+}
+
+
+static int parse_uid_args(struct action_entry *action, int args, char **argv,
+								void **data)
+{
+	long long uid;
+	struct uid_info *uid_info;
+
+	uid = parse_uid(argv[0]);
+	if (uid == -1)
+		return 0;
+
+	uid_info = malloc(sizeof(struct uid_info));
+	if (uid_info == NULL)
+		MEM_ERROR();
+
+	uid_info->uid = uid;
+	*data = uid_info;
+
+	return 1;
+}
+
+
+static int parse_gid_args(struct action_entry *action, int args, char **argv,
+								void **data)
+{
+	long long gid;
+	struct gid_info *gid_info;
+
+	gid = parse_gid(argv[0]);
+	if (gid == -1)
+		return 0;
+
+	gid_info = malloc(sizeof(struct gid_info));
+	if (gid_info == NULL)
+		MEM_ERROR();
+
+	gid_info->gid = gid;
+	*data = gid_info;
+
+	return 1;
+}
+
+
+static int parse_guid_args(struct action_entry *action, int args, char **argv,
+								void **data)
+{
+	long long uid, gid;
+	struct guid_info *guid_info;
+
+	uid = parse_uid(argv[0]);
+	if (uid == -1)
+		return 0;
+
+	gid = parse_gid(argv[1]);
+	if (gid == -1)
+		return 0;
+
+	guid_info = malloc(sizeof(struct guid_info));
+	if (guid_info == NULL)
+		MEM_ERROR();
+
+	guid_info->uid = uid;
+	guid_info->gid = gid;
+	*data = guid_info;
+
+	return 1;
+}
+
+
+static void uid_action(struct action *action, struct dir_ent *dir_ent)
+{
+	struct inode_info *inode = dir_ent->inode;
+	struct uid_info *uid_info = action->data;
+
+	inode->buf.st_uid = uid_info->uid;
+}
+
+static void gid_action(struct action *action, struct dir_ent *dir_ent)
+{
+	struct inode_info *inode = dir_ent->inode;
+	struct gid_info *gid_info = action->data;
+
+	inode->buf.st_gid = gid_info->gid;
+}
+
+static void guid_action(struct action *action, struct dir_ent *dir_ent)
+{
+	struct inode_info *inode = dir_ent->inode;
+	struct guid_info *guid_info = action->data;
+
+	inode->buf.st_uid = guid_info->uid;
+	inode->buf.st_gid = guid_info->gid;
+
+}
+
+
+/*
+ * Mode specific action code
+ */
+static int parse_octal_mode_args(int args, char **argv,
+			void **data)
+{
+	int n, bytes;
+	unsigned int mode;
+	struct mode_data *mode_data;
+
+	/* octal mode number? */
+	n = sscanf(argv[0], "%o%n", &mode, &bytes);
+	if (n == 0)
+		return -1; /* not an octal number arg */
+
+
+	/* check there's no trailing junk */
+	if (argv[0][bytes] != '\0') {
+		SYNTAX_ERROR("Unexpected trailing bytes after octal "
+			"mode number\n");
+		return 0; /* bad octal number arg */
+	}
+
+	/* check there's only one argument */
+	if (args > 1) {
+		SYNTAX_ERROR("Octal mode number is first argument, "
+			"expected one argument, got %d\n", args);
+		return 0; /* bad octal number arg */
+	}
+
+	/*  check mode is within range */
+	if (mode > 07777) {
+		SYNTAX_ERROR("Octal mode %o is out of range\n", mode);
+		return 0; /* bad octal number arg */
+	}
+
+	mode_data = malloc(sizeof(struct mode_data));
+	if (mode_data == NULL)
+		MEM_ERROR();
+
+	mode_data->operation = ACTION_MODE_OCT;
+	mode_data->mode = mode;
+	mode_data->next = NULL;
+	*data = mode_data;
+
+	return 1;
+}
+
+
+/*
+ * Parse symbolic mode of format [ugoa]*[[+-=]PERMS]+
+ * PERMS = [rwxXst]+ or [ugo]
+ */
+static int parse_sym_mode_arg(char *arg, struct mode_data **head,
+	struct mode_data **cur)
+{
+	struct mode_data *mode_data;
+	int mode;
+	int mask = 0;
+	int op;
+	char X;
+
+	if (arg[0] != 'u' && arg[0] != 'g' && arg[0] != 'o' && arg[0] != 'a') {
+		/* no ownership specifiers, default to a */
+		mask = 0777;
+		goto parse_operation;
+	}
+
+	/* parse ownership specifiers */
+	while(1) {
+		switch(*arg) {
+		case 'u':
+			mask |= 04700;
+			break;
+		case 'g':
+			mask |= 02070;
+			break;
+		case 'o':
+			mask |= 01007;
+			break;
+		case 'a':
+			mask = 07777;
+			break;
+		default:
+			goto parse_operation;
+		}
+		arg ++;
+	}
+
+parse_operation:
+	/* trap a symbolic mode with just an ownership specification */
+	if(*arg == '\0') {
+		SYNTAX_ERROR("Expected one of '+', '-' or '=', got EOF\n");
+		goto failed;
+	}
+
+	while(*arg != '\0') {
+		mode = 0;
+		X = 0;
+
+		switch(*arg) {
+		case '+':
+			op = ACTION_MODE_ADD;
+			break;
+		case '-':
+			op = ACTION_MODE_REM;
+			break;
+		case '=':
+			op = ACTION_MODE_SET;
+			break;
+		default:
+			SYNTAX_ERROR("Expected one of '+', '-' or '=', got "
+				"'%c'\n", *arg);
+			goto failed;
+		}
+	
+		arg ++;
+	
+		/* Parse PERMS */
+		if (*arg == 'u' || *arg == 'g' || *arg == 'o') {
+	 		/* PERMS = [ugo] */
+			mode = - *arg;
+			arg ++;
+		} else {
+	 		/* PERMS = [rwxXst]* */
+			while(1) {
+				switch(*arg) {
+				case 'r':
+					mode |= 0444;
+					break;
+				case 'w':
+					mode |= 0222;
+					break;
+				case 'x':
+					mode |= 0111;
+					break;
+				case 's':
+					mode |= 06000;
+					break;
+				case 't':
+					mode |= 01000;
+					break;
+				case 'X':
+					X = 1;
+					break;
+				case '+':
+				case '-':
+				case '=':
+				case '\0':
+					mode &= mask;
+					goto perms_parsed;
+				default:
+					SYNTAX_ERROR("Unrecognised permission "
+								"'%c'\n", *arg);
+					goto failed;
+				}
+	
+				arg ++;
+			}
+		}
+	
+perms_parsed:
+		mode_data = malloc(sizeof(*mode_data));
+		if (mode_data == NULL)
+			MEM_ERROR();
+
+		mode_data->operation = op;
+		mode_data->mode = mode;
+		mode_data->mask = mask;
+		mode_data->X = X;
+		mode_data->next = NULL;
+
+		if (*cur) {
+			(*cur)->next = mode_data;
+			*cur = mode_data;
+		} else
+			*head = *cur = mode_data;
+	}
+
+	return 1;
+
+failed:
+	return 0;
+}
+
+
+static int parse_sym_mode_args(struct action_entry *action, int args,
+					char **argv, void **data)
+{
+	int i, res = 1;
+	struct mode_data *head = NULL, *cur = NULL;
+
+	for (i = 0; i < args && res; i++)
+		res = parse_sym_mode_arg(argv[i], &head, &cur);
+
+	*data = head;
+
+	return res;
+}
+
+
+static int parse_mode_args(struct action_entry *action, int args,
+					char **argv, void **data)
+{
+	int res;
+
+	if (args == 0) {
+		SYNTAX_ERROR("Mode action expects one or more arguments\n");
+		return 0;
+	}
+
+	res = parse_octal_mode_args(args, argv, data);
+	if(res >= 0)
+		/* Got an octal mode argument */
+		return res;
+	else  /* not an octal mode argument */
+		return parse_sym_mode_args(action, args, argv, data);
+}
+
+
+static int mode_execute(struct mode_data *mode_data, int st_mode)
+{
+	int mode = 0;
+
+	for (;mode_data; mode_data = mode_data->next) {
+		if (mode_data->mode < 0) {
+			/* 'u', 'g' or 'o' */
+			switch(-mode_data->mode) {
+			case 'u':
+				mode = (st_mode >> 6) & 07;
+				break;
+			case 'g':
+				mode = (st_mode >> 3) & 07;
+				break;
+			case 'o':
+				mode = st_mode & 07;
+				break;
+			}
+			mode = ((mode << 6) | (mode << 3) | mode) &
+				mode_data->mask;
+		} else if (mode_data->X &&
+				((st_mode & S_IFMT) == S_IFDIR ||
+				(st_mode & 0111)))
+			/* X permission, only takes effect if inode is a
+			 * directory or x is set for some owner */
+			mode = mode_data->mode | (0111 & mode_data->mask);
+		else
+			mode = mode_data->mode;
+
+		switch(mode_data->operation) {
+		case ACTION_MODE_OCT:
+			st_mode = (st_mode & S_IFMT) | mode;
+			break;
+		case ACTION_MODE_SET:
+			st_mode = (st_mode & ~mode_data->mask) | mode;
+			break;
+		case ACTION_MODE_ADD:
+			st_mode |= mode;
+			break;
+		case ACTION_MODE_REM:
+			st_mode &= ~mode;
+		}
+	}
+
+	return st_mode;
+}
+
+
+static void mode_action(struct action *action, struct dir_ent *dir_ent)
+{
+	dir_ent->inode->buf.st_mode = mode_execute(action->data,
+					dir_ent->inode->buf.st_mode);
+}
+
+
+/*
+ *  Empty specific action code
+ */
+int empty_actions()
+{
+	return empty_count;
+}
+
+
+static int parse_empty_args(struct action_entry *action, int args,
+					char **argv, void **data)
+{
+	struct empty_data *empty_data;
+	int val;
+
+	if (args >= 2) {
+		SYNTAX_ERROR("Empty action expects zero or one argument\n");
+		return 0;
+	}
+
+	if (args == 0 || strcmp(argv[0], "all") == 0)
+		val = EMPTY_ALL;
+	else if (strcmp(argv[0], "source") == 0)
+		val = EMPTY_SOURCE;
+	else if (strcmp(argv[0], "excluded") == 0)
+		val = EMPTY_EXCLUDED;
+	else {
+		SYNTAX_ERROR("Empty action expects zero arguments, or one"
+			"argument containing \"all\", \"source\", or \"excluded\""
+			"\n");
+		return 0;
+	}
+
+	empty_data = malloc(sizeof(*empty_data));
+	if (empty_data == NULL)
+		MEM_ERROR();
+
+	empty_data->val = val;
+	*data = empty_data;
+
+	return 1;
+}
+
+
+int eval_empty_actions(struct dir_info *root, struct dir_ent *dir_ent)
+{
+	int i, match = 0;
+	struct action_data action_data;
+	struct empty_data *data;
+	struct dir_info *dir = dir_ent->dir;
+
+	/*
+	 * Empty action only works on empty directories
+	 */
+	if (dir->count != 0)
+		return 0;
+
+	action_data.name = dir_ent->name;
+	action_data.pathname = strdup(pathname(dir_ent));
+	action_data.subpath = strdup(subpathname(dir_ent));
+	action_data.buf = &dir_ent->inode->buf;
+	action_data.depth = dir_ent->our_dir->depth;
+	action_data.dir_ent = dir_ent;
+	action_data.root = root;
+
+	for (i = 0; i < empty_count && !match; i++) {
+		data = empty_spec[i].data;
+
+		/*
+		 * determine the cause of the empty directory and evaluate
+		 * the empty action specified.  Three empty actions:
+		 * - EMPTY_SOURCE: empty action triggers only if the directory
+		 *	was originally empty, i.e directories that are empty
+		 *	only due to excluding are ignored.
+		 * - EMPTY_EXCLUDED: empty action triggers only if the directory
+		 *	is empty because of excluding, i.e. directories that
+		 *	were originally empty are ignored.
+		 * - EMPTY_ALL (the default): empty action triggers if the
+		 *	directory is empty, irrespective of the reason, i.e.
+		 *	the directory could have been originally empty or could
+		 *	be empty due to excluding.
+		 */
+		if ((data->val == EMPTY_EXCLUDED && !dir->excluded) ||
+				(data->val == EMPTY_SOURCE && dir->excluded))
+			continue;
+		
+		match = eval_expr_top(&empty_spec[i], &action_data);
+	}
+
+	free(action_data.pathname);
+	free(action_data.subpath);
+
+	return match;
+}
+
+
+/*
+ *  Move specific action code
+ */
+static struct move_ent *move_list = NULL;
+
+
+int move_actions()
+{
+	return move_count;
+}
+
+
+static char *move_pathname(struct move_ent *move)
+{
+	struct dir_info *dest;
+	char *name, *pathname;
+	int res;
+
+	dest = (move->ops & ACTION_MOVE_MOVE) ?
+		move->dest : move->dir_ent->our_dir;
+	name = (move->ops & ACTION_MOVE_RENAME) ?
+		move->name : move->dir_ent->name;
+
+	if(dest->subpath[0] != '\0')
+		res = asprintf(&pathname, "%s/%s", dest->subpath, name);
+	else
+		res = asprintf(&pathname, "/%s", name);
+
+	if(res == -1)
+		BAD_ERROR("asprintf failed in move_pathname\n");
+
+	return pathname;
+}
+
+
+static char *get_comp(char **pathname)
+{
+	char *path = *pathname, *start;
+
+	while(*path == '/')
+		path ++;
+
+	if(*path == '\0')
+		return NULL;
+
+	start = path;
+	while(*path != '/' && *path != '\0')
+		path ++;
+
+	*pathname = path;
+	return strndup(start, path - start);
+}
+
+
+static struct dir_ent *lookup_comp(char *comp, struct dir_info *dest)
+{
+	struct dir_ent *dir_ent;
+
+	for(dir_ent = dest->list; dir_ent; dir_ent = dir_ent->next)
+		if(strcmp(comp, dir_ent->name) == 0)
+			break;
+
+	return dir_ent;
+}
+
+
+void eval_move(struct action_data *action_data, struct move_ent *move,
+		struct dir_info *root, struct dir_ent *dir_ent, char *pathname)
+{
+	struct dir_info *dest, *source = dir_ent->our_dir;
+	struct dir_ent *comp_ent;
+	char *comp, *path = pathname;
+
+	/*
+	 * Walk pathname to get the destination directory
+	 *
+	 * Like the mv command, if the last component exists and it
+	 * is a directory, then move the file into that directory,
+	 * otherwise, move the file into parent directory of the last
+	 * component and rename to the last component.
+	 */
+	if (pathname[0] == '/')
+		/* absolute pathname, walk from root directory */
+		dest = root;
+	else
+		/* relative pathname, walk from current directory */
+		dest = source;
+
+	for(comp = get_comp(&pathname); comp; free(comp),
+						comp = get_comp(&pathname)) {
+
+		if (strcmp(comp, ".") == 0)
+			continue;
+
+		if (strcmp(comp, "..") == 0) {
+			/* if we're in the root directory then ignore */
+			if(dest->depth > 1)
+				dest = dest->dir_ent->our_dir;
+			continue;
+		}
+
+		/*
+		 * Look up comp in current directory, if it exists and it is a
+		 * directory continue walking the pathname, otherwise exit,
+		 * we've walked as far as we can go, normally this is because
+		 * we've arrived at the leaf component which we are going to
+		 * rename source to
+		 */
+		comp_ent = lookup_comp(comp, dest);
+		if (comp_ent == NULL || (comp_ent->inode->buf.st_mode & S_IFMT)
+							!= S_IFDIR)
+			break;
+
+		dest = comp_ent->dir;
+	}
+
+	if(comp) {
+		/* Leaf component? If so we're renaming to this  */
+		char *remainder = get_comp(&pathname);
+		free(remainder);
+
+		if(remainder) {
+			/*
+			 * trying to move source to a subdirectory of
+			 * comp, but comp either doesn't exist, or it isn't
+			 * a directory, which is impossible
+			 */
+			if (comp_ent == NULL)
+				ERROR("Move action: cannot move %s to %s, no "
+					"such directory %s\n",
+					action_data->subpath, path, comp);
+			else
+				ERROR("Move action: cannot move %s to %s, %s "
+					"is not a directory\n",
+					action_data->subpath, path, comp);
+			free(comp);
+			return;
+		}
+
+		/*
+		 * Multiple move actions triggering on one file can be merged
+		 * if one is a RENAME and the other is a MOVE.  Multiple RENAMEs
+		 * can only merge if they're doing the same thing
+	 	 */
+		if(move->ops & ACTION_MOVE_RENAME) {
+			if(strcmp(comp, move->name) != 0) {
+				char *conf_path = move_pathname(move);
+				ERROR("Move action: Cannot move %s to %s, "
+					"conflicting move, already moving "
+					"to %s via another move action!\n",
+					action_data->subpath, path, conf_path);
+				free(conf_path);
+				free(comp);
+				return;
+			}
+			free(comp);
+		} else {
+			move->name = comp;
+			move->ops |= ACTION_MOVE_RENAME;
+		}
+	}
+
+	if(dest != source) {
+		/*
+		 * Multiple move actions triggering on one file can be merged
+		 * if one is a RENAME and the other is a MOVE.  Multiple MOVEs
+		 * can only merge if they're doing the same thing
+	 	 */
+		if(move->ops & ACTION_MOVE_MOVE) {
+			if(dest != move->dest) {
+				char *conf_path = move_pathname(move);
+				ERROR("Move action: Cannot move %s to %s, "
+					"conflicting move, already moving "
+					"to %s via another move action!\n",
+					action_data->subpath, path, conf_path);
+				free(conf_path);
+				return;
+			}
+		} else {
+			move->dest = dest;
+			move->ops |= ACTION_MOVE_MOVE;
+		}
+	}
+}
+
+
+static int subdirectory(struct dir_info *source, struct dir_info *dest)
+{
+	if(source == NULL)
+		return 0;
+
+	return strlen(source->subpath) <= strlen(dest->subpath) &&
+		(dest->subpath[strlen(source->subpath)] == '/' ||
+		dest->subpath[strlen(source->subpath)] == '\0') &&
+		strncmp(source->subpath, dest->subpath,
+		strlen(source->subpath)) == 0;
+}
+
+
+void eval_move_actions(struct dir_info *root, struct dir_ent *dir_ent)
+{
+	int i;
+	struct action_data action_data;
+	struct move_ent *move = NULL;
+
+	action_data.name = dir_ent->name;
+	action_data.pathname = strdup(pathname(dir_ent));
+	action_data.subpath = strdup(subpathname(dir_ent));
+	action_data.buf = &dir_ent->inode->buf;
+	action_data.depth = dir_ent->our_dir->depth;
+	action_data.dir_ent = dir_ent;
+	action_data.root = root;
+
+	/*
+	 * Evaluate each move action against the current file.  For any
+	 * move actions that match don't actually perform the move now, but,
+	 * store it, and execute all the stored move actions together once the
+	 * directory scan is complete.  This is done to ensure each separate
+	 * move action does not nondeterministically interfere with other move
+	 * actions.  Each move action is considered to act independently, and
+	 * each move action sees the directory tree in the same state.
+	 */
+	for (i = 0; i < move_count; i++) {
+		struct action *action = &move_spec[i];
+		int match = eval_expr_top(action, &action_data);
+
+		if(match) {
+			if(move == NULL) {
+				move = malloc(sizeof(*move));
+				if(move == NULL)
+					MEM_ERROR();
+
+				move->ops = 0;
+				move->dir_ent = dir_ent;
+			}
+			eval_move(&action_data, move, root, dir_ent,
+				action->argv[0]);
+		}
+	}
+
+	if(move) {
+		struct dir_ent *comp_ent;
+		struct dir_info *dest;
+		char *name;
+
+		/*
+		 * Move contains the result of all triggered move actions.
+		 * Check the destination doesn't already exist
+		 */
+		if(move->ops == 0) {
+			free(move);
+			goto finish;
+		}
+
+		dest = (move->ops & ACTION_MOVE_MOVE) ?
+			move->dest : dir_ent->our_dir;
+		name = (move->ops & ACTION_MOVE_RENAME) ?
+			move->name : dir_ent->name;
+		comp_ent = lookup_comp(name, dest);
+		if(comp_ent) {
+			char *conf_path = move_pathname(move);
+			ERROR("Move action: Cannot move %s to %s, "
+				"destination already exists\n",
+				action_data.subpath, conf_path);
+			free(conf_path);
+			free(move);
+			goto finish;
+		}
+
+		/*
+		 * If we're moving a directory, check we're not moving it to a
+		 * subdirectory of itself
+		 */
+		if(subdirectory(dir_ent->dir, dest)) {
+			char *conf_path = move_pathname(move);
+			ERROR("Move action: Cannot move %s to %s, this is a "
+				"subdirectory of itself\n",
+				action_data.subpath, conf_path);
+			free(conf_path);
+			free(move);
+			goto finish;
+		}
+		move->next = move_list;
+		move_list = move;
+	}
+
+finish:
+	free(action_data.pathname);
+	free(action_data.subpath);
+}
+
+
+static void move_dir(struct dir_ent *dir_ent)
+{
+	struct dir_info *dir = dir_ent->dir;
+	struct dir_ent *comp_ent;
+
+	/* update our directory's subpath name */
+	free(dir->subpath);
+	dir->subpath = strdup(subpathname(dir_ent));
+
+	/* recursively update the subpaths of any sub-directories */
+	for(comp_ent = dir->list; comp_ent; comp_ent = comp_ent->next)
+		if(comp_ent->dir)
+			move_dir(comp_ent);
+}
+
+
+static void move_file(struct move_ent *move_ent)
+{
+	struct dir_ent *dir_ent = move_ent->dir_ent;
+
+	if(move_ent->ops & ACTION_MOVE_MOVE) {
+		struct dir_ent *comp_ent, *prev = NULL;
+		struct dir_info *source = dir_ent->our_dir,
+							*dest = move_ent->dest;
+		char *filename = pathname(dir_ent);
+
+		/*
+		 * If we're moving a directory, check we're not moving it to a
+		 * subdirectory of itself
+		 */
+		if(subdirectory(dir_ent->dir, dest)) {
+			char *conf_path = move_pathname(move_ent);
+			ERROR("Move action: Cannot move %s to %s, this is a "
+				"subdirectory of itself\n",
+				subpathname(dir_ent), conf_path);
+			free(conf_path);
+			return;
+		}
+
+		/* Remove the file from source directory */
+		for(comp_ent = source->list; comp_ent != dir_ent;
+				prev = comp_ent, comp_ent = comp_ent->next);
+
+		if(prev)
+			prev->next = comp_ent->next;
+		else
+			source->list = comp_ent->next;
+
+		source->count --;
+		if((comp_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+			source->directory_count --;
+
+		/* Add the file to dest directory */
+		comp_ent->next = dest->list;
+		dest->list = comp_ent;
+		comp_ent->our_dir = dest;
+
+		dest->count ++;
+		if((comp_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+			dest->directory_count ++;
+
+		/*
+		 * We've moved the file, and so we can't now use the
+		 * parent directory's pathname to calculate the pathname
+		 */
+		if(dir_ent->nonstandard_pathname == NULL) {
+			dir_ent->nonstandard_pathname = strdup(filename);
+			if(dir_ent->source_name) {
+				free(dir_ent->source_name);
+				dir_ent->source_name = NULL;
+			}
+		}
+	}
+
+	if(move_ent->ops & ACTION_MOVE_RENAME) {
+		/*
+		 * If we're using name in conjunction with the parent
+		 * directory's pathname to calculate the pathname, we need
+		 * to use source_name to override.  Otherwise it's already being
+		 * over-ridden
+		 */
+		if(dir_ent->nonstandard_pathname == NULL &&
+						dir_ent->source_name == NULL)
+			dir_ent->source_name = dir_ent->name;
+		else
+			free(dir_ent->name);
+
+		dir_ent->name = move_ent->name;
+	}
+
+	if(dir_ent->dir)
+		/*
+		 * dir_ent is a directory, and we have to recursively fix-up
+		 * its subpath, and the subpaths of all of its sub-directories
+		 */
+		move_dir(dir_ent);
+}
+
+
+void do_move_actions()
+{
+	while(move_list) {
+		struct move_ent *temp = move_list;
+		struct dir_info *dest = (move_list->ops & ACTION_MOVE_MOVE) ?
+			move_list->dest : move_list->dir_ent->our_dir;
+		char *name = (move_list->ops & ACTION_MOVE_RENAME) ?
+			move_list->name : move_list->dir_ent->name;
+		struct dir_ent *comp_ent = lookup_comp(name, dest);
+		if(comp_ent) {
+			char *conf_path = move_pathname(move_list);
+			ERROR("Move action: Cannot move %s to %s, "
+				"destination already exists\n",
+				subpathname(move_list->dir_ent), conf_path);
+			free(conf_path);
+		} else
+			move_file(move_list);
+
+		move_list = move_list->next;
+		free(temp);
+	}
+}
+
+
+/*
+ * Prune specific action code
+ */
+int prune_actions()
+{
+	return prune_count;
+}
+
+
+int eval_prune_actions(struct dir_info *root, struct dir_ent *dir_ent)
+{
+	int i, match = 0;
+	struct action_data action_data;
+
+	action_data.name = dir_ent->name;
+	action_data.pathname = strdup(pathname(dir_ent));
+	action_data.subpath = strdup(subpathname(dir_ent));
+	action_data.buf = &dir_ent->inode->buf;
+	action_data.depth = dir_ent->our_dir->depth;
+	action_data.dir_ent = dir_ent;
+	action_data.root = root;
+
+	for (i = 0; i < prune_count && !match; i++)
+		match = eval_expr_top(&prune_spec[i], &action_data);
+
+	free(action_data.pathname);
+	free(action_data.subpath);
+
+	return match;
+}
+
+
+/*
+ * Noop specific action code
+ */
+static void noop_action(struct action *action, struct dir_ent *dir_ent)
+{
+}
+
+
+/*
+ * General test evaluation code
+ */
+
+/*
+ * A number can be of the form [range]number[size]
+ * [range] is either:
+ *	'<' or '-', match on less than number
+ *	'>' or '+', match on greater than number
+ *	'' (nothing), match on exactly number
+ * [size] is either:
+ *	'' (nothing), number
+ *	'k' or 'K', number * 2^10
+ * 	'm' or 'M', number * 2^20
+ *	'g' or 'G', number * 2^30
+ */
+static int parse_number(char *start, long long *size, int *range, char **error)
+{
+	char *end;
+	long long number;
+
+	if (*start == '>' || *start == '+') {
+		*range = NUM_GREATER;
+		start ++;
+	} else if (*start == '<' || *start == '-') {
+		*range = NUM_LESS;
+		start ++;
+	} else
+		*range = NUM_EQ;
+
+	errno = 0; /* To enable failure after call to be determined */
+	number = strtoll(start, &end, 10);
+
+	if((errno == ERANGE && (number == LLONG_MAX || number == LLONG_MIN))
+				|| (errno != 0 && number == 0)) {
+		/* long long underflow or overflow in conversion, or other
+		 * conversion error.
+		 * Note: we don't check for LLONG_MIN and LLONG_MAX only
+		 * because strtoll can validly return that if the
+		 * user used these values
+		 */
+		*error = "Long long underflow, overflow or other conversion "
+								"error";
+		return 0;
+	}
+
+	if (end == start) {
+		/* Couldn't read any number  */
+		*error = "Number expected";
+		return 0;
+	}
+
+	switch (end[0]) {
+	case 'g':
+	case 'G':
+		number *= 1024;
+	case 'm':
+	case 'M':
+		number *= 1024;
+	case 'k':
+	case 'K':
+		number *= 1024;
+
+		if (end[1] != '\0') {
+			*error = "Trailing junk after size specifier";
+			return 0;
+		}
+
+		break;
+	case '\0':
+		break;
+	default:
+		*error = "Trailing junk after number";
+		return 0;
+	}
+
+	*size = number;
+
+	return 1;
+}
+
+
+static int parse_number_arg(struct test_entry *test, struct atom *atom)
+{
+	struct test_number_arg *number;
+	long long size;
+	int range;
+	char *error;
+	int res = parse_number(atom->argv[0], &size, &range, &error);
+
+	if (res == 0) {
+		TEST_SYNTAX_ERROR(test, 0, "%s\n", error);
+		return 0;
+	}
+
+	number = malloc(sizeof(*number));
+	if (number == NULL)
+		MEM_ERROR();
+
+	number->range = range;
+	number->size = size;
+
+	atom->data = number;
+
+	return 1;
+}
+
+
+static int parse_range_args(struct test_entry *test, struct atom *atom)
+{
+	struct test_range_args *range;
+	long long start, end;
+	int type;
+	int res;
+	char *error;
+
+	res = parse_number(atom->argv[0], &start, &type, &error);
+	if (res == 0) {
+		TEST_SYNTAX_ERROR(test, 0, "%s\n", error);
+		return 0;
+	}
+
+	if (type != NUM_EQ) {
+		TEST_SYNTAX_ERROR(test, 0, "Range specifier (<, >, -, +) not "
+			"expected\n");
+		return 0;
+	}
+ 
+	res = parse_number(atom->argv[1], &end, &type, &error);
+	if (res == 0) {
+		TEST_SYNTAX_ERROR(test, 1, "%s\n", error);
+		return 0;
+	}
+
+	if (type != NUM_EQ) {
+		TEST_SYNTAX_ERROR(test, 1, "Range specifier (<, >, -, +) not "
+			"expected\n");
+		return 0;
+	}
+ 
+	range = malloc(sizeof(*range));
+	if (range == NULL)
+		MEM_ERROR();
+
+	range->start = start;
+	range->end = end;
+
+	atom->data = range;
+
+	return 1;
+}
+
+
+/*
+ * Generic test code macro
+ */
+#define TEST_FN(NAME, MATCH, CODE) \
+static int NAME##_fn(struct atom *atom, struct action_data *action_data) \
+{ \
+	/* test operates on MATCH file types only */ \
+	if (!file_type_match(action_data->buf->st_mode, MATCH)) \
+		return 0; \
+ \
+	CODE \
+}
+
+/*
+ * Generic test code macro testing VAR for size (eq, less than, greater than)
+ */
+#define TEST_VAR_FN(NAME, MATCH, VAR) TEST_FN(NAME, MATCH, \
+	{ \
+	int match = 0; \
+	struct test_number_arg *number = atom->data; \
+	\
+	switch (number->range) { \
+	case NUM_EQ: \
+		match = VAR == number->size; \
+		break; \
+	case NUM_LESS: \
+		match = VAR < number->size; \
+		break; \
+	case NUM_GREATER: \
+		match = VAR > number->size; \
+		break; \
+	} \
+	\
+	return match; \
+	})	
+
+
+/*
+ * Generic test code macro testing VAR for range [x, y] (value between x and y
+ * inclusive).
+ */	
+#define TEST_VAR_RANGE_FN(NAME, MATCH, VAR) TEST_FN(NAME##_range, MATCH, \
+	{ \
+	struct test_range_args *range = atom->data; \
+	\
+	return range->start <= VAR && VAR <= range->end; \
+	})	
+
+
+/*
+ * Name, Pathname and Subpathname test specific code
+ */
+
+/*
+ * Add a leading "/" if subpathname and pathname lacks it
+ */
+static int check_pathname(struct test_entry *test, struct atom *atom)
+{
+	int res;
+	char *name;
+
+	if(atom->argv[0][0] != '/') {
+		res = asprintf(&name, "/%s", atom->argv[0]);
+		if(res == -1)
+			BAD_ERROR("asprintf failed in check_pathname\n");
+
+		free(atom->argv[0]);
+		atom->argv[0] = name;
+	}
+
+	return 1;
+}
+
+
+TEST_FN(name, ACTION_ALL_LNK, \
+	return fnmatch(atom->argv[0], action_data->name,
+				FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;)
+
+TEST_FN(pathname, ACTION_ALL_LNK, \
+	return fnmatch(atom->argv[0], action_data->subpath,
+				FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;)
+
+
+static int count_components(char *path)
+{
+	int count;
+
+	for (count = 0; *path != '\0'; count ++) {
+		while (*path == '/')
+			path ++;
+
+		while (*path != '\0' && *path != '/')
+			path ++;
+	}
+
+	return count;
+}
+
+
+static char *get_start(char *s, int n)
+{
+	int count;
+	char *path = s;
+
+	for (count = 0; *path != '\0' && count < n; count ++) {
+		while (*path == '/')
+			path ++;
+
+		while (*path != '\0' && *path != '/')
+			path ++;
+	}
+
+	if (count == n)
+		*path = '\0';
+
+	return s;
+}
+	
+
+static int subpathname_fn(struct atom *atom, struct action_data *action_data)
+{
+	char *path = strdup(action_data->subpath);
+	int is_match = fnmatch(atom->argv[0], get_start(path,
+		count_components(atom->argv[0])),
+		FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;
+	free(path);
+	return is_match;
+}
+
+/*
+ * Inode attribute test operations using generic
+ * TEST_VAR_FN(test name, file scope, attribute name) macro.
+ * This is for tests that do not need to be specially handled in any way.
+ * They just take a variable and compare it against a number.
+ */
+TEST_VAR_FN(filesize, ACTION_REG, action_data->buf->st_size)
+
+TEST_VAR_FN(dirsize, ACTION_DIR, action_data->buf->st_size)
+
+TEST_VAR_FN(size, ACTION_ALL_LNK, action_data->buf->st_size)
+
+TEST_VAR_FN(inode, ACTION_ALL_LNK, action_data->buf->st_ino)
+
+TEST_VAR_FN(nlink, ACTION_ALL_LNK, action_data->buf->st_nlink)
+
+TEST_VAR_FN(fileblocks, ACTION_REG, action_data->buf->st_blocks)
+
+TEST_VAR_FN(dirblocks, ACTION_DIR, action_data->buf->st_blocks)
+
+TEST_VAR_FN(blocks, ACTION_ALL_LNK, action_data->buf->st_blocks)
+
+TEST_VAR_FN(dircount, ACTION_DIR, action_data->dir_ent->dir->count)
+
+TEST_VAR_FN(depth, ACTION_ALL_LNK, action_data->depth)
+
+TEST_VAR_RANGE_FN(filesize, ACTION_REG, action_data->buf->st_size)
+
+TEST_VAR_RANGE_FN(dirsize, ACTION_DIR, action_data->buf->st_size)
+
+TEST_VAR_RANGE_FN(size, ACTION_ALL_LNK, action_data->buf->st_size)
+
+TEST_VAR_RANGE_FN(inode, ACTION_ALL_LNK, action_data->buf->st_ino)
+
+TEST_VAR_RANGE_FN(nlink, ACTION_ALL_LNK, action_data->buf->st_nlink)
+
+TEST_VAR_RANGE_FN(fileblocks, ACTION_REG, action_data->buf->st_blocks)
+
+TEST_VAR_RANGE_FN(dirblocks, ACTION_DIR, action_data->buf->st_blocks)
+
+TEST_VAR_RANGE_FN(blocks, ACTION_ALL_LNK, action_data->buf->st_blocks)
+
+TEST_VAR_RANGE_FN(gid, ACTION_ALL_LNK, action_data->buf->st_gid)
+
+TEST_VAR_RANGE_FN(uid, ACTION_ALL_LNK, action_data->buf->st_uid)
+
+TEST_VAR_RANGE_FN(depth, ACTION_ALL_LNK, action_data->depth)
+
+TEST_VAR_RANGE_FN(dircount, ACTION_DIR, action_data->dir_ent->dir->count)
+
+/*
+ * uid specific test code
+ */
+TEST_VAR_FN(uid, ACTION_ALL_LNK, action_data->buf->st_uid)
+
+static int parse_uid_arg(struct test_entry *test, struct atom *atom)
+{
+	struct test_number_arg *number;
+	long long size;
+	int range;
+	char *error;
+
+	if(parse_number(atom->argv[0], &size, &range, &error)) {
+		/* managed to fully parse argument as a number */
+		if(size < 0 || size > (((long long) 1 << 32) - 1)) {
+			TEST_SYNTAX_ERROR(test, 1, "Numeric uid out of "
+								"range\n");
+			return 0;
+		}
+	} else {
+		/* couldn't parse (fully) as a number, is it a user name? */
+		struct passwd *uid = getpwnam(atom->argv[0]);
+		if(uid) {
+			size = uid->pw_uid;
+			range = NUM_EQ;
+		} else {
+			TEST_SYNTAX_ERROR(test, 1, "Invalid uid or unknown "
+								"user\n");
+			return 0;
+		}
+	}
+
+	number = malloc(sizeof(*number));
+	if(number == NULL)
+		MEM_ERROR();
+
+	number->range = range;
+	number->size= size;
+
+	atom->data = number;
+
+	return 1;
+}
+
+
+/*
+ * gid specific test code
+ */
+TEST_VAR_FN(gid, ACTION_ALL_LNK, action_data->buf->st_gid)
+
+static int parse_gid_arg(struct test_entry *test, struct atom *atom)
+{
+	struct test_number_arg *number;
+	long long size;
+	int range;
+	char *error;
+
+	if(parse_number(atom->argv[0], &size, &range, &error)) {
+		/* managed to fully parse argument as a number */
+		if(size < 0 || size > (((long long) 1 << 32) - 1)) {
+			TEST_SYNTAX_ERROR(test, 1, "Numeric gid out of "
+								"range\n");
+			return 0;
+		}
+	} else {
+		/* couldn't parse (fully) as a number, is it a group name? */
+		struct group *gid = getgrnam(atom->argv[0]);
+		if(gid) {
+			size = gid->gr_gid;
+			range = NUM_EQ;
+		} else {
+			TEST_SYNTAX_ERROR(test, 1, "Invalid gid or unknown "
+								"group\n");
+			return 0;
+		}
+	}
+
+	number = malloc(sizeof(*number));
+	if(number == NULL)
+		MEM_ERROR();
+
+	number->range = range;
+	number->size= size;
+
+	atom->data = number;
+
+	return 1;
+}
+
+
+/*
+ * Type test specific code
+ */
+struct type_entry type_table[] = {
+	{ S_IFSOCK, 's' },
+	{ S_IFLNK, 'l' },
+	{ S_IFREG, 'f' },
+	{ S_IFBLK, 'b' },
+	{ S_IFDIR, 'd' },
+	{ S_IFCHR, 'c' },
+	{ S_IFIFO, 'p' },
+	{ 0, 0 },
+};
+
+
+static int parse_type_arg(struct test_entry *test, struct atom *atom)
+{
+	int i;
+
+	if (strlen(atom->argv[0]) != 1)
+		goto failed;
+
+	for(i = 0; type_table[i].type != 0; i++)
+		if (type_table[i].type == atom->argv[0][0])
+			break;
+
+	atom->data = &type_table[i];
+
+	if(type_table[i].type != 0)
+		return 1;
+
+failed:
+	TEST_SYNTAX_ERROR(test, 0, "Unexpected file type, expected 'f', 'd', "
+		"'c', 'b', 'l', 's' or 'p'\n");
+	return 0;
+}
+	
+
+static int type_fn(struct atom *atom, struct action_data *action_data)
+{
+	struct type_entry *type = atom->data;
+
+	return (action_data->buf->st_mode & S_IFMT) == type->value;
+}
+
+
+/*
+ * True test specific code
+ */
+static int true_fn(struct atom *atom, struct action_data *action_data)
+{
+	return 1;
+}
+
+
+/*
+ *  False test specific code
+ */
+static int false_fn(struct atom *atom, struct action_data *action_data)
+{
+	return 0;
+}
+
+
+/*
+ *  File test specific code
+ */
+static int parse_file_arg(struct test_entry *test, struct atom *atom)
+{
+	int res;
+	regex_t *preg = malloc(sizeof(regex_t));
+
+	if (preg == NULL)
+		MEM_ERROR();
+
+	res = regcomp(preg, atom->argv[0], REG_EXTENDED);
+	if (res) {
+		char str[1024]; /* overflow safe */
+
+		regerror(res, preg, str, 1024);
+		free(preg);
+		TEST_SYNTAX_ERROR(test, 0, "invalid regex \"%s\" because "
+			"\"%s\"\n", atom->argv[0], str);
+		return 0;
+	}
+
+	atom->data = preg;
+
+	return 1;
+}
+
+
+static int file_fn(struct atom *atom, struct action_data *action_data)
+{
+	int child, res, size = 0, status;
+	int pipefd[2];
+	char *buffer = NULL;
+	regex_t *preg = atom->data;
+
+	res = pipe(pipefd);
+	if (res == -1)
+		BAD_ERROR("file_fn pipe failed\n");
+
+	child = fork();
+	if (child == -1)
+		BAD_ERROR("file_fn fork_failed\n");
+
+	if (child == 0) {
+		/*
+		 * Child process
+		 * Connect stdout to pipefd[1] and execute file command
+		 */
+		close(STDOUT_FILENO);
+		res = dup(pipefd[1]);
+		if (res == -1)
+			exit(EXIT_FAILURE);
+
+		execlp("file", "file", "-b", action_data->pathname,
+			(char *) NULL);
+		exit(EXIT_FAILURE);
+	}
+
+	/*
+	 * Parent process.  Read stdout from file command
+ 	 */
+	close(pipefd[1]);
+
+	do {
+		buffer = realloc(buffer, size + 512);
+		if (buffer == NULL)
+			MEM_ERROR();
+
+		res = read_bytes(pipefd[0], buffer + size, 512);
+
+		if (res == -1)
+			BAD_ERROR("file_fn pipe read error\n");
+
+		size += 512;
+
+	} while (res == 512);
+
+	size = size + res - 512;
+
+	buffer[size] = '\0';
+
+	res = waitpid(child,  &status, 0);
+
+	if (res == -1)
+		BAD_ERROR("file_fn waitpid failed\n");
+ 
+	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+		BAD_ERROR("file_fn file returned error\n");
+
+	close(pipefd[0]);
+
+	res = regexec(preg, buffer, (size_t) 0, NULL, 0);
+
+	free(buffer);
+
+	return res == 0;
+}
+
+
+/*
+ *  Exec test specific code
+ */
+static int exec_fn(struct atom *atom, struct action_data *action_data)
+{
+	int child, i, res, status;
+
+	child = fork();
+	if (child == -1)
+		BAD_ERROR("exec_fn fork_failed\n");
+
+	if (child == 0) {
+		/*
+		 * Child process
+		 * redirect stdin, stdout & stderr to /dev/null and
+		 * execute atom->argv[0]
+		 */
+		int fd = open("/dev/null", O_RDWR);
+		if(fd == -1)
+			exit(EXIT_FAILURE);
+
+		close(STDIN_FILENO);
+		close(STDOUT_FILENO);
+		close(STDERR_FILENO);
+		for(i = 0; i < 3; i++) {
+			res = dup(fd);
+			if (res == -1)
+				exit(EXIT_FAILURE);
+		}
+		close(fd);
+
+		/*
+		 * Create environment variables
+		 * NAME: name of file
+		 * PATHNAME: pathname of file relative to squashfs root
+		 * SOURCE_PATHNAME: the pathname of the file in the source
+		 *                  directory
+		 */
+		res = setenv("NAME", action_data->name, 1);
+		if(res == -1)
+			exit(EXIT_FAILURE);
+
+		res = setenv("PATHNAME", action_data->subpath, 1);
+		if(res == -1)
+			exit(EXIT_FAILURE);
+
+		res = setenv("SOURCE_PATHNAME", action_data->pathname, 1);
+		if(res == -1)
+			exit(EXIT_FAILURE);
+
+		execl("/bin/sh", "sh", "-c", atom->argv[0], (char *) NULL);
+		exit(EXIT_FAILURE);
+	}
+
+	/*
+	 * Parent process. 
+ 	 */
+
+	res = waitpid(child,  &status, 0);
+
+	if (res == -1)
+		BAD_ERROR("exec_fn waitpid failed\n");
+ 
+	return WIFEXITED(status) ? WEXITSTATUS(status) == 0 : 0;
+}
+
+
+/*
+ * Symbolic link specific test code
+ */
+
+/*
+ * Walk the supplied pathname and return the directory entry corresponding
+ * to the pathname.  If any symlinks are encountered whilst walking the
+ * pathname, then recursively walk these, to obtain the fully
+ * dereferenced canonicalised directory entry.
+ *
+ * If follow_path fails to walk a pathname either because a component
+ * doesn't exist, it is a non directory component when a directory
+ * component is expected, a symlink with an absolute path is encountered,
+ * or a symlink is encountered which cannot be recursively walked due to
+ * the above failures, then return NULL.
+ */
+static struct dir_ent *follow_path(struct dir_info *dir, char *pathname)
+{
+	char *comp, *path = pathname;
+	struct dir_ent *dir_ent = NULL;
+
+	/* We cannot follow absolute paths */
+	if(pathname[0] == '/')
+		return NULL;
+
+	for(comp = get_comp(&path); comp; free(comp), comp = get_comp(&path)) {
+		if(strcmp(comp, ".") == 0)
+			continue;
+
+		if(strcmp(comp, "..") == 0) {
+			/* Move to parent if we're not in the root directory */
+			if(dir->depth > 1) {
+				dir = dir->dir_ent->our_dir;
+				dir_ent = NULL; /* lazily eval at loop exit */
+				continue;
+			} else
+				/* Failed to walk pathname */
+				return NULL;
+		}
+
+		/* Lookup comp in current directory */
+		dir_ent = lookup_comp(comp, dir);
+		if(dir_ent == NULL)
+			/* Doesn't exist, failed to walk pathname */
+			return NULL;
+
+		if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFLNK) {
+			/* Symbolic link, try to walk it */
+			dir_ent = follow_path(dir, dir_ent->inode->symlink);
+			if(dir_ent == NULL)
+				/* Failed to follow symlink */
+				return NULL;
+		}
+
+		if((dir_ent->inode->buf.st_mode & S_IFMT) != S_IFDIR)
+			/* Cannot walk further */
+			break;
+
+		dir = dir_ent->dir;
+	}
+
+	/* We will have exited the loop either because we've processed
+	 * all the components, which means we've successfully walked the
+	 * pathname, or because we've hit a non-directory, in which case
+	 * it's success if this is the leaf component */
+	if(comp) {
+		free(comp);
+		comp = get_comp(&path);
+		free(comp);
+		if(comp != NULL)
+			/* Not a leaf component */
+			return NULL;
+	} else {
+		/* Fully walked pathname, dir_ent contains correct value unless
+		 * we've walked to the parent ("..") in which case we need
+		 * to resolve it here */
+		if(!dir_ent)
+			dir_ent = dir->dir_ent;
+	}
+
+	return dir_ent;
+}
+
+
+static int exists_fn(struct atom *atom, struct action_data *action_data)
+{
+	/*
+	 * Test if a symlink exists within the output filesystem, that is,
+	 * the symlink has a relative path, and the relative path refers
+	 * to an entry within the output filesystem.
+	 *
+	 * This test function evaluates the path for symlinks - that is it
+	 * follows any symlinks in the path (and any symlinks that it contains
+ 	 * etc.), to discover the fully dereferenced canonicalised relative
+	 * path.
+	 *
+	 * If any symlinks within the path do not exist or are absolute
+	 * then the symlink is considered to not exist, as it cannot be
+	 * fully dereferenced.
+	 *
+	 * exists operates on symlinks only, other files by definition
+	 * exist
+	 */
+	if (!file_type_match(action_data->buf->st_mode, ACTION_LNK))
+		return 1;
+
+	/* dereference the symlink, and return TRUE if it exists */
+	return follow_path(action_data->dir_ent->our_dir,
+			action_data->dir_ent->inode->symlink) ? 1 : 0;
+}
+
+
+static int absolute_fn(struct atom *atom, struct action_data *action_data)
+{
+	/*
+	 * Test if a symlink has an absolute path, which by definition
+	 * means the symbolic link may be broken (even if the absolute path
+	 * does point into the filesystem being squashed, because the resultant
+	 * filesystem can be mounted/unsquashed anywhere, it is unlikely the
+	 * absolute path will still point to the right place).  If you know that
+	 * an absolute symlink will point to the right place then you don't need
+	 * to use this function, and/or these symlinks can be excluded by
+	 * use of other test operators.
+	 *
+	 * absolute operates on symlinks only, other files by definition
+	 * don't have problems
+	 */
+	if (!file_type_match(action_data->buf->st_mode, ACTION_LNK))
+		return 0;
+
+	return action_data->dir_ent->inode->symlink[0] == '/';
+}
+
+
+static int parse_expr_argX(struct test_entry *test, struct atom *atom,
+	int argno)
+{
+	/* Call parse_expr to parse argument, which should be an expression */
+
+	 /* save the current parser state */
+	char *save_cur_ptr = cur_ptr;
+	char *save_source = source;
+
+	cur_ptr = source = atom->argv[argno];
+	atom->data = parse_expr(0);
+
+	cur_ptr = save_cur_ptr;
+	source = save_source;
+
+	if(atom->data == NULL) {
+		/* parse_expr(0) will have reported the exact syntax error,
+		 * but, because we recursively evaluated the expression, it
+		 * will have been reported without the context of the stat
+		 * test().  So here additionally report our failure to parse
+		 * the expression in the stat() test to give context */
+		TEST_SYNTAX_ERROR(test, 0, "Failed to parse expression\n");
+		return 0;
+	}
+
+	return 1;
+}
+
+
+static int parse_expr_arg0(struct test_entry *test, struct atom *atom)
+{
+	return parse_expr_argX(test, atom, 0);
+}
+
+
+static int parse_expr_arg1(struct test_entry *test, struct atom *atom)
+{
+	return parse_expr_argX(test, atom, 1);
+}
+
+
+static int stat_fn(struct atom *atom, struct action_data *action_data)
+{
+	struct stat buf;
+	struct action_data eval_action;
+	int match, res;
+
+	/* evaluate the expression using the context of the inode
+	 * pointed to by the symlink.  This allows the inode attributes
+	 * of the file pointed to by the symlink to be evaluated, rather
+	 * than the symlink itself.
+	 *
+	 * Note, stat() deliberately does not evaluate the pathname, name or
+	 * depth of the symlink, these are left with the symlink values.
+	 * This allows stat() to be used on any symlink, rather than
+	 * just symlinks which are contained (if the symlink is *not*
+	 * contained then pathname, name and depth are meaningless as they
+	 * are relative to the filesystem being squashed). */
+
+	/* if this isn't a symlink then stat will just return the current
+	 * information, i.e. stat(expr) == expr.  This is harmless and
+	 * is better than returning TRUE or FALSE in a non symlink case */
+	res = stat(action_data->pathname, &buf);
+	if(res == -1) {
+		if(expr_log_cmnd(LOG_ENABLED)) {
+			expr_log(atom->test->name);
+			expr_log("(");
+			expr_log_match(0);
+			expr_log(")");
+		}
+		return 0;
+	}
+
+	/* fill in the inode values of the file pointed to by the
+	 * symlink, but, leave everything else the same */
+	memcpy(&eval_action, action_data, sizeof(struct action_data));
+	eval_action.buf = &buf;
+
+	if(expr_log_cmnd(LOG_ENABLED)) {
+		expr_log(atom->test->name);
+		expr_log("(");
+		match = eval_expr_log(atom->data, &eval_action);
+		expr_log(")");
+	} else
+		match = eval_expr(atom->data, &eval_action);
+
+	return match;
+}
+
+
+static int readlink_fn(struct atom *atom, struct action_data *action_data)
+{
+	int match = 0;
+	struct dir_ent *dir_ent;
+	struct action_data eval_action;
+
+	/* Dereference the symlink and evaluate the expression in the
+	 * context of the file pointed to by the symlink.
+	 * All attributes are updated to refer to the file that is pointed to.
+	 * Thus the inode attributes, pathname, name and depth all refer to
+	 * the dereferenced file, and not the symlink.
+	 *
+	 * If the symlink cannot be dereferenced because it doesn't exist in
+	 * the output filesystem, or due to some other failure to
+	 * walk the pathname (see follow_path above), then FALSE is returned.
+	 *
+	 * If you wish to evaluate the inode attributes of symlinks which
+	 * exist in the source filestem (but not in the output filesystem then
+	 * use stat instead (see above).
+	 *
+	 * readlink operates on symlinks only */
+	if (!file_type_match(action_data->buf->st_mode, ACTION_LNK))
+		goto finish;
+
+	/* dereference the symlink, and get the directory entry it points to */
+	dir_ent = follow_path(action_data->dir_ent->our_dir,
+			action_data->dir_ent->inode->symlink);
+	if(dir_ent == NULL)
+		goto finish;
+
+	eval_action.name = dir_ent->name;
+	eval_action.pathname = strdup(pathname(dir_ent));
+	eval_action.subpath = strdup(subpathname(dir_ent));
+	eval_action.buf = &dir_ent->inode->buf;
+	eval_action.depth = dir_ent->our_dir->depth;
+	eval_action.dir_ent = dir_ent;
+	eval_action.root = action_data->root;
+
+	if(expr_log_cmnd(LOG_ENABLED)) {
+		expr_log(atom->test->name);
+		expr_log("(");
+		match = eval_expr_log(atom->data, &eval_action);
+		expr_log(")");
+	} else
+		match = eval_expr(atom->data, &eval_action);
+
+	free(eval_action.pathname);
+	free(eval_action.subpath);
+
+	return match;
+
+finish:
+	if(expr_log_cmnd(LOG_ENABLED)) {
+		expr_log(atom->test->name);
+		expr_log("(");
+		expr_log_match(0);
+		expr_log(")");
+	}
+
+	return 0;
+}
+
+
+static int eval_fn(struct atom *atom, struct action_data *action_data)
+{
+	int match;
+	char *path = atom->argv[0];
+	struct dir_ent *dir_ent = action_data->dir_ent;
+	struct stat *buf = action_data->buf;
+	struct action_data eval_action;
+
+	/* Follow path (arg1) and evaluate the expression (arg2)
+	 * in the context of the file discovered.  All attributes are updated
+	 * to refer to the file that is pointed to.
+	 *
+	 * This test operation allows you to add additional context to the
+	 * evaluation of the file being scanned, such as "if current file is
+	 * XXX and the parent is YYY, then ..."  Often times you need or
+	 * want to test a combination of file status
+	 *
+	 * If the file referenced by the path does not exist in
+	 * the output filesystem, or some other failure is experienced in
+	 * walking the path (see follow_path above), then FALSE is returned.
+	 *
+	 * If you wish to evaluate the inode attributes of files which
+	 * exist in the source filestem (but not in the output filesystem then
+	 * use stat instead (see above). */
+
+	/* try to follow path, and get the directory entry it points to */
+	if(path[0] == '/') {
+		/* absolute, walk from root - first skip the leading / */
+		while(path[0] == '/')
+			path ++;
+		if(path[0] == '\0')
+			dir_ent = action_data->root->dir_ent;
+		else
+			dir_ent = follow_path(action_data->root, path);
+	} else {
+		/* relative, if first component is ".." walk from parent,
+		 * otherwise walk from dir_ent.
+		 * Note: this has to be handled here because follow_path
+		 * will quite correctly refuse to execute ".." on anything
+		 * which isn't a directory */
+		if(strncmp(path, "..", 2) == 0 && (path[2] == '\0' ||
+							path[2] == '/')) {
+			/* walk from parent */
+			path += 2;
+			while(path[0] == '/')
+				path ++;
+			if(path[0] == '\0')
+				dir_ent = dir_ent->our_dir->dir_ent;
+			else 
+				dir_ent = follow_path(dir_ent->our_dir, path);
+		} else if(!file_type_match(buf->st_mode, ACTION_DIR))
+			dir_ent = NULL;
+		else
+			dir_ent = follow_path(dir_ent->dir, path);
+	}
+
+	if(dir_ent == NULL) {
+		if(expr_log_cmnd(LOG_ENABLED)) {
+			expr_log(atom->test->name);
+			expr_log("(");
+			expr_log(atom->argv[0]);
+			expr_log(",");
+			expr_log_match(0);
+			expr_log(")");
+		}
+
+		return 0;
+	}
+
+	eval_action.name = dir_ent->name;
+	eval_action.pathname = strdup(pathname(dir_ent));
+	eval_action.subpath = strdup(subpathname(dir_ent));
+	eval_action.buf = &dir_ent->inode->buf;
+	eval_action.depth = dir_ent->our_dir->depth;
+	eval_action.dir_ent = dir_ent;
+	eval_action.root = action_data->root;
+
+	if(expr_log_cmnd(LOG_ENABLED)) {
+		expr_log(atom->test->name);
+		expr_log("(");
+		expr_log(eval_action.subpath);
+		expr_log(",");
+		match = eval_expr_log(atom->data, &eval_action);
+		expr_log(")");
+	} else
+		match = eval_expr(atom->data, &eval_action);
+
+	free(eval_action.pathname);
+	free(eval_action.subpath);
+
+	return match;
+}
+
+
+/*
+ * Perm specific test code
+ */
+static int parse_perm_args(struct test_entry *test, struct atom *atom)
+{
+	int res = 1, mode, op, i;
+	char *arg;
+	struct mode_data *head = NULL, *cur = NULL;
+	struct perm_data *perm_data;
+
+	if(atom->args == 0) {
+		TEST_SYNTAX_ERROR(test, 0, "One or more arguments expected\n");
+		return 0;
+	}
+
+	switch(atom->argv[0][0]) {
+	case '-':
+		op = PERM_ALL;
+		arg = atom->argv[0] + 1;
+		break;
+	case '/':
+		op = PERM_ANY;
+		arg = atom->argv[0] + 1;
+		break;
+	default:
+		op = PERM_EXACT;
+		arg = atom->argv[0];
+		break;
+	}
+
+	/* try to parse as an octal number */
+	res = parse_octal_mode_args(atom->args, atom->argv, (void **) &head);
+	if(res == -1) {
+		/* parse as sym mode argument */
+		for(i = 0; i < atom->args && res; i++, arg = atom->argv[i])
+			res = parse_sym_mode_arg(arg, &head, &cur);
+	}
+
+	if (res == 0)
+		goto finish;
+
+	/*
+	 * Evaluate the symbolic mode against a permission of 0000 octal
+	 */
+	mode = mode_execute(head, 0);
+
+	perm_data = malloc(sizeof(struct perm_data));
+	if (perm_data == NULL)
+		MEM_ERROR();
+
+	perm_data->op = op;
+	perm_data->mode = mode;
+
+	atom->data = perm_data;
+	
+finish:
+	while(head) {
+		struct mode_data *tmp = head;
+		head = head->next;
+		free(tmp);
+	}
+
+	return res;
+}
+
+
+static int perm_fn(struct atom *atom, struct action_data *action_data)
+{
+	struct perm_data *perm_data = atom->data;
+	struct stat *buf = action_data->buf;
+
+	switch(perm_data->op) {
+	case PERM_EXACT:
+		return (buf->st_mode & ~S_IFMT) == perm_data->mode;
+	case PERM_ALL:
+		return (buf->st_mode & perm_data->mode) == perm_data->mode;
+	case PERM_ANY:
+	default:
+		/*
+		 * if no permission bits are set in perm_data->mode match
+		 * on any file, this is to be consistent with find, which
+		 * does this to be consistent with the behaviour of
+		 * -perm -000
+		 */
+		return perm_data->mode == 0 || (buf->st_mode & perm_data->mode);
+	}
+}
+
+
+#ifdef SQUASHFS_TRACE
+static void dump_parse_tree(struct expr *expr)
+{
+	int i;
+
+	if(expr->type == ATOM_TYPE) {
+		printf("%s", expr->atom.test->name);
+		if(expr->atom.args) {
+			printf("(");
+			for(i = 0; i < expr->atom.args; i++) {
+				printf("%s", expr->atom.argv[i]);
+				if (i + 1 < expr->atom.args)
+					printf(",");
+			}
+			printf(")");
+		}
+	} else if (expr->type == UNARY_TYPE) {
+		printf("%s", token_table[expr->unary_op.op].string);
+		dump_parse_tree(expr->unary_op.expr);
+	} else {
+		printf("(");
+		dump_parse_tree(expr->expr_op.lhs);
+		printf("%s", token_table[expr->expr_op.op].string);
+		dump_parse_tree(expr->expr_op.rhs);
+		printf(")");
+	}
+}
+
+
+void dump_action_list(struct action *spec_list, int spec_count)
+{
+	int i;
+
+	for (i = 0; i < spec_count; i++) {
+		printf("%s", spec_list[i].action->name);
+		if (spec_list[i].args) {
+			int n;
+
+			printf("(");
+			for (n = 0; n < spec_list[i].args; n++) {
+				printf("%s", spec_list[i].argv[n]);
+				if (n + 1 < spec_list[i].args)
+					printf(",");
+			}
+			printf(")");
+		}
+		printf("=");
+		dump_parse_tree(spec_list[i].expr);
+		printf("\n");
+	}
+}
+
+
+void dump_actions()
+{
+	dump_action_list(exclude_spec, exclude_count);
+	dump_action_list(fragment_spec, fragment_count);
+	dump_action_list(other_spec, other_count);
+	dump_action_list(move_spec, move_count);
+	dump_action_list(empty_spec, empty_count);
+}
+#else
+void dump_actions()
+{
+}
+#endif
+
+
+static struct test_entry test_table[] = {
+	{ "name", 1, name_fn, NULL, 1},
+	{ "pathname", 1, pathname_fn, check_pathname, 1, 0},
+	{ "subpathname", 1, subpathname_fn, check_pathname, 1, 0},
+	{ "filesize", 1, filesize_fn, parse_number_arg, 1, 0},
+	{ "dirsize", 1, dirsize_fn, parse_number_arg, 1, 0},
+	{ "size", 1, size_fn, parse_number_arg, 1, 0},
+	{ "inode", 1, inode_fn, parse_number_arg, 1, 0},
+	{ "nlink", 1, nlink_fn, parse_number_arg, 1, 0},
+	{ "fileblocks", 1, fileblocks_fn, parse_number_arg, 1, 0},
+	{ "dirblocks", 1, dirblocks_fn, parse_number_arg, 1, 0},
+	{ "blocks", 1, blocks_fn, parse_number_arg, 1, 0},
+	{ "gid", 1, gid_fn, parse_gid_arg, 1, 0},
+	{ "uid", 1, uid_fn, parse_uid_arg, 1, 0},
+	{ "depth", 1, depth_fn, parse_number_arg, 1, 0},
+	{ "dircount", 1, dircount_fn, parse_number_arg, 0, 0},
+	{ "filesize_range", 2, filesize_range_fn, parse_range_args, 1, 0},
+	{ "dirsize_range", 2, dirsize_range_fn, parse_range_args, 1, 0},
+	{ "size_range", 2, size_range_fn, parse_range_args, 1, 0},
+	{ "inode_range", 2, inode_range_fn, parse_range_args, 1, 0},
+	{ "nlink_range", 2, nlink_range_fn, parse_range_args, 1, 0},
+	{ "fileblocks_range", 2, fileblocks_range_fn, parse_range_args, 1, 0},
+	{ "dirblocks_range", 2, dirblocks_range_fn, parse_range_args, 1, 0},
+	{ "blocks_range", 2, blocks_range_fn, parse_range_args, 1, 0},
+	{ "gid_range", 2, gid_range_fn, parse_range_args, 1, 0},
+	{ "uid_range", 2, uid_range_fn, parse_range_args, 1, 0},
+	{ "depth_range", 2, depth_range_fn, parse_range_args, 1, 0},
+	{ "dircount_range", 2, dircount_range_fn, parse_range_args, 0, 0},
+	{ "type", 1, type_fn, parse_type_arg, 1, 0},
+	{ "true", 0, true_fn, NULL, 1, 0},
+	{ "false", 0, false_fn, NULL, 1, 0},
+	{ "file", 1, file_fn, parse_file_arg, 1, 0},
+	{ "exec", 1, exec_fn, NULL, 1, 0},
+	{ "exists", 0, exists_fn, NULL, 0, 0},
+	{ "absolute", 0, absolute_fn, NULL, 0, 0},
+	{ "stat", 1, stat_fn, parse_expr_arg0, 1, 1},
+	{ "readlink", 1, readlink_fn, parse_expr_arg0, 0, 1},
+	{ "eval", 2, eval_fn, parse_expr_arg1, 0, 1},
+	{ "perm", -2, perm_fn, parse_perm_args, 1, 0},
+	{ "", -1 }
+};
+
+
+static struct action_entry action_table[] = {
+	{ "fragment", FRAGMENT_ACTION, 1, ACTION_REG, NULL, NULL},
+	{ "exclude", EXCLUDE_ACTION, 0, ACTION_ALL_LNK, NULL, NULL},
+	{ "fragments", FRAGMENTS_ACTION, 0, ACTION_REG, NULL, frag_action},
+	{ "no-fragments", NO_FRAGMENTS_ACTION, 0, ACTION_REG, NULL,
+						no_frag_action},
+	{ "always-use-fragments", ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL,
+						always_frag_action},
+	{ "dont-always-use-fragments", NO_ALWAYS_FRAGS_ACTION, 0, ACTION_REG,	
+						NULL, no_always_frag_action},
+	{ "compressed", COMPRESSED_ACTION, 0, ACTION_REG, NULL, comp_action},
+	{ "uncompressed", UNCOMPRESSED_ACTION, 0, ACTION_REG, NULL,
+						uncomp_action},
+	{ "uid", UID_ACTION, 1, ACTION_ALL_LNK, parse_uid_args, uid_action},
+	{ "gid", GID_ACTION, 1, ACTION_ALL_LNK, parse_gid_args, gid_action},
+	{ "guid", GUID_ACTION, 2, ACTION_ALL_LNK, parse_guid_args, guid_action},
+	{ "mode", MODE_ACTION, -2, ACTION_ALL, parse_mode_args, mode_action },
+	{ "empty", EMPTY_ACTION, -2, ACTION_DIR, parse_empty_args, NULL},
+	{ "move", MOVE_ACTION, 1, ACTION_ALL_LNK, NULL, NULL},
+	{ "prune", PRUNE_ACTION, 0, ACTION_ALL_LNK, NULL, NULL},
+	{ "chmod", MODE_ACTION, -2, ACTION_ALL, parse_mode_args, mode_action },
+	{ "noop", NOOP_ACTION, 0, ACTION_ALL, NULL, noop_action },
+	{ "", 0, -1, 0, NULL, NULL}
+};
diff --git a/squashfs-tools/squashfs-tools/action.h b/squashfs-tools/squashfs-tools/action.h
new file mode 100644
index 0000000..0a8de7c
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/action.h
@@ -0,0 +1,328 @@
+#ifndef ACTION_H
+#define ACTION_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2011, 2012, 2013, 2014
+ * 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.
+ *
+ * action.h
+ */
+
+/*
+ * Lexical analyser definitions
+ */
+#define TOK_OPEN_BRACKET	0
+#define TOK_CLOSE_BRACKET	1
+#define TOK_AND			2
+#define TOK_OR			3
+#define TOK_NOT			4
+#define TOK_COMMA		5
+#define TOK_AT			6
+#define TOK_WHITE_SPACE		7
+#define TOK_STRING		8
+#define TOK_EOF			9
+
+#define TOK_TO_STR(OP, S) ({ \
+	char *s; \
+	switch(OP) { \
+	case TOK_EOF: \
+		s = "EOF"; \
+		break; \
+	case TOK_STRING: \
+		s = S; \
+		break; \
+	default: \
+		s = token_table[OP].string; \
+		break; \
+	} \
+	s; \
+})
+
+
+struct token_entry {
+	char *string;
+	int token;
+	int size;
+};
+
+/*
+ * Expression parser definitions
+ */
+#define OP_TYPE			0
+#define ATOM_TYPE		1
+#define UNARY_TYPE		2
+
+#define SYNTAX_ERROR(S, ARGS...) { \
+	char *src = strdup(source); \
+	src[cur_ptr - source] = '\0'; \
+	fprintf(stderr, "Failed to parse action \"%s\"\n", source); \
+	fprintf(stderr, "Syntax error: "S, ##ARGS); \
+	fprintf(stderr, "Got here \"%s\"\n", src); \
+	free(src); \
+}
+
+#define TEST_SYNTAX_ERROR(TEST, ARG, S, ARGS...) { \
+	char *src = strdup(source); \
+	src[cur_ptr - source] = '\0'; \
+	fprintf(stderr, "Failed to parse action \"%s\"\n", source); \
+	fprintf(stderr, "Syntax error in \"%s()\", arg %d: "S, TEST->name, \
+			 ARG, ##ARGS); \
+	fprintf(stderr, "Got here \"%s\"\n", src); \
+	free(src); \
+}
+
+struct expr;
+
+struct expr_op {
+	struct expr *lhs;
+	struct expr *rhs;
+	int op;
+};
+
+
+struct atom {
+	struct test_entry *test;
+	int args;
+	char **argv;
+	void *data;
+};
+
+
+struct unary_op {
+	struct expr *expr;
+	int op;
+};
+
+
+struct expr {
+	int type;
+	union {
+		struct atom atom;
+		struct expr_op expr_op;
+		struct unary_op unary_op;
+	};
+};
+
+/*
+ * Test operation definitions
+ */
+#define NUM_EQ		1
+#define NUM_LESS	2
+#define NUM_GREATER	3
+
+struct test_number_arg {
+	long long size;
+	int range;
+};
+
+struct test_range_args {
+	long long start;
+	long long end;
+};
+
+struct action;
+struct action_data;
+
+struct test_entry {
+	char *name;
+	int args;
+	int (*fn)(struct atom *, struct action_data *);
+	int (*parse_args)(struct test_entry *, struct atom *);
+	int exclude_ok;
+	int handle_logging;
+};
+
+
+/*
+ * Type test specific definitions
+ */
+struct type_entry {
+	int value;
+	char type;
+};
+
+
+/*
+ * Action definitions
+ */
+#define FRAGMENT_ACTION 0
+#define EXCLUDE_ACTION 1
+#define FRAGMENTS_ACTION 2
+#define NO_FRAGMENTS_ACTION 3
+#define ALWAYS_FRAGS_ACTION 4
+#define NO_ALWAYS_FRAGS_ACTION 5
+#define COMPRESSED_ACTION 6
+#define UNCOMPRESSED_ACTION 7
+#define UID_ACTION 8
+#define GID_ACTION 9
+#define GUID_ACTION 10
+#define MODE_ACTION 11
+#define EMPTY_ACTION 12
+#define MOVE_ACTION 13
+#define PRUNE_ACTION 14
+#define NOOP_ACTION 15
+
+/*
+ * Define what file types each action operates over
+ */
+#define ACTION_DIR 1
+#define ACTION_REG 2
+#define ACTION_ALL_LNK 3
+#define ACTION_ALL 4
+#define ACTION_LNK 5
+
+
+/*
+ * Action logging requested, specified by the various
+ * -action, -true-action, -false-action and -verbose-action
+ * options
+ */
+#define ACTION_LOG_NONE	0
+#define ACTION_LOG_TRUE 1
+#define ACTION_LOG_FALSE 2
+#define ACTION_LOG_VERBOSE ACTION_LOG_TRUE | ACTION_LOG_FALSE
+
+struct action_entry {
+	char *name;
+	int type;
+	int args;
+	int file_types;
+	int (*parse_args)(struct action_entry *, int, char **, void **);
+	void (*run_action)(struct action *, struct dir_ent *);
+};
+
+
+struct action_data {
+	int depth;
+	char *name;
+	char *pathname;
+	char *subpath;
+	struct stat *buf;
+	struct dir_ent *dir_ent;
+	struct dir_info *root;
+};
+
+
+struct action {
+	int type;
+	struct action_entry *action;
+	int args;
+	char **argv;
+	struct expr *expr;
+	void *data;
+	int verbose;
+};
+
+
+/*
+ * Uid/gid action specific definitions
+ */
+struct uid_info {
+	uid_t uid;
+};
+
+struct gid_info {
+	gid_t gid;
+};
+
+struct guid_info {
+	uid_t uid;
+	gid_t gid;
+};
+
+
+/*
+ * Mode action specific definitions
+ */
+#define ACTION_MODE_SET 0
+#define ACTION_MODE_ADD 1
+#define ACTION_MODE_REM 2
+#define ACTION_MODE_OCT 3
+
+struct mode_data {
+	struct mode_data *next;
+	int operation;
+	int mode;
+	unsigned int mask;
+	char X;
+};
+
+
+/*
+ * Empty action specific definitions
+ */
+#define EMPTY_ALL 0
+#define EMPTY_SOURCE 1
+#define EMPTY_EXCLUDED 2
+
+struct empty_data {
+	int val;
+};
+
+
+/*
+ * Move action specific definitions
+ */
+#define ACTION_MOVE_RENAME 1
+#define ACTION_MOVE_MOVE 2
+
+struct move_ent {
+	int ops;
+	struct dir_ent *dir_ent;
+	char *name;
+	struct dir_info *dest;
+	struct move_ent *next;
+};
+
+
+/*
+ * Perm test function specific definitions
+ */
+#define PERM_ALL 1
+#define PERM_ANY 2
+#define PERM_EXACT 3
+
+struct perm_data {
+	int op;
+	int mode;
+};
+
+
+/*
+ * External function definitions
+ */
+extern int parse_action(char *, int verbose);
+extern void dump_actions();
+extern void *eval_frag_actions(struct dir_info *, struct dir_ent *);
+extern void *get_frag_action(void *);
+extern int eval_exclude_actions(char *, char *, char *, struct stat *, int,
+							struct dir_ent *);
+extern void eval_actions(struct dir_info *, struct dir_ent *);
+extern int eval_empty_actions(struct dir_info *, struct dir_ent *dir_ent);
+extern void eval_move_actions(struct dir_info *, struct dir_ent *);
+extern int eval_prune_actions(struct dir_info *, struct dir_ent *);
+extern void do_move_actions();
+extern int read_bytes(int, void *, int);
+extern int actions();
+extern int move_actions();
+extern int empty_actions();
+extern int read_action_file(char *, int);
+extern int exclude_actions();
+extern int prune_actions();
+#endif
diff --git a/squashfs-tools/squashfs-tools/android.c b/squashfs-tools/squashfs-tools/android.c
new file mode 100644
index 0000000..eeef2aa
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/android.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This file is used to define the properties of the filesystem
+** images generated by build tools (mkbootfs and mkyaffs2image) and
+** by the device side of adb.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+
+#include "android.h"
+#include "private/android_filesystem_config.h"
+#include "private/canned_fs_config.h"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+void alloc_mounted_path(const char *mount_point, const char *subpath, char **mounted_path) {
+    *mounted_path = malloc(strlen(mount_point) + strlen(subpath) + 1);
+    if (*mounted_path == NULL) {
+        perror("Malloc Failure.");
+        exit(EXIT_FAILURE);
+    }
+    strcpy(*mounted_path, mount_point);
+    strcat(*mounted_path, subpath);
+}
+
+void android_fs_config(fs_config_func_t fs_config_func, const char *path, struct stat *stat,
+        const char *target_out_path, uint64_t *capabilities) {
+    // filesystem_config does not preserve file type bits
+    mode_t stat_file_type_mask = stat->st_mode & S_IFMT;
+    if (fs_config_func)
+        fs_config_func(path, S_ISDIR(stat->st_mode), target_out_path,
+                  &stat->st_uid, &stat->st_gid, &stat->st_mode, capabilities);
+    stat->st_mode |= stat_file_type_mask;
+}
+
+
+struct selabel_handle *get_sehnd(const char *context_file) {
+    struct selinux_opt seopts[] = {
+        {
+            .type = SELABEL_OPT_PATH,
+            .value = context_file
+        }
+    };
+    struct selabel_handle *sehnd =
+        selabel_open(SELABEL_CTX_FILE, seopts, ARRAY_SIZE(seopts));
+
+    if (!sehnd) {
+        perror("Error running selabel_open.");
+        exit(EXIT_FAILURE);
+    }
+    return sehnd;
+}
+
+
+char *set_selabel(const char *path, unsigned int mode, struct selabel_handle *sehnd) {
+    char *secontext;
+    if (sehnd != NULL) {
+        int full_name_size = strlen(path) + 2;
+        char* full_name = (char*) malloc(full_name_size);
+        if (full_name == NULL) {
+            perror("Malloc Failure.");
+            exit(EXIT_FAILURE);
+        }
+
+        full_name[0] = '/';
+        strncpy(full_name + 1, path, full_name_size - 1);
+
+        if (selabel_lookup(sehnd, &secontext, full_name, mode)) {
+            secontext = strdup("u:object_r:unlabeled:s0");
+        }
+
+        free(full_name);
+        return secontext;
+    }
+    perror("Selabel handle is NULL.");
+    exit(EXIT_FAILURE);
+}
+
+struct vfs_cap_data set_caps(uint64_t capabilities) {
+    struct vfs_cap_data cap_data;
+    memset(&cap_data, 0, sizeof(cap_data));
+
+    if (capabilities == 0)
+        return cap_data;
+
+    cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE;
+    cap_data.data[0].permitted = (uint32_t) capabilities;
+    cap_data.data[0].inheritable = 0;
+    cap_data.data[1].permitted = (uint32_t) (capabilities >> 32);
+    cap_data.data[1].inheritable = 0;
+
+    return cap_data;
+}
diff --git a/squashfs-tools/squashfs-tools/android.h b/squashfs-tools/squashfs-tools/android.h
new file mode 100644
index 0000000..998f710
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/android.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_H_
+#define _ANDROID_H_
+
+#include <stdint.h>
+typedef void (*fs_config_func_t)(const char *path, int dir, const char *target_out_path,
+                unsigned *uid, unsigned *gid, unsigned *mode, uint64_t *capabilities);
+
+void alloc_mounted_path(const char *mount_point, const char *subpath, char **mounted_path);
+void android_fs_config(fs_config_func_t fs_config_func, const char *path, struct stat *stat, const char *target_out_path, uint64_t *capabilities);
+struct selabel_handle *get_sehnd(const char *context_file);
+char *set_selabel(const char *path, unsigned int mode, struct selabel_handle *sehnd);
+struct vfs_cap_data set_caps(uint64_t capabilities);
+
+#endif
diff --git a/squashfs-tools/squashfs-tools/caches-queues-lists.c b/squashfs-tools/squashfs-tools/caches-queues-lists.c
new file mode 100644
index 0000000..7ad54e8
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/caches-queues-lists.c
@@ -0,0 +1,644 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * 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.
+ *
+ * caches-queues-lists.c
+ */
+
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "error.h"
+#include "caches-queues-lists.h"
+
+extern int add_overflow(int, int);
+extern int multiply_overflow(int, int);
+
+#define TRUE 1
+#define FALSE 0
+
+struct queue *queue_init(int size)
+{
+	struct queue *queue = malloc(sizeof(struct queue));
+
+	if(queue == NULL)
+		MEM_ERROR();
+
+	if(add_overflow(size, 1) ||
+				multiply_overflow(size + 1, sizeof(void *)))
+		BAD_ERROR("Size too large in queue_init\n");
+
+	queue->data = malloc(sizeof(void *) * (size + 1));
+	if(queue->data == NULL)
+		MEM_ERROR();
+
+	queue->size = size + 1;
+	queue->readp = queue->writep = 0;
+	pthread_mutex_init(&queue->mutex, NULL);
+	pthread_cond_init(&queue->empty, NULL);
+	pthread_cond_init(&queue->full, NULL);
+
+	return queue;
+}
+
+
+void queue_put(struct queue *queue, void *data)
+{
+	int nextp;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+	pthread_mutex_lock(&queue->mutex);
+
+	while((nextp = (queue->writep + 1) % queue->size) == queue->readp)
+		pthread_cond_wait(&queue->full, &queue->mutex);
+
+	queue->data[queue->writep] = data;
+	queue->writep = nextp;
+	pthread_cond_signal(&queue->empty);
+	pthread_cleanup_pop(1);
+}
+
+
+void *queue_get(struct queue *queue)
+{
+	void *data;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+	pthread_mutex_lock(&queue->mutex);
+
+	while(queue->readp == queue->writep)
+		pthread_cond_wait(&queue->empty, &queue->mutex);
+
+	data = queue->data[queue->readp];
+	queue->readp = (queue->readp + 1) % queue->size;
+	pthread_cond_signal(&queue->full);
+	pthread_cleanup_pop(1);
+
+	return data;
+}
+
+
+int queue_empty(struct queue *queue)
+{
+	int empty;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+	pthread_mutex_lock(&queue->mutex);
+
+	empty = queue->readp == queue->writep;
+
+	pthread_cleanup_pop(1);
+
+	return empty;
+}
+
+
+void queue_flush(struct queue *queue)
+{
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+	pthread_mutex_lock(&queue->mutex);
+
+	queue->readp = queue->writep;
+
+	pthread_cleanup_pop(1);
+}
+
+
+void dump_queue(struct queue *queue)
+{
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+	pthread_mutex_lock(&queue->mutex);
+
+	printf("\tMax size %d, size %d%s\n", queue->size - 1,  
+		queue->readp <= queue->writep ? queue->writep - queue->readp :
+			queue->size - queue->readp + queue->writep,
+		queue->readp == queue->writep ? " (EMPTY)" :
+			((queue->writep + 1) % queue->size) == queue->readp ?
+			" (FULL)" : "");
+
+	pthread_cleanup_pop(1);
+}
+
+
+/* define seq queue hash tables */
+#define CALCULATE_SEQ_HASH(N) CALCULATE_HASH(N)
+
+/* Called with the seq queue mutex held */
+INSERT_HASH_TABLE(seq, struct seq_queue, CALCULATE_SEQ_HASH, sequence, seq)
+
+/* Called with the cache mutex held */
+REMOVE_HASH_TABLE(seq, struct seq_queue, CALCULATE_SEQ_HASH, sequence, seq);
+
+static unsigned int sequence = 0;
+
+
+struct seq_queue *seq_queue_init()
+{
+	struct seq_queue *queue = malloc(sizeof(struct seq_queue));
+	if(queue == NULL)
+		MEM_ERROR();
+
+	memset(queue, 0, sizeof(struct seq_queue));
+
+	pthread_mutex_init(&queue->mutex, NULL);
+	pthread_cond_init(&queue->wait, NULL);
+
+	return queue;
+}
+
+
+void seq_queue_put(struct seq_queue *queue, struct file_buffer *entry)
+{
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+	pthread_mutex_lock(&queue->mutex);
+
+	insert_seq_hash_table(queue, entry);
+
+	if(entry->fragment)
+		queue->fragment_count ++;
+	else
+		queue->block_count ++;
+
+	if(entry->sequence == sequence)
+		pthread_cond_signal(&queue->wait);
+
+	pthread_cleanup_pop(1);
+}
+
+
+struct file_buffer *seq_queue_get(struct seq_queue *queue)
+{
+	/*
+	 * Look-up buffer matching sequence in the queue, if found return
+	 * it, otherwise wait until it arrives
+	 */
+	int hash = CALCULATE_SEQ_HASH(sequence);
+	struct file_buffer *entry;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+	pthread_mutex_lock(&queue->mutex);
+
+	while(1) {
+		for(entry = queue->hash_table[hash]; entry;
+						entry = entry->seq_next)
+			if(entry->sequence == sequence)
+				break;
+
+		if(entry) {
+			/*
+			 * found the buffer in the queue, decrement the
+			 * appropriate count, and remove from hash list
+			 */
+			if(entry->fragment)
+				queue->fragment_count --;
+			else
+				queue->block_count --;
+
+			remove_seq_hash_table(queue, entry);
+
+			sequence ++;
+
+			break;
+		}
+
+		/* entry not found, wait for it to arrive */	
+		pthread_cond_wait(&queue->wait, &queue->mutex);
+	}
+
+	pthread_cleanup_pop(1);
+
+	return entry;
+}
+
+
+void seq_queue_flush(struct seq_queue *queue)
+{
+	int i;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+	pthread_mutex_lock(&queue->mutex);
+
+	for(i = 0; i < HASH_SIZE; i++)
+		queue->hash_table[i] = NULL;
+
+	queue->fragment_count = queue->block_count = 0;
+
+	pthread_cleanup_pop(1);
+}
+
+
+void dump_seq_queue(struct seq_queue *queue, int fragment_queue)
+{
+	int size;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+	pthread_mutex_lock(&queue->mutex);
+
+	size = fragment_queue ? queue->fragment_count : queue->block_count;
+
+	printf("\tMax size unlimited, size %d%s\n", size,
+						size == 0 ? " (EMPTY)" : "");
+
+	pthread_cleanup_pop(1);
+}
+
+
+/* define cache hash tables */
+#define CALCULATE_CACHE_HASH(N) CALCULATE_HASH(llabs(N))
+
+/* Called with the cache mutex held */
+INSERT_HASH_TABLE(cache, struct cache, CALCULATE_CACHE_HASH, index, hash)
+
+/* Called with the cache mutex held */
+REMOVE_HASH_TABLE(cache, struct cache, CALCULATE_CACHE_HASH, index, hash);
+
+/* define cache free list */
+
+/* Called with the cache mutex held */
+INSERT_LIST(free, struct file_buffer)
+
+/* Called with the cache mutex held */
+REMOVE_LIST(free, struct file_buffer)
+
+
+struct cache *cache_init(int buffer_size, int max_buffers, int noshrink_lookup,
+	int first_freelist)
+{
+	struct cache *cache = malloc(sizeof(struct cache));
+
+	if(cache == NULL)
+		MEM_ERROR();
+
+	cache->max_buffers = max_buffers;
+	cache->buffer_size = buffer_size;
+	cache->count = 0;
+	cache->used = 0;
+	cache->free_list = NULL;
+
+	/*
+	 * The cache will grow up to max_buffers in size in response to
+	 * an increase in readhead/number of buffers in flight.  But
+	 * once the outstanding buffers gets returned, we can either elect
+	 * to shrink the cache, or to put the freed blocks onto a free list.
+	 *
+	 * For the caches where we want to do lookup (fragment/writer),
+	 * a don't shrink policy is best, for the reader cache it
+	 * makes no sense to keep buffers around longer than necessary as
+	 * we don't do any lookup on those blocks.
+	 */
+	cache->noshrink_lookup = noshrink_lookup;
+
+	/*
+	 * The default use freelist before growing cache policy behaves
+	 * poorly with appending - with many duplicates the caches
+	 * do not grow due to the fact that large queues of outstanding
+	 * fragments/writer blocks do not occur, leading to small caches
+	 * and un-uncessary performance loss to frequent cache
+	 * replacement in the small caches.  Therefore with appending
+	 * change the policy to grow the caches before reusing blocks
+	 * from the freelist
+	 */
+	cache->first_freelist = first_freelist;
+
+	memset(cache->hash_table, 0, sizeof(struct file_buffer *) * 65536);
+	pthread_mutex_init(&cache->mutex, NULL);
+	pthread_cond_init(&cache->wait_for_free, NULL);
+	pthread_cond_init(&cache->wait_for_unlock, NULL);
+
+	return cache;
+}
+
+
+struct file_buffer *cache_lookup(struct cache *cache, long long index)
+{
+	/* Lookup block in the cache, if found return with usage count
+ 	 * incremented, if not found return NULL */
+	int hash = CALCULATE_CACHE_HASH(index);
+	struct file_buffer *entry;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+	pthread_mutex_lock(&cache->mutex);
+
+	for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next)
+		if(entry->index == index)
+			break;
+
+	if(entry) {
+		/* found the block in the cache, increment used count and
+ 		 * if necessary remove from free list so it won't disappear
+ 		 */
+		if(entry->used == 0) {
+			remove_free_list(&cache->free_list, entry);
+			cache->used ++;
+		}
+		entry->used ++;
+	}
+
+	pthread_cleanup_pop(1);
+
+	return entry;
+}
+
+
+static struct file_buffer *cache_freelist(struct cache *cache)
+{
+	struct file_buffer *entry = cache->free_list;
+
+	remove_free_list(&cache->free_list, entry);
+
+	/* a block on the free_list is hashed */
+	remove_cache_hash_table(cache, entry);
+
+	cache->used ++;
+	return entry;
+}
+
+
+static struct file_buffer *cache_alloc(struct cache *cache)
+{
+	struct file_buffer *entry = malloc(sizeof(struct file_buffer) +
+							cache->buffer_size);
+	if(entry == NULL)
+			MEM_ERROR();
+
+	entry->cache = cache;
+	entry->free_prev = entry->free_next = NULL;
+	cache->count ++;
+	return entry;
+}
+
+
+static struct file_buffer *_cache_get(struct cache *cache, long long index,
+	int hash)
+{
+	/* Get a free block out of the cache indexed on index. */
+	struct file_buffer *entry = NULL;
+ 
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+	pthread_mutex_lock(&cache->mutex);
+
+	while(1) {
+		if(cache->noshrink_lookup) {	
+			/* first try to get a block from the free list */
+			if(cache->first_freelist && cache->free_list)
+				entry = cache_freelist(cache);
+			else if(cache->count < cache->max_buffers) {
+				entry = cache_alloc(cache);
+				cache->used ++;
+			} else if(!cache->first_freelist && cache->free_list)
+				entry = cache_freelist(cache);
+		} else { /* shrinking non-lookup cache */
+			if(cache->count < cache->max_buffers) {
+				entry = cache_alloc(cache);
+				if(cache->count > cache->max_count)
+					cache->max_count = cache->count;
+			}
+		}
+
+		if(entry)
+			break;
+
+		/* wait for a block */
+		pthread_cond_wait(&cache->wait_for_free, &cache->mutex);
+	}
+
+	/* initialise block and if hash is set insert into the hash table */
+	entry->used = 1;
+	entry->locked = FALSE;
+	entry->wait_on_unlock = FALSE;
+	entry->error = FALSE;
+	if(hash) {
+		entry->index = index;
+		insert_cache_hash_table(cache, entry);
+	}
+
+	pthread_cleanup_pop(1);
+
+	return entry;
+}
+
+
+struct file_buffer *cache_get(struct cache *cache, long long index)
+{
+	return _cache_get(cache, index, 1);
+}
+
+
+struct file_buffer *cache_get_nohash(struct cache *cache)
+{
+	return _cache_get(cache, 0, 0);
+}
+
+
+void cache_hash(struct file_buffer *entry, long long index)
+{
+	struct cache *cache = entry->cache;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+	pthread_mutex_lock(&cache->mutex);
+
+	entry->index = index;
+	insert_cache_hash_table(cache, entry);
+
+	pthread_cleanup_pop(1);
+}
+
+
+void cache_block_put(struct file_buffer *entry)
+{
+	struct cache *cache;
+
+	/*
+	 * Finished with this cache entry, once the usage count reaches zero it
+ 	 * can be reused.
+	 *
+	 * If noshrink_lookup is set, put the block onto the free list.
+ 	 * As blocks remain accessible via the hash table they can be found
+ 	 * getting a new lease of life before they are reused.
+	 *
+	 * if noshrink_lookup is not set then shrink the cache.
+	 */
+
+	if(entry == NULL)
+		return;
+
+	cache = entry->cache;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+	pthread_mutex_lock(&cache->mutex);
+
+	entry->used --;
+	if(entry->used == 0) {
+		if(cache->noshrink_lookup) {
+			insert_free_list(&cache->free_list, entry);
+			cache->used --;
+		} else {
+			free(entry);
+			cache->count --;
+		}
+
+		/* One or more threads may be waiting on this block */
+		pthread_cond_signal(&cache->wait_for_free);
+	}
+
+	pthread_cleanup_pop(1);
+}
+
+
+void dump_cache(struct cache *cache)
+{
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+	pthread_mutex_lock(&cache->mutex);
+
+	if(cache->noshrink_lookup)
+		printf("\tMax buffers %d, Current size %d, Used %d,  %s\n",
+			cache->max_buffers, cache->count, cache->used,
+			cache->free_list ?  "Free buffers" : "No free buffers");
+	else
+		printf("\tMax buffers %d, Current size %d, Maximum historical "
+			"size %d\n", cache->max_buffers, cache->count,
+			cache->max_count);
+
+	pthread_cleanup_pop(1);
+}
+
+
+struct file_buffer *cache_get_nowait(struct cache *cache, long long index)
+{
+	struct file_buffer *entry = NULL;
+	/*
+	 * block doesn't exist, create it, but return it with the
+	 * locked flag set, so nothing tries to use it while it doesn't
+	 * contain data.
+	 *
+	 * If there's no space in the cache then return NULL.
+	 */
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+	pthread_mutex_lock(&cache->mutex);
+
+	/* first try to get a block from the free list */
+	if(cache->first_freelist && cache->free_list)
+		entry = cache_freelist(cache);
+	else if(cache->count < cache->max_buffers) {
+		entry = cache_alloc(cache);
+		cache->used ++;
+	} else if(!cache->first_freelist && cache->free_list)
+		entry = cache_freelist(cache);
+
+	if(entry) {
+		/* initialise block and insert into the hash table */
+		entry->used = 1;
+		entry->locked = TRUE;
+		entry->wait_on_unlock = FALSE;
+		entry->error = FALSE;
+		entry->index = index;
+		insert_cache_hash_table(cache, entry);
+	}
+
+	pthread_cleanup_pop(1);
+
+	return entry;
+}
+
+
+struct file_buffer *cache_lookup_nowait(struct cache *cache, long long index,
+	char *locked)
+{
+	/*
+	 * Lookup block in the cache, if found return it with the locked flag
+	 * indicating whether it is currently locked.  In both cases increment
+	 * the used count.
+	 *
+	 * If it doesn't exist in the cache return NULL;
+	 */
+	int hash = CALCULATE_CACHE_HASH(index);
+	struct file_buffer *entry;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+	pthread_mutex_lock(&cache->mutex);
+
+	/* first check if the entry already exists */
+	for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next)
+		if(entry->index == index)
+			break;
+
+	if(entry) {
+		if(entry->used == 0) {
+			remove_free_list(&cache->free_list, entry);
+			cache->used ++;
+		}
+		entry->used ++;
+		*locked = entry->locked;
+	}
+
+	pthread_cleanup_pop(1);
+
+	return entry;
+}
+
+
+void cache_wait_unlock(struct file_buffer *buffer)
+{
+	struct cache *cache = buffer->cache;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+	pthread_mutex_lock(&cache->mutex);
+
+	while(buffer->locked) {
+		/*
+		 * another thread is filling this in, wait until it
+		 * becomes unlocked.  Used has been incremented to ensure it
+		 * doesn't get reused.  By definition a block can't be
+		 * locked and unused, and so we don't need to worry
+		 * about it being on the freelist now, but, it may
+		 * become unused when unlocked unless used is
+		 * incremented
+		 */
+		buffer->wait_on_unlock = TRUE;
+		pthread_cond_wait(&cache->wait_for_unlock, &cache->mutex);
+	}
+
+	pthread_cleanup_pop(1);
+}
+
+
+void cache_unlock(struct file_buffer *entry)
+{
+	struct cache *cache = entry->cache;
+
+	/*
+	 * Unlock this locked cache entry.  If anything is waiting for this
+	 * to become unlocked, wake it up.
+	 */
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+	pthread_mutex_lock(&cache->mutex);
+
+	entry->locked = FALSE;
+
+	if(entry->wait_on_unlock) {
+		entry->wait_on_unlock = FALSE;
+		pthread_cond_broadcast(&cache->wait_for_unlock);
+	}
+
+	pthread_cleanup_pop(1);
+}
diff --git a/squashfs-tools/squashfs-tools/caches-queues-lists.h b/squashfs-tools/squashfs-tools/caches-queues-lists.h
new file mode 100644
index 0000000..c4dcf28
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/caches-queues-lists.h
@@ -0,0 +1,200 @@
+#ifndef CACHES_QUEUES_LISTS_H
+#define CACHES_QUEUES_LISTS_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * 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.
+ *
+ * caches-queues-lists.h
+ */
+
+#define INSERT_LIST(NAME, TYPE) \
+void insert_##NAME##_list(TYPE **list, TYPE *entry) { \
+	if(*list) { \
+		entry->NAME##_next = *list; \
+		entry->NAME##_prev = (*list)->NAME##_prev; \
+		(*list)->NAME##_prev->NAME##_next = entry; \
+		(*list)->NAME##_prev = entry; \
+	} else { \
+		*list = entry; \
+		entry->NAME##_prev = entry->NAME##_next = entry; \
+	} \
+}
+
+
+#define REMOVE_LIST(NAME, TYPE) \
+void remove_##NAME##_list(TYPE **list, TYPE *entry) { \
+	if(entry->NAME##_prev == entry && entry->NAME##_next == entry) { \
+		/* only this entry in the list */ \
+		*list = NULL; \
+	} else if(entry->NAME##_prev != NULL && entry->NAME##_next != NULL) { \
+		/* more than one entry in the list */ \
+		entry->NAME##_next->NAME##_prev = entry->NAME##_prev; \
+		entry->NAME##_prev->NAME##_next = entry->NAME##_next; \
+		if(*list == entry) \
+			*list = entry->NAME##_next; \
+	} \
+	entry->NAME##_prev = entry->NAME##_next = NULL; \
+}
+
+
+#define INSERT_HASH_TABLE(NAME, TYPE, HASH_FUNCTION, FIELD, LINK) \
+void insert_##NAME##_hash_table(TYPE *container, struct file_buffer *entry) \
+{ \
+	int hash = HASH_FUNCTION(entry->FIELD); \
+\
+	entry->LINK##_next = container->hash_table[hash]; \
+	container->hash_table[hash] = entry; \
+	entry->LINK##_prev = NULL; \
+	if(entry->LINK##_next) \
+		entry->LINK##_next->LINK##_prev = entry; \
+}
+
+
+#define REMOVE_HASH_TABLE(NAME, TYPE, HASH_FUNCTION, FIELD, LINK) \
+void remove_##NAME##_hash_table(TYPE *container, struct file_buffer *entry) \
+{ \
+	if(entry->LINK##_prev) \
+		entry->LINK##_prev->LINK##_next = entry->LINK##_next; \
+	else \
+		container->hash_table[HASH_FUNCTION(entry->FIELD)] = \
+			entry->LINK##_next; \
+	if(entry->LINK##_next) \
+		entry->LINK##_next->LINK##_prev = entry->LINK##_prev; \
+\
+	entry->LINK##_prev = entry->LINK##_next = NULL; \
+}
+
+#define HASH_SIZE 65536
+#define CALCULATE_HASH(n) ((n) & 0xffff)
+
+
+/* struct describing a cache entry passed between threads */
+struct file_buffer {
+	union {
+		long long index;
+		long long sequence;
+	};
+	long long file_size;
+	union {
+		long long block;
+		unsigned short checksum;
+	};
+	struct cache *cache;
+	union {
+		struct file_info *dupl_start;
+		struct file_buffer *hash_next;
+	};
+	union {
+		int duplicate;
+		struct file_buffer *hash_prev;
+	};
+	union {
+		struct {
+			struct file_buffer *free_next;
+			struct file_buffer *free_prev;
+		};
+		struct {
+			struct file_buffer *seq_next;
+			struct file_buffer *seq_prev;
+		};
+	};
+	int size;
+	int c_byte;
+	char used;
+	char fragment;
+	char error;
+	char locked;
+	char wait_on_unlock;
+	char noD;
+	char data[0];
+};
+
+
+/* struct describing queues used to pass data between threads */
+struct queue {
+	int			size;
+	int			readp;
+	int			writep;
+	pthread_mutex_t		mutex;
+	pthread_cond_t		empty;
+	pthread_cond_t		full;
+	void			**data;
+};
+
+
+/*
+ * struct describing seq_queues used to pass data between the read
+ * thread and the deflate and main threads
+ */
+struct seq_queue {
+	int			fragment_count;
+	int			block_count;
+	struct file_buffer	*hash_table[HASH_SIZE];
+	pthread_mutex_t		mutex;
+	pthread_cond_t		wait;
+};
+
+
+/* Cache status struct.  Caches are used to keep
+  track of memory buffers passed between different threads */
+struct cache {
+	int	max_buffers;
+	int	count;
+	int	buffer_size;
+	int	noshrink_lookup;
+	int	first_freelist;
+	union {
+		int	used;
+		int	max_count;
+	};
+	pthread_mutex_t	mutex;
+	pthread_cond_t wait_for_free;
+	pthread_cond_t wait_for_unlock;
+	struct file_buffer *free_list;
+	struct file_buffer *hash_table[HASH_SIZE];
+};
+
+
+extern struct queue *queue_init(int);
+extern void queue_put(struct queue *, void *);
+extern void *queue_get(struct queue *);
+extern int queue_empty(struct queue *);
+extern void queue_flush(struct queue *);
+extern void dump_queue(struct queue *);
+extern struct seq_queue *seq_queue_init();
+extern void seq_queue_put(struct seq_queue *, struct file_buffer *);
+extern void dump_seq_queue(struct seq_queue *, int);
+extern struct file_buffer *seq_queue_get(struct seq_queue *);
+extern void seq_queue_flush(struct seq_queue *);
+extern struct cache *cache_init(int, int, int, int);
+extern struct file_buffer *cache_lookup(struct cache *, long long);
+extern struct file_buffer *cache_get(struct cache *, long long);
+extern struct file_buffer *cache_get_nohash(struct cache *);
+extern void cache_hash(struct file_buffer *, long long);
+extern void cache_block_put(struct file_buffer *);
+extern void dump_cache(struct cache *);
+extern struct file_buffer *cache_get_nowait(struct cache *, long long);
+extern struct file_buffer *cache_lookup_nowait(struct cache *, long long,
+	char *);
+extern void cache_wait_unlock(struct file_buffer *);
+extern void cache_unlock(struct file_buffer *);
+
+extern int first_freelist;
+#endif
diff --git a/squashfs-tools/squashfs-tools/compressor.c b/squashfs-tools/squashfs-tools/compressor.c
new file mode 100644
index 0000000..525e316
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/compressor.c
@@ -0,0 +1,137 @@
+/*
+ *
+ * Copyright (c) 2009, 2010, 2011
+ * 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.
+ *
+ * compressor.c
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "compressor.h"
+#include "squashfs_fs.h"
+
+#ifndef GZIP_SUPPORT
+static struct compressor gzip_comp_ops =  {
+	ZLIB_COMPRESSION, "gzip"
+};
+#else
+extern struct compressor gzip_comp_ops;
+#endif
+
+#ifndef LZMA_SUPPORT
+static struct compressor lzma_comp_ops = {
+	LZMA_COMPRESSION, "lzma"
+};
+#else
+extern struct compressor lzma_comp_ops;
+#endif
+
+#ifndef LZO_SUPPORT
+static struct compressor lzo_comp_ops = {
+	LZO_COMPRESSION, "lzo"
+};
+#else
+extern struct compressor lzo_comp_ops;
+#endif
+
+#ifndef LZ4_SUPPORT
+static struct compressor lz4_comp_ops = {
+	LZ4_COMPRESSION, "lz4"
+};
+#else
+extern struct compressor lz4_comp_ops;
+#endif
+
+#ifndef XZ_SUPPORT
+static struct compressor xz_comp_ops = {
+	XZ_COMPRESSION, "xz"
+};
+#else
+extern struct compressor xz_comp_ops;
+#endif
+
+
+static struct compressor unknown_comp_ops = {
+	0, "unknown"
+};
+
+
+struct compressor *compressor[] = {
+	&gzip_comp_ops,
+	&lzma_comp_ops,
+	&lzo_comp_ops,
+	&lz4_comp_ops,
+	&xz_comp_ops,
+	&unknown_comp_ops
+};
+
+
+struct compressor *lookup_compressor(char *name)
+{
+	int i;
+
+	for(i = 0; compressor[i]->id; i++)
+		if(strcmp(compressor[i]->name, name) == 0)
+			break;
+
+	return compressor[i];
+}
+
+
+struct compressor *lookup_compressor_id(int id)
+{
+	int i;
+
+	for(i = 0; compressor[i]->id; i++)
+		if(id == compressor[i]->id)
+			break;
+
+	return compressor[i];
+}
+
+
+void display_compressors(char *indent, char *def_comp)
+{
+	int i;
+
+	for(i = 0; compressor[i]->id; i++)
+		if(compressor[i]->supported)
+			fprintf(stderr, "%s\t%s%s\n", indent,
+				compressor[i]->name,
+				strcmp(compressor[i]->name, def_comp) == 0 ?
+				" (default)" : "");
+}
+
+
+void display_compressor_usage(char *def_comp)
+{
+	int i;
+
+	for(i = 0; compressor[i]->id; i++)
+		if(compressor[i]->supported) {
+			char *str = strcmp(compressor[i]->name, def_comp) == 0 ?
+				" (default)" : "";
+			if(compressor[i]->usage) {
+				fprintf(stderr, "\t%s%s\n",
+					compressor[i]->name, str);
+				compressor[i]->usage();
+			} else
+				fprintf(stderr, "\t%s (no options)%s\n",
+					compressor[i]->name, str);
+		}
+}
diff --git a/squashfs-tools/squashfs-tools/compressor.h b/squashfs-tools/squashfs-tools/compressor.h
new file mode 100644
index 0000000..4679d91
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/compressor.h
@@ -0,0 +1,124 @@
+#ifndef COMPRESSOR_H
+#define COMPRESSOR_H
+/*
+ *
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014
+ * 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.
+ *
+ * compressor.h
+ */
+
+struct compressor {
+	int id;
+	char *name;
+	int supported;
+	int (*init)(void **, int, int);
+	int (*compress)(void *, void *, void *, int, int, int *);
+	int (*uncompress)(void *, void *, int, int, int *);
+	int (*options)(char **, int);
+	int (*options_post)(int);
+	void *(*dump_options)(int, int *);
+	int (*extract_options)(int, void *, int);
+	int (*check_options)(int, void *, int);
+	void (*display_options)(void *, int);
+	void (*usage)();
+};
+
+extern struct compressor *lookup_compressor(char *);
+extern struct compressor *lookup_compressor_id(int);
+extern void display_compressors(char *, char *);
+extern void display_compressor_usage(char *);
+
+static inline int compressor_init(struct compressor *comp, void **stream,
+	int block_size, int datablock)
+{
+	if(comp->init == NULL)
+		return 0;
+	return comp->init(stream, block_size, datablock);
+}
+
+
+static inline int compressor_compress(struct compressor *comp, void *strm,
+	void *dest, void *src, int size, int block_size, int *error)
+{
+	return comp->compress(strm, dest, src, size, block_size, error);
+}
+
+
+static inline int compressor_uncompress(struct compressor *comp, void *dest,
+	void *src, int size, int block_size, int *error)
+{
+	return comp->uncompress(dest, src, size, block_size, error);
+}
+
+
+/*
+ * For the following functions please see the lzo, lz4 or xz
+ * compressors for commented examples of how they are used.
+ */
+static inline int compressor_options(struct compressor *comp, char *argv[],
+	int argc)
+{
+	if(comp->options == NULL)
+		return -1;
+
+	return comp->options(argv, argc);
+}
+
+
+static inline int compressor_options_post(struct compressor *comp, int block_size)
+{
+	if(comp->options_post == NULL)
+		return 0;
+	return comp->options_post(block_size);
+}
+
+
+static inline void *compressor_dump_options(struct compressor *comp,
+	int block_size, int *size)
+{
+	if(comp->dump_options == NULL)
+		return NULL;
+	return comp->dump_options(block_size, size);
+}
+
+
+static inline int compressor_extract_options(struct compressor *comp,
+	int block_size, void *buffer, int size)
+{
+	if(comp->extract_options == NULL)
+		return size ? -1 : 0;
+	return comp->extract_options(block_size, buffer, size);
+}
+
+
+static inline int compressor_check_options(struct compressor *comp,
+	int block_size, void *buffer, int size)
+{
+	if(comp->check_options == NULL)
+		return 0;
+	return comp->check_options(block_size, buffer, size);
+}
+
+
+static inline void compressor_display_options(struct compressor *comp,
+	void *buffer, int size)
+{
+	if(comp->display_options != NULL)
+		comp->display_options(buffer, size);
+}
+#endif
diff --git a/squashfs-tools/squashfs-tools/error.h b/squashfs-tools/squashfs-tools/error.h
new file mode 100644
index 0000000..358ad68
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/error.h
@@ -0,0 +1,90 @@
+#ifndef ERROR_H
+#define ERROR_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2012, 2013, 2014
+ * 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.
+ *
+ * error.h
+ */
+
+extern int exit_on_error;
+
+extern void prep_exit();
+extern void progressbar_error(char *fmt, ...);
+extern void progressbar_info(char *fmt, ...);
+
+#ifdef SQUASHFS_TRACE
+#define TRACE(s, args...) \
+		do { \
+			progressbar_info("squashfs: "s, ## args);\
+		} while(0)
+#else
+#define TRACE(s, args...)
+#endif
+
+#define INFO(s, args...) \
+		do {\
+			 if(!silent)\
+				progressbar_info(s, ## args);\
+		} while(0)
+
+#define ERROR(s, args...) \
+		do {\
+			progressbar_error(s, ## args); \
+		} while(0)
+
+#define ERROR_START(s, args...) \
+		do { \
+			disable_progress_bar(); \
+			fprintf(stderr, s, ## args); \
+		} while(0)
+
+#define ERROR_EXIT(s, args...) \
+		do {\
+			if (exit_on_error) { \
+				fprintf(stderr, "\n"); \
+				EXIT_MKSQUASHFS(); \
+			} else { \
+				fprintf(stderr, s, ## args); \
+				enable_progress_bar(); \
+			} \
+		} while(0)
+
+#define EXIT_MKSQUASHFS() \
+		do {\
+			prep_exit();\
+			exit(1);\
+		} while(0)
+
+#define BAD_ERROR(s, args...) \
+		do {\
+			progressbar_error("FATAL ERROR:" s, ##args); \
+			EXIT_MKSQUASHFS();\
+		} while(0)
+
+#define EXIT_UNSQUASH(s, args...) BAD_ERROR(s, ##args)
+
+#define MEM_ERROR() \
+	do {\
+		progressbar_error("FATAL ERROR: Out of memory (%s)\n", \
+								__func__); \
+		EXIT_MKSQUASHFS();\
+	} while(0)
+#endif
diff --git a/squashfs-tools/squashfs-tools/gzip_wrapper.c b/squashfs-tools/squashfs-tools/gzip_wrapper.c
new file mode 100644
index 0000000..076a587
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/gzip_wrapper.c
@@ -0,0 +1,500 @@
+/*
+ * Copyright (c) 2009, 2010, 2013, 2014
+ * 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.
+ *
+ * gzip_wrapper.c
+ *
+ * Support for ZLIB compression http://www.zlib.net
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <zlib.h>
+
+#include "squashfs_fs.h"
+#include "gzip_wrapper.h"
+#include "compressor.h"
+
+static struct strategy strategy[] = {
+	{ "default", Z_DEFAULT_STRATEGY, 0 },
+	{ "filtered", Z_FILTERED, 0 },
+	{ "huffman_only", Z_HUFFMAN_ONLY, 0 },
+	{ "run_length_encoded", Z_RLE, 0 },
+	{ "fixed", Z_FIXED, 0 },
+	{ NULL, 0, 0 }
+};
+
+static int strategy_count = 0;
+
+/* default compression level */
+static int compression_level = GZIP_DEFAULT_COMPRESSION_LEVEL;
+
+/* default window size */
+static int window_size = GZIP_DEFAULT_WINDOW_SIZE;
+
+/*
+ * This function is called by the options parsing code in mksquashfs.c
+ * to parse any -X compressor option.
+ *
+ * This function returns:
+ *	>=0 (number of additional args parsed) on success
+ *	-1 if the option was unrecognised, or
+ *	-2 if the option was recognised, but otherwise bad in
+ *	   some way (e.g. invalid parameter)
+ *
+ * Note: this function sets internal compressor state, but does not
+ * pass back the results of the parsing other than success/failure.
+ * The gzip_dump_options() function is called later to get the options in
+ * a format suitable for writing to the filesystem.
+ */
+static int gzip_options(char *argv[], int argc)
+{
+	if(strcmp(argv[0], "-Xcompression-level") == 0) {
+		if(argc < 2) {
+			fprintf(stderr, "gzip: -Xcompression-level missing "
+				"compression level\n");
+			fprintf(stderr, "gzip: -Xcompression-level it "
+				"should be 1 >= n <= 9\n");
+			goto failed;
+		}
+
+		compression_level = atoi(argv[1]);
+		if(compression_level < 1 || compression_level > 9) {
+			fprintf(stderr, "gzip: -Xcompression-level invalid, it "
+				"should be 1 >= n <= 9\n");
+			goto failed;
+		}
+
+		return 1;
+	} else if(strcmp(argv[0], "-Xwindow-size") == 0) {
+		if(argc < 2) {
+			fprintf(stderr, "gzip: -Xwindow-size missing window "
+				"	size\n");
+			fprintf(stderr, "gzip: -Xwindow-size <window-size>\n");
+			goto failed;
+		}
+
+		window_size = atoi(argv[1]);
+		if(window_size < 8 || window_size > 15) {
+			fprintf(stderr, "gzip: -Xwindow-size invalid, it "
+				"should be 8 >= n <= 15\n");
+			goto failed;
+		}
+
+		return 1;
+	} else if(strcmp(argv[0], "-Xstrategy") == 0) {
+		char *name;
+		int i;
+
+		if(argc < 2) {
+			fprintf(stderr, "gzip: -Xstrategy missing "
+							"strategies\n");
+			goto failed;
+		}
+
+		name = argv[1];
+		while(name[0] != '\0') {
+			for(i = 0; strategy[i].name; i++) {
+				int n = strlen(strategy[i].name);
+				if((strncmp(name, strategy[i].name, n) == 0) &&
+						(name[n] == '\0' ||
+						 name[n] == ',')) {
+					if(strategy[i].selected == 0) {
+				 		strategy[i].selected = 1;
+						strategy_count++;
+					}
+					name += name[n] == ',' ? n + 1 : n;
+					break;
+				}
+			}
+			if(strategy[i].name == NULL) {
+				fprintf(stderr, "gzip: -Xstrategy unrecognised "
+					"strategy\n");
+				goto failed;
+			}
+		}
+	
+		return 1;
+	}
+
+	return -1;
+
+failed:
+	return -2;
+}
+
+
+/*
+ * This function is called after all options have been parsed.
+ * It is used to do post-processing on the compressor options using
+ * values that were not expected to be known at option parse time.
+ *
+ * This function returns 0 on successful post processing, or
+ *			-1 on error
+ */
+static int gzip_options_post(int block_size)
+{
+	if(strategy_count == 1 && strategy[0].selected) {
+		strategy_count = 0;
+		strategy[0].selected = 0;
+	}
+
+	return 0;
+}
+
+
+/*
+ * This function is called by mksquashfs to dump the parsed
+ * compressor options in a format suitable for writing to the
+ * compressor options field in the filesystem (stored immediately
+ * after the superblock).
+ *
+ * This function returns a pointer to the compression options structure
+ * to be stored (and the size), or NULL if there are no compression
+ * options
+ *
+ */
+static void *gzip_dump_options(int block_size, int *size)
+{
+	static struct gzip_comp_opts comp_opts;
+	int i, strategies = 0;
+
+	/*
+	 * If default compression options of:
+	 * compression-level: 8 and
+	 * window-size: 15 and
+	 * strategy_count == 0 then
+	 * don't store a compression options structure (this is compatible
+	 * with the legacy implementation of GZIP for Squashfs)
+	 */
+	if(compression_level == GZIP_DEFAULT_COMPRESSION_LEVEL &&
+				window_size == GZIP_DEFAULT_WINDOW_SIZE &&
+				strategy_count == 0)
+		return NULL;
+
+	for(i = 0; strategy[i].name; i++)
+		strategies |= strategy[i].selected << i;
+
+	comp_opts.compression_level = compression_level;
+	comp_opts.window_size = window_size;
+	comp_opts.strategy = strategies;
+
+	SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
+
+	*size = sizeof(comp_opts);
+	return &comp_opts;
+}
+
+
+/*
+ * This function is a helper specifically for the append mode of
+ * mksquashfs.  Its purpose is to set the internal compressor state
+ * to the stored compressor options in the passed compressor options
+ * structure.
+ *
+ * In effect this function sets up the compressor options
+ * to the same state they were when the filesystem was originally
+ * generated, this is to ensure on appending, the compressor uses
+ * the same compression options that were used to generate the
+ * original filesystem.
+ *
+ * Note, even if there are no compressor options, this function is still
+ * called with an empty compressor structure (size == 0), to explicitly
+ * set the default options, this is to ensure any user supplied
+ * -X options on the appending mksquashfs command line are over-ridden
+ *
+ * This function returns 0 on sucessful extraction of options, and
+ *			-1 on error
+ */
+static int gzip_extract_options(int block_size, void *buffer, int size)
+{
+	struct gzip_comp_opts *comp_opts = buffer;
+	int i;
+
+	if(size == 0) {
+		/* Set default values */
+		compression_level = GZIP_DEFAULT_COMPRESSION_LEVEL;
+		window_size = GZIP_DEFAULT_WINDOW_SIZE;
+		strategy_count = 0;
+		return 0;
+	}
+
+	/* we expect a comp_opts structure of sufficient size to be present */
+	if(size < sizeof(*comp_opts))
+		goto failed;
+
+	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+	/* Check comp_opts structure for correctness */
+	if(comp_opts->compression_level < 1 ||
+			comp_opts->compression_level > 9) {
+		fprintf(stderr, "gzip: bad compression level in "
+			"compression options structure\n");
+		goto failed;
+	}
+	compression_level = comp_opts->compression_level;
+
+	if(comp_opts->window_size < 8 ||
+			comp_opts->window_size > 15) {
+		fprintf(stderr, "gzip: bad window size in "
+			"compression options structure\n");
+		goto failed;
+	}
+	window_size = comp_opts->window_size;
+
+	strategy_count = 0;
+	for(i = 0; strategy[i].name; i++) {
+		if((comp_opts->strategy >> i) & 1) {
+			strategy[i].selected = 1;
+			strategy_count ++;
+		} else
+			strategy[i].selected = 0;
+	}
+	
+	return 0;
+
+failed:
+	fprintf(stderr, "gzip: error reading stored compressor options from "
+		"filesystem!\n");
+
+	return -1;
+}
+
+
+void gzip_display_options(void *buffer, int size)
+{
+	struct gzip_comp_opts *comp_opts = buffer;
+	int i, printed;
+
+	/* we expect a comp_opts structure of sufficient size to be present */
+	if(size < sizeof(*comp_opts))
+		goto failed;
+
+	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+	/* Check comp_opts structure for correctness */
+	if(comp_opts->compression_level < 1 ||
+			comp_opts->compression_level > 9) {
+		fprintf(stderr, "gzip: bad compression level in "
+			"compression options structure\n");
+		goto failed;
+	}
+	printf("\tcompression-level %d\n", comp_opts->compression_level);
+
+	if(comp_opts->window_size < 8 ||
+			comp_opts->window_size > 15) {
+		fprintf(stderr, "gzip: bad window size in "
+			"compression options structure\n");
+		goto failed;
+	}
+	printf("\twindow-size %d\n", comp_opts->window_size);
+
+	for(i = 0, printed = 0; strategy[i].name; i++) {
+		if((comp_opts->strategy >> i) & 1) {
+			if(printed)
+				printf(", ");
+			else
+				printf("\tStrategies selected: ");
+			printf("%s", strategy[i].name);
+			printed = 1;
+		}
+	}
+
+	if(!printed)
+		printf("\tStrategies selected: default\n");
+	else
+		printf("\n");
+
+	return;
+
+failed:
+	fprintf(stderr, "gzip: error reading stored compressor options from "
+		"filesystem!\n");
+}	
+
+
+/*
+ * This function is called by mksquashfs to initialise the
+ * compressor, before compress() is called.
+ *
+ * This function returns 0 on success, and
+ *			-1 on error
+ */
+static int gzip_init(void **strm, int block_size, int datablock)
+{
+	int i, j, res;
+	struct gzip_stream *stream;
+
+	if(!datablock || !strategy_count) {
+		stream = malloc(sizeof(*stream) + sizeof(struct gzip_strategy));
+		if(stream == NULL)
+			goto failed;
+
+		stream->strategies = 1;
+		stream->strategy[0].strategy = Z_DEFAULT_STRATEGY;
+	} else {
+		stream = malloc(sizeof(*stream) +
+			sizeof(struct gzip_strategy) * strategy_count);
+		if(stream == NULL)
+			goto failed;
+
+		memset(stream->strategy, 0, sizeof(struct gzip_strategy) *
+			strategy_count);
+
+		stream->strategies = strategy_count;
+
+		for(i = 0, j = 0; strategy[i].name; i++) {
+			if(!strategy[i].selected)
+				continue;
+
+			stream->strategy[j].strategy = strategy[i].strategy;
+			if(j) {
+				stream->strategy[j].buffer = malloc(block_size);
+				if(stream->strategy[j].buffer == NULL)
+					goto failed2;
+			}
+			j++;
+		}
+	}
+		
+	stream->stream.zalloc = Z_NULL;
+	stream->stream.zfree = Z_NULL;
+	stream->stream.opaque = 0;
+
+	res = deflateInit2(&stream->stream, compression_level, Z_DEFLATED,
+		window_size, 8, stream->strategy[0].strategy);
+	if(res != Z_OK)
+		goto failed2;
+
+	*strm = stream;
+	return 0;
+
+failed2:
+	for(i = 1; i < stream->strategies; i++)
+		free(stream->strategy[i].buffer);
+	free(stream);
+failed:
+	return -1;
+}
+
+
+static int gzip_compress(void *strm, void *d, void *s, int size, int block_size,
+		int *error)
+{
+	int i, res;
+	struct gzip_stream *stream = strm;
+	struct gzip_strategy *selected = NULL;
+
+	stream->strategy[0].buffer = d;
+
+	for(i = 0; i < stream->strategies; i++) {
+		struct gzip_strategy *strategy = &stream->strategy[i];
+
+		res = deflateReset(&stream->stream);
+		if(res != Z_OK)
+			goto failed;
+
+		stream->stream.next_in = s;
+		stream->stream.avail_in = size;
+		stream->stream.next_out = strategy->buffer;
+		stream->stream.avail_out = block_size;
+
+		if(stream->strategies > 1) {
+			res = deflateParams(&stream->stream,
+				compression_level, strategy->strategy);
+			if(res != Z_OK)
+				goto failed;
+		}
+
+		res = deflate(&stream->stream, Z_FINISH);
+		strategy->length = stream->stream.total_out;
+		if(res == Z_STREAM_END) {
+			if(!selected || selected->length > strategy->length)
+				selected = strategy;
+		} else if(res != Z_OK)
+			goto failed;
+	}
+
+	if(!selected)
+		/*
+		 * Output buffer overflow.  Return out of buffer space
+		 */
+		return 0;
+
+	if(selected->buffer != d)
+		memcpy(d, selected->buffer, selected->length);
+
+	return (int) selected->length;
+
+failed:
+	/*
+	 * All other errors return failure, with the compressor
+	 * specific error code in *error
+	 */
+	*error = res;
+	return -1;
+}
+
+
+static int gzip_uncompress(void *d, void *s, int size, int outsize, int *error)
+{
+	int res;
+	unsigned long bytes = outsize;
+
+	res = uncompress(d, &bytes, s, size);
+
+	if(res == Z_OK)
+		return (int) bytes;
+	else {
+		*error = res;
+		return -1;
+	}
+}
+
+
+void gzip_usage()
+{
+	fprintf(stderr, "\t  -Xcompression-level <compression-level>\n");
+	fprintf(stderr, "\t\t<compression-level> should be 1 .. 9 (default "
+		"%d)\n", GZIP_DEFAULT_COMPRESSION_LEVEL);
+	fprintf(stderr, "\t  -Xwindow-size <window-size>\n");
+	fprintf(stderr, "\t\t<window-size> should be 8 .. 15 (default "
+		"%d)\n", GZIP_DEFAULT_WINDOW_SIZE);
+	fprintf(stderr, "\t  -Xstrategy strategy1,strategy2,...,strategyN\n");
+	fprintf(stderr, "\t\tCompress using strategy1,strategy2,...,strategyN"
+		" in turn\n");
+	fprintf(stderr, "\t\tand choose the best compression.\n");
+	fprintf(stderr, "\t\tAvailable strategies: default, filtered, "
+		"huffman_only,\n\t\trun_length_encoded and fixed\n");
+}
+
+
+struct compressor gzip_comp_ops = {
+	.init = gzip_init,
+	.compress = gzip_compress,
+	.uncompress = gzip_uncompress,
+	.options = gzip_options,
+	.options_post = gzip_options_post,
+	.dump_options = gzip_dump_options,
+	.extract_options = gzip_extract_options,
+	.display_options = gzip_display_options,
+	.usage = gzip_usage,
+	.id = ZLIB_COMPRESSION,
+	.name = "gzip",
+	.supported = 1
+};
diff --git a/squashfs-tools/squashfs-tools/gzip_wrapper.h b/squashfs-tools/squashfs-tools/gzip_wrapper.h
new file mode 100644
index 0000000..463e9f4
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/gzip_wrapper.h
@@ -0,0 +1,75 @@
+#ifndef GZIP_WRAPPER_H
+#define GZIP_WRAPPER_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2014
+ * 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.
+ *
+ * gzip_wrapper.h
+ *
+ */
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+extern unsigned int inswap_le16(unsigned short);
+extern unsigned int inswap_le32(unsigned int);
+
+#define SQUASHFS_INSWAP_COMP_OPTS(s) { \
+	(s)->compression_level = inswap_le32((s)->compression_level); \
+	(s)->window_size = inswap_le16((s)->window_size); \
+	(s)->strategy = inswap_le16((s)->strategy); \
+}
+#else
+#define SQUASHFS_INSWAP_COMP_OPTS(s)
+#endif
+
+/* Default compression */
+#define GZIP_DEFAULT_COMPRESSION_LEVEL 9
+#define GZIP_DEFAULT_WINDOW_SIZE 15
+
+struct gzip_comp_opts {
+	int compression_level;
+	short window_size;
+	short strategy;
+};
+
+struct strategy {
+	char *name;
+	int strategy;
+	int selected;
+};
+
+struct gzip_strategy {
+	int strategy;
+	int length;
+	void *buffer;
+};
+
+struct gzip_stream {
+	z_stream stream;
+	int strategies;
+	struct gzip_strategy strategy[0];
+};
+#endif
diff --git a/squashfs-tools/squashfs-tools/info.c b/squashfs-tools/squashfs-tools/info.c
new file mode 100644
index 0000000..60a3f5a
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/info.c
@@ -0,0 +1,176 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * 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.
+ *
+ * info.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "error.h"
+#include "progressbar.h"
+#include "caches-queues-lists.h"
+
+static int silent = 0;
+static struct dir_ent *ent = NULL;
+
+pthread_t info_thread;
+
+
+void disable_info()
+{
+	ent = NULL;
+}
+
+
+void update_info(struct dir_ent *dir_ent)
+{
+	ent = dir_ent;
+}
+
+
+void print_filename()
+{
+	struct dir_ent *dir_ent = ent;
+
+	if(dir_ent == NULL)
+		return;
+
+	if(dir_ent->our_dir->subpath[0] != '\0')
+		INFO("%s/%s\n", dir_ent->our_dir->subpath, dir_ent->name);
+	else
+		INFO("/%s\n", dir_ent->name);
+}
+
+
+void dump_state()
+{
+	disable_progress_bar();
+
+	printf("Queue and Cache status dump\n");
+	printf("===========================\n");
+
+	printf("file buffer queue (reader thread -> deflate thread(s))\n");
+	dump_queue(to_deflate);
+
+	printf("uncompressed fragment queue (reader thread -> fragment"
+						" thread(s))\n");
+	dump_queue(to_process_frag);
+
+	printf("processed fragment queue (fragment thread(s) -> main"
+						" thread)\n");
+	dump_seq_queue(to_main, 1);
+
+	printf("compressed block queue (deflate thread(s) -> main thread)\n");
+	dump_seq_queue(to_main, 0);
+
+	printf("uncompressed packed fragment queue (main thread -> fragment"
+						" deflate thread(s))\n");
+	dump_queue(to_frag);
+
+
+	printf("locked frag queue (compressed frags waiting while multi-block"
+						" file is written)\n");
+	dump_queue(locked_fragment);
+
+	printf("compressed block queue (main & fragment deflate threads(s) ->"
+						" writer thread)\n");
+	dump_queue(to_writer);
+
+	printf("read cache (uncompressed blocks read by reader thread)\n");
+	dump_cache(reader_buffer);
+
+	printf("block write cache (compressed blocks waiting for the writer"
+						" thread)\n");
+	dump_cache(bwriter_buffer);
+	printf("fragment write cache (compressed fragments waiting for the"
+						" writer thread)\n");
+	dump_cache(fwriter_buffer);
+
+	printf("fragment cache (frags waiting to be compressed by fragment"
+						" deflate thread(s))\n");
+	dump_cache(fragment_buffer);
+
+	printf("fragment reserve cache (avoids pipeline stall if frag cache"
+						" full in dup check)\n");
+	dump_cache(reserve_cache);
+
+	enable_progress_bar();
+}
+
+
+void *info_thrd(void *arg)
+{
+	sigset_t sigmask;
+	int sig, err, waiting = 0;
+
+	sigemptyset(&sigmask);
+	sigaddset(&sigmask, SIGQUIT);
+	sigaddset(&sigmask, SIGHUP);
+	sigaddset(&sigmask, SIGALRM);
+
+	while(1) {
+		err = sigwait(&sigmask, &sig);
+
+		if(err == -1) {
+			switch(errno) {
+			case EINTR:
+				continue;
+			default:
+				BAD_ERROR("sigwait failed "
+					"because %s\n", strerror(errno));
+			}
+		}
+
+		if(sig == SIGQUIT && !waiting) {
+			print_filename();
+
+			/* set one second interval period, if ^\ received
+			   within then, dump queue and cache status */
+			waiting = 1;
+			alarm(1);
+		} else if (sig == SIGQUIT) {
+			dump_state();
+		} else if (sig == SIGALRM) {
+			waiting = 0;
+		}
+	}
+}
+
+
+void init_info()
+{
+	pthread_create(&info_thread, NULL, info_thrd, NULL);
+}
diff --git a/squashfs-tools/squashfs-tools/info.h b/squashfs-tools/squashfs-tools/info.h
new file mode 100644
index 0000000..bcf03a2
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/info.h
@@ -0,0 +1,30 @@
+#ifndef INFO_H
+#define INFO_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * 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.
+ *
+ * info.h
+ */
+
+extern void disable_info();
+extern void update_info(struct dir_ent *);
+extern void init_info();
+#endif
diff --git a/squashfs-tools/squashfs-tools/lz4_wrapper.c b/squashfs-tools/squashfs-tools/lz4_wrapper.c
new file mode 100644
index 0000000..b87cfe0
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/lz4_wrapper.c
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2013
+ * 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.
+ *
+ * lz4_wrapper.c
+ *
+ * Support for LZ4 compression http://fastcompression.blogspot.com/p/lz4.html
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <lz4.h>
+#include <lz4hc.h>
+
+#include "squashfs_fs.h"
+#include "lz4_wrapper.h"
+#include "compressor.h"
+
+static int hc = 0;
+
+/*
+ * This function is called by the options parsing code in mksquashfs.c
+ * to parse any -X compressor option.
+ *
+ * This function returns:
+ *	>=0 (number of additional args parsed) on success
+ *	-1 if the option was unrecognised, or
+ *	-2 if the option was recognised, but otherwise bad in
+ *	   some way (e.g. invalid parameter)
+ *
+ * Note: this function sets internal compressor state, but does not
+ * pass back the results of the parsing other than success/failure.
+ * The lz4_dump_options() function is called later to get the options in
+ * a format suitable for writing to the filesystem.
+ */
+static int lz4_options(char *argv[], int argc)
+{
+	if(strcmp(argv[0], "-Xhc") == 0) {
+		hc = 1;
+		return 0;
+	}
+
+	return -1;
+}
+
+
+/*
+ * This function is called by mksquashfs to dump the parsed
+ * compressor options in a format suitable for writing to the
+ * compressor options field in the filesystem (stored immediately
+ * after the superblock).
+ *
+ * This function returns a pointer to the compression options structure
+ * to be stored (and the size), or NULL if there are no compression
+ * options
+ *
+ * Currently LZ4 always returns a comp_opts structure, with
+ * the version indicating LZ4_LEGACY stream fomat.  This is to
+ * easily accomodate changes in the kernel code to different
+ * stream formats 
+ */
+static void *lz4_dump_options(int block_size, int *size)
+{
+	static struct lz4_comp_opts comp_opts;
+
+	comp_opts.version = LZ4_LEGACY;
+	comp_opts.flags = hc ? LZ4_HC : 0;
+	SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
+
+	*size = sizeof(comp_opts);
+	return &comp_opts;
+}
+
+
+/*
+ * This function is a helper specifically for the append mode of
+ * mksquashfs.  Its purpose is to set the internal compressor state
+ * to the stored compressor options in the passed compressor options
+ * structure.
+ *
+ * In effect this function sets up the compressor options
+ * to the same state they were when the filesystem was originally
+ * generated, this is to ensure on appending, the compressor uses
+ * the same compression options that were used to generate the
+ * original filesystem.
+ *
+ * Note, even if there are no compressor options, this function is still
+ * called with an empty compressor structure (size == 0), to explicitly
+ * set the default options, this is to ensure any user supplied
+ * -X options on the appending mksquashfs command line are over-ridden
+ *
+ * This function returns 0 on sucessful extraction of options, and
+ *			-1 on error
+ */
+static int lz4_extract_options(int block_size, void *buffer, int size)
+{
+	struct lz4_comp_opts *comp_opts = buffer;
+
+	/* we expect a comp_opts structure to be present */
+	if(size < sizeof(*comp_opts))
+		goto failed;
+
+	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+	/* we expect the stream format to be LZ4_LEGACY */
+	if(comp_opts->version != LZ4_LEGACY) {
+		fprintf(stderr, "lz4: unknown LZ4 version\n");
+		goto failed;
+	}
+
+	/*
+	 * Check compression flags, currently only LZ4_HC ("high compression")
+	 * can be set.
+	 */
+	if(comp_opts->flags == LZ4_HC)
+		hc = 1;
+	else if(comp_opts->flags != 0) {
+		fprintf(stderr, "lz4: unknown LZ4 flags\n");
+		goto failed;
+	}
+
+	return 0;
+
+failed:
+	fprintf(stderr, "lz4: error reading stored compressor options from "
+		"filesystem!\n");
+
+	return -1;
+}
+
+
+/*
+ * This function is a helper specifically for unsquashfs.
+ * Its purpose is to check that the compression options are
+ * understood by this version of LZ4.
+ *
+ * This is important for LZ4 because the format understood by the
+ * Linux kernel may change from the already obsolete legacy format
+ * currently supported.
+ *
+ * If this does happen, then this version of LZ4 will not be able to decode
+ * the newer format.  So we need to check for this.
+ *
+ * This function returns 0 on sucessful checking of options, and
+ *			-1 on error
+ */
+static int lz4_check_options(int block_size, void *buffer, int size)
+{
+	struct lz4_comp_opts *comp_opts = buffer;
+
+	/* we expect a comp_opts structure to be present */
+	if(size < sizeof(*comp_opts))
+		goto failed;
+
+	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+	/* we expect the stream format to be LZ4_LEGACY */
+	if(comp_opts->version != LZ4_LEGACY) {
+		fprintf(stderr, "lz4: unknown LZ4 version\n");
+		goto failed;
+	}
+
+	return 0;
+
+failed:
+	fprintf(stderr, "lz4: error reading stored compressor options from "
+		"filesystem!\n");
+	return -1;
+}
+
+
+void lz4_display_options(void *buffer, int size)
+{
+	struct lz4_comp_opts *comp_opts = buffer;
+
+	/* check passed comp opts struct is of the correct length */
+	if(size < sizeof(*comp_opts))
+		goto failed;
+
+	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+	/* we expect the stream format to be LZ4_LEGACY */
+	if(comp_opts->version != LZ4_LEGACY) {
+		fprintf(stderr, "lz4: unknown LZ4 version\n");
+		goto failed;
+	}
+
+	/*
+	 * Check compression flags, currently only LZ4_HC ("high compression")
+	 * can be set.
+	 */
+	if(comp_opts->flags & ~LZ4_FLAGS_MASK) {
+		fprintf(stderr, "lz4: unknown LZ4 flags\n");
+		goto failed;
+	}
+
+	if(comp_opts->flags & LZ4_HC)
+		printf("\tHigh Compression option specified (-Xhc)\n");
+
+	return;
+
+failed:
+	fprintf(stderr, "lz4: error reading stored compressor options from "
+		"filesystem!\n");
+}	
+
+
+static int lz4_compress(void *strm, void *dest, void *src,  int size,
+	int block_size, int *error)
+{
+	int res;
+
+	if(hc)
+		res = LZ4_compressHC_limitedOutput(src, dest, size, block_size);
+	else
+		res = LZ4_compress_limitedOutput(src, dest, size, block_size);
+
+	if(res == 0) {
+		/*
+	 	 * Output buffer overflow.  Return out of buffer space
+	 	 */
+		return 0;
+	} else if(res < 0) {
+		/*
+	 	 * All other errors return failure, with the compressor
+	 	 * specific error code in *error
+	 	 */
+		*error = res;
+		return -1;
+	}
+
+	return res;
+}
+
+
+static int lz4_uncompress(void *dest, void *src, int size, int outsize,
+	int *error)
+{
+	int res = LZ4_decompress_safe(src, dest, size, outsize);
+	if(res < 0) {
+		*error = res;
+		return -1;
+	}
+
+	return res;
+}
+
+
+void lz4_usage()
+{
+	fprintf(stderr, "\t  -Xhc\n");
+	fprintf(stderr, "\t\tCompress using LZ4 High Compression\n");
+}
+
+
+struct compressor lz4_comp_ops = {
+	.compress = lz4_compress,
+	.uncompress = lz4_uncompress,
+	.options = lz4_options,
+	.dump_options = lz4_dump_options,
+	.extract_options = lz4_extract_options,
+	.check_options = lz4_check_options,
+	.display_options = lz4_display_options,
+	.usage = lz4_usage,
+	.id = LZ4_COMPRESSION,
+	.name = "lz4",
+	.supported = 1
+};
diff --git a/squashfs-tools/squashfs-tools/lz4_wrapper.h b/squashfs-tools/squashfs-tools/lz4_wrapper.h
new file mode 100644
index 0000000..d6638a5
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/lz4_wrapper.h
@@ -0,0 +1,61 @@
+#ifndef LZ4_WRAPPER_H
+#define LZ4_WRAPPER_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2013
+ * 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.
+ *
+ * lz4_wrapper.h
+ *
+ */
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+extern unsigned int inswap_le32(unsigned int);
+
+#define SQUASHFS_INSWAP_COMP_OPTS(s) { \
+	(s)->version = inswap_le32((s)->version); \
+	(s)->flags = inswap_le32((s)->flags); \
+}
+#else
+#define SQUASHFS_INSWAP_COMP_OPTS(s)
+#endif
+
+/*
+ * Define the various stream formats recognised.
+ * Currently omly legacy stream format is supported by the
+ * kernel
+ */
+#define LZ4_LEGACY	1
+#define LZ4_FLAGS_MASK	1
+
+/* Define the compression flags recognised. */
+#define LZ4_HC		1
+
+struct lz4_comp_opts {
+	int version;
+	int flags;
+};
+#endif
diff --git a/squashfs-tools/squashfs-tools/lzma_wrapper.c b/squashfs-tools/squashfs-tools/lzma_wrapper.c
new file mode 100644
index 0000000..8d64e3d
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/lzma_wrapper.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2009, 2010, 2013
+ * 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.
+ *
+ * lzma_wrapper.c
+ *
+ * Support for LZMA1 compression using LZMA SDK (4.65 used in
+ * development, other versions may work) http://www.7-zip.org/sdk.html
+ */
+
+#include <LzmaLib.h>
+
+#include "squashfs_fs.h"
+#include "compressor.h"
+
+#define LZMA_HEADER_SIZE	(LZMA_PROPS_SIZE + 8)
+
+static int lzma_compress(void *strm, void *dest, void *src, int size, int block_size,
+		int *error)
+{
+	unsigned char *d = dest;
+	size_t props_size = LZMA_PROPS_SIZE,
+		outlen = block_size - LZMA_HEADER_SIZE;
+	int res;
+
+	res = LzmaCompress(dest + LZMA_HEADER_SIZE, &outlen, src, size, dest,
+		&props_size, 5, block_size, 3, 0, 2, 32, 1);
+	
+	if(res == SZ_ERROR_OUTPUT_EOF) {
+		/*
+		 * Output buffer overflow.  Return out of buffer space error
+		 */
+		return 0;
+	}
+
+	if(res != SZ_OK) {
+		/*
+		 * All other errors return failure, with the compressor
+		 * specific error code in *error
+		 */
+		*error = res;
+		return -1;
+	}
+
+	/*
+	 * Fill in the 8 byte little endian uncompressed size field in the
+	 * LZMA header.  8 bytes is excessively large for squashfs but
+	 * this is the standard LZMA header and which is expected by the kernel
+	 * code
+	 */
+	d[LZMA_PROPS_SIZE] = size & 255;
+	d[LZMA_PROPS_SIZE + 1] = (size >> 8) & 255;
+	d[LZMA_PROPS_SIZE + 2] = (size >> 16) & 255;
+	d[LZMA_PROPS_SIZE + 3] = (size >> 24) & 255;
+	d[LZMA_PROPS_SIZE + 4] = 0;
+	d[LZMA_PROPS_SIZE + 5] = 0;
+	d[LZMA_PROPS_SIZE + 6] = 0;
+	d[LZMA_PROPS_SIZE + 7] = 0;
+
+	/*
+	 * Success, return the compressed size.  Outlen returned by the LZMA
+	 * compressor does not include the LZMA header space
+	 */
+	return outlen + LZMA_HEADER_SIZE;
+}
+
+
+static int lzma_uncompress(void *dest, void *src, int size, int outsize,
+	int *error)
+{
+	unsigned char *s = src;
+	size_t outlen, inlen = size - LZMA_HEADER_SIZE;
+	int res;
+
+	outlen = s[LZMA_PROPS_SIZE] |
+		(s[LZMA_PROPS_SIZE + 1] << 8) |
+		(s[LZMA_PROPS_SIZE + 2] << 16) |
+		(s[LZMA_PROPS_SIZE + 3] << 24);
+
+	if(outlen > outsize) {
+		*error = 0;
+		return -1;
+	}
+
+	res = LzmaUncompress(dest, &outlen, src + LZMA_HEADER_SIZE, &inlen, src,
+		LZMA_PROPS_SIZE);
+	
+	if(res == SZ_OK)
+		return outlen;
+	else {
+		*error = res;
+		return -1;
+	}
+}
+
+
+struct compressor lzma_comp_ops = {
+	.init = NULL,
+	.compress = lzma_compress,
+	.uncompress = lzma_uncompress,
+	.options = NULL,
+	.usage = NULL,
+	.id = LZMA_COMPRESSION,
+	.name = "lzma",
+	.supported = 1
+};
+
diff --git a/squashfs-tools/squashfs-tools/lzma_xz_wrapper.c b/squashfs-tools/squashfs-tools/lzma_xz_wrapper.c
new file mode 100644
index 0000000..55a6813
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/lzma_xz_wrapper.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2010, 2013
+ * 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.
+ *
+ * lzma_xz_wrapper.c
+ *
+ * Support for LZMA1 compression using XZ Utils liblzma http://tukaani.org/xz/
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <lzma.h>
+
+#include "squashfs_fs.h"
+#include "compressor.h"
+
+#define LZMA_PROPS_SIZE 5
+#define LZMA_UNCOMP_SIZE 8
+#define LZMA_HEADER_SIZE (LZMA_PROPS_SIZE + LZMA_UNCOMP_SIZE)
+
+#define LZMA_OPTIONS 5
+#define MEMLIMIT (32 * 1024 * 1024)
+
+static int lzma_compress(void *dummy, void *dest, void *src,  int size,
+	int block_size, int *error)
+{
+	unsigned char *d = (unsigned char *) dest;
+	lzma_options_lzma opt;
+	lzma_stream strm = LZMA_STREAM_INIT;
+	int res;
+
+	lzma_lzma_preset(&opt, LZMA_OPTIONS);
+	opt.dict_size = block_size;
+
+	res = lzma_alone_encoder(&strm, &opt);
+	if(res != LZMA_OK) {
+		lzma_end(&strm);
+		goto failed;
+	}
+
+	strm.next_out = dest;
+	strm.avail_out = block_size;
+	strm.next_in = src;
+	strm.avail_in = size;
+
+	res = lzma_code(&strm, LZMA_FINISH);
+	lzma_end(&strm);
+
+	if(res == LZMA_STREAM_END) {
+		/*
+	 	 * Fill in the 8 byte little endian uncompressed size field in
+		 * the LZMA header.  8 bytes is excessively large for squashfs
+		 * but this is the standard LZMA header and which is expected by
+		 * the kernel code
+	 	 */
+
+		d[LZMA_PROPS_SIZE] = size & 255;
+		d[LZMA_PROPS_SIZE + 1] = (size >> 8) & 255;
+		d[LZMA_PROPS_SIZE + 2] = (size >> 16) & 255;
+		d[LZMA_PROPS_SIZE + 3] = (size >> 24) & 255;
+		d[LZMA_PROPS_SIZE + 4] = 0;
+		d[LZMA_PROPS_SIZE + 5] = 0;
+		d[LZMA_PROPS_SIZE + 6] = 0;
+		d[LZMA_PROPS_SIZE + 7] = 0;
+
+		return (int) strm.total_out;
+	}
+
+	if(res == LZMA_OK)
+		/*
+	 	 * Output buffer overflow.  Return out of buffer space
+	 	 */
+		return 0;
+
+failed:
+	/*
+	 * All other errors return failure, with the compressor
+	 * specific error code in *error
+	 */
+	*error = res;
+	return -1;
+}
+
+
+static int lzma_uncompress(void *dest, void *src, int size, int outsize,
+	int *error)
+{
+	lzma_stream strm = LZMA_STREAM_INIT;
+	int uncompressed_size = 0, res;
+	unsigned char lzma_header[LZMA_HEADER_SIZE];
+
+	res = lzma_alone_decoder(&strm, MEMLIMIT);
+	if(res != LZMA_OK) {
+		lzma_end(&strm);
+		goto failed;
+	}
+
+	memcpy(lzma_header, src, LZMA_HEADER_SIZE);
+	uncompressed_size = lzma_header[LZMA_PROPS_SIZE] |
+		(lzma_header[LZMA_PROPS_SIZE + 1] << 8) |
+		(lzma_header[LZMA_PROPS_SIZE + 2] << 16) |
+		(lzma_header[LZMA_PROPS_SIZE + 3] << 24);
+
+	if(uncompressed_size > outsize) {
+		res = 0;
+		goto failed;
+	}
+
+	memset(lzma_header + LZMA_PROPS_SIZE, 255, LZMA_UNCOMP_SIZE);
+
+	strm.next_out = dest;
+	strm.avail_out = outsize;
+	strm.next_in = lzma_header;
+	strm.avail_in = LZMA_HEADER_SIZE;
+
+	res = lzma_code(&strm, LZMA_RUN);
+
+	if(res != LZMA_OK || strm.avail_in != 0) {
+		lzma_end(&strm);
+		goto failed;
+	}
+
+	strm.next_in = src + LZMA_HEADER_SIZE;
+	strm.avail_in = size - LZMA_HEADER_SIZE;
+
+	res = lzma_code(&strm, LZMA_FINISH);
+	lzma_end(&strm);
+
+	if(res == LZMA_STREAM_END || (res == LZMA_OK &&
+		strm.total_out >= uncompressed_size && strm.avail_in == 0))
+		return uncompressed_size;
+
+failed:
+	*error = res;
+	return -1;
+}
+
+
+struct compressor lzma_comp_ops = {
+	.init = NULL,
+	.compress = lzma_compress,
+	.uncompress = lzma_uncompress,
+	.options = NULL,
+	.usage = NULL,
+	.id = LZMA_COMPRESSION,
+	.name = "lzma",
+	.supported = 1
+};
+
diff --git a/squashfs-tools/squashfs-tools/lzo_wrapper.c b/squashfs-tools/squashfs-tools/lzo_wrapper.c
new file mode 100644
index 0000000..8c9bf95
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/lzo_wrapper.c
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2013, 2014
+ * 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.
+ *
+ * lzo_wrapper.c
+ *
+ * Support for LZO compression http://www.oberhumer.com/opensource/lzo
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <lzo/lzoconf.h>
+#include <lzo/lzo1x.h>
+
+#include "squashfs_fs.h"
+#include "lzo_wrapper.h"
+#include "compressor.h"
+
+static struct lzo_algorithm lzo[] = {
+	{ "lzo1x_1", LZO1X_1_MEM_COMPRESS, lzo1x_1_compress },
+	{ "lzo1x_1_11", LZO1X_1_11_MEM_COMPRESS, lzo1x_1_11_compress },
+	{ "lzo1x_1_12", LZO1X_1_12_MEM_COMPRESS, lzo1x_1_12_compress },
+	{ "lzo1x_1_15", LZO1X_1_15_MEM_COMPRESS, lzo1x_1_15_compress },
+	{ "lzo1x_999", LZO1X_999_MEM_COMPRESS, lzo1x_999_wrapper },
+	{ NULL, 0, NULL } 
+};
+
+/* default LZO compression algorithm and compression level */
+static int algorithm = SQUASHFS_LZO1X_999;
+static int compression_level = SQUASHFS_LZO1X_999_COMP_DEFAULT;
+
+/* user specified compression level */
+static int user_comp_level = -1;
+
+
+/*
+ * This function is called by the options parsing code in mksquashfs.c
+ * to parse any -X compressor option.
+ *
+ * This function returns:
+ *	>=0 (number of additional args parsed) on success
+ *	-1 if the option was unrecognised, or
+ *	-2 if the option was recognised, but otherwise bad in
+ *	   some way (e.g. invalid parameter)
+ *
+ * Note: this function sets internal compressor state, but does not
+ * pass back the results of the parsing other than success/failure.
+ * The lzo_dump_options() function is called later to get the options in
+ * a format suitable for writing to the filesystem.
+ */
+static int lzo_options(char *argv[], int argc)
+{
+	int i;
+
+	if(strcmp(argv[0], "-Xalgorithm") == 0) {
+		if(argc < 2) {
+			fprintf(stderr, "lzo: -Xalgorithm missing algorithm\n");
+			fprintf(stderr, "lzo: -Xalgorithm <algorithm>\n");
+			goto failed2;
+		}
+
+		for(i = 0; lzo[i].name; i++) {
+			if(strcmp(argv[1], lzo[i].name) == 0) {
+				algorithm = i;
+				return 1;
+			}
+		}
+
+		fprintf(stderr, "lzo: -Xalgorithm unrecognised algorithm\n");
+		goto failed2;
+	} else if(strcmp(argv[0], "-Xcompression-level") == 0) {
+		if(argc < 2) {
+			fprintf(stderr, "lzo: -Xcompression-level missing "
+				"compression level\n");
+			fprintf(stderr, "lzo: -Xcompression-level it "
+				"should be 1 >= n <= 9\n");
+			goto failed;
+		}
+
+		user_comp_level = atoi(argv[1]);
+		if(user_comp_level < 1 || user_comp_level > 9) {
+			fprintf(stderr, "lzo: -Xcompression-level invalid, it "
+				"should be 1 >= n <= 9\n");
+			goto failed;
+		}
+
+		return 1;
+	}
+
+	return -1;
+
+failed:
+	return -2;
+
+failed2:
+	fprintf(stderr, "lzo: compression algorithm should be one of:\n");
+	for(i = 0; lzo[i].name; i++)
+		fprintf(stderr, "\t%s\n", lzo[i].name);
+	return -2;
+}
+
+
+/*
+ * This function is called after all options have been parsed.
+ * It is used to do post-processing on the compressor options using
+ * values that were not expected to be known at option parse time.
+ *
+ * In this case the LZO algorithm may not be known until after the
+ * compression level has been set (-Xalgorithm used after -Xcompression-level)
+ *
+ * This function returns 0 on successful post processing, or
+ *			-1 on error
+ */
+static int lzo_options_post(int block_size)
+{
+	/*
+	 * Use of compression level only makes sense for
+	 * LZO1X_999 algorithm
+	 */
+	if(user_comp_level != -1) {
+		if(algorithm != SQUASHFS_LZO1X_999) {
+			fprintf(stderr, "lzo: -Xcompression-level not "
+				"supported by selected %s algorithm\n",
+				lzo[algorithm].name);
+			fprintf(stderr, "lzo: -Xcompression-level is only "
+				"applicable for the lzo1x_999 algorithm\n");
+			goto failed;
+		}
+		compression_level = user_comp_level;
+	}
+
+	return 0;
+
+failed:
+	return -1;
+}
+
+
+/*
+ * This function is called by mksquashfs to dump the parsed
+ * compressor options in a format suitable for writing to the
+ * compressor options field in the filesystem (stored immediately
+ * after the superblock).
+ *
+ * This function returns a pointer to the compression options structure
+ * to be stored (and the size), or NULL if there are no compression
+ * options
+ *
+ */
+static void *lzo_dump_options(int block_size, int *size)
+{
+	static struct lzo_comp_opts comp_opts;
+
+	/*
+	 * If default compression options of SQUASHFS_LZO1X_999 and
+	 * compression level of SQUASHFS_LZO1X_999_COMP_DEFAULT then
+	 * don't store a compression options structure (this is compatible
+	 * with the legacy implementation of LZO for Squashfs)
+	 */
+	if(algorithm == SQUASHFS_LZO1X_999 &&
+			compression_level == SQUASHFS_LZO1X_999_COMP_DEFAULT)
+		return NULL;
+
+	comp_opts.algorithm = algorithm;
+	comp_opts.compression_level = algorithm == SQUASHFS_LZO1X_999 ?
+		compression_level : 0;
+
+	SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
+
+	*size = sizeof(comp_opts);
+	return &comp_opts;
+}
+
+
+/*
+ * This function is a helper specifically for the append mode of
+ * mksquashfs.  Its purpose is to set the internal compressor state
+ * to the stored compressor options in the passed compressor options
+ * structure.
+ *
+ * In effect this function sets up the compressor options
+ * to the same state they were when the filesystem was originally
+ * generated, this is to ensure on appending, the compressor uses
+ * the same compression options that were used to generate the
+ * original filesystem.
+ *
+ * Note, even if there are no compressor options, this function is still
+ * called with an empty compressor structure (size == 0), to explicitly
+ * set the default options, this is to ensure any user supplied
+ * -X options on the appending mksquashfs command line are over-ridden
+ *
+ * This function returns 0 on sucessful extraction of options, and
+ *			-1 on error
+ */
+static int lzo_extract_options(int block_size, void *buffer, int size)
+{
+	struct lzo_comp_opts *comp_opts = buffer;
+
+	if(size == 0) {
+		/* Set default values */
+		algorithm = SQUASHFS_LZO1X_999;
+		compression_level = SQUASHFS_LZO1X_999_COMP_DEFAULT;
+		return 0;
+	}
+
+	/* we expect a comp_opts structure of sufficient size to be present */
+	if(size < sizeof(*comp_opts))
+		goto failed;
+
+	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+	/* Check comp_opts structure for correctness */
+	switch(comp_opts->algorithm) {
+	case SQUASHFS_LZO1X_1:
+	case SQUASHFS_LZO1X_1_11:
+	case SQUASHFS_LZO1X_1_12:
+	case SQUASHFS_LZO1X_1_15:
+		if(comp_opts->compression_level != 0) {
+			fprintf(stderr, "lzo: bad compression level in "
+				"compression options structure\n");
+			goto failed;
+		}
+		break;
+	case SQUASHFS_LZO1X_999:
+		if(comp_opts->compression_level < 1 ||
+				comp_opts->compression_level > 9) {
+			fprintf(stderr, "lzo: bad compression level in "
+				"compression options structure\n");
+			goto failed;
+		}
+		compression_level = comp_opts->compression_level;
+		break;
+	default:
+		fprintf(stderr, "lzo: bad algorithm in compression options "
+				"structure\n");
+			goto failed;
+	}
+
+	algorithm = comp_opts->algorithm;
+
+	return 0;
+
+failed:
+	fprintf(stderr, "lzo: error reading stored compressor options from "
+		"filesystem!\n");
+
+	return -1;
+}
+
+
+void lzo_display_options(void *buffer, int size)
+{
+	struct lzo_comp_opts *comp_opts = buffer;
+
+	/* we expect a comp_opts structure of sufficient size to be present */
+	if(size < sizeof(*comp_opts))
+		goto failed;
+
+	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+	/* Check comp_opts structure for correctness */
+	switch(comp_opts->algorithm) {
+	case SQUASHFS_LZO1X_1:
+	case SQUASHFS_LZO1X_1_11:
+	case SQUASHFS_LZO1X_1_12:
+	case SQUASHFS_LZO1X_1_15:
+		printf("\talgorithm %s\n", lzo[comp_opts->algorithm].name);
+		break;
+	case SQUASHFS_LZO1X_999:
+		if(comp_opts->compression_level < 1 ||
+				comp_opts->compression_level > 9) {
+			fprintf(stderr, "lzo: bad compression level in "
+				"compression options structure\n");
+			goto failed;
+		}
+		printf("\talgorithm %s\n", lzo[comp_opts->algorithm].name);
+		printf("\tcompression level %d\n",
+						comp_opts->compression_level);
+		break;
+	default:
+		fprintf(stderr, "lzo: bad algorithm in compression options "
+				"structure\n");
+			goto failed;
+	}
+
+	return;
+
+failed:
+	fprintf(stderr, "lzo: error reading stored compressor options from "
+		"filesystem!\n");
+}	
+
+
+/*
+ * This function is called by mksquashfs to initialise the
+ * compressor, before compress() is called.
+ *
+ * This function returns 0 on success, and
+ *			-1 on error
+ */
+static int squashfs_lzo_init(void **strm, int block_size, int datablock)
+{
+	struct lzo_stream *stream;
+
+	stream = *strm = malloc(sizeof(struct lzo_stream));
+	if(stream == NULL)
+		goto failed;
+
+	stream->workspace = malloc(lzo[algorithm].size);
+	if(stream->workspace == NULL)
+		goto failed2;
+
+	stream->buffer = malloc(LZO_MAX_EXPANSION(block_size));
+	if(stream->buffer != NULL)
+		return 0;
+
+	free(stream->workspace);
+failed2:
+	free(stream);
+failed:
+	return -1;
+}
+
+
+static int lzo_compress(void *strm, void *dest, void *src,  int size,
+	int block_size, int *error)
+{
+	int res;
+	lzo_uint compsize, orig_size = size;
+	struct lzo_stream *stream = strm;
+
+	res = lzo[algorithm].compress(src, size, stream->buffer, &compsize,
+							stream->workspace);
+	if(res != LZO_E_OK)
+		goto failed;	
+
+	/* Successful compression, however, we need to check that
+	 * the compressed size is not larger than the available
+	 * buffer space.  Normally in other compressor APIs they take
+	 * a destination buffer size, and overflows return an error.
+	 * With LZO it lacks a destination size and so we must output
+	 * to a temporary buffer large enough to accomodate any
+	 * result, and explictly check here for overflow
+	 */
+	if(compsize > block_size)
+		return 0;
+
+	res = lzo1x_optimize(stream->buffer, compsize, src, &orig_size, NULL);
+
+	if (res != LZO_E_OK || orig_size != size)
+		goto failed;
+
+	memcpy(dest, stream->buffer, compsize);
+	return compsize;
+
+failed:
+	/* fail, compressor specific error code returned in error */
+	*error = res;
+	return -1;
+}
+
+
+static int lzo_uncompress(void *dest, void *src, int size, int outsize,
+	int *error)
+{
+	int res;
+	lzo_uint outlen = outsize;
+
+	res = lzo1x_decompress_safe(src, size, dest, &outlen, NULL);
+	if(res != LZO_E_OK) {
+		*error = res;
+		return -1;
+	}
+
+	return outlen;
+}
+
+
+void lzo_usage()
+{
+	int i;
+
+	fprintf(stderr, "\t  -Xalgorithm <algorithm>\n");
+	fprintf(stderr, "\t\tWhere <algorithm> is one of:\n");
+
+	for(i = 0; lzo[i].name; i++)
+		fprintf(stderr, "\t\t\t%s%s\n", lzo[i].name,
+				i == SQUASHFS_LZO1X_999 ? " (default)" : "");
+
+	fprintf(stderr, "\t  -Xcompression-level <compression-level>\n");
+	fprintf(stderr, "\t\t<compression-level> should be 1 .. 9 (default "
+		"%d)\n", SQUASHFS_LZO1X_999_COMP_DEFAULT);
+	fprintf(stderr, "\t\tOnly applies to lzo1x_999 algorithm\n");
+}
+
+
+/*
+ * Helper function for lzo1x_999 compression algorithm.
+ * All other lzo1x_xxx compressors do not take a compression level,
+ * so we need to wrap lzo1x_999 to pass the compression level which
+ * is applicable to it
+ */
+int lzo1x_999_wrapper(const lzo_bytep src, lzo_uint src_len, lzo_bytep dst,
+	lzo_uintp compsize, lzo_voidp workspace)
+{
+	return lzo1x_999_compress_level(src, src_len, dst, compsize,
+		workspace, NULL, 0, 0, compression_level);
+}
+
+
+struct compressor lzo_comp_ops = {
+	.init = squashfs_lzo_init,
+	.compress = lzo_compress,
+	.uncompress = lzo_uncompress,
+	.options = lzo_options,
+	.options_post = lzo_options_post,
+	.dump_options = lzo_dump_options,
+	.extract_options = lzo_extract_options,
+	.display_options = lzo_display_options,
+	.usage = lzo_usage,
+	.id = LZO_COMPRESSION,
+	.name = "lzo",
+	.supported = 1
+};
diff --git a/squashfs-tools/squashfs-tools/lzo_wrapper.h b/squashfs-tools/squashfs-tools/lzo_wrapper.h
new file mode 100644
index 0000000..804e53c
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/lzo_wrapper.h
@@ -0,0 +1,78 @@
+#ifndef LZO_WRAPPER_H
+#define LZO_WRAPPER_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2013
+ * 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.
+ *
+ * lzo_wrapper.h
+ *
+ */
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+extern unsigned int inswap_le32(unsigned int);
+
+#define SQUASHFS_INSWAP_COMP_OPTS(s) { \
+	(s)->algorithm = inswap_le32((s)->algorithm); \
+	(s)->compression_level = inswap_le32((s)->compression_level); \
+}
+#else
+#define SQUASHFS_INSWAP_COMP_OPTS(s)
+#endif
+
+/* Define the compression flags recognised. */
+#define SQUASHFS_LZO1X_1	0
+#define SQUASHFS_LZO1X_1_11	1
+#define SQUASHFS_LZO1X_1_12	2
+#define SQUASHFS_LZO1X_1_15	3
+#define SQUASHFS_LZO1X_999	4
+
+/* Default compression level used by SQUASHFS_LZO1X_999 */
+#define SQUASHFS_LZO1X_999_COMP_DEFAULT	8
+
+struct lzo_comp_opts {
+	int algorithm;
+	int compression_level;
+};
+
+struct lzo_algorithm {
+	char *name;
+	int size;
+	int (*compress) (const lzo_bytep, lzo_uint, lzo_bytep, lzo_uintp,
+		lzo_voidp);
+};
+
+struct lzo_stream {
+	void *workspace;
+	void *buffer;
+};
+
+#define LZO_MAX_EXPANSION(size)	(size + (size / 16) + 64 + 3)
+
+int lzo1x_999_wrapper(const lzo_bytep, lzo_uint, lzo_bytep, lzo_uintp,
+		lzo_voidp);
+
+#endif
diff --git a/squashfs-tools/squashfs-tools/mksquashfs.c b/squashfs-tools/squashfs-tools/mksquashfs.c
new file mode 100644
index 0000000..033bc8e
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/mksquashfs.c
@@ -0,0 +1,6192 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011,
+ * 2012, 2013, 2014
+ * 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.
+ *
+ * mksquashfs.c
+ */
+
+#define FALSE 0
+#define TRUE 1
+#define MAX_LINE 16384
+
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <regex.h>
+#include <fnmatch.h>
+#include <sys/wait.h>
+#include <limits.h>
+#include <ctype.h>
+
+#ifndef FNM_EXTMATCH /* glibc extension */
+    #define FNM_EXTMATCH 0
+#endif
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#include <sys/sysctl.h>
+#else
+#include <endian.h>
+#include <sys/sysinfo.h>
+#endif
+
+#include "squashfs_fs.h"
+#include "squashfs_swap.h"
+#include "mksquashfs.h"
+#include "sort.h"
+#include "pseudo.h"
+#include "compressor.h"
+#include "xattr.h"
+#include "action.h"
+#include "error.h"
+#include "progressbar.h"
+#include "info.h"
+#include "caches-queues-lists.h"
+#include "read_fs.h"
+#include "restore.h"
+#include "process_fragments.h"
+
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+#include "android.h"
+#include "private/android_filesystem_config.h"
+#include "private/canned_fs_config.h"
+int android_config = FALSE;
+char *context_file = NULL;
+char *mount_point = NULL;
+char *target_out_path = NULL;
+fs_config_func_t fs_config_func = NULL;
+#endif
+/* ANDROID CHANGES END */
+
+int delete = FALSE;
+int fd;
+struct squashfs_super_block sBlk;
+
+/* filesystem flags for building */
+int comp_opts = FALSE;
+int no_xattrs = XATTR_DEF;
+int noX = FALSE;
+int duplicate_checking = TRUE;
+int noF = FALSE;
+int no_fragments = FALSE;
+int always_use_fragments = FALSE;
+int noI = FALSE;
+int noD = FALSE;
+int silent = TRUE;
+int exportable = TRUE;
+int sparse_files = TRUE;
+int old_exclude = TRUE;
+int use_regex = FALSE;
+int nopad = FALSE;
+int exit_on_error = FALSE;
+
+long long global_uid = -1, global_gid = -1;
+
+/* superblock attributes */
+int block_size = SQUASHFS_FILE_SIZE, block_log;
+unsigned int id_count = 0;
+int file_count = 0, sym_count = 0, dev_count = 0, dir_count = 0, fifo_count = 0,
+	sock_count = 0;
+
+/* write position within data section */
+long long bytes = 0, total_bytes = 0;
+
+/* in memory directory table - possibly compressed */
+char *directory_table = NULL;
+unsigned int directory_bytes = 0, directory_size = 0, total_directory_bytes = 0;
+
+/* cached directory table */
+char *directory_data_cache = NULL;
+unsigned int directory_cache_bytes = 0, directory_cache_size = 0;
+
+/* in memory inode table - possibly compressed */
+char *inode_table = NULL;
+unsigned int inode_bytes = 0, inode_size = 0, total_inode_bytes = 0;
+
+/* cached inode table */
+char *data_cache = NULL;
+unsigned int cache_bytes = 0, cache_size = 0, inode_count = 0;
+
+/* inode lookup table */
+squashfs_inode *inode_lookup_table = NULL;
+
+/* in memory directory data */
+#define I_COUNT_SIZE		128
+#define DIR_ENTRIES		32
+#define INODE_HASH_SIZE		65536
+#define INODE_HASH_MASK		(INODE_HASH_SIZE - 1)
+#define INODE_HASH(dev, ino)	(ino & INODE_HASH_MASK)
+
+struct cached_dir_index {
+	struct squashfs_dir_index	index;
+	char				*name;
+};
+
+struct directory {
+	unsigned int		start_block;
+	unsigned int		size;
+	unsigned char		*buff;
+	unsigned char		*p;
+	unsigned int		entry_count;
+	unsigned char		*entry_count_p;
+	unsigned int		i_count;
+	unsigned int		i_size;
+	struct cached_dir_index	*index;
+	unsigned char		*index_count_p;
+	unsigned int		inode_number;
+};
+
+struct inode_info *inode_info[INODE_HASH_SIZE];
+
+/* hash tables used to do fast duplicate searches in duplicate check */
+struct file_info *dupl[65536];
+int dup_files = 0;
+
+/* exclude file handling */
+/* list of exclude dirs/files */
+struct exclude_info {
+	dev_t			st_dev;
+	ino_t			st_ino;
+};
+
+#define EXCLUDE_SIZE 8192
+int exclude = 0;
+struct exclude_info *exclude_paths = NULL;
+int old_excluded(char *filename, struct stat *buf);
+
+struct path_entry {
+	char *name;
+	regex_t *preg;
+	struct pathname *paths;
+};
+
+struct pathname {
+	int names;
+	struct path_entry *name;
+};
+
+struct pathnames {
+	int count;
+	struct pathname *path[0];
+};
+#define PATHS_ALLOC_SIZE 10
+
+struct pathnames *paths = NULL;
+struct pathname *path = NULL;
+struct pathname *stickypath = NULL;
+int excluded(char *name, struct pathnames *paths, struct pathnames **new);
+
+int fragments = 0;
+
+#define FRAG_SIZE 32768
+
+struct squashfs_fragment_entry *fragment_table = NULL;
+int fragments_outstanding = 0;
+
+int fragments_locked = FALSE;
+
+/* current inode number for directories and non directories */
+unsigned int inode_no = 1;
+unsigned int root_inode_number = 0;
+
+/* list of source dirs/files */
+int source = 0;
+char **source_path;
+
+/* list of root directory entries read from original filesystem */
+int old_root_entries = 0;
+struct old_root_entry_info {
+	char			*name;
+	struct inode_info	inode;
+};
+struct old_root_entry_info *old_root_entry;
+
+/* restore orignal filesystem state if appending to existing filesystem is
+ * cancelled */
+int appending = FALSE;
+char *sdata_cache, *sdirectory_data_cache, *sdirectory_compressed;
+
+long long sbytes, stotal_bytes;
+
+unsigned int sinode_bytes, scache_bytes, sdirectory_bytes,
+	sdirectory_cache_bytes, sdirectory_compressed_bytes,
+	stotal_inode_bytes, stotal_directory_bytes,
+	sinode_count = 0, sfile_count, ssym_count, sdev_count,
+	sdir_count, sfifo_count, ssock_count, sdup_files;
+int sfragments;
+int threads;
+
+/* flag whether destination file is a block device */
+int block_device = FALSE;
+
+/* flag indicating whether files are sorted using sort list(s) */
+int sorted = FALSE;
+
+/* save destination file name for deleting on error */
+char *destination_file = NULL;
+
+/* recovery file for abnormal exit on appending */
+char *recovery_file = NULL;
+int recover = TRUE;
+
+struct id *id_hash_table[ID_ENTRIES];
+struct id *id_table[SQUASHFS_IDS], *sid_table[SQUASHFS_IDS];
+unsigned int uid_count = 0, guid_count = 0;
+unsigned int sid_count = 0, suid_count = 0, sguid_count = 0;
+
+struct cache *reader_buffer, *fragment_buffer, *reserve_cache;
+struct cache *bwriter_buffer, *fwriter_buffer;
+struct queue *to_reader, *to_deflate, *to_writer, *from_writer,
+	*to_frag, *locked_fragment, *to_process_frag;
+struct seq_queue *to_main;
+pthread_t reader_thread, writer_thread, main_thread;
+pthread_t *deflator_thread, *frag_deflator_thread, *frag_thread;
+pthread_t *restore_thread = NULL;
+pthread_mutex_t	fragment_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t	pos_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t	dup_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* user options that control parallelisation */
+int processors = -1;
+int bwriter_size;
+
+/* compression operations */
+struct compressor *comp = NULL;
+int compressor_opt_parsed = FALSE;
+void *stream = NULL;
+
+/* xattr stats */
+unsigned int xattr_bytes = 0, total_xattr_bytes = 0;
+
+/* fragment to file mapping used when appending */
+int append_fragments = 0;
+struct append_file **file_mapping;
+
+/* root of the in-core directory structure */
+struct dir_info *root_dir;
+
+static char *read_from_disk(long long start, unsigned int avail_bytes);
+void add_old_root_entry(char *name, squashfs_inode inode, int inode_number,
+	int type);
+struct file_info *duplicate(long long file_size, long long bytes,
+	unsigned int **block_list, long long *start, struct fragment **fragment,
+	struct file_buffer *file_buffer, int blocks, unsigned short checksum,
+	int checksum_flag);
+struct dir_info *dir_scan1(char *, char *, struct pathnames *,
+	struct dir_ent *(_readdir)(struct dir_info *), int);
+void dir_scan2(struct dir_info *dir, struct pseudo *pseudo);
+void dir_scan3(struct dir_info *dir);
+void dir_scan4(struct dir_info *dir);
+void dir_scan5(struct dir_info *dir);
+void dir_scan6(struct dir_info *dir);
+void dir_scan7(squashfs_inode *inode, struct dir_info *dir_info);
+struct file_info *add_non_dup(long long file_size, long long bytes,
+	unsigned int *block_list, long long start, struct fragment *fragment,
+	unsigned short checksum, unsigned short fragment_checksum,
+	int checksum_flag, int checksum_frag_flag);
+long long generic_write_table(int, void *, int, void *, int);
+void restorefs();
+struct dir_info *scan1_opendir(char *pathname, char *subpath, int depth);
+void write_filesystem_tables(struct squashfs_super_block *sBlk, int nopad);
+unsigned short get_checksum_mem(char *buff, int bytes);
+void check_usable_phys_mem(int total_mem);
+
+
+void prep_exit()
+{
+	if(restore_thread) {
+		if(pthread_self() == *restore_thread) {
+			/*
+			 * Recursive failure when trying to restore filesystem!
+			 * Nothing to do except to exit, otherwise we'll just
+			 * appear to hang.  The user should be able to restore
+			 * from the recovery file (which is why it was added, in
+			 * case of catastrophic failure in Mksquashfs)
+			 */
+			exit(1);
+		} else {
+			/* signal the restore thread to restore */
+			pthread_kill(*restore_thread, SIGUSR1);
+			pthread_exit(NULL);
+		}
+	} else if(delete) {
+		if(destination_file && !block_device)
+			unlink(destination_file);
+	} else if(recovery_file)
+		unlink(recovery_file);
+}
+
+
+int add_overflow(int a, int b)
+{
+	return (INT_MAX - a) < b;
+}
+
+
+int shift_overflow(int a, int shift)
+{
+	return (INT_MAX >> shift) < a;
+}
+
+ 
+int multiply_overflow(int a, int multiplier)
+{
+	return (INT_MAX / multiplier) < a;
+}
+
+
+int multiply_overflowll(long long a, int multiplier)
+{
+	return (LLONG_MAX / multiplier) < a;
+}
+
+
+#define MKINODE(A)	((squashfs_inode)(((squashfs_inode) inode_bytes << 16) \
+			+ (((char *)A) - data_cache)))
+
+
+void restorefs()
+{
+	ERROR("Exiting - restoring original filesystem!\n\n");
+
+	bytes = sbytes;
+	memcpy(data_cache, sdata_cache, cache_bytes = scache_bytes);
+	memcpy(directory_data_cache, sdirectory_data_cache,
+		sdirectory_cache_bytes);
+	directory_cache_bytes = sdirectory_cache_bytes;
+	inode_bytes = sinode_bytes;
+	directory_bytes = sdirectory_bytes;
+ 	memcpy(directory_table + directory_bytes, sdirectory_compressed,
+		sdirectory_compressed_bytes);
+ 	directory_bytes += sdirectory_compressed_bytes;
+	total_bytes = stotal_bytes;
+	total_inode_bytes = stotal_inode_bytes;
+	total_directory_bytes = stotal_directory_bytes;
+	inode_count = sinode_count;
+	file_count = sfile_count;
+	sym_count = ssym_count;
+	dev_count = sdev_count;
+	dir_count = sdir_count;
+	fifo_count = sfifo_count;
+	sock_count = ssock_count;
+	dup_files = sdup_files;
+	fragments = sfragments;
+	id_count = sid_count;
+	restore_xattrs();
+	write_filesystem_tables(&sBlk, nopad);
+	exit(1);
+}
+
+
+void sighandler()
+{
+	EXIT_MKSQUASHFS();
+}
+
+
+int mangle2(void *strm, char *d, char *s, int size,
+	int block_size, int uncompressed, int data_block)
+{
+	int error, c_byte = 0;
+
+	if(!uncompressed) {
+		c_byte = compressor_compress(comp, strm, d, s, size, block_size,
+			 &error);
+		if(c_byte == -1)
+			BAD_ERROR("mangle2:: %s compress failed with error "
+				"code %d\n", comp->name, error);
+	}
+
+	if(c_byte == 0 || c_byte >= size) {
+		memcpy(d, s, size);
+		return size | (data_block ? SQUASHFS_COMPRESSED_BIT_BLOCK :
+			SQUASHFS_COMPRESSED_BIT);
+	}
+
+	return c_byte;
+}
+
+
+int mangle(char *d, char *s, int size, int block_size,
+	int uncompressed, int data_block)
+{
+	return mangle2(stream, d, s, size, block_size, uncompressed,
+		data_block);
+}
+
+
+void *get_inode(int req_size)
+{
+	int data_space;
+	unsigned short c_byte;
+
+	while(cache_bytes >= SQUASHFS_METADATA_SIZE) {
+		if((inode_size - inode_bytes) <
+				((SQUASHFS_METADATA_SIZE << 1)) + 2) {
+			void *it = realloc(inode_table, inode_size +
+				(SQUASHFS_METADATA_SIZE << 1) + 2);
+			if(it == NULL)
+				MEM_ERROR();
+			inode_table = it;
+			inode_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+		}
+
+		c_byte = mangle(inode_table + inode_bytes + BLOCK_OFFSET,
+			data_cache, SQUASHFS_METADATA_SIZE,
+			SQUASHFS_METADATA_SIZE, noI, 0);
+		TRACE("Inode block @ 0x%x, size %d\n", inode_bytes, c_byte);
+		SQUASHFS_SWAP_SHORTS(&c_byte, inode_table + inode_bytes, 1);
+		inode_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+		total_inode_bytes += SQUASHFS_METADATA_SIZE + BLOCK_OFFSET;
+		memmove(data_cache, data_cache + SQUASHFS_METADATA_SIZE,
+			cache_bytes - SQUASHFS_METADATA_SIZE);
+		cache_bytes -= SQUASHFS_METADATA_SIZE;
+	}
+
+	data_space = (cache_size - cache_bytes);
+	if(data_space < req_size) {
+			int realloc_size = cache_size == 0 ?
+				((req_size + SQUASHFS_METADATA_SIZE) &
+				~(SQUASHFS_METADATA_SIZE - 1)) : req_size -
+				data_space;
+
+			void *dc = realloc(data_cache, cache_size +
+				realloc_size);
+			if(dc == NULL)
+				MEM_ERROR();
+			cache_size += realloc_size;
+			data_cache = dc;
+	}
+
+	cache_bytes += req_size;
+
+	return data_cache + cache_bytes - req_size;
+}
+
+
+int read_bytes(int fd, void *buff, int bytes)
+{
+	int res, count;
+
+	for(count = 0; count < bytes; count += res) {
+		res = read(fd, buff + count, bytes - count);
+		if(res < 1) {
+			if(res == 0)
+				goto bytes_read;
+			else if(errno != EINTR) {
+				ERROR("Read failed because %s\n",
+						strerror(errno));
+				return -1;
+			} else
+				res = 0;
+		}
+	}
+
+bytes_read:
+	return count;
+}
+
+
+int read_fs_bytes(int fd, long long byte, int bytes, void *buff)
+{
+	off_t off = byte;
+	int res = 1;
+
+	TRACE("read_fs_bytes: reading from position 0x%llx, bytes %d\n",
+		byte, bytes);
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &pos_mutex);
+	pthread_mutex_lock(&pos_mutex);
+	if(lseek(fd, off, SEEK_SET) == -1) {
+		ERROR("read_fs_bytes: Lseek on destination failed because %s, "
+			"offset=0x%llx\n", strerror(errno), off);
+		res = 0;
+	} else if(read_bytes(fd, buff, bytes) < bytes) {
+		ERROR("Read on destination failed\n");
+		res = 0;
+	}
+
+	pthread_cleanup_pop(1);
+	return res;
+}
+
+
+int write_bytes(int fd, void *buff, int bytes)
+{
+	int res, count;
+
+	for(count = 0; count < bytes; count += res) {
+		res = write(fd, buff + count, bytes - count);
+		if(res == -1) {
+			if(errno != EINTR) {
+				ERROR("Write failed because %s\n",
+						strerror(errno));
+				return -1;
+			}
+			res = 0;
+		}
+	}
+
+	return 0;
+}
+
+
+void write_destination(int fd, long long byte, int bytes, void *buff)
+{
+	off_t off = byte;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &pos_mutex);
+	pthread_mutex_lock(&pos_mutex);
+
+	if(lseek(fd, off, SEEK_SET) == -1) {
+		ERROR("write_destination: Lseek on destination "
+			"failed because %s, offset=0x%llx\n", strerror(errno),
+			off);
+		BAD_ERROR("Probably out of space on output %s\n",
+			block_device ? "block device" : "filesystem");
+	}
+
+	if(write_bytes(fd, buff, bytes) == -1)
+		BAD_ERROR("Failed to write to output %s\n",
+			block_device ? "block device" : "filesystem");
+
+	pthread_cleanup_pop(1);
+}
+
+
+long long write_inodes()
+{
+	unsigned short c_byte;
+	int avail_bytes;
+	char *datap = data_cache;
+	long long start_bytes = bytes;
+
+	while(cache_bytes) {
+		if(inode_size - inode_bytes <
+				((SQUASHFS_METADATA_SIZE << 1) + 2)) {
+			void *it = realloc(inode_table, inode_size +
+				((SQUASHFS_METADATA_SIZE << 1) + 2));
+			if(it == NULL)
+				MEM_ERROR();
+			inode_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+			inode_table = it;
+		}
+		avail_bytes = cache_bytes > SQUASHFS_METADATA_SIZE ?
+			SQUASHFS_METADATA_SIZE : cache_bytes;
+		c_byte = mangle(inode_table + inode_bytes + BLOCK_OFFSET, datap,
+			avail_bytes, SQUASHFS_METADATA_SIZE, noI, 0);
+		TRACE("Inode block @ 0x%x, size %d\n", inode_bytes, c_byte);
+		SQUASHFS_SWAP_SHORTS(&c_byte, inode_table + inode_bytes, 1); 
+		inode_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+		total_inode_bytes += avail_bytes + BLOCK_OFFSET;
+		datap += avail_bytes;
+		cache_bytes -= avail_bytes;
+	}
+
+	write_destination(fd, bytes, inode_bytes,  inode_table);
+	bytes += inode_bytes;
+
+	return start_bytes;
+}
+
+
+long long write_directories()
+{
+	unsigned short c_byte;
+	int avail_bytes;
+	char *directoryp = directory_data_cache;
+	long long start_bytes = bytes;
+
+	while(directory_cache_bytes) {
+		if(directory_size - directory_bytes <
+				((SQUASHFS_METADATA_SIZE << 1) + 2)) {
+			void *dt = realloc(directory_table,
+				directory_size + ((SQUASHFS_METADATA_SIZE << 1)
+				+ 2));
+			if(dt == NULL)
+				MEM_ERROR();
+			directory_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+			directory_table = dt;
+		}
+		avail_bytes = directory_cache_bytes > SQUASHFS_METADATA_SIZE ?
+			SQUASHFS_METADATA_SIZE : directory_cache_bytes;
+		c_byte = mangle(directory_table + directory_bytes +
+			BLOCK_OFFSET, directoryp, avail_bytes,
+			SQUASHFS_METADATA_SIZE, noI, 0);
+		TRACE("Directory block @ 0x%x, size %d\n", directory_bytes,
+			c_byte);
+		SQUASHFS_SWAP_SHORTS(&c_byte,
+			directory_table + directory_bytes, 1);
+		directory_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) +
+			BLOCK_OFFSET;
+		total_directory_bytes += avail_bytes + BLOCK_OFFSET;
+		directoryp += avail_bytes;
+		directory_cache_bytes -= avail_bytes;
+	}
+	write_destination(fd, bytes, directory_bytes, directory_table);
+	bytes += directory_bytes;
+
+	return start_bytes;
+}
+
+
+long long write_id_table()
+{
+	unsigned int id_bytes = SQUASHFS_ID_BYTES(id_count);
+	unsigned int p[id_count];
+	int i;
+
+	TRACE("write_id_table: ids %d, id_bytes %d\n", id_count, id_bytes);
+	for(i = 0; i < id_count; i++) {
+		TRACE("write_id_table: id index %d, id %d", i, id_table[i]->id);
+		SQUASHFS_SWAP_INTS(&id_table[i]->id, p + i, 1);
+	}
+
+	return generic_write_table(id_bytes, p, 0, NULL, noI);
+}
+
+
+struct id *get_id(unsigned int id)
+{
+	int hash = ID_HASH(id);
+	struct id *entry = id_hash_table[hash];
+
+	for(; entry; entry = entry->next)
+		if(entry->id == id)
+			break;
+
+	return entry;
+}
+
+
+struct id *create_id(unsigned int id)
+{
+	int hash = ID_HASH(id);
+	struct id *entry = malloc(sizeof(struct id));
+	if(entry == NULL)
+		MEM_ERROR();
+	entry->id = id;
+	entry->index = id_count ++;
+	entry->flags = 0;
+	entry->next = id_hash_table[hash];
+	id_hash_table[hash] = entry;
+	id_table[entry->index] = entry;
+	return entry;
+}
+
+
+unsigned int get_uid(unsigned int uid)
+{
+	struct id *entry = get_id(uid);
+
+	if(entry == NULL) {
+		if(id_count == SQUASHFS_IDS)
+			BAD_ERROR("Out of uids!\n");
+		entry = create_id(uid);
+	}
+
+	if((entry->flags & ISA_UID) == 0) {
+		entry->flags |= ISA_UID;
+		uid_count ++;
+	}
+
+	return entry->index;
+}
+
+
+unsigned int get_guid(unsigned int guid)
+{
+	struct id *entry = get_id(guid);
+
+	if(entry == NULL) {
+		if(id_count == SQUASHFS_IDS)
+			BAD_ERROR("Out of gids!\n");
+		entry = create_id(guid);
+	}
+
+	if((entry->flags & ISA_GID) == 0) {
+		entry->flags |= ISA_GID;
+		guid_count ++;
+	}
+
+	return entry->index;
+}
+
+
+#define ALLOC_SIZE 128
+
+char *_pathname(struct dir_ent *dir_ent, char *pathname, int *size)
+{
+	if(pathname == NULL) {
+		pathname = malloc(ALLOC_SIZE);
+		if(pathname == NULL)
+			MEM_ERROR();
+	}
+
+	for(;;) {
+		int res = snprintf(pathname, *size, "%s/%s", 
+			dir_ent->our_dir->pathname,
+			dir_ent->source_name ? : dir_ent->name);
+
+		if(res < 0)
+			BAD_ERROR("snprintf failed in pathname\n");
+		else if(res >= *size) {
+			/*
+			 * pathname is too small to contain the result, so
+			 * increase it and try again
+			 */
+			*size = (res + ALLOC_SIZE) & ~(ALLOC_SIZE - 1);
+			pathname = realloc(pathname, *size);
+			if(pathname == NULL)
+				MEM_ERROR();
+		} else
+			break;
+	}
+
+	return pathname;
+}
+
+
+char *pathname(struct dir_ent *dir_ent)
+{
+	static char *pathname = NULL;
+	static int size = ALLOC_SIZE;
+
+	if (dir_ent->nonstandard_pathname)
+		return dir_ent->nonstandard_pathname;
+
+	return pathname = _pathname(dir_ent, pathname, &size);
+}
+
+
+char *pathname_reader(struct dir_ent *dir_ent)
+{
+	static char *pathname = NULL;
+	static int size = ALLOC_SIZE;
+
+	if (dir_ent->nonstandard_pathname)
+		return dir_ent->nonstandard_pathname;
+
+	return pathname = _pathname(dir_ent, pathname, &size);
+}
+
+
+char *subpathname(struct dir_ent *dir_ent)
+{
+	static char *subpath = NULL;
+	static int size = ALLOC_SIZE;
+	int res;
+
+	if(subpath == NULL) {
+		subpath = malloc(ALLOC_SIZE);
+		if(subpath == NULL)
+			MEM_ERROR();
+	}
+
+	for(;;) {
+		if(dir_ent->our_dir->subpath[0] != '\0')
+			res = snprintf(subpath, size, "%s/%s",
+				dir_ent->our_dir->subpath, dir_ent->name);
+		else
+			res = snprintf(subpath, size, "/%s", dir_ent->name);
+
+		if(res < 0)
+			BAD_ERROR("snprintf failed in subpathname\n");
+		else if(res >= size) {
+			/*
+			 * subpath is too small to contain the result, so
+			 * increase it and try again
+			 */
+			size = (res + ALLOC_SIZE) & ~(ALLOC_SIZE - 1);
+			subpath = realloc(subpath, size);
+			if(subpath == NULL)
+				MEM_ERROR();
+		} else
+			break;
+	}
+
+	return subpath;
+}
+
+
+static inline unsigned int get_inode_no(struct inode_info *inode)
+{
+	return inode->inode_number;
+}
+
+
+static inline unsigned int get_parent_no(struct dir_info *dir)
+{
+	return dir->depth ? get_inode_no(dir->dir_ent->inode) : inode_no;
+}
+
+	
+int create_inode(squashfs_inode *i_no, struct dir_info *dir_info,
+	struct dir_ent *dir_ent, int type, long long byte_size,
+	long long start_block, unsigned int offset, unsigned int *block_list,
+	struct fragment *fragment, struct directory *dir_in, long long sparse)
+{
+	struct stat *buf = &dir_ent->inode->buf;
+	union squashfs_inode_header inode_header;
+	struct squashfs_base_inode_header *base = &inode_header.base;
+	void *inode;
+	char *filename = pathname(dir_ent);
+	int nlink = dir_ent->inode->nlink;
+	int xattr = read_xattrs(dir_ent);
+
+	switch(type) {
+	case SQUASHFS_FILE_TYPE:
+		if(dir_ent->inode->nlink > 1 ||
+				byte_size >= (1LL << 32) ||
+				start_block >= (1LL << 32) ||
+				sparse || IS_XATTR(xattr))
+			type = SQUASHFS_LREG_TYPE;
+		break;
+	case SQUASHFS_DIR_TYPE:
+		if(dir_info->dir_is_ldir || IS_XATTR(xattr))
+			type = SQUASHFS_LDIR_TYPE;
+		break;
+	case SQUASHFS_SYMLINK_TYPE:
+		if(IS_XATTR(xattr))
+			type = SQUASHFS_LSYMLINK_TYPE;
+		break;
+	case SQUASHFS_BLKDEV_TYPE:
+		if(IS_XATTR(xattr))
+			type = SQUASHFS_LBLKDEV_TYPE;
+		break;
+	case SQUASHFS_CHRDEV_TYPE:
+		if(IS_XATTR(xattr))
+			type = SQUASHFS_LCHRDEV_TYPE;
+		break;
+	case SQUASHFS_FIFO_TYPE:
+		if(IS_XATTR(xattr))
+			type = SQUASHFS_LFIFO_TYPE;
+		break;
+	case SQUASHFS_SOCKET_TYPE:
+		if(IS_XATTR(xattr))
+			type = SQUASHFS_LSOCKET_TYPE;
+		break;
+	}
+			
+	base->mode = SQUASHFS_MODE(buf->st_mode);
+	base->uid = get_uid((unsigned int) global_uid == -1 ?
+		buf->st_uid : global_uid);
+	base->inode_type = type;
+	base->guid = get_guid((unsigned int) global_gid == -1 ?
+		buf->st_gid : global_gid);
+	base->mtime = buf->st_mtime;
+	base->inode_number = get_inode_no(dir_ent->inode);
+
+	if(type == SQUASHFS_FILE_TYPE) {
+		int i;
+		struct squashfs_reg_inode_header *reg = &inode_header.reg;
+		size_t off = offsetof(struct squashfs_reg_inode_header, block_list);
+
+		inode = get_inode(sizeof(*reg) + offset * sizeof(unsigned int));
+		reg->file_size = byte_size;
+		reg->start_block = start_block;
+		reg->fragment = fragment->index;
+		reg->offset = fragment->offset;
+		SQUASHFS_SWAP_REG_INODE_HEADER(reg, inode);
+		SQUASHFS_SWAP_INTS(block_list, inode + off, offset);
+		TRACE("File inode, file_size %lld, start_block 0x%llx, blocks "
+			"%d, fragment %d, offset %d, size %d\n", byte_size,
+			start_block, offset, fragment->index, fragment->offset,
+			fragment->size);
+		for(i = 0; i < offset; i++)
+			TRACE("Block %d, size %d\n", i, block_list[i]);
+	}
+	else if(type == SQUASHFS_LREG_TYPE) {
+		int i;
+		struct squashfs_lreg_inode_header *reg = &inode_header.lreg;
+		size_t off = offsetof(struct squashfs_lreg_inode_header, block_list);
+
+		inode = get_inode(sizeof(*reg) + offset * sizeof(unsigned int));
+		reg->nlink = nlink;
+		reg->file_size = byte_size;
+		reg->start_block = start_block;
+		reg->fragment = fragment->index;
+		reg->offset = fragment->offset;
+		if(sparse && sparse >= byte_size)
+			sparse = byte_size - 1;
+		reg->sparse = sparse;
+		reg->xattr = xattr;
+		SQUASHFS_SWAP_LREG_INODE_HEADER(reg, inode);
+		SQUASHFS_SWAP_INTS(block_list, inode + off, offset);
+		TRACE("Long file inode, file_size %lld, start_block 0x%llx, "
+			"blocks %d, fragment %d, offset %d, size %d, nlink %d"
+			"\n", byte_size, start_block, offset, fragment->index,
+			fragment->offset, fragment->size, nlink);
+		for(i = 0; i < offset; i++)
+			TRACE("Block %d, size %d\n", i, block_list[i]);
+	}
+	else if(type == SQUASHFS_LDIR_TYPE) {
+		int i;
+		unsigned char *p;
+		struct squashfs_ldir_inode_header *dir = &inode_header.ldir;
+		struct cached_dir_index *index = dir_in->index;
+		unsigned int i_count = dir_in->i_count;
+		unsigned int i_size = dir_in->i_size;
+
+		if(byte_size >= 1 << 27)
+			BAD_ERROR("directory greater than 2^27-1 bytes!\n");
+
+		inode = get_inode(sizeof(*dir) + i_size);
+		dir->inode_type = SQUASHFS_LDIR_TYPE;
+		dir->nlink = dir_ent->dir->directory_count + 2;
+		dir->file_size = byte_size;
+		dir->offset = offset;
+		dir->start_block = start_block;
+		dir->i_count = i_count;
+		dir->parent_inode = get_parent_no(dir_ent->our_dir);
+		dir->xattr = xattr;
+
+		SQUASHFS_SWAP_LDIR_INODE_HEADER(dir, inode);
+		p = inode + offsetof(struct squashfs_ldir_inode_header, index);
+		for(i = 0; i < i_count; i++) {
+			SQUASHFS_SWAP_DIR_INDEX(&index[i].index, p);
+			p += offsetof(struct squashfs_dir_index, name);
+			memcpy(p, index[i].name, index[i].index.size + 1);
+			p += index[i].index.size + 1;
+		}
+		TRACE("Long directory inode, file_size %lld, start_block "
+			"0x%llx, offset 0x%x, nlink %d\n", byte_size,
+			start_block, offset, dir_ent->dir->directory_count + 2);
+	}
+	else if(type == SQUASHFS_DIR_TYPE) {
+		struct squashfs_dir_inode_header *dir = &inode_header.dir;
+
+		inode = get_inode(sizeof(*dir));
+		dir->nlink = dir_ent->dir->directory_count + 2;
+		dir->file_size = byte_size;
+		dir->offset = offset;
+		dir->start_block = start_block;
+		dir->parent_inode = get_parent_no(dir_ent->our_dir);
+		SQUASHFS_SWAP_DIR_INODE_HEADER(dir, inode);
+		TRACE("Directory inode, file_size %lld, start_block 0x%llx, "
+			"offset 0x%x, nlink %d\n", byte_size, start_block,
+			offset, dir_ent->dir->directory_count + 2);
+	}
+	else if(type == SQUASHFS_CHRDEV_TYPE || type == SQUASHFS_BLKDEV_TYPE) {
+		struct squashfs_dev_inode_header *dev = &inode_header.dev;
+		unsigned int major = major(buf->st_rdev);
+		unsigned int minor = minor(buf->st_rdev);
+
+		if(major > 0xfff) {
+			ERROR("Major %d out of range in device node %s, "
+				"truncating to %d\n", major, filename,
+				major & 0xfff);
+			major &= 0xfff;
+		}
+		if(minor > 0xfffff) {
+			ERROR("Minor %d out of range in device node %s, "
+				"truncating to %d\n", minor, filename,
+				minor & 0xfffff);
+			minor &= 0xfffff;
+		}
+		inode = get_inode(sizeof(*dev));
+		dev->nlink = nlink;
+		dev->rdev = (major << 8) | (minor & 0xff) |
+				((minor & ~0xff) << 12);
+		SQUASHFS_SWAP_DEV_INODE_HEADER(dev, inode);
+		TRACE("Device inode, rdev 0x%x, nlink %d\n", dev->rdev, nlink);
+	}
+	else if(type == SQUASHFS_LCHRDEV_TYPE || type == SQUASHFS_LBLKDEV_TYPE) {
+		struct squashfs_ldev_inode_header *dev = &inode_header.ldev;
+		unsigned int major = major(buf->st_rdev);
+		unsigned int minor = minor(buf->st_rdev);
+
+		if(major > 0xfff) {
+			ERROR("Major %d out of range in device node %s, "
+				"truncating to %d\n", major, filename,
+				major & 0xfff);
+			major &= 0xfff;
+		}
+		if(minor > 0xfffff) {
+			ERROR("Minor %d out of range in device node %s, "
+				"truncating to %d\n", minor, filename,
+				minor & 0xfffff);
+			minor &= 0xfffff;
+		}
+		inode = get_inode(sizeof(*dev));
+		dev->nlink = nlink;
+		dev->rdev = (major << 8) | (minor & 0xff) |
+				((minor & ~0xff) << 12);
+		dev->xattr = xattr;
+		SQUASHFS_SWAP_LDEV_INODE_HEADER(dev, inode);
+		TRACE("Device inode, rdev 0x%x, nlink %d\n", dev->rdev, nlink);
+	}
+	else if(type == SQUASHFS_SYMLINK_TYPE) {
+		struct squashfs_symlink_inode_header *symlink = &inode_header.symlink;
+		int byte = strlen(dir_ent->inode->symlink);
+		size_t off = offsetof(struct squashfs_symlink_inode_header, symlink);
+
+		inode = get_inode(sizeof(*symlink) + byte);
+		symlink->nlink = nlink;
+		symlink->symlink_size = byte;
+		SQUASHFS_SWAP_SYMLINK_INODE_HEADER(symlink, inode);
+		strncpy(inode + off, dir_ent->inode->symlink, byte);
+		TRACE("Symbolic link inode, symlink_size %d, nlink %d\n", byte,
+			nlink);
+	}
+	else if(type == SQUASHFS_LSYMLINK_TYPE) {
+		struct squashfs_symlink_inode_header *symlink = &inode_header.symlink;
+		int byte = strlen(dir_ent->inode->symlink);
+		size_t off = offsetof(struct squashfs_symlink_inode_header, symlink);
+
+		inode = get_inode(sizeof(*symlink) + byte +
+						sizeof(unsigned int));
+		symlink->nlink = nlink;
+		symlink->symlink_size = byte;
+		SQUASHFS_SWAP_SYMLINK_INODE_HEADER(symlink, inode);
+		strncpy(inode + off, dir_ent->inode->symlink, byte);
+		SQUASHFS_SWAP_INTS(&xattr, inode + off + byte, 1);
+		TRACE("Symbolic link inode, symlink_size %d, nlink %d\n", byte,
+			nlink);
+	}
+	else if(type == SQUASHFS_FIFO_TYPE || type == SQUASHFS_SOCKET_TYPE) {
+		struct squashfs_ipc_inode_header *ipc = &inode_header.ipc;
+
+		inode = get_inode(sizeof(*ipc));
+		ipc->nlink = nlink;
+		SQUASHFS_SWAP_IPC_INODE_HEADER(ipc, inode);
+		TRACE("ipc inode, type %s, nlink %d\n", type ==
+			SQUASHFS_FIFO_TYPE ? "fifo" : "socket", nlink);
+	}
+	else if(type == SQUASHFS_LFIFO_TYPE || type == SQUASHFS_LSOCKET_TYPE) {
+		struct squashfs_lipc_inode_header *ipc = &inode_header.lipc;
+
+		inode = get_inode(sizeof(*ipc));
+		ipc->nlink = nlink;
+		ipc->xattr = xattr;
+		SQUASHFS_SWAP_LIPC_INODE_HEADER(ipc, inode);
+		TRACE("ipc inode, type %s, nlink %d\n", type ==
+			SQUASHFS_FIFO_TYPE ? "fifo" : "socket", nlink);
+	} else
+		BAD_ERROR("Unrecognised inode %d in create_inode\n", type);
+
+	*i_no = MKINODE(inode);
+	inode_count ++;
+
+	TRACE("Created inode 0x%llx, type %d, uid %d, guid %d\n", *i_no, type,
+		base->uid, base->guid);
+
+	return TRUE;
+}
+
+
+void add_dir(squashfs_inode inode, unsigned int inode_number, char *name,
+	int type, struct directory *dir)
+{
+	unsigned char *buff;
+	struct squashfs_dir_entry idir;
+	unsigned int start_block = inode >> 16;
+	unsigned int offset = inode & 0xffff;
+	unsigned int size = strlen(name);
+	size_t name_off = offsetof(struct squashfs_dir_entry, name);
+
+	if(size > SQUASHFS_NAME_LEN) {
+		size = SQUASHFS_NAME_LEN;
+		ERROR("Filename is greater than %d characters, truncating! ..."
+			"\n", SQUASHFS_NAME_LEN);
+	}
+
+	if(dir->p + sizeof(struct squashfs_dir_entry) + size +
+			sizeof(struct squashfs_dir_header)
+			>= dir->buff + dir->size) {
+		buff = realloc(dir->buff, dir->size += SQUASHFS_METADATA_SIZE);
+		if(buff == NULL)
+			MEM_ERROR();
+
+		dir->p = (dir->p - dir->buff) + buff;
+		if(dir->entry_count_p) 
+			dir->entry_count_p = (dir->entry_count_p - dir->buff +
+			buff);
+		dir->index_count_p = dir->index_count_p - dir->buff + buff;
+		dir->buff = buff;
+	}
+
+	if(dir->entry_count == 256 || start_block != dir->start_block ||
+			((dir->entry_count_p != NULL) &&
+			((dir->p + sizeof(struct squashfs_dir_entry) + size -
+			dir->index_count_p) > SQUASHFS_METADATA_SIZE)) ||
+			((long long) inode_number - dir->inode_number) > 32767
+			|| ((long long) inode_number - dir->inode_number)
+			< -32768) {
+		if(dir->entry_count_p) {
+			struct squashfs_dir_header dir_header;
+
+			if((dir->p + sizeof(struct squashfs_dir_entry) + size -
+					dir->index_count_p) >
+					SQUASHFS_METADATA_SIZE) {
+				if(dir->i_count % I_COUNT_SIZE == 0) {
+					dir->index = realloc(dir->index,
+						(dir->i_count + I_COUNT_SIZE) *
+						sizeof(struct cached_dir_index));
+					if(dir->index == NULL)
+						MEM_ERROR();
+				}
+				dir->index[dir->i_count].index.index =
+					dir->p - dir->buff;
+				dir->index[dir->i_count].index.size = size - 1;
+				dir->index[dir->i_count++].name = name;
+				dir->i_size += sizeof(struct squashfs_dir_index)
+					+ size;
+				dir->index_count_p = dir->p;
+			}
+
+			dir_header.count = dir->entry_count - 1;
+			dir_header.start_block = dir->start_block;
+			dir_header.inode_number = dir->inode_number;
+			SQUASHFS_SWAP_DIR_HEADER(&dir_header,
+				dir->entry_count_p);
+
+		}
+
+
+		dir->entry_count_p = dir->p;
+		dir->start_block = start_block;
+		dir->entry_count = 0;
+		dir->inode_number = inode_number;
+		dir->p += sizeof(struct squashfs_dir_header);
+	}
+
+	idir.offset = offset;
+	idir.type = type;
+	idir.size = size - 1;
+	idir.inode_number = ((long long) inode_number - dir->inode_number);
+	SQUASHFS_SWAP_DIR_ENTRY(&idir, dir->p);
+	strncpy((char *) dir->p + name_off, name, size);
+	dir->p += sizeof(struct squashfs_dir_entry) + size;
+	dir->entry_count ++;
+}
+
+
+void write_dir(squashfs_inode *inode, struct dir_info *dir_info,
+	struct directory *dir)
+{
+	unsigned int dir_size = dir->p - dir->buff;
+	int data_space = directory_cache_size - directory_cache_bytes;
+	unsigned int directory_block, directory_offset, i_count, index;
+	unsigned short c_byte;
+
+	if(data_space < dir_size) {
+		int realloc_size = directory_cache_size == 0 ?
+			((dir_size + SQUASHFS_METADATA_SIZE) &
+			~(SQUASHFS_METADATA_SIZE - 1)) : dir_size - data_space;
+
+		void *dc = realloc(directory_data_cache,
+			directory_cache_size + realloc_size);
+		if(dc == NULL)
+			MEM_ERROR();
+		directory_cache_size += realloc_size;
+		directory_data_cache = dc;
+	}
+
+	if(dir_size) {
+		struct squashfs_dir_header dir_header;
+
+		dir_header.count = dir->entry_count - 1;
+		dir_header.start_block = dir->start_block;
+		dir_header.inode_number = dir->inode_number;
+		SQUASHFS_SWAP_DIR_HEADER(&dir_header, dir->entry_count_p);
+		memcpy(directory_data_cache + directory_cache_bytes, dir->buff,
+			dir_size);
+	}
+	directory_offset = directory_cache_bytes;
+	directory_block = directory_bytes;
+	directory_cache_bytes += dir_size;
+	i_count = 0;
+	index = SQUASHFS_METADATA_SIZE - directory_offset;
+
+	while(1) {
+		while(i_count < dir->i_count &&
+				dir->index[i_count].index.index < index)
+			dir->index[i_count++].index.start_block =
+				directory_bytes;
+		index += SQUASHFS_METADATA_SIZE;
+
+		if(directory_cache_bytes < SQUASHFS_METADATA_SIZE)
+			break;
+
+		if((directory_size - directory_bytes) <
+					((SQUASHFS_METADATA_SIZE << 1) + 2)) {
+			void *dt = realloc(directory_table,
+				directory_size + (SQUASHFS_METADATA_SIZE << 1)
+				+ 2);
+			if(dt == NULL)
+				MEM_ERROR();
+			directory_size += SQUASHFS_METADATA_SIZE << 1;
+			directory_table = dt;
+		}
+
+		c_byte = mangle(directory_table + directory_bytes +
+				BLOCK_OFFSET, directory_data_cache,
+				SQUASHFS_METADATA_SIZE, SQUASHFS_METADATA_SIZE,
+				noI, 0);
+		TRACE("Directory block @ 0x%x, size %d\n", directory_bytes,
+			c_byte);
+		SQUASHFS_SWAP_SHORTS(&c_byte,
+			directory_table + directory_bytes, 1);
+		directory_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) +
+			BLOCK_OFFSET;
+		total_directory_bytes += SQUASHFS_METADATA_SIZE + BLOCK_OFFSET;
+		memmove(directory_data_cache, directory_data_cache +
+			SQUASHFS_METADATA_SIZE, directory_cache_bytes -
+			SQUASHFS_METADATA_SIZE);
+		directory_cache_bytes -= SQUASHFS_METADATA_SIZE;
+	}
+
+	create_inode(inode, dir_info, dir_info->dir_ent, SQUASHFS_DIR_TYPE,
+		dir_size + 3, directory_block, directory_offset, NULL, NULL,
+		dir, 0);
+
+#ifdef SQUASHFS_TRACE
+	{
+		unsigned char *dirp;
+		int count;
+
+		TRACE("Directory contents of inode 0x%llx\n", *inode);
+		dirp = dir->buff;
+		while(dirp < dir->p) {
+			char buffer[SQUASHFS_NAME_LEN + 1];
+			struct squashfs_dir_entry idir, *idirp;
+			struct squashfs_dir_header dirh;
+			SQUASHFS_SWAP_DIR_HEADER((struct squashfs_dir_header *) dirp,
+				&dirh);
+			count = dirh.count + 1;
+			dirp += sizeof(struct squashfs_dir_header);
+
+			TRACE("\tStart block 0x%x, count %d\n",
+				dirh.start_block, count);
+
+			while(count--) {
+				idirp = (struct squashfs_dir_entry *) dirp;
+				SQUASHFS_SWAP_DIR_ENTRY(idirp, &idir);
+				strncpy(buffer, idirp->name, idir.size + 1);
+				buffer[idir.size + 1] = '\0';
+				TRACE("\t\tname %s, inode offset 0x%x, type "
+					"%d\n", buffer, idir.offset, idir.type);
+				dirp += sizeof(struct squashfs_dir_entry) + idir.size +
+					1;
+			}
+		}
+	}
+#endif
+	dir_count ++;
+}
+
+
+static struct file_buffer *get_fragment(struct fragment *fragment)
+{
+	struct squashfs_fragment_entry *disk_fragment;
+	struct file_buffer *buffer, *compressed_buffer;
+	long long start_block;
+	int res, size, index = fragment->index;
+	char locked;
+
+	/*
+	 * Lookup fragment block in cache.
+	 * If the fragment block doesn't exist, then get the compressed version
+	 * from the writer cache or off disk, and decompress it.
+	 *
+	 * This routine has two things which complicate the code:
+	 *
+	 *	1. Multiple threads can simultaneously lookup/create the
+	 *	   same buffer.  This means a buffer needs to be "locked"
+	 *	   when it is being filled in, to prevent other threads from
+	 *	   using it when it is not ready.  This is because we now do
+	 *	   fragment duplicate checking in parallel.
+	 *	2. We have two caches which need to be checked for the
+	 *	   presence of fragment blocks: the normal fragment cache
+	 *	   and a "reserve" cache.  The reserve cache is used to
+	 *	   prevent an unnecessary pipeline stall when the fragment cache
+	 *	   is full of fragments waiting to be compressed.
+	 */
+
+	if(fragment->index == SQUASHFS_INVALID_FRAG)
+		return NULL;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+	pthread_mutex_lock(&dup_mutex);
+
+again:
+	buffer = cache_lookup_nowait(fragment_buffer, index, &locked);
+	if(buffer) {
+		pthread_mutex_unlock(&dup_mutex);
+		if(locked)
+			/* got a buffer being filled in.  Wait for it */
+			cache_wait_unlock(buffer);
+		goto finished;
+	}
+
+	/* not in fragment cache, is it in the reserve cache? */
+	buffer = cache_lookup_nowait(reserve_cache, index, &locked);
+	if(buffer) {
+		pthread_mutex_unlock(&dup_mutex);
+		if(locked)
+			/* got a buffer being filled in.  Wait for it */
+			cache_wait_unlock(buffer);
+		goto finished;
+	}
+
+	/* in neither cache, try to get it from the fragment cache */
+	buffer = cache_get_nowait(fragment_buffer, index);
+	if(!buffer) {
+		/*
+		 * no room, get it from the reserve cache, this is
+		 * dimensioned so it will always have space (no more than
+		 * processors + 1 can have an outstanding reserve buffer)
+		 */
+		buffer = cache_get_nowait(reserve_cache, index);
+		if(!buffer) {
+			/* failsafe */
+			ERROR("no space in reserve cache\n");
+			goto again;
+		}
+	}
+
+	pthread_mutex_unlock(&dup_mutex);
+
+	compressed_buffer = cache_lookup(fwriter_buffer, index);
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+	pthread_mutex_lock(&fragment_mutex);
+	disk_fragment = &fragment_table[index];
+	size = SQUASHFS_COMPRESSED_SIZE_BLOCK(disk_fragment->size);
+	start_block = disk_fragment->start_block;
+	pthread_cleanup_pop(1);
+
+	if(SQUASHFS_COMPRESSED_BLOCK(disk_fragment->size)) {
+		int error;
+		char *data;
+
+		if(compressed_buffer)
+			data = compressed_buffer->data;
+		else {
+			data = read_from_disk(start_block, size);
+			if(data == NULL) {
+				ERROR("Failed to read fragment from output"
+					" filesystem\n");
+				BAD_ERROR("Output filesystem corrupted?\n");
+			}
+		}
+
+		res = compressor_uncompress(comp, buffer->data, data, size,
+			block_size, &error);
+		if(res == -1)
+			BAD_ERROR("%s uncompress failed with error code %d\n",
+				comp->name, error);
+	} else if(compressed_buffer)
+		memcpy(buffer->data, compressed_buffer->data, size);
+	else {
+		res = read_fs_bytes(fd, start_block, size, buffer->data);
+		if(res == 0) {
+			ERROR("Failed to read fragment from output "
+				"filesystem\n");
+			BAD_ERROR("Output filesystem corrupted?\n");
+		}
+	}
+
+	cache_unlock(buffer);
+	cache_block_put(compressed_buffer);
+
+finished:
+	pthread_cleanup_pop(0);
+
+	return buffer;
+}
+
+
+unsigned short get_fragment_checksum(struct file_info *file)
+{
+	struct file_buffer *frag_buffer;
+	struct append_file *append;
+	int res, index = file->fragment->index;
+	unsigned short checksum;
+
+	if(index == SQUASHFS_INVALID_FRAG)
+		return 0;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+	pthread_mutex_lock(&dup_mutex);
+	res = file->have_frag_checksum;
+	checksum = file->fragment_checksum;
+	pthread_cleanup_pop(1);
+
+	if(res)
+		return checksum;
+
+	frag_buffer = get_fragment(file->fragment);
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+
+	for(append = file_mapping[index]; append; append = append->next) {
+		int offset = append->file->fragment->offset;
+		int size = append->file->fragment->size;
+		unsigned short cksum =
+			get_checksum_mem(frag_buffer->data + offset, size);
+
+		if(file == append->file)
+			checksum = cksum;
+
+		pthread_mutex_lock(&dup_mutex);
+		append->file->fragment_checksum = cksum;
+		append->file->have_frag_checksum = TRUE;
+		pthread_mutex_unlock(&dup_mutex);
+	}
+
+	cache_block_put(frag_buffer);
+	pthread_cleanup_pop(0);
+
+	return checksum;
+}
+
+
+void lock_fragments()
+{
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+	pthread_mutex_lock(&fragment_mutex);
+	fragments_locked = TRUE;
+	pthread_cleanup_pop(1);
+}
+
+
+void unlock_fragments()
+{
+	int frg, size;
+	struct file_buffer *write_buffer;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+	pthread_mutex_lock(&fragment_mutex);
+
+	/*
+	 * Note queue_empty() is inherently racy with respect to concurrent
+	 * queue get and pushes.  We avoid this because we're holding the
+	 * fragment_mutex which ensures no other threads can be using the
+	 * queue at this time.
+	 */
+	while(!queue_empty(locked_fragment)) {
+		write_buffer = queue_get(locked_fragment);
+		frg = write_buffer->block;	
+		size = SQUASHFS_COMPRESSED_SIZE_BLOCK(fragment_table[frg].size);
+		fragment_table[frg].start_block = bytes;
+		write_buffer->block = bytes;
+		bytes += size;
+		fragments_outstanding --;
+		queue_put(to_writer, write_buffer);
+		TRACE("fragment_locked writing fragment %d, compressed size %d"
+			"\n", frg, size);
+	}
+	fragments_locked = FALSE;
+	pthread_cleanup_pop(1);
+}
+
+/* Called with the fragment_mutex locked */
+void add_pending_fragment(struct file_buffer *write_buffer, int c_byte,
+	int fragment)
+{
+	fragment_table[fragment].size = c_byte;
+	write_buffer->block = fragment;
+
+	queue_put(locked_fragment, write_buffer);
+}
+
+
+void write_fragment(struct file_buffer *fragment)
+{
+	if(fragment == NULL)
+		return;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+	pthread_mutex_lock(&fragment_mutex);
+	fragment_table[fragment->block].unused = 0;
+	fragments_outstanding ++;
+	queue_put(to_frag, fragment);
+	pthread_cleanup_pop(1);
+}
+
+
+struct file_buffer *allocate_fragment()
+{
+	struct file_buffer *fragment = cache_get(fragment_buffer, fragments);
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+	pthread_mutex_lock(&fragment_mutex);
+
+	if(fragments % FRAG_SIZE == 0) {
+		void *ft = realloc(fragment_table, (fragments +
+			FRAG_SIZE) * sizeof(struct squashfs_fragment_entry));
+		if(ft == NULL)
+			MEM_ERROR();
+		fragment_table = ft;
+	}
+
+	fragment->size = 0;
+	fragment->block = fragments ++;
+
+	pthread_cleanup_pop(1);
+
+	return fragment;
+}
+
+
+static struct fragment empty_fragment = {SQUASHFS_INVALID_FRAG, 0, 0};
+
+
+void free_fragment(struct fragment *fragment)
+{
+	if(fragment != &empty_fragment)
+		free(fragment);
+}
+
+
+struct fragment *get_and_fill_fragment(struct file_buffer *file_buffer,
+	struct dir_ent *dir_ent)
+{
+	struct fragment *ffrg;
+	struct file_buffer **fragment;
+
+	if(file_buffer == NULL || file_buffer->size == 0)
+		return &empty_fragment;
+
+	fragment = eval_frag_actions(root_dir, dir_ent);
+
+	if((*fragment) && (*fragment)->size + file_buffer->size > block_size) {
+		write_fragment(*fragment);
+		*fragment = NULL;
+	}
+
+	ffrg = malloc(sizeof(struct fragment));
+	if(ffrg == NULL)
+		MEM_ERROR();
+
+	if(*fragment == NULL)
+		*fragment = allocate_fragment();
+
+	ffrg->index = (*fragment)->block;
+	ffrg->offset = (*fragment)->size;
+	ffrg->size = file_buffer->size;
+	memcpy((*fragment)->data + (*fragment)->size, file_buffer->data,
+		file_buffer->size);
+	(*fragment)->size += file_buffer->size;
+
+	return ffrg;
+}
+
+
+long long generic_write_table(int length, void *buffer, int length2,
+	void *buffer2, int uncompressed)
+{
+	int meta_blocks = (length + SQUASHFS_METADATA_SIZE - 1) /
+		SQUASHFS_METADATA_SIZE;
+	long long *list, start_bytes;
+	int compressed_size, i, list_size = meta_blocks * sizeof(long long);
+	unsigned short c_byte;
+	char cbuffer[(SQUASHFS_METADATA_SIZE << 2) + 2];
+	
+#ifdef SQUASHFS_TRACE
+	long long obytes = bytes;
+	int olength = length;
+#endif
+
+	list = malloc(list_size);
+	if(list == NULL)
+		MEM_ERROR();
+
+	for(i = 0; i < meta_blocks; i++) {
+		int avail_bytes = length > SQUASHFS_METADATA_SIZE ?
+			SQUASHFS_METADATA_SIZE : length;
+		c_byte = mangle(cbuffer + BLOCK_OFFSET, buffer + i *
+			SQUASHFS_METADATA_SIZE , avail_bytes,
+			SQUASHFS_METADATA_SIZE, uncompressed, 0);
+		SQUASHFS_SWAP_SHORTS(&c_byte, cbuffer, 1);
+		list[i] = bytes;
+		compressed_size = SQUASHFS_COMPRESSED_SIZE(c_byte) +
+			BLOCK_OFFSET;
+		TRACE("block %d @ 0x%llx, compressed size %d\n", i, bytes,
+			compressed_size);
+		write_destination(fd, bytes, compressed_size, cbuffer);
+		bytes += compressed_size;
+		total_bytes += avail_bytes;
+		length -= avail_bytes;
+	}
+
+	start_bytes = bytes;
+	if(length2) {
+		write_destination(fd, bytes, length2, buffer2);
+		bytes += length2;
+		total_bytes += length2;
+	}
+		
+	SQUASHFS_INSWAP_LONG_LONGS(list, meta_blocks);
+	write_destination(fd, bytes, list_size, list);
+	bytes += list_size;
+	total_bytes += list_size;
+
+	TRACE("generic_write_table: total uncompressed %d compressed %lld\n",
+		olength, bytes - obytes);
+
+	free(list);
+
+	return start_bytes;
+}
+
+
+long long write_fragment_table()
+{
+	unsigned int frag_bytes = SQUASHFS_FRAGMENT_BYTES(fragments);
+	int i;
+
+	TRACE("write_fragment_table: fragments %d, frag_bytes %d\n", fragments,
+		frag_bytes);
+	for(i = 0; i < fragments; i++) {
+		TRACE("write_fragment_table: fragment %d, start_block 0x%llx, "
+			"size %d\n", i, fragment_table[i].start_block,
+			fragment_table[i].size);
+		SQUASHFS_INSWAP_FRAGMENT_ENTRY(&fragment_table[i]);
+	}
+
+	return generic_write_table(frag_bytes, fragment_table, 0, NULL, noF);
+}
+
+
+char read_from_file_buffer[SQUASHFS_FILE_MAX_SIZE];
+static char *read_from_disk(long long start, unsigned int avail_bytes)
+{
+	int res;
+
+	res = read_fs_bytes(fd, start, avail_bytes, read_from_file_buffer);
+	if(res == 0)
+		return NULL;
+
+	return read_from_file_buffer;
+}
+
+
+char read_from_file_buffer2[SQUASHFS_FILE_MAX_SIZE];
+char *read_from_disk2(long long start, unsigned int avail_bytes)
+{
+	int res;
+
+	res = read_fs_bytes(fd, start, avail_bytes, read_from_file_buffer2);
+	if(res == 0)
+		return NULL;
+
+	return read_from_file_buffer2;
+}
+
+
+/*
+ * Compute 16 bit BSD checksum over the data
+ */
+unsigned short get_checksum(char *buff, int bytes, unsigned short chksum)
+{
+	unsigned char *b = (unsigned char *) buff;
+
+	while(bytes --) {
+		chksum = (chksum & 1) ? (chksum >> 1) | 0x8000 : chksum >> 1;
+		chksum += *b++;
+	}
+
+	return chksum;
+}
+
+
+unsigned short get_checksum_disk(long long start, long long l,
+	unsigned int *blocks)
+{
+	unsigned short chksum = 0;
+	unsigned int bytes;
+	struct file_buffer *write_buffer;
+	int i;
+
+	for(i = 0; l; i++)  {
+		bytes = SQUASHFS_COMPRESSED_SIZE_BLOCK(blocks[i]);
+		if(bytes == 0) /* sparse block */
+			continue;
+		write_buffer = cache_lookup(bwriter_buffer, start);
+		if(write_buffer) {
+			chksum = get_checksum(write_buffer->data, bytes,
+				chksum);
+			cache_block_put(write_buffer);
+		} else {
+			void *data = read_from_disk(start, bytes);
+			if(data == NULL) {	
+				ERROR("Failed to checksum data from output"
+					" filesystem\n");
+				BAD_ERROR("Output filesystem corrupted?\n");
+			}
+
+			chksum = get_checksum(data, bytes, chksum);
+		}
+
+		l -= bytes;
+		start += bytes;
+	}
+
+	return chksum;
+}
+
+
+unsigned short get_checksum_mem(char *buff, int bytes)
+{
+	return get_checksum(buff, bytes, 0);
+}
+
+
+unsigned short get_checksum_mem_buffer(struct file_buffer *file_buffer)
+{
+	if(file_buffer == NULL)
+		return 0;
+	else
+		return get_checksum(file_buffer->data, file_buffer->size, 0);
+}
+
+
+#define DUP_HASH(a) (a & 0xffff)
+void add_file(long long start, long long file_size, long long file_bytes,
+	unsigned int *block_listp, int blocks, unsigned int fragment,
+	int offset, int bytes)
+{
+	struct fragment *frg;
+	unsigned int *block_list = block_listp;
+	struct file_info *dupl_ptr = dupl[DUP_HASH(file_size)];
+	struct append_file *append_file;
+	struct file_info *file;
+
+	if(!duplicate_checking || file_size == 0)
+		return;
+
+	for(; dupl_ptr; dupl_ptr = dupl_ptr->next) {
+		if(file_size != dupl_ptr->file_size)
+			continue;
+		if(blocks != 0 && start != dupl_ptr->start)
+			continue;
+		if(fragment != dupl_ptr->fragment->index)
+			continue;
+		if(fragment != SQUASHFS_INVALID_FRAG && (offset !=
+				dupl_ptr->fragment->offset || bytes !=
+				dupl_ptr->fragment->size))
+			continue;
+		return;
+	}
+
+	frg = malloc(sizeof(struct fragment));
+	if(frg == NULL)
+		MEM_ERROR();
+
+	frg->index = fragment;
+	frg->offset = offset;
+	frg->size = bytes;
+
+	file = add_non_dup(file_size, file_bytes, block_list, start, frg, 0, 0,
+		FALSE, FALSE);
+
+	if(fragment == SQUASHFS_INVALID_FRAG)
+		return;
+
+	append_file = malloc(sizeof(struct append_file));
+	if(append_file == NULL)
+		MEM_ERROR();
+
+	append_file->file = file;
+	append_file->next = file_mapping[fragment];
+	file_mapping[fragment] = append_file;
+}
+
+
+int pre_duplicate(long long file_size)
+{
+	struct file_info *dupl_ptr = dupl[DUP_HASH(file_size)];
+
+	for(; dupl_ptr; dupl_ptr = dupl_ptr->next)
+		if(dupl_ptr->file_size == file_size)
+			return TRUE;
+
+	return FALSE;
+}
+
+
+struct file_info *add_non_dup(long long file_size, long long bytes,
+	unsigned int *block_list, long long start, struct fragment *fragment,
+	unsigned short checksum, unsigned short fragment_checksum,
+	int checksum_flag, int checksum_frag_flag)
+{
+	struct file_info *dupl_ptr = malloc(sizeof(struct file_info));
+
+	if(dupl_ptr == NULL)
+		MEM_ERROR();
+
+	dupl_ptr->file_size = file_size;
+	dupl_ptr->bytes = bytes;
+	dupl_ptr->block_list = block_list;
+	dupl_ptr->start = start;
+	dupl_ptr->fragment = fragment;
+	dupl_ptr->checksum = checksum;
+	dupl_ptr->fragment_checksum = fragment_checksum;
+	dupl_ptr->have_frag_checksum = checksum_frag_flag;
+	dupl_ptr->have_checksum = checksum_flag;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+        pthread_mutex_lock(&dup_mutex);
+	dupl_ptr->next = dupl[DUP_HASH(file_size)];
+	dupl[DUP_HASH(file_size)] = dupl_ptr;
+	dup_files ++;
+	pthread_cleanup_pop(1);
+
+	return dupl_ptr;
+}
+
+
+struct fragment *frag_duplicate(struct file_buffer *file_buffer, char *dont_put)
+{
+	struct file_info *dupl_ptr;
+	struct file_buffer *buffer;
+	struct file_info *dupl_start = file_buffer->dupl_start;
+	long long file_size = file_buffer->file_size;
+	unsigned short checksum = file_buffer->checksum;
+	int res;
+
+	if(file_buffer->duplicate) {
+		TRACE("Found duplicate file, fragment %d, size %d, offset %d, "
+			"checksum 0x%x\n", dupl_start->fragment->index,
+			file_size, dupl_start->fragment->offset, checksum);
+		*dont_put = TRUE;
+		return dupl_start->fragment;
+	} else {
+		*dont_put = FALSE;
+		dupl_ptr = dupl[DUP_HASH(file_size)];
+	}
+
+	for(; dupl_ptr && dupl_ptr != dupl_start; dupl_ptr = dupl_ptr->next) {
+		if(file_size == dupl_ptr->file_size && file_size ==
+				dupl_ptr->fragment->size) {
+			if(get_fragment_checksum(dupl_ptr) == checksum) {
+				buffer = get_fragment(dupl_ptr->fragment);
+				res = memcmp(file_buffer->data, buffer->data +
+					dupl_ptr->fragment->offset, file_size);
+				cache_block_put(buffer);
+				if(res == 0)
+					break;
+			}
+		}
+	}
+
+	if(!dupl_ptr || dupl_ptr == dupl_start)
+		return NULL;
+
+	TRACE("Found duplicate file, fragment %d, size %d, offset %d, "
+		"checksum 0x%x\n", dupl_ptr->fragment->index, file_size,
+		dupl_ptr->fragment->offset, checksum);
+
+	return dupl_ptr->fragment;
+}
+
+
+struct file_info *duplicate(long long file_size, long long bytes,
+	unsigned int **block_list, long long *start, struct fragment **fragment,
+	struct file_buffer *file_buffer, int blocks, unsigned short checksum,
+	int checksum_flag)
+{
+	struct file_info *dupl_ptr = dupl[DUP_HASH(file_size)];
+	int frag_bytes = file_buffer ? file_buffer->size : 0;
+	unsigned short fragment_checksum = file_buffer ?
+		file_buffer->checksum : 0;
+
+	for(; dupl_ptr; dupl_ptr = dupl_ptr->next)
+		if(file_size == dupl_ptr->file_size && bytes == dupl_ptr->bytes
+				 && frag_bytes == dupl_ptr->fragment->size) {
+			long long target_start, dup_start = dupl_ptr->start;
+			int block;
+
+			if(memcmp(*block_list, dupl_ptr->block_list, blocks *
+					sizeof(unsigned int)) != 0)
+				continue;
+
+			if(checksum_flag == FALSE) {
+				checksum = get_checksum_disk(*start, bytes,
+					*block_list);
+				checksum_flag = TRUE;
+			}
+
+			if(!dupl_ptr->have_checksum) {
+				dupl_ptr->checksum =
+					get_checksum_disk(dupl_ptr->start,
+					dupl_ptr->bytes, dupl_ptr->block_list);
+				dupl_ptr->have_checksum = TRUE;
+			}
+
+			if(checksum != dupl_ptr->checksum ||
+					fragment_checksum !=
+					get_fragment_checksum(dupl_ptr))
+				continue;
+
+			target_start = *start;
+			for(block = 0; block < blocks; block ++) {
+				int size = SQUASHFS_COMPRESSED_SIZE_BLOCK
+					((*block_list)[block]);
+				struct file_buffer *target_buffer = NULL;
+				struct file_buffer *dup_buffer = NULL;
+				char *target_data, *dup_data;
+				int res;
+
+				if(size == 0)
+					continue;
+				target_buffer = cache_lookup(bwriter_buffer,
+					target_start);
+				if(target_buffer)
+					target_data = target_buffer->data;
+				else {
+					target_data =
+						read_from_disk(target_start,
+						size);
+					if(target_data == NULL) {
+						ERROR("Failed to read data from"
+							" output filesystem\n");
+						BAD_ERROR("Output filesystem"
+							" corrupted?\n");
+					}
+				}
+
+				dup_buffer = cache_lookup(bwriter_buffer,
+					dup_start);
+				if(dup_buffer)
+					dup_data = dup_buffer->data;
+				else {
+					dup_data = read_from_disk2(dup_start,
+						size);
+					if(dup_data == NULL) {
+						ERROR("Failed to read data from"
+							" output filesystem\n");
+						BAD_ERROR("Output filesystem"
+							" corrupted?\n");
+					}
+				}
+
+				res = memcmp(target_data, dup_data, size);
+				cache_block_put(target_buffer);
+				cache_block_put(dup_buffer);
+				if(res != 0)
+					break;
+				target_start += size;
+				dup_start += size;
+			}
+			if(block == blocks) {
+				struct file_buffer *frag_buffer =
+					get_fragment(dupl_ptr->fragment);
+
+				if(frag_bytes == 0 ||
+						memcmp(file_buffer->data,
+						frag_buffer->data +
+						dupl_ptr->fragment->offset,
+						frag_bytes) == 0) {
+					TRACE("Found duplicate file, start "
+						"0x%llx, size %lld, checksum "
+						"0x%x, fragment %d, size %d, "
+						"offset %d, checksum 0x%x\n",
+						dupl_ptr->start,
+						dupl_ptr->bytes,
+						dupl_ptr->checksum,
+						dupl_ptr->fragment->index,
+						frag_bytes,
+						dupl_ptr->fragment->offset,
+						fragment_checksum);
+					*block_list = dupl_ptr->block_list;
+					*start = dupl_ptr->start;
+					*fragment = dupl_ptr->fragment;
+					cache_block_put(frag_buffer);
+					return 0;
+				}
+				cache_block_put(frag_buffer);
+			}
+		}
+
+
+	return add_non_dup(file_size, bytes, *block_list, *start, *fragment,
+		checksum, fragment_checksum, checksum_flag, TRUE);
+}
+
+
+static inline int is_fragment(struct inode_info *inode)
+{
+	off_t file_size = inode->buf.st_size;
+
+	/*
+	 * If this block is to be compressed differently to the
+	 * fragment compression then it cannot be a fragment
+	 */
+	if(inode->noF != noF)
+		return FALSE;
+
+	return !inode->no_fragments && file_size && (file_size < block_size ||
+		(inode->always_use_fragments && file_size & (block_size - 1)));
+}
+
+
+void put_file_buffer(struct file_buffer *file_buffer)
+{
+	/*
+	 * Decide where to send the file buffer:
+	 * - compressible non-fragment blocks go to the deflate threads,
+	 * - fragments go to the process fragment threads,
+	 * - all others go directly to the main thread
+	 */
+	if(file_buffer->error) {
+		file_buffer->fragment = 0;
+		seq_queue_put(to_main, file_buffer);
+	} else if (file_buffer->file_size == 0)
+		seq_queue_put(to_main, file_buffer);
+ 	else if(file_buffer->fragment)
+		queue_put(to_process_frag, file_buffer);
+	else
+		queue_put(to_deflate, file_buffer);
+}
+
+
+static int seq = 0;
+void reader_read_process(struct dir_ent *dir_ent)
+{
+	long long bytes = 0;
+	struct inode_info *inode = dir_ent->inode;
+	struct file_buffer *prev_buffer = NULL, *file_buffer;
+	int status, byte, res, child;
+	int file = pseudo_exec_file(get_pseudo_file(inode->pseudo_id), &child);
+
+	if(!file) {
+		file_buffer = cache_get_nohash(reader_buffer);
+		file_buffer->sequence = seq ++;
+		goto read_err;
+	}
+
+	while(1) {
+		file_buffer = cache_get_nohash(reader_buffer);
+		file_buffer->sequence = seq ++;
+		file_buffer->noD = inode->noD;
+
+		byte = read_bytes(file, file_buffer->data, block_size);
+		if(byte == -1)
+			goto read_err2;
+
+		file_buffer->size = byte;
+		file_buffer->file_size = -1;
+		file_buffer->error = FALSE;
+		file_buffer->fragment = FALSE;
+		bytes += byte;
+
+		if(byte == 0)
+			break;
+
+		/*
+		 * Update progress bar size.  This is done
+		 * on every block rather than waiting for all blocks to be
+		 * read incase write_file_process() is running in parallel
+		 * with this.  Otherwise the current progress bar position
+		 * may get ahead of the progress bar size.
+		 */ 
+		progress_bar_size(1);
+
+		if(prev_buffer)
+			put_file_buffer(prev_buffer);
+		prev_buffer = file_buffer;
+	}
+
+	/*
+ 	 * Update inode file size now that the size of the dynamic pseudo file
+	 * is known.  This is needed for the -info option.
+	 */
+	inode->buf.st_size = bytes;
+
+	res = waitpid(child, &status, 0);
+	close(file);
+
+	if(res == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
+		goto read_err;
+
+	if(prev_buffer == NULL)
+		prev_buffer = file_buffer;
+	else {
+		cache_block_put(file_buffer);
+		seq --;
+	}
+	prev_buffer->file_size = bytes;
+	prev_buffer->fragment = is_fragment(inode);
+	put_file_buffer(prev_buffer);
+
+	return;
+
+read_err2:
+	close(file);
+read_err:
+	if(prev_buffer) {
+		cache_block_put(file_buffer);
+		seq --;
+		file_buffer = prev_buffer;
+	}
+	file_buffer->error = TRUE;
+	put_file_buffer(file_buffer);
+}
+
+
+void reader_read_file(struct dir_ent *dir_ent)
+{
+	struct stat *buf = &dir_ent->inode->buf, buf2;
+	struct file_buffer *file_buffer;
+	int blocks, file, res;
+	long long bytes, read_size;
+	struct inode_info *inode = dir_ent->inode;
+
+	if(inode->read)
+		return;
+
+	inode->read = TRUE;
+again:
+	bytes = 0;
+	read_size = buf->st_size;
+	blocks = (read_size + block_size - 1) >> block_log;
+
+	file = open(pathname_reader(dir_ent), O_RDONLY);
+	if(file == -1) {
+		file_buffer = cache_get_nohash(reader_buffer);
+		file_buffer->sequence = seq ++;
+		goto read_err2;
+	}
+
+	do {
+		file_buffer = cache_get_nohash(reader_buffer);
+		file_buffer->file_size = read_size;
+		file_buffer->sequence = seq ++;
+		file_buffer->noD = inode->noD;
+		file_buffer->error = FALSE;
+
+		/*
+		 * Always try to read block_size bytes from the file rather
+		 * than expected bytes (which will be less than the block_size
+		 * at the file tail) to check that the file hasn't grown
+		 * since being stated.  If it is longer (or shorter) than
+		 * expected, then restat, and try again.  Note the special
+		 * case where the file is an exact multiple of the block_size
+		 * is dealt with later.
+		 */
+		file_buffer->size = read_bytes(file, file_buffer->data,
+			block_size);
+		if(file_buffer->size == -1)
+			goto read_err;
+
+		bytes += file_buffer->size;
+
+		if(blocks > 1) {
+			/* non-tail block should be exactly block_size */
+			if(file_buffer->size < block_size)
+				goto restat;
+
+			file_buffer->fragment = FALSE;
+			put_file_buffer(file_buffer);
+		}
+	} while(-- blocks > 0);
+
+	/* Overall size including tail should match */
+	if(read_size != bytes)
+		goto restat;
+
+	if(read_size && read_size % block_size == 0) {
+		/*
+		 * Special case where we've not tried to read past the end of
+		 * the file.  We expect to get EOF, i.e. the file isn't larger
+		 * than we expect.
+		 */
+		char buffer;
+		int res;
+
+		res = read_bytes(file, &buffer, 1);
+		if(res == -1)
+			goto read_err;
+
+		if(res != 0)
+			goto restat;
+	}
+
+	file_buffer->fragment = is_fragment(inode);
+	put_file_buffer(file_buffer);
+
+	close(file);
+
+	return;
+
+restat:
+	res = fstat(file, &buf2);
+	if(res == -1) {
+		ERROR("Cannot stat dir/file %s because %s\n",
+			pathname_reader(dir_ent), strerror(errno));
+		goto read_err;
+	}
+
+	if(read_size != buf2.st_size) {
+		close(file);
+		memcpy(buf, &buf2, sizeof(struct stat));
+		file_buffer->error = 2;
+		put_file_buffer(file_buffer);
+		goto again;
+	}
+read_err:
+	close(file);
+read_err2:
+	file_buffer->error = TRUE;
+	put_file_buffer(file_buffer);
+}
+
+
+void reader_scan(struct dir_info *dir) {
+	struct dir_ent *dir_ent = dir->list;
+
+	for(; dir_ent; dir_ent = dir_ent->next) {
+		struct stat *buf = &dir_ent->inode->buf;
+		if(dir_ent->inode->root_entry)
+			continue;
+
+		if(IS_PSEUDO_PROCESS(dir_ent->inode)) {
+			reader_read_process(dir_ent);
+			continue;
+		}
+
+		switch(buf->st_mode & S_IFMT) {
+			case S_IFREG:
+				reader_read_file(dir_ent);
+				break;
+			case S_IFDIR:
+				reader_scan(dir_ent->dir);
+				break;
+		}
+	}
+}
+
+
+void *reader(void *arg)
+{
+	if(!sorted)
+		reader_scan(queue_get(to_reader));
+	else {
+		int i;
+		struct priority_entry *entry;
+
+		queue_get(to_reader);
+		for(i = 65535; i >= 0; i--)
+			for(entry = priority_list[i]; entry;
+							entry = entry->next)
+				reader_read_file(entry->dir);
+	}
+
+	pthread_exit(NULL);
+}
+
+
+void *writer(void *arg)
+{
+	while(1) {
+		struct file_buffer *file_buffer = queue_get(to_writer);
+		off_t off;
+
+		if(file_buffer == NULL) {
+			queue_put(from_writer, NULL);
+			continue;
+		}
+
+		off = file_buffer->block;
+
+		pthread_cleanup_push((void *) pthread_mutex_unlock, &pos_mutex);
+		pthread_mutex_lock(&pos_mutex);
+
+		if(lseek(fd, off, SEEK_SET) == -1) {
+			ERROR("writer: Lseek on destination failed because "
+				"%s, offset=0x%llx\n", strerror(errno), off);
+			BAD_ERROR("Probably out of space on output "
+				"%s\n", block_device ? "block device" :
+				"filesystem");
+		}
+
+		if(write_bytes(fd, file_buffer->data,
+				file_buffer->size) == -1)
+			BAD_ERROR("Failed to write to output %s\n",
+				block_device ? "block device" : "filesystem");
+
+		pthread_cleanup_pop(1);
+
+		cache_block_put(file_buffer);
+	}
+}
+
+
+int all_zero(struct file_buffer *file_buffer)
+{
+	int i;
+	long entries = file_buffer->size / sizeof(long);
+	long *p = (long *) file_buffer->data;
+
+	for(i = 0; i < entries && p[i] == 0; i++);
+
+	if(i == entries) {
+		for(i = file_buffer->size & ~(sizeof(long) - 1);
+			i < file_buffer->size && file_buffer->data[i] == 0;
+			i++);
+
+		return i == file_buffer->size;
+	}
+
+	return 0;
+}
+
+
+void *deflator(void *arg)
+{
+	struct file_buffer *write_buffer = cache_get_nohash(bwriter_buffer);
+	void *stream = NULL;
+	int res;
+
+	res = compressor_init(comp, &stream, block_size, 1);
+	if(res)
+		BAD_ERROR("deflator:: compressor_init failed\n");
+
+	while(1) {
+		struct file_buffer *file_buffer = queue_get(to_deflate);
+
+		if(sparse_files && all_zero(file_buffer)) { 
+			file_buffer->c_byte = 0;
+			seq_queue_put(to_main, file_buffer);
+		} else {
+			write_buffer->c_byte = mangle2(stream,
+				write_buffer->data, file_buffer->data,
+				file_buffer->size, block_size,
+				file_buffer->noD, 1);
+			write_buffer->sequence = file_buffer->sequence;
+			write_buffer->file_size = file_buffer->file_size;
+			write_buffer->block = file_buffer->block;
+			write_buffer->size = SQUASHFS_COMPRESSED_SIZE_BLOCK
+				(write_buffer->c_byte);
+			write_buffer->fragment = FALSE;
+			write_buffer->error = FALSE;
+			cache_block_put(file_buffer);
+			seq_queue_put(to_main, write_buffer);
+			write_buffer = cache_get_nohash(bwriter_buffer);
+		}
+	}
+}
+
+
+void *frag_deflator(void *arg)
+{
+	void *stream = NULL;
+	int res;
+
+	res = compressor_init(comp, &stream, block_size, 1);
+	if(res)
+		BAD_ERROR("frag_deflator:: compressor_init failed\n");
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+
+	while(1) {
+		int c_byte, compressed_size;
+		struct file_buffer *file_buffer = queue_get(to_frag);
+		struct file_buffer *write_buffer =
+			cache_get(fwriter_buffer, file_buffer->block);
+
+		c_byte = mangle2(stream, write_buffer->data, file_buffer->data,
+			file_buffer->size, block_size, noF, 1);
+		compressed_size = SQUASHFS_COMPRESSED_SIZE_BLOCK(c_byte);
+		write_buffer->size = compressed_size;
+		pthread_mutex_lock(&fragment_mutex);
+		if(fragments_locked == FALSE) {
+			fragment_table[file_buffer->block].size = c_byte;
+			fragment_table[file_buffer->block].start_block = bytes;
+			write_buffer->block = bytes;
+			bytes += compressed_size;
+			fragments_outstanding --;
+			queue_put(to_writer, write_buffer);
+			pthread_mutex_unlock(&fragment_mutex);
+			TRACE("Writing fragment %lld, uncompressed size %d, "
+				"compressed size %d\n", file_buffer->block,
+				file_buffer->size, compressed_size);
+		} else {
+				add_pending_fragment(write_buffer, c_byte,
+					file_buffer->block);
+				pthread_mutex_unlock(&fragment_mutex);
+		}
+		cache_block_put(file_buffer);
+	}
+
+	pthread_cleanup_pop(0);
+}
+
+
+struct file_buffer *get_file_buffer()
+{
+	struct file_buffer *file_buffer = seq_queue_get(to_main);
+
+	return file_buffer;
+}
+
+
+void write_file_empty(squashfs_inode *inode, struct dir_ent *dir_ent,
+	struct file_buffer *file_buffer, int *duplicate_file)
+{
+	file_count ++;
+	*duplicate_file = FALSE;
+	cache_block_put(file_buffer);
+	create_inode(inode, NULL, dir_ent, SQUASHFS_FILE_TYPE, 0, 0, 0,
+		 NULL, &empty_fragment, NULL, 0);
+}
+
+
+void write_file_frag(squashfs_inode *inode, struct dir_ent *dir_ent,
+	struct file_buffer *file_buffer, int *duplicate_file)
+{
+	int size = file_buffer->file_size;
+	struct fragment *fragment;
+	unsigned short checksum = file_buffer->checksum;
+	char dont_put;
+
+	fragment = frag_duplicate(file_buffer, &dont_put);
+	*duplicate_file = !fragment;
+	if(!fragment) {
+		fragment = get_and_fill_fragment(file_buffer, dir_ent);
+		if(duplicate_checking)
+			add_non_dup(size, 0, NULL, 0, fragment, 0, checksum,
+				TRUE, TRUE);
+	}
+
+	if(dont_put)
+		free(file_buffer);
+	else
+		cache_block_put(file_buffer);
+
+	total_bytes += size;
+	file_count ++;
+
+	inc_progress_bar();
+
+	create_inode(inode, NULL, dir_ent, SQUASHFS_FILE_TYPE, size, 0,
+			0, NULL, fragment, NULL, 0);
+
+	if(!duplicate_checking)
+		free_fragment(fragment);
+}
+
+
+int write_file_process(squashfs_inode *inode, struct dir_ent *dir_ent,
+	struct file_buffer *read_buffer, int *duplicate_file)
+{
+	long long read_size, file_bytes, start;
+	struct fragment *fragment;
+	unsigned int *block_list = NULL;
+	int block = 0, status;
+	long long sparse = 0;
+	struct file_buffer *fragment_buffer = NULL;
+
+	*duplicate_file = FALSE;
+
+	lock_fragments();
+
+	file_bytes = 0;
+	start = bytes;
+	while (1) {
+		read_size = read_buffer->file_size;
+		if(read_buffer->fragment)
+			fragment_buffer = read_buffer;
+		else {
+			block_list = realloc(block_list, (block + 1) *
+				sizeof(unsigned int));
+			if(block_list == NULL)
+				MEM_ERROR();
+			block_list[block ++] = read_buffer->c_byte;
+			if(read_buffer->c_byte) {
+				read_buffer->block = bytes;
+				bytes += read_buffer->size;
+				cache_hash(read_buffer, read_buffer->block);
+				file_bytes += read_buffer->size;
+				queue_put(to_writer, read_buffer);
+			} else {
+				sparse += read_buffer->size;
+				cache_block_put(read_buffer);
+			}
+		}
+		inc_progress_bar();
+
+		if(read_size != -1)
+			break;
+
+		read_buffer = get_file_buffer();
+		if(read_buffer->error)
+			goto read_err;
+	}
+
+	unlock_fragments();
+	fragment = get_and_fill_fragment(fragment_buffer, dir_ent);
+
+	if(duplicate_checking)
+		add_non_dup(read_size, file_bytes, block_list, start, fragment,
+			0, fragment_buffer ? fragment_buffer->checksum : 0,
+			FALSE, TRUE);
+	cache_block_put(fragment_buffer);
+	file_count ++;
+	total_bytes += read_size;
+
+	create_inode(inode, NULL, dir_ent, SQUASHFS_FILE_TYPE, read_size, start,
+		 block, block_list, fragment, NULL, sparse);
+
+	if(duplicate_checking == FALSE) {
+		free(block_list);
+		free_fragment(fragment);
+	}
+
+	return 0;
+
+read_err:
+	dec_progress_bar(block);
+	status = read_buffer->error;
+	bytes = start;
+	if(!block_device) {
+		int res;
+
+		queue_put(to_writer, NULL);
+		if(queue_get(from_writer) != 0)
+			EXIT_MKSQUASHFS();
+		res = ftruncate(fd, bytes);
+		if(res != 0)
+			BAD_ERROR("Failed to truncate dest file because %s\n",
+				strerror(errno));
+	}
+	unlock_fragments();
+	free(block_list);
+	cache_block_put(read_buffer);
+	return status;
+}
+
+
+int write_file_blocks_dup(squashfs_inode *inode, struct dir_ent *dir_ent,
+	struct file_buffer *read_buffer, int *duplicate_file)
+{
+	int block, thresh;
+	long long read_size = read_buffer->file_size;
+	long long file_bytes, dup_start, start;
+	struct fragment *fragment;
+	struct file_info *dupl_ptr;
+	int blocks = (read_size + block_size - 1) >> block_log;
+	unsigned int *block_list, *block_listp;
+	struct file_buffer **buffer_list;
+	int status;
+	long long sparse = 0;
+	struct file_buffer *fragment_buffer = NULL;
+
+	block_list = malloc(blocks * sizeof(unsigned int));
+	if(block_list == NULL)
+		MEM_ERROR();
+	block_listp = block_list;
+
+	buffer_list = malloc(blocks * sizeof(struct file_buffer *));
+	if(buffer_list == NULL)
+		MEM_ERROR();
+
+	lock_fragments();
+
+	file_bytes = 0;
+	start = dup_start = bytes;
+	thresh = blocks > bwriter_size ? blocks - bwriter_size : 0;
+
+	for(block = 0; block < blocks;) {
+		if(read_buffer->fragment) {
+			block_list[block] = 0;
+			buffer_list[block] = NULL;
+			fragment_buffer = read_buffer;
+			blocks = read_size >> block_log;
+		} else {
+			block_list[block] = read_buffer->c_byte;
+
+			if(read_buffer->c_byte) {
+				read_buffer->block = bytes;
+				bytes += read_buffer->size;
+				file_bytes += read_buffer->size;
+				cache_hash(read_buffer, read_buffer->block);
+				if(block < thresh) {
+					buffer_list[block] = NULL;
+					queue_put(to_writer, read_buffer);
+				} else
+					buffer_list[block] = read_buffer;
+			} else {
+				buffer_list[block] = NULL;
+				sparse += read_buffer->size;
+				cache_block_put(read_buffer);
+			}
+		}
+		inc_progress_bar();
+
+		if(++block < blocks) {
+			read_buffer = get_file_buffer();
+			if(read_buffer->error)
+				goto read_err;
+		}
+	}
+
+	dupl_ptr = duplicate(read_size, file_bytes, &block_listp, &dup_start,
+		&fragment, fragment_buffer, blocks, 0, FALSE);
+
+	if(dupl_ptr) {
+		*duplicate_file = FALSE;
+		for(block = thresh; block < blocks; block ++)
+			if(buffer_list[block])
+				queue_put(to_writer, buffer_list[block]);
+		fragment = get_and_fill_fragment(fragment_buffer, dir_ent);
+		dupl_ptr->fragment = fragment;
+	} else {
+		*duplicate_file = TRUE;
+		for(block = thresh; block < blocks; block ++)
+			cache_block_put(buffer_list[block]);
+		bytes = start;
+		if(thresh && !block_device) {
+			int res;
+
+			queue_put(to_writer, NULL);
+			if(queue_get(from_writer) != 0)
+				EXIT_MKSQUASHFS();
+			res = ftruncate(fd, bytes);
+			if(res != 0)
+				BAD_ERROR("Failed to truncate dest file because"
+					"  %s\n", strerror(errno));
+		}
+	}
+
+	unlock_fragments();
+	cache_block_put(fragment_buffer);
+	free(buffer_list);
+	file_count ++;
+	total_bytes += read_size;
+
+	/*
+	 * sparse count is needed to ensure squashfs correctly reports a
+ 	 * a smaller block count on stat calls to sparse files.  This is
+ 	 * to ensure intelligent applications like cp correctly handle the
+ 	 * file as a sparse file.  If the file in the original filesystem isn't
+ 	 * stored as a sparse file then still store it sparsely in squashfs, but
+ 	 * report it as non-sparse on stat calls to preserve semantics
+ 	 */
+	if(sparse && (dir_ent->inode->buf.st_blocks << 9) >= read_size)
+		sparse = 0;
+
+	create_inode(inode, NULL, dir_ent, SQUASHFS_FILE_TYPE, read_size,
+		dup_start, blocks, block_listp, fragment, NULL, sparse);
+
+	if(*duplicate_file == TRUE)
+		free(block_list);
+
+	return 0;
+
+read_err:
+	dec_progress_bar(block);
+	status = read_buffer->error;
+	bytes = start;
+	if(thresh && !block_device) {
+		int res;
+
+		queue_put(to_writer, NULL);
+		if(queue_get(from_writer) != 0)
+			EXIT_MKSQUASHFS();
+		res = ftruncate(fd, bytes);
+		if(res != 0)
+			BAD_ERROR("Failed to truncate dest file because %s\n",
+				strerror(errno));
+	}
+	unlock_fragments();
+	for(blocks = thresh; blocks < block; blocks ++)
+		cache_block_put(buffer_list[blocks]);
+	free(buffer_list);
+	free(block_list);
+	cache_block_put(read_buffer);
+	return status;
+}
+
+
+int write_file_blocks(squashfs_inode *inode, struct dir_ent *dir_ent,
+	struct file_buffer *read_buffer, int *dup)
+{
+	long long read_size = read_buffer->file_size;
+	long long file_bytes, start;
+	struct fragment *fragment;
+	unsigned int *block_list;
+	int block, status;
+	int blocks = (read_size + block_size - 1) >> block_log;
+	long long sparse = 0;
+	struct file_buffer *fragment_buffer = NULL;
+
+	if(pre_duplicate(read_size))
+		return write_file_blocks_dup(inode, dir_ent, read_buffer, dup);
+
+	*dup = FALSE;
+
+	block_list = malloc(blocks * sizeof(unsigned int));
+	if(block_list == NULL)
+		MEM_ERROR();
+
+	lock_fragments();
+
+	file_bytes = 0;
+	start = bytes;
+	for(block = 0; block < blocks;) {
+		if(read_buffer->fragment) {
+			block_list[block] = 0;
+			fragment_buffer = read_buffer;
+			blocks = read_size >> block_log;
+		} else {
+			block_list[block] = read_buffer->c_byte;
+			if(read_buffer->c_byte) {
+				read_buffer->block = bytes;
+				bytes += read_buffer->size;
+				cache_hash(read_buffer, read_buffer->block);
+				file_bytes += read_buffer->size;
+				queue_put(to_writer, read_buffer);
+			} else {
+				sparse += read_buffer->size;
+				cache_block_put(read_buffer);
+			}
+		}
+		inc_progress_bar();
+
+		if(++block < blocks) {
+			read_buffer = get_file_buffer();
+			if(read_buffer->error)
+				goto read_err;
+		}
+	}
+
+	unlock_fragments();
+	fragment = get_and_fill_fragment(fragment_buffer, dir_ent);
+
+	if(duplicate_checking)
+		add_non_dup(read_size, file_bytes, block_list, start, fragment,
+			0, fragment_buffer ? fragment_buffer->checksum : 0,
+			FALSE, TRUE);
+	cache_block_put(fragment_buffer);
+	file_count ++;
+	total_bytes += read_size;
+
+	/*
+	 * sparse count is needed to ensure squashfs correctly reports a
+ 	 * a smaller block count on stat calls to sparse files.  This is
+ 	 * to ensure intelligent applications like cp correctly handle the
+ 	 * file as a sparse file.  If the file in the original filesystem isn't
+ 	 * stored as a sparse file then still store it sparsely in squashfs, but
+ 	 * report it as non-sparse on stat calls to preserve semantics
+ 	 */
+	if(sparse && (dir_ent->inode->buf.st_blocks << 9) >= read_size)
+		sparse = 0;
+
+	create_inode(inode, NULL, dir_ent, SQUASHFS_FILE_TYPE, read_size, start,
+		 blocks, block_list, fragment, NULL, sparse);
+
+	if(duplicate_checking == FALSE) {
+		free(block_list);
+		free_fragment(fragment);
+	}
+
+	return 0;
+
+read_err:
+	dec_progress_bar(block);
+	status = read_buffer->error;
+	bytes = start;
+	if(!block_device) {
+		int res;
+
+		queue_put(to_writer, NULL);
+		if(queue_get(from_writer) != 0)
+			EXIT_MKSQUASHFS();
+		res = ftruncate(fd, bytes);
+		if(res != 0)
+			BAD_ERROR("Failed to truncate dest file because %s\n",
+				strerror(errno));
+	}
+	unlock_fragments();
+	free(block_list);
+	cache_block_put(read_buffer);
+	return status;
+}
+
+
+void write_file(squashfs_inode *inode, struct dir_ent *dir, int *dup)
+{
+	int status;
+	struct file_buffer *read_buffer;
+
+again:
+	read_buffer = get_file_buffer();
+	status = read_buffer->error;
+
+	if(status)
+		cache_block_put(read_buffer);
+	else if(read_buffer->file_size == -1)
+		status = write_file_process(inode, dir, read_buffer, dup);
+	else if(read_buffer->file_size == 0)
+		write_file_empty(inode, dir, read_buffer, dup);
+	else if(read_buffer->fragment && read_buffer->c_byte)
+		write_file_frag(inode, dir, read_buffer, dup);
+	else
+		status = write_file_blocks(inode, dir, read_buffer, dup);
+
+	if(status == 2) {
+		ERROR("File %s changed size while reading filesystem, "
+			"attempting to re-read\n", pathname(dir));
+		goto again;
+	} else if(status == 1) {
+		ERROR_START("Failed to read file %s", pathname(dir));
+		ERROR_EXIT(", creating empty file\n");
+		write_file_empty(inode, dir, NULL, dup);
+	}
+}
+
+
+#define BUFF_SIZE 512
+char *name;
+char *basename_r();
+
+char *getbase(char *pathname)
+{
+	static char *b_buffer = NULL;
+	static int b_size = BUFF_SIZE;
+	char *result;
+
+	if(b_buffer == NULL) {
+		b_buffer = malloc(b_size);
+		if(b_buffer == NULL)
+			MEM_ERROR();
+	}
+
+	while(1) {
+		if(*pathname != '/') {
+			result = getcwd(b_buffer, b_size);
+			if(result == NULL && errno != ERANGE)
+				BAD_ERROR("Getcwd failed in getbase\n");
+
+			/* enough room for pathname + "/" + '\0' terminator? */
+			if(result && strlen(pathname) + 2 <=
+						b_size - strlen(b_buffer)) {
+				strcat(strcat(b_buffer, "/"), pathname);
+				break;
+			}
+		} else if(strlen(pathname) < b_size) {
+			strcpy(b_buffer, pathname);
+			break;
+		}
+
+		/* Buffer not large enough, realloc and try again */
+		b_buffer = realloc(b_buffer, b_size += BUFF_SIZE);
+		if(b_buffer == NULL)
+			MEM_ERROR();
+	}
+
+	name = b_buffer;
+	if(((result = basename_r()) == NULL) || (strcmp(result, "..") == 0))
+		return NULL;
+	else
+		return result;
+}
+
+
+char *basename_r()
+{
+	char *s;
+	char *p;
+	int n = 1;
+
+	for(;;) {
+		s = name;
+		if(*name == '\0')
+			return NULL;
+		if(*name != '/') {
+			while(*name != '\0' && *name != '/') name++;
+			n = name - s;
+		}
+		while(*name == '/') name++;
+		if(strncmp(s, ".", n) == 0)
+			continue;
+		if((*name == '\0') || (strncmp(s, "..", n) == 0) ||
+				((p = basename_r()) == NULL)) {
+			s[n] = '\0';
+			return s;
+		}
+		if(strcmp(p, "..") == 0)
+			continue;
+		return p;
+	}
+}
+
+
+struct inode_info *lookup_inode3(struct stat *buf, int pseudo, int id,
+	char *symlink, int bytes)
+{
+	int ino_hash = INODE_HASH(buf->st_dev, buf->st_ino);
+	struct inode_info *inode;
+
+	/*
+	 * Look-up inode in hash table, if it already exists we have a
+	 * hard-link, so increment the nlink count and return it.
+	 * Don't do the look-up for directories because we don't hard-link
+	 * directories.
+	 */
+	if ((buf->st_mode & S_IFMT) != S_IFDIR) {
+		for(inode = inode_info[ino_hash]; inode; inode = inode->next) {
+			if(memcmp(buf, &inode->buf, sizeof(struct stat)) == 0) {
+				inode->nlink ++;
+				return inode;
+			}
+		}
+	}
+
+	inode = malloc(sizeof(struct inode_info) + bytes);
+	if(inode == NULL)
+		MEM_ERROR();
+
+	if(bytes)
+		memcpy(&inode->symlink, symlink, bytes);
+	memcpy(&inode->buf, buf, sizeof(struct stat));
+	inode->read = FALSE;
+	inode->root_entry = FALSE;
+	inode->pseudo_file = pseudo;
+	inode->pseudo_id = id;
+	inode->inode = SQUASHFS_INVALID_BLK;
+	inode->nlink = 1;
+	inode->inode_number = 0;
+
+	/*
+	 * Copy filesystem wide defaults into inode, these filesystem
+	 * wide defaults may be altered on an individual inode basis by
+	 * user specified actions
+	 *
+	*/
+	inode->no_fragments = no_fragments;
+	inode->always_use_fragments = always_use_fragments;
+	inode->noD = noD;
+	inode->noF = noF;
+
+	inode->next = inode_info[ino_hash];
+	inode_info[ino_hash] = inode;
+
+	return inode;
+}
+
+
+static inline struct inode_info *lookup_inode2(struct stat *buf, int pseudo, int id)
+{
+	return lookup_inode3(buf, pseudo, id, NULL, 0);
+}
+
+
+static inline struct inode_info *lookup_inode(struct stat *buf)
+{
+	return lookup_inode2(buf, 0, 0);
+}
+
+
+static inline void alloc_inode_no(struct inode_info *inode, unsigned int use_this)
+{
+	if (inode->inode_number == 0) {
+		inode->inode_number = use_this ? : inode_no ++;
+		if((inode->buf.st_mode & S_IFMT) == S_IFREG)
+			progress_bar_size((inode->buf.st_size + block_size - 1)
+								 >> block_log);
+	}
+}
+
+
+static inline struct dir_ent *create_dir_entry(char *name, char *source_name,
+	char *nonstandard_pathname, struct dir_info *dir)
+{
+	struct dir_ent *dir_ent = malloc(sizeof(struct dir_ent));
+	if(dir_ent == NULL)
+		MEM_ERROR();
+
+	dir_ent->name = name;
+	dir_ent->source_name = source_name;
+	dir_ent->nonstandard_pathname = nonstandard_pathname;
+	dir_ent->our_dir = dir;
+	dir_ent->inode = NULL;
+	dir_ent->next = NULL;
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+	dir_ent->capabilities = 0;
+#endif
+/* ANDROID CHANGES END */
+
+	return dir_ent;
+}
+
+
+static inline void add_dir_entry(struct dir_ent *dir_ent, struct dir_info *sub_dir,
+	struct inode_info *inode_info)
+{
+	struct dir_info *dir = dir_ent->our_dir;
+
+	if(sub_dir)
+		sub_dir->dir_ent = dir_ent;
+
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+	if (android_config) {
+		if (mount_point) {
+			char *mounted_path;
+			char *rel_path;
+
+			alloc_mounted_path(mount_point, subpathname(dir_ent), &mounted_path);
+			rel_path = mounted_path;
+			while (rel_path && *rel_path == '/')
+				rel_path++;
+			android_fs_config(fs_config_func, rel_path, &inode_info->buf, target_out_path, &dir_ent->capabilities);
+			free(mounted_path);
+		} else {
+			android_fs_config(fs_config_func, pathname(dir_ent), &inode_info->buf, target_out_path, &dir_ent->capabilities);
+		}
+	}
+#endif
+/* ANDROID CHANGES END */
+
+	dir_ent->inode = inode_info;
+	dir_ent->dir = sub_dir;
+
+	dir_ent->next = dir->list;
+	dir->list = dir_ent;
+	dir->count++;
+}
+
+static inline void add_dir_entry2(char *name, char *source_name,
+	char *nonstandard_pathname, struct dir_info *sub_dir,
+	struct inode_info *inode_info, struct dir_info *dir)
+{
+	struct dir_ent *dir_ent = create_dir_entry(name, source_name,
+		nonstandard_pathname, dir);
+
+
+	add_dir_entry(dir_ent, sub_dir, inode_info);
+}
+
+
+static inline void free_dir_entry(struct dir_ent *dir_ent)
+{
+	if(dir_ent->name)
+		free(dir_ent->name);
+
+	if(dir_ent->source_name)
+		free(dir_ent->source_name);
+
+	if(dir_ent->nonstandard_pathname)
+		free(dir_ent->nonstandard_pathname);
+
+	/* if this entry has been associated with an inode, then we need
+	 * to update the inode nlink count.  Orphaned inodes are harmless, and
+	 * is easier to leave them than go to the bother of deleting them */
+	if(dir_ent->inode && !dir_ent->inode->root_entry)
+		dir_ent->inode->nlink --;
+
+	free(dir_ent);
+}
+
+
+static inline void add_excluded(struct dir_info *dir)
+{
+	dir->excluded ++;
+}
+
+
+void dir_scan(squashfs_inode *inode, char *pathname,
+	struct dir_ent *(_readdir)(struct dir_info *), int progress)
+{
+	struct stat buf;
+	struct dir_ent *dir_ent;
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+	uint64_t caps = 0;
+#endif
+/* ANDROID CHANGES END */
+	
+	root_dir = dir_scan1(pathname, "", paths, _readdir, 1);
+	if(root_dir == NULL)
+		return;
+
+	/* Create root directory dir_ent and associated inode, and connect
+	 * it to the root directory dir_info structure */
+	dir_ent = create_dir_entry("", NULL, pathname,
+						scan1_opendir("", "", 0));
+
+	if(pathname[0] == '\0') {
+		/*
+ 		 * dummy top level directory, if multiple sources specified on
+		 * command line
+		 */
+		memset(&buf, 0, sizeof(buf));
+		buf.st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR;
+		buf.st_uid = getuid();
+		buf.st_gid = getgid();
+		buf.st_mtime = time(NULL);
+		buf.st_dev = 0;
+		buf.st_ino = 0;
+		dir_ent->inode = lookup_inode2(&buf, PSEUDO_FILE_OTHER, 0);
+	} else {
+		if(lstat(pathname, &buf) == -1)
+			/* source directory has disappeared? */
+			BAD_ERROR("Cannot stat source directory %s because %s\n",
+				pathname, strerror(errno));
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+		if (android_config) {
+			if (mount_point)
+				android_fs_config(fs_config_func, mount_point, &buf, target_out_path, &caps);
+			else
+				android_fs_config(fs_config_func, pathname, &buf, target_out_path, &caps);
+		}
+#endif
+/* ANDROID CHANGES END */
+		dir_ent->inode = lookup_inode(&buf);
+	}
+
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+	dir_ent->capabilities = caps;
+#endif
+/* ANDROID CHANGES END */
+
+	dir_ent->dir = root_dir;
+	root_dir->dir_ent = dir_ent;
+
+	/*
+	 * Process most actions and any pseudo files
+	 */
+	if(actions() || get_pseudo())
+		dir_scan2(root_dir, get_pseudo());
+
+	/*
+	 * Process move actions
+	 */
+	if(move_actions()) {
+		dir_scan3(root_dir);
+		do_move_actions();
+	}
+
+	/*
+	 * Process prune actions
+	 */
+	if(prune_actions())
+		dir_scan4(root_dir);
+
+	/*
+	 * Process empty actions
+	 */
+	if(empty_actions())
+		dir_scan5(root_dir);
+
+ 	/*
+	 * Sort directories and compute the inode numbers
+	 */
+	dir_scan6(root_dir);
+
+	alloc_inode_no(dir_ent->inode, root_inode_number);
+
+	eval_actions(root_dir, dir_ent);
+
+	if(sorted)
+		generate_file_priorities(root_dir, 0,
+			&root_dir->dir_ent->inode->buf);
+
+	if(appending) {
+		sigset_t sigmask;
+
+		restore_thread = init_restore_thread();
+		sigemptyset(&sigmask);
+		sigaddset(&sigmask, SIGINT);
+		sigaddset(&sigmask, SIGTERM);
+		sigaddset(&sigmask, SIGUSR1);
+		if(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == -1)
+			BAD_ERROR("Failed to set signal mask\n");
+		write_destination(fd, SQUASHFS_START, 4, "\0\0\0\0");
+	}
+
+	queue_put(to_reader, root_dir);
+
+	set_progressbar_state(progress);
+
+	if(sorted)
+		sort_files_and_write(root_dir);
+
+	dir_scan7(inode, root_dir);
+	dir_ent->inode->inode = *inode;
+	dir_ent->inode->type = SQUASHFS_DIR_TYPE;
+}
+
+
+/*
+ * dir_scan1 routines...
+ * These scan the source directories into memory for processing.
+ * Exclude actions are processed here (in contrast to the other actions)
+ * because they affect what is scanned.
+ */
+struct dir_info *scan1_opendir(char *pathname, char *subpath, int depth)
+{
+	struct dir_info *dir;
+
+	dir = malloc(sizeof(struct dir_info));
+	if(dir == NULL)
+		MEM_ERROR();
+
+	if(pathname[0] != '\0') {
+		dir->linuxdir = opendir(pathname);
+		if(dir->linuxdir == NULL) {
+			free(dir);
+			return NULL;
+		}
+	}
+
+	dir->pathname = strdup(pathname);
+	dir->subpath = strdup(subpath);
+	dir->count = 0;
+	dir->directory_count = 0;
+	dir->dir_is_ldir = TRUE;
+	dir->list = NULL;
+	dir->depth = depth;
+	dir->excluded = 0;
+
+	return dir;
+}
+
+
+struct dir_ent *scan1_encomp_readdir(struct dir_info *dir)
+{
+	static int index = 0;
+
+	if(dir->count < old_root_entries) {
+		int i;
+
+		for(i = 0; i < old_root_entries; i++) {
+			if(old_root_entry[i].inode.type == SQUASHFS_DIR_TYPE)
+				dir->directory_count ++;
+			add_dir_entry2(old_root_entry[i].name, NULL, NULL, NULL,
+				&old_root_entry[i].inode, dir);
+		}
+	}
+
+	while(index < source) {
+		char *basename = NULL;
+		char *dir_name = getbase(source_path[index]);
+		int pass = 1, res;
+
+		if(dir_name == NULL) {
+			ERROR_START("Bad source directory %s",
+				source_path[index]);
+			ERROR_EXIT(" - skipping ...\n");
+			index ++;
+			continue;
+		}
+		dir_name = strdup(dir_name);
+		for(;;) {
+			struct dir_ent *dir_ent = dir->list;
+
+			for(; dir_ent && strcmp(dir_ent->name, dir_name) != 0;
+				dir_ent = dir_ent->next);
+			if(dir_ent == NULL)
+				break;
+			ERROR("Source directory entry %s already used! - trying"
+				" ", dir_name);
+			if(pass == 1)
+				basename = dir_name;
+			else
+				free(dir_name);
+			res = asprintf(&dir_name, "%s_%d", basename, pass++);
+			if(res == -1)
+				BAD_ERROR("asprintf failed in "
+					"scan1_encomp_readdir\n");
+			ERROR("%s\n", dir_name);
+		}
+		return create_dir_entry(dir_name, basename,
+			strdup(source_path[index ++]), dir);
+	}
+	return NULL;
+}
+
+
+struct dir_ent *scan1_single_readdir(struct dir_info *dir)
+{
+	struct dirent *d_name;
+	int i;
+
+	if(dir->count < old_root_entries) {
+		for(i = 0; i < old_root_entries; i++) {
+			if(old_root_entry[i].inode.type == SQUASHFS_DIR_TYPE)
+				dir->directory_count ++;
+			add_dir_entry2(old_root_entry[i].name, NULL, NULL, NULL,
+				&old_root_entry[i].inode, dir);
+		}
+	}
+
+	if((d_name = readdir(dir->linuxdir)) != NULL) {
+		char *basename = NULL;
+		char *dir_name = strdup(d_name->d_name);
+		int pass = 1, res;
+
+		for(;;) {
+			struct dir_ent *dir_ent = dir->list;
+
+			for(; dir_ent && strcmp(dir_ent->name, dir_name) != 0;
+				dir_ent = dir_ent->next);
+			if(dir_ent == NULL)
+				break;
+			ERROR("Source directory entry %s already used! - trying"
+				" ", dir_name);
+			if (pass == 1)
+				basename = dir_name;
+			else
+				free(dir_name);
+			res = asprintf(&dir_name, "%s_%d", d_name->d_name, pass++);
+			if(res == -1)
+				BAD_ERROR("asprintf failed in "
+					"scan1_single_readdir\n");
+			ERROR("%s\n", dir_name);
+		}
+		return create_dir_entry(dir_name, basename, NULL, dir);
+	}
+
+	return NULL;
+}
+
+
+struct dir_ent *scan1_readdir(struct dir_info *dir)
+{
+	struct dirent *d_name = readdir(dir->linuxdir);
+
+	return d_name ?
+		create_dir_entry(strdup(d_name->d_name), NULL, NULL, dir) :
+		NULL;
+}
+
+
+void scan1_freedir(struct dir_info *dir)
+{
+	if(dir->pathname[0] != '\0')
+		closedir(dir->linuxdir);
+}
+
+
+struct dir_info *dir_scan1(char *filename, char *subpath,
+	struct pathnames *paths,
+	struct dir_ent *(_readdir)(struct dir_info *), int depth)
+{
+	struct dir_info *dir = scan1_opendir(filename, subpath, depth);
+	struct dir_ent *dir_ent;
+
+	if(dir == NULL) {
+		ERROR_START("Could not open %s", filename);
+		ERROR_EXIT(", skipping...\n");
+		return NULL;
+	}
+
+	while((dir_ent = _readdir(dir))) {
+		struct dir_info *sub_dir;
+		struct stat buf;
+		struct pathnames *new = NULL;
+		char *filename = pathname(dir_ent);
+		char *subpath = NULL;
+		char *dir_name = dir_ent->name;
+
+		if(strcmp(dir_name, ".") == 0 || strcmp(dir_name, "..") == 0) {
+			free_dir_entry(dir_ent);
+			continue;
+		}
+
+		if(lstat(filename, &buf) == -1) {
+			ERROR_START("Cannot stat dir/file %s because %s",
+				filename, strerror(errno));
+			ERROR_EXIT(", ignoring\n");
+			free_dir_entry(dir_ent);
+			continue;
+		}
+
+		if((buf.st_mode & S_IFMT) != S_IFREG &&
+					(buf.st_mode & S_IFMT) != S_IFDIR &&
+					(buf.st_mode & S_IFMT) != S_IFLNK &&
+					(buf.st_mode & S_IFMT) != S_IFCHR &&
+					(buf.st_mode & S_IFMT) != S_IFBLK &&
+					(buf.st_mode & S_IFMT) != S_IFIFO &&
+					(buf.st_mode & S_IFMT) != S_IFSOCK) {
+			ERROR_START("File %s has unrecognised filetype %d",
+				filename, buf.st_mode & S_IFMT);
+			ERROR_EXIT(", ignoring\n");
+			free_dir_entry(dir_ent);
+			continue;
+		}
+
+		if((old_exclude && old_excluded(filename, &buf)) ||
+			(!old_exclude && excluded(dir_name, paths, &new))) {
+			add_excluded(dir);
+			free_dir_entry(dir_ent);
+			continue;
+		}
+
+		if(exclude_actions()) {
+			subpath = subpathname(dir_ent);
+			
+			if(eval_exclude_actions(dir_name, filename, subpath,
+							&buf, depth, dir_ent)) {
+				add_excluded(dir);
+				free_dir_entry(dir_ent);
+				continue;
+			}
+		}
+
+		switch(buf.st_mode & S_IFMT) {
+		case S_IFDIR:
+			if(subpath == NULL)
+				subpath = subpathname(dir_ent);
+
+			sub_dir = dir_scan1(filename, subpath, new,
+					scan1_readdir, depth + 1);
+			if(sub_dir) {
+				dir->directory_count ++;
+				add_dir_entry(dir_ent, sub_dir,
+							lookup_inode(&buf));
+			} else
+				free_dir_entry(dir_ent);
+			break;
+		case S_IFLNK: {
+			int byte;
+			static char buff[65536]; /* overflow safe */
+
+			byte = readlink(filename, buff, 65536);
+			if(byte == -1) {
+				ERROR_START("Failed to read symlink %s",
+								filename);
+				ERROR_EXIT(", ignoring\n");
+			} else if(byte == 65536) {
+				ERROR_START("Symlink %s is greater than 65536 "
+							"bytes!", filename);
+				ERROR_EXIT(", ignoring\n");
+			} else {
+				/* readlink doesn't 0 terminate the returned
+				 * path */
+				buff[byte] = '\0';
+				add_dir_entry(dir_ent, NULL, lookup_inode3(&buf,
+							 0, 0, buff, byte + 1));
+			}
+			break;
+		}
+		default:
+			add_dir_entry(dir_ent, NULL, lookup_inode(&buf));
+		}
+
+		free(new);
+	}
+
+	scan1_freedir(dir);
+
+	return dir;
+}
+
+
+/*
+ * dir_scan2 routines...
+ * This processes most actions and any pseudo files
+ */
+struct dir_ent *scan2_readdir(struct dir_info *dir, struct dir_ent *dir_ent)
+{
+	if (dir_ent == NULL)
+		dir_ent = dir->list;
+	else
+		dir_ent = dir_ent->next;
+
+	for(; dir_ent && dir_ent->inode->root_entry; dir_ent = dir_ent->next);
+
+	return dir_ent;	
+}
+
+
+struct dir_ent *scan2_lookup(struct dir_info *dir, char *name)
+{
+	struct dir_ent *dir_ent = dir->list;
+
+	for(; dir_ent && strcmp(dir_ent->name, name) != 0;
+					dir_ent = dir_ent->next);
+
+	return dir_ent;
+}
+
+
+void dir_scan2(struct dir_info *dir, struct pseudo *pseudo)
+{
+	struct dir_ent *dir_ent = NULL;
+	struct pseudo_entry *pseudo_ent;
+	struct stat buf;
+	static int pseudo_ino = 1;
+	
+	while((dir_ent = scan2_readdir(dir, dir_ent)) != NULL) {
+		struct inode_info *inode_info = dir_ent->inode;
+		struct stat *buf = &inode_info->buf;
+		char *name = dir_ent->name;
+
+		eval_actions(root_dir, dir_ent);
+
+		if((buf->st_mode & S_IFMT) == S_IFDIR)
+			dir_scan2(dir_ent->dir, pseudo_subdir(name, pseudo));
+	}
+
+	while((pseudo_ent = pseudo_readdir(pseudo)) != NULL) {
+		dir_ent = scan2_lookup(dir, pseudo_ent->name);
+		if(pseudo_ent->dev->type == 'm') {
+			struct stat *buf;
+			if(dir_ent == NULL) {
+				ERROR_START("Pseudo modify file \"%s\" does "
+					"not exist in source filesystem.",
+					pseudo_ent->pathname);
+				ERROR_EXIT("  Ignoring.\n");
+				continue;
+			}
+			if(dir_ent->inode->root_entry) {
+				ERROR_START("Pseudo modify file \"%s\" is a "
+					"pre-existing file in the filesystem "
+					"being appended to.  It cannot be "\
+					"modified.", pseudo_ent->pathname);
+				ERROR_EXIT("  Ignoring.\n");
+				continue;
+			}
+			buf = &dir_ent->inode->buf;
+			buf->st_mode = (buf->st_mode & S_IFMT) |
+				pseudo_ent->dev->mode;
+			buf->st_uid = pseudo_ent->dev->uid;
+			buf->st_gid = pseudo_ent->dev->gid;
+			continue;
+		}
+
+		if(dir_ent) {
+			if(dir_ent->inode->root_entry) {
+				ERROR_START("Pseudo file \"%s\" is a "
+					"pre-existing file in the filesystem "
+					"being appended to.",
+					pseudo_ent->pathname);
+				ERROR_EXIT("  Ignoring.\n");
+			} else {
+				ERROR_START("Pseudo file \"%s\" exists in "
+					"source filesystem \"%s\".",
+					pseudo_ent->pathname,
+					pathname(dir_ent));
+				ERROR_EXIT("\nIgnoring, exclude it (-e/-ef) to "
+					"override.\n");
+			}
+			continue;
+		}
+
+		memset(&buf, 0, sizeof(buf));
+		buf.st_mode = pseudo_ent->dev->mode;
+		buf.st_uid = pseudo_ent->dev->uid;
+		buf.st_gid = pseudo_ent->dev->gid;
+		buf.st_rdev = makedev(pseudo_ent->dev->major,
+			pseudo_ent->dev->minor);
+		buf.st_mtime = time(NULL);
+		buf.st_ino = pseudo_ino ++;
+
+		if(pseudo_ent->dev->type == 'd') {
+			struct dir_ent *dir_ent =
+				create_dir_entry(pseudo_ent->name, NULL,
+						pseudo_ent->pathname, dir);
+			char *subpath = strdup(subpathname(dir_ent));
+			struct dir_info *sub_dir = scan1_opendir("", subpath,
+						dir->depth + 1);
+			if(sub_dir == NULL) {
+				ERROR_START("Could not create pseudo directory "
+					"\"%s\"", pseudo_ent->pathname);
+				ERROR_EXIT(", skipping...\n");
+				free(subpath);
+				pseudo_ino --;
+				continue;
+			}
+			dir_scan2(sub_dir, pseudo_ent->pseudo);
+			dir->directory_count ++;
+			add_dir_entry(dir_ent, sub_dir,
+				lookup_inode2(&buf, PSEUDO_FILE_OTHER, 0));
+		} else if(pseudo_ent->dev->type == 'f') {
+			add_dir_entry2(pseudo_ent->name, NULL,
+				pseudo_ent->pathname, NULL,
+				lookup_inode2(&buf, PSEUDO_FILE_PROCESS,
+				pseudo_ent->dev->pseudo_id), dir);
+		} else {
+			add_dir_entry2(pseudo_ent->name, NULL,
+				pseudo_ent->pathname, NULL,
+				lookup_inode2(&buf, PSEUDO_FILE_OTHER, 0), dir);
+		}
+	}
+}
+
+
+/*
+ * dir_scan3 routines...
+ * This processes the move action
+ */
+void dir_scan3(struct dir_info *dir)
+{
+	struct dir_ent *dir_ent = NULL;
+
+	while((dir_ent = scan2_readdir(dir, dir_ent)) != NULL) {
+
+		eval_move_actions(root_dir, dir_ent);
+
+		if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+			dir_scan3(dir_ent->dir);
+	}
+}
+
+
+/*
+ * dir_scan4 routines...
+ * This processes the prune action.  This action is designed to do fine
+ * grained tuning of the in-core directory structure after the exclude,
+ * move and pseudo actions have been performed.  This allows complex
+ * tests to be performed which are impossible at exclude time (i.e.
+ * tests which rely on the in-core directory structure)
+ */
+void free_dir(struct dir_info *dir)
+{
+	struct dir_ent *dir_ent = dir->list;
+
+	while(dir_ent) {
+		struct dir_ent *tmp = dir_ent;
+
+		if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+			free_dir(dir_ent->dir);
+
+		dir_ent = dir_ent->next;
+		free_dir_entry(tmp);
+	}
+
+	free(dir->pathname);
+	free(dir->subpath);
+	free(dir);
+}
+	
+
+void dir_scan4(struct dir_info *dir)
+{
+	struct dir_ent *dir_ent = dir->list, *prev = NULL;
+
+	while(dir_ent) {
+		if(dir_ent->inode->root_entry) {
+			prev = dir_ent;
+			dir_ent = dir_ent->next;
+			continue;
+		}
+
+		if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+			dir_scan4(dir_ent->dir);
+
+		if(eval_prune_actions(root_dir, dir_ent)) {
+			struct dir_ent *tmp = dir_ent;
+
+			if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR) {
+				free_dir(dir_ent->dir);
+				dir->directory_count --;
+			}
+
+			dir->count --;
+
+			/* remove dir_ent from list */
+			dir_ent = dir_ent->next;
+			if(prev)
+				prev->next = dir_ent;
+			else
+				dir->list = dir_ent;
+			
+			/* free it */
+			free_dir_entry(tmp);
+
+			add_excluded(dir);
+			continue;
+		}
+
+		prev = dir_ent;
+		dir_ent = dir_ent->next;
+	}
+}
+
+
+/*
+ * dir_scan5 routines...
+ * This processes the empty action.  This action has to be processed after
+ * all other actions because the previous exclude and move actions and the
+ * pseudo actions affect whether a directory is empty
+ */
+void dir_scan5(struct dir_info *dir)
+{
+	struct dir_ent *dir_ent = dir->list, *prev = NULL;
+
+	while(dir_ent) {
+		if(dir_ent->inode->root_entry) {
+			prev = dir_ent;
+			dir_ent = dir_ent->next;
+			continue;
+		}
+
+		if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR) {
+			dir_scan5(dir_ent->dir);
+
+			if(eval_empty_actions(root_dir, dir_ent)) {
+				struct dir_ent *tmp = dir_ent;
+
+				/*
+				 * delete sub-directory, this is by definition
+				 * empty
+				 */
+				free(dir_ent->dir->pathname);
+				free(dir_ent->dir->subpath);
+				free(dir_ent->dir);
+
+				/* remove dir_ent from list */
+				dir_ent = dir_ent->next;
+				if(prev)
+					prev->next = dir_ent;
+				else
+					dir->list = dir_ent;
+			
+				/* free it */
+				free_dir_entry(tmp);
+
+				/* update counts */
+				dir->directory_count --;
+				dir->count --;
+				add_excluded(dir);
+				continue;
+			}
+		}
+
+		prev = dir_ent;
+		dir_ent = dir_ent->next;
+	}
+}
+
+
+/*
+ * dir_scan6 routines...
+ * This sorts every directory and computes the inode numbers
+ */
+
+/*
+ * Bottom up linked list merge sort.
+ *
+ * Qsort and other O(n log n) algorithms work well with arrays but not
+ * linked lists.  Merge sort another O(n log n) sort algorithm on the other hand
+ * is not ideal for arrays (as it needs an additonal n storage locations
+ * as sorting is not done in place), but it is ideal for linked lists because
+ * it doesn't require any extra storage,
+ */ 
+void sort_directory(struct dir_info *dir)
+{
+	struct dir_ent *cur, *l1, *l2, *next;
+	int len1, len2, stride = 1;
+
+	if(dir->list == NULL || dir->count < 2)
+		return;
+
+	/*
+	 * We can consider our linked-list to be made up of stride length
+	 * sublists.  Eacn iteration around this loop merges adjacent
+	 * stride length sublists into larger 2*stride sublists.  We stop
+	 * when stride becomes equal to the entire list.
+	 *
+	 * Initially stride = 1 (by definition a sublist of 1 is sorted), and
+	 * these 1 element sublists are merged into 2 element sublists,  which
+	 * are then merged into 4 element sublists and so on.
+	 */
+	do {
+		l2 = dir->list; /* head of current linked list */
+		cur = NULL; /* empty output list */
+
+		/*
+		 * Iterate through the linked list, merging adjacent sublists.
+		 * On each interation l2 points to the next sublist pair to be
+		 * merged (if there's only one sublist left this is simply added
+		 * to the output list)
+		 */
+		while(l2) {
+			l1 = l2;
+			for(len1 = 0; l2 && len1 < stride; len1 ++, l2 = l2->next);
+			len2 = stride;
+
+			/*
+			 * l1 points to first sublist.
+			 * l2 points to second sublist.
+			 * Merge them onto the output list
+			 */
+			while(len1 && l2 && len2) {
+				if(strcmp(l1->name, l2->name) <= 0) {
+					next = l1;
+					l1 = l1->next;
+					len1 --;
+				} else {
+					next = l2;
+					l2 = l2->next;
+					len2 --;
+				}
+
+				if(cur) {
+					cur->next = next;
+					cur = next;
+				} else
+					dir->list = cur = next;
+			}
+			/*
+			 * One sublist is now empty, copy the other one onto the
+			 * output list
+			 */
+			for(; len1; len1 --, l1 = l1->next) {
+				if(cur) {
+					cur->next = l1;
+					cur = l1;
+				} else
+					dir->list = cur = l1;
+			}
+			for(; l2 && len2; len2 --, l2 = l2->next) {
+				if(cur) {
+					cur->next = l2;
+					cur = l2;
+				} else
+					dir->list = cur = l2;
+			}
+		}
+		cur->next = NULL;
+		stride = stride << 1;
+	} while(stride < dir->count);
+}
+
+
+void dir_scan6(struct dir_info *dir)
+{
+	struct dir_ent *dir_ent;
+	unsigned int byte_count = 0;
+
+	sort_directory(dir);
+
+	for(dir_ent = dir->list; dir_ent; dir_ent = dir_ent->next) {
+		byte_count += strlen(dir_ent->name) +
+			sizeof(struct squashfs_dir_entry);
+
+		if(dir_ent->inode->root_entry)
+			continue;
+
+		alloc_inode_no(dir_ent->inode, 0);
+
+		if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+			dir_scan6(dir_ent->dir);
+	}
+
+	if((dir->count < 257 && byte_count < SQUASHFS_METADATA_SIZE))
+		dir->dir_is_ldir = FALSE;
+}
+
+
+/*
+ * dir_scan6 routines...
+ * This generates the filesystem metadata and writes it out to the destination
+ */
+void scan7_init_dir(struct directory *dir)
+{
+	dir->buff = malloc(SQUASHFS_METADATA_SIZE);
+	if(dir->buff == NULL)
+		MEM_ERROR();
+
+	dir->size = SQUASHFS_METADATA_SIZE;
+	dir->p = dir->index_count_p = dir->buff;
+	dir->entry_count = 256;
+	dir->entry_count_p = NULL;
+	dir->index = NULL;
+	dir->i_count = dir->i_size = 0;
+}
+
+
+struct dir_ent *scan7_readdir(struct directory *dir, struct dir_info *dir_info,
+	struct dir_ent *dir_ent)
+{
+	if (dir_ent == NULL)
+		dir_ent = dir_info->list;
+	else
+		dir_ent = dir_ent->next;
+
+	for(; dir_ent && dir_ent->inode->root_entry; dir_ent = dir_ent->next)
+		add_dir(dir_ent->inode->inode, dir_ent->inode->inode_number,
+			dir_ent->name, dir_ent->inode->type, dir);
+
+	return dir_ent;	
+}
+
+
+void scan7_freedir(struct directory *dir)
+{
+	if(dir->index)
+		free(dir->index);
+	free(dir->buff);
+}
+
+
+void dir_scan7(squashfs_inode *inode, struct dir_info *dir_info)
+{
+	int squashfs_type;
+	int duplicate_file;
+	struct directory dir;
+	struct dir_ent *dir_ent = NULL;
+	
+	scan7_init_dir(&dir);
+	
+	while((dir_ent = scan7_readdir(&dir, dir_info, dir_ent)) != NULL) {
+		struct stat *buf = &dir_ent->inode->buf;
+
+		update_info(dir_ent);
+
+		if(dir_ent->inode->inode == SQUASHFS_INVALID_BLK) {
+			switch(buf->st_mode & S_IFMT) {
+				case S_IFREG:
+					squashfs_type = SQUASHFS_FILE_TYPE;
+					write_file(inode, dir_ent,
+						&duplicate_file);
+					INFO("file %s, uncompressed size %lld "
+						"bytes %s\n",
+						subpathname(dir_ent),
+						(long long) buf->st_size,
+						duplicate_file ?  "DUPLICATE" :
+						 "");
+					break;
+
+				case S_IFDIR:
+					squashfs_type = SQUASHFS_DIR_TYPE;
+					dir_scan7(inode, dir_ent->dir);
+					break;
+
+				case S_IFLNK:
+					squashfs_type = SQUASHFS_SYMLINK_TYPE;
+					create_inode(inode, NULL, dir_ent,
+						squashfs_type, 0, 0, 0, NULL,
+						NULL, NULL, 0);
+					INFO("symbolic link %s inode 0x%llx\n",
+						subpathname(dir_ent), *inode);
+					sym_count ++;
+					break;
+
+				case S_IFCHR:
+					squashfs_type = SQUASHFS_CHRDEV_TYPE;
+					create_inode(inode, NULL, dir_ent,
+						squashfs_type, 0, 0, 0, NULL,
+						NULL, NULL, 0);
+					INFO("character device %s inode 0x%llx"
+						"\n", subpathname(dir_ent),
+						*inode);
+					dev_count ++;
+					break;
+
+				case S_IFBLK:
+					squashfs_type = SQUASHFS_BLKDEV_TYPE;
+					create_inode(inode, NULL, dir_ent,
+						squashfs_type, 0, 0, 0, NULL,
+						NULL, NULL, 0);
+					INFO("block device %s inode 0x%llx\n",
+						subpathname(dir_ent), *inode);
+					dev_count ++;
+					break;
+
+				case S_IFIFO:
+					squashfs_type = SQUASHFS_FIFO_TYPE;
+					create_inode(inode, NULL, dir_ent,
+						squashfs_type, 0, 0, 0, NULL,
+						NULL, NULL, 0);
+					INFO("fifo %s inode 0x%llx\n",
+						subpathname(dir_ent), *inode);
+					fifo_count ++;
+					break;
+
+				case S_IFSOCK:
+					squashfs_type = SQUASHFS_SOCKET_TYPE;
+					create_inode(inode, NULL, dir_ent,
+						squashfs_type, 0, 0, 0, NULL,
+						NULL, NULL, 0);
+					INFO("unix domain socket %s inode "
+						"0x%llx\n",
+						subpathname(dir_ent), *inode);
+					sock_count ++;
+					break;
+
+				default:
+					BAD_ERROR("%s unrecognised file type, "
+						"mode is %x\n",
+						subpathname(dir_ent),
+						buf->st_mode);
+			}
+			dir_ent->inode->inode = *inode;
+			dir_ent->inode->type = squashfs_type;
+		 } else {
+			*inode = dir_ent->inode->inode;
+			squashfs_type = dir_ent->inode->type;
+			switch(squashfs_type) {
+				case SQUASHFS_FILE_TYPE:
+					if(!sorted)
+						INFO("file %s, uncompressed "
+							"size %lld bytes LINK"
+							"\n",
+							subpathname(dir_ent),
+							(long long)
+							buf->st_size);
+					break;
+				case SQUASHFS_SYMLINK_TYPE:
+					INFO("symbolic link %s inode 0x%llx "
+						"LINK\n", subpathname(dir_ent),
+						 *inode);
+					break;
+				case SQUASHFS_CHRDEV_TYPE:
+					INFO("character device %s inode 0x%llx "
+						"LINK\n", subpathname(dir_ent),
+						*inode);
+					break;
+				case SQUASHFS_BLKDEV_TYPE:
+					INFO("block device %s inode 0x%llx "
+						"LINK\n", subpathname(dir_ent),
+						*inode);
+					break;
+				case SQUASHFS_FIFO_TYPE:
+					INFO("fifo %s inode 0x%llx LINK\n",
+						subpathname(dir_ent), *inode);
+					break;
+				case SQUASHFS_SOCKET_TYPE:
+					INFO("unix domain socket %s inode "
+						"0x%llx LINK\n",
+						subpathname(dir_ent), *inode);
+					break;
+			}
+		}
+		
+		add_dir(*inode, get_inode_no(dir_ent->inode), dir_ent->name,
+			squashfs_type, &dir);
+	}
+
+	write_dir(inode, dir_info, &dir);
+	INFO("directory %s inode 0x%llx\n", subpathname(dir_info->dir_ent),
+		*inode);
+
+	scan7_freedir(&dir);
+}
+
+
+unsigned int slog(unsigned int block)
+{
+	int i;
+
+	for(i = 12; i <= 20; i++)
+		if(block == (1 << i))
+			return i;
+	return 0;
+}
+
+
+int old_excluded(char *filename, struct stat *buf)
+{
+	int i;
+
+	for(i = 0; i < exclude; i++)
+		if((exclude_paths[i].st_dev == buf->st_dev) &&
+				(exclude_paths[i].st_ino == buf->st_ino))
+			return TRUE;
+	return FALSE;
+}
+
+
+#define ADD_ENTRY(buf) \
+	if(exclude % EXCLUDE_SIZE == 0) { \
+		exclude_paths = realloc(exclude_paths, (exclude + EXCLUDE_SIZE) \
+			* sizeof(struct exclude_info)); \
+		if(exclude_paths == NULL) \
+			MEM_ERROR(); \
+	} \
+	exclude_paths[exclude].st_dev = buf.st_dev; \
+	exclude_paths[exclude++].st_ino = buf.st_ino;
+int old_add_exclude(char *path)
+{
+	int i;
+	char *filename;
+	struct stat buf;
+
+	if(path[0] == '/' || strncmp(path, "./", 2) == 0 ||
+			strncmp(path, "../", 3) == 0) {
+		if(lstat(path, &buf) == -1) {
+			ERROR_START("Cannot stat exclude dir/file %s because "
+				"%s", path, strerror(errno));
+			ERROR_EXIT(", ignoring\n");
+			return TRUE;
+		}
+		ADD_ENTRY(buf);
+		return TRUE;
+	}
+
+	for(i = 0; i < source; i++) {
+		int res = asprintf(&filename, "%s/%s", source_path[i], path);
+		if(res == -1)
+			BAD_ERROR("asprintf failed in old_add_exclude\n");
+		if(lstat(filename, &buf) == -1) {
+			if(!(errno == ENOENT || errno == ENOTDIR)) {
+				ERROR_START("Cannot stat exclude dir/file %s "
+					"because %s", filename, strerror(errno));
+				ERROR_EXIT(", ignoring\n");
+			}
+			free(filename);
+			continue;
+		}
+		free(filename);
+		ADD_ENTRY(buf);
+	}
+	return TRUE;
+}
+
+
+void add_old_root_entry(char *name, squashfs_inode inode, int inode_number,
+	int type)
+{
+	old_root_entry = realloc(old_root_entry,
+		sizeof(struct old_root_entry_info) * (old_root_entries + 1));
+	if(old_root_entry == NULL)
+		MEM_ERROR();
+
+	old_root_entry[old_root_entries].name = strdup(name);
+	old_root_entry[old_root_entries].inode.inode = inode;
+	old_root_entry[old_root_entries].inode.inode_number = inode_number;
+	old_root_entry[old_root_entries].inode.type = type;
+	old_root_entry[old_root_entries++].inode.root_entry = TRUE;
+}
+
+
+void initialise_threads(int readq, int fragq, int bwriteq, int fwriteq,
+	int freelst, char *destination_file)
+{
+	int i;
+	sigset_t sigmask, old_mask;
+	int total_mem = readq;
+	int reader_size;
+	int fragment_size;
+	int fwriter_size;
+	/*
+	 * bwriter_size is global because it is needed in
+	 * write_file_blocks_dup()
+	 */
+
+	/*
+	 * Never allow the total size of the queues to be larger than
+	 * physical memory
+	 *
+	 * When adding together the possibly user supplied values, make
+	 * sure they've not been deliberately contrived to overflow an int
+	 */
+	if(add_overflow(total_mem, fragq))
+		BAD_ERROR("Queue sizes rediculously too large\n");
+	total_mem += fragq;
+	if(add_overflow(total_mem, bwriteq))
+		BAD_ERROR("Queue sizes rediculously too large\n");
+	total_mem += bwriteq;
+	if(add_overflow(total_mem, fwriteq))
+		BAD_ERROR("Queue sizes rediculously too large\n");
+	total_mem += fwriteq;
+
+	check_usable_phys_mem(total_mem);
+
+	/*
+	 * convert from queue size in Mbytes to queue size in
+	 * blocks.
+	 *
+	 * This isn't going to overflow an int unless there exists
+	 * systems with more than 8 Petabytes of RAM!
+	 */
+	reader_size = readq << (20 - block_log);
+	fragment_size = fragq << (20 - block_log);
+	bwriter_size = bwriteq << (20 - block_log);
+	fwriter_size = fwriteq << (20 - block_log);
+
+	/*
+	 * setup signal handlers for the main thread, these cleanup
+	 * deleting the destination file, if appending the
+	 * handlers for SIGTERM and SIGINT will be replaced with handlers
+	 * allowing the user to press ^C twice to restore the existing
+	 * filesystem.
+	 *
+	 * SIGUSR1 is an internal signal, which is used by the sub-threads
+	 * to tell the main thread to terminate, deleting the destination file,
+	 * or if necessary restoring the filesystem on appending
+	 */
+	signal(SIGTERM, sighandler);
+	signal(SIGINT, sighandler);
+	signal(SIGUSR1, sighandler);
+
+	/* block SIGQUIT and SIGHUP, these are handled by the info thread */
+	sigemptyset(&sigmask);
+	sigaddset(&sigmask, SIGQUIT);
+	sigaddset(&sigmask, SIGHUP);
+	sigaddset(&sigmask, SIGALRM);
+	if(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == -1)
+		BAD_ERROR("Failed to set signal mask in intialise_threads\n");
+
+	/*
+	 * temporarily block these signals, so the created sub-threads
+	 * will ignore them, ensuring the main thread handles them
+	 */
+	sigemptyset(&sigmask);
+	sigaddset(&sigmask, SIGINT);
+	sigaddset(&sigmask, SIGTERM);
+	sigaddset(&sigmask, SIGUSR1);
+	if(pthread_sigmask(SIG_BLOCK, &sigmask, &old_mask) == -1)
+		BAD_ERROR("Failed to set signal mask in intialise_threads\n");
+
+	if(processors == -1) {
+#ifndef linux
+		int mib[2];
+		size_t len = sizeof(processors);
+
+		mib[0] = CTL_HW;
+#ifdef HW_AVAILCPU
+		mib[1] = HW_AVAILCPU;
+#else
+		mib[1] = HW_NCPU;
+#endif
+
+		if(sysctl(mib, 2, &processors, &len, NULL, 0) == -1) {
+			ERROR_START("Failed to get number of available "
+				"processors.");
+			ERROR_EXIT("  Defaulting to 1\n");
+			processors = 1;
+		}
+#else
+		processors = sysconf(_SC_NPROCESSORS_ONLN);
+#endif
+	}
+
+	if(multiply_overflow(processors, 3) ||
+			multiply_overflow(processors * 3, sizeof(pthread_t)))
+		BAD_ERROR("Processors too large\n");
+
+	deflator_thread = malloc(processors * 3 * sizeof(pthread_t));
+	if(deflator_thread == NULL)
+		MEM_ERROR();
+
+	frag_deflator_thread = &deflator_thread[processors];
+	frag_thread = &frag_deflator_thread[processors];
+
+	to_reader = queue_init(1);
+	to_deflate = queue_init(reader_size);
+	to_process_frag = queue_init(reader_size);
+	to_writer = queue_init(bwriter_size + fwriter_size);
+	from_writer = queue_init(1);
+	to_frag = queue_init(fragment_size);
+	locked_fragment = queue_init(fragment_size);
+	to_main = seq_queue_init();
+	reader_buffer = cache_init(block_size, reader_size, 0, 0);
+	bwriter_buffer = cache_init(block_size, bwriter_size, 1, freelst);
+	fwriter_buffer = cache_init(block_size, fwriter_size, 1, freelst);
+	fragment_buffer = cache_init(block_size, fragment_size, 1, 0);
+	reserve_cache = cache_init(block_size, processors + 1, 1, 0);
+	pthread_create(&reader_thread, NULL, reader, NULL);
+	pthread_create(&writer_thread, NULL, writer, NULL);
+	init_progress_bar();
+	init_info();
+
+	for(i = 0; i < processors; i++) {
+		if(pthread_create(&deflator_thread[i], NULL, deflator, NULL))
+			BAD_ERROR("Failed to create thread\n");
+		if(pthread_create(&frag_deflator_thread[i], NULL, frag_deflator,
+				NULL) != 0)
+			BAD_ERROR("Failed to create thread\n");
+		if(pthread_create(&frag_thread[i], NULL, frag_thrd,
+				(void *) destination_file) != 0)
+			BAD_ERROR("Failed to create thread\n");
+	}
+
+	main_thread = pthread_self();
+
+	printf("Parallel mksquashfs: Using %d processor%s\n", processors,
+			processors == 1 ? "" : "s");
+
+	/* Restore the signal mask for the main thread */
+	if(pthread_sigmask(SIG_SETMASK, &old_mask, NULL) == -1)
+		BAD_ERROR("Failed to set signal mask in intialise_threads\n");
+}
+
+
+long long write_inode_lookup_table()
+{
+	int i, inode_number, lookup_bytes = SQUASHFS_LOOKUP_BYTES(inode_count);
+	void *it;
+
+	if(inode_count == sinode_count)
+		goto skip_inode_hash_table;
+
+	it = realloc(inode_lookup_table, lookup_bytes);
+	if(it == NULL)
+		MEM_ERROR();
+	inode_lookup_table = it;
+
+	for(i = 0; i < INODE_HASH_SIZE; i ++) {
+		struct inode_info *inode;
+
+		for(inode = inode_info[i]; inode; inode = inode->next) {
+
+			inode_number = get_inode_no(inode);
+
+			/* The empty action will produce orphaned inode
+			 * entries in the inode_info[] table.  These
+			 * entries because they are orphaned will not be
+			 * allocated an inode number in dir_scan5(), so
+			 * skip any entries with the default dummy inode
+			 * number of 0 */
+			if(inode_number == 0)
+				continue;
+
+			SQUASHFS_SWAP_LONG_LONGS(&inode->inode,
+				&inode_lookup_table[inode_number - 1], 1);
+
+		}
+	}
+
+skip_inode_hash_table:
+	return generic_write_table(lookup_bytes, inode_lookup_table, 0, NULL,
+		noI);
+}
+
+
+char *get_component(char *target, char **targname)
+{
+	char *start;
+
+	while(*target == '/')
+		target ++;
+
+	start = target;
+	while(*target != '/' && *target != '\0')
+		target ++;
+
+	*targname = strndup(start, target - start);
+
+	while(*target == '/')
+		target ++;
+
+	return target;
+}
+
+
+void free_path(struct pathname *paths)
+{
+	int i;
+
+	for(i = 0; i < paths->names; i++) {
+		if(paths->name[i].paths)
+			free_path(paths->name[i].paths);
+		free(paths->name[i].name);
+		if(paths->name[i].preg) {
+			regfree(paths->name[i].preg);
+			free(paths->name[i].preg);
+		}
+	}
+
+	free(paths);
+}
+
+
+struct pathname *add_path(struct pathname *paths, char *target, char *alltarget)
+{
+	char *targname;
+	int i, error;
+
+	target = get_component(target, &targname);
+
+	if(paths == NULL) {
+		paths = malloc(sizeof(struct pathname));
+		if(paths == NULL)
+			MEM_ERROR();
+
+		paths->names = 0;
+		paths->name = NULL;
+	}
+
+	for(i = 0; i < paths->names; i++)
+		if(strcmp(paths->name[i].name, targname) == 0)
+			break;
+
+	if(i == paths->names) {
+		/* allocate new name entry */
+		paths->names ++;
+		paths->name = realloc(paths->name, (i + 1) *
+			sizeof(struct path_entry));
+		if(paths->name == NULL)
+			MEM_ERROR();
+		paths->name[i].name = targname;
+		paths->name[i].paths = NULL;
+		if(use_regex) {
+			paths->name[i].preg = malloc(sizeof(regex_t));
+			if(paths->name[i].preg == NULL)
+				MEM_ERROR();
+			error = regcomp(paths->name[i].preg, targname,
+				REG_EXTENDED|REG_NOSUB);
+			if(error) {
+				char str[1024]; /* overflow safe */
+
+				regerror(error, paths->name[i].preg, str, 1024);
+				BAD_ERROR("invalid regex %s in export %s, "
+					"because %s\n", targname, alltarget,
+					str);
+			}
+		} else
+			paths->name[i].preg = NULL;
+
+		if(target[0] == '\0')
+			/* at leaf pathname component */
+			paths->name[i].paths = NULL;
+		else
+			/* recurse adding child components */
+			paths->name[i].paths = add_path(NULL, target,
+				alltarget);
+	} else {
+		/* existing matching entry */
+		free(targname);
+
+		if(paths->name[i].paths == NULL) {
+			/* No sub-directory which means this is the leaf
+			 * component of a pre-existing exclude which subsumes
+			 * the exclude currently being added, in which case stop
+			 * adding components */
+		} else if(target[0] == '\0') {
+			/* at leaf pathname component and child components exist
+			 * from more specific excludes, delete as they're
+			 * subsumed by this exclude */
+			free_path(paths->name[i].paths);
+			paths->name[i].paths = NULL;
+		} else
+			/* recurse adding child components */
+			add_path(paths->name[i].paths, target, alltarget);
+	}
+
+	return paths;
+}
+
+
+void add_exclude(char *target)
+{
+
+	if(target[0] == '/' || strncmp(target, "./", 2) == 0 ||
+			strncmp(target, "../", 3) == 0)
+		BAD_ERROR("/, ./ and ../ prefixed excludes not supported with "
+			"-wildcards or -regex options\n");	
+	else if(strncmp(target, "... ", 4) == 0)
+		stickypath = add_path(stickypath, target + 4, target + 4);
+	else	
+		path = add_path(path, target, target);
+}
+
+
+void display_path(int depth, struct pathname *paths)
+{
+	int i, n;
+
+	if(paths == NULL)
+		return;
+
+	for(i = 0; i < paths->names; i++) {
+		for(n = 0; n < depth; n++)
+			printf("\t");
+		printf("%d: %s\n", depth, paths->name[i].name);
+		display_path(depth + 1, paths->name[i].paths);
+	}
+}
+
+
+void display_path2(struct pathname *paths, char *string)
+{
+	int i;
+	char *path;
+
+	if(paths == NULL) {
+		printf("%s\n", string);
+		return;
+	}
+
+	for(i = 0; i < paths->names; i++) {
+		int res = asprintf(&path, "%s/%s", string, paths->name[i].name);
+		if(res == -1)
+			BAD_ERROR("asprintf failed in display_path2\n");
+		display_path2(paths->name[i].paths, path);
+		free(path);
+	}
+}
+
+
+struct pathnames *add_subdir(struct pathnames *paths, struct pathname *path)
+{
+	int count = paths == NULL ? 0 : paths->count;
+
+	if(count % PATHS_ALLOC_SIZE == 0) {
+		paths = realloc(paths, sizeof(struct pathnames) +
+			(count + PATHS_ALLOC_SIZE) * sizeof(struct pathname *));
+		if(paths == NULL)
+			MEM_ERROR();
+	}
+
+	paths->path[count] = path;
+	paths->count = count  + 1;
+	return paths;
+}
+
+
+int excluded_match(char *name, struct pathname *path, struct pathnames **new)
+{
+	int i;
+
+	for(i = 0; i < path->names; i++) {
+		int match = use_regex ?
+			regexec(path->name[i].preg, name, (size_t) 0,
+					NULL, 0) == 0 :
+			fnmatch(path->name[i].name, name,
+				FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;
+
+		if(match) {
+			 if(path->name[i].paths == NULL || new == NULL)
+				/* match on a leaf component, any subdirectories
+			 	* in the filesystem should be excluded */
+				return TRUE;
+			else
+				/* match on a non-leaf component, add any
+				 * subdirectories to the new set of
+				 * subdirectories to scan for this name */
+				*new = add_subdir(*new, path->name[i].paths);
+		}
+	}
+
+	return FALSE;
+}
+
+
+int excluded(char *name, struct pathnames *paths, struct pathnames **new)
+{
+	int n;
+		
+	if(stickypath && excluded_match(name, stickypath, NULL))
+		return TRUE;
+
+	for(n = 0; paths && n < paths->count; n++) {
+		int res = excluded_match(name, paths->path[n], new);
+		if(res) {
+			free(*new);
+			*new = NULL;
+			return TRUE;
+		}
+	}
+
+	/*
+	 * Either:
+	 * -  no matching names found, return empty new search set, or
+	 * -  one or more matches with sub-directories found (no leaf matches),
+	 *    in which case return new search set.
+	 *
+	 * In either case return FALSE as we don't want to exclude this entry
+	 */
+	return FALSE;
+}
+
+
+void process_exclude_file(char *argv)
+{
+	FILE *fd;
+	char buffer[MAX_LINE + 1]; /* overflow safe */
+	char *filename;
+
+	fd = fopen(argv, "r");
+	if(fd == NULL)
+		BAD_ERROR("Failed to open exclude file \"%s\" because %s\n",
+			argv, strerror(errno));
+
+	while(fgets(filename = buffer, MAX_LINE + 1, fd) != NULL) {
+		int len = strlen(filename);
+
+		if(len == MAX_LINE && filename[len - 1] != '\n')
+			/* line too large */
+			BAD_ERROR("Line too long when reading "
+				"exclude file \"%s\", larger than %d "
+				"bytes\n", argv, MAX_LINE);
+
+		/*
+		 * Remove '\n' terminator if it exists (the last line
+		 * in the file may not be '\n' terminated)
+		 */
+		if(len && filename[len - 1] == '\n')
+			filename[len - 1] = '\0';
+
+		/* Skip any leading whitespace */
+		while(isspace(*filename))
+			filename ++;
+
+		/* if comment line, skip */
+		if(*filename == '#')
+			continue;
+
+		/*
+		 * check for initial backslash, to accommodate
+		 * filenames with leading space or leading # character
+		 */
+		if(*filename == '\\')
+			filename ++;
+
+		/* if line is now empty after skipping characters, skip it */
+		if(*filename == '\0')
+			continue;
+
+		if(old_exclude)
+			old_add_exclude(filename);
+		else
+			add_exclude(filename);
+	}
+
+	if(ferror(fd))
+		BAD_ERROR("Reading exclude file \"%s\" failed because %s\n",
+			argv, strerror(errno));
+
+	fclose(fd);
+}
+
+
+#define RECOVER_ID "Squashfs recovery file v1.0\n"
+#define RECOVER_ID_SIZE 28
+
+void write_recovery_data(struct squashfs_super_block *sBlk)
+{
+	int res, recoverfd, bytes = sBlk->bytes_used - sBlk->inode_table_start;
+	pid_t pid = getpid();
+	char *metadata;
+	char header[] = RECOVER_ID;
+
+	if(recover == FALSE) {
+		printf("No recovery data option specified.\n");
+		printf("Skipping saving recovery file.\n\n");
+		return;
+	}
+
+	metadata = malloc(bytes);
+	if(metadata == NULL)
+		MEM_ERROR();
+
+	res = read_fs_bytes(fd, sBlk->inode_table_start, bytes, metadata);
+	if(res == 0) {
+		ERROR("Failed to read append filesystem metadata\n");
+		BAD_ERROR("Filesystem corrupted?\n");
+	}
+
+	res = asprintf(&recovery_file, "squashfs_recovery_%s_%d",
+		getbase(destination_file), pid);
+	if(res == -1)
+		MEM_ERROR();
+
+	recoverfd = open(recovery_file, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);
+	if(recoverfd == -1)
+		BAD_ERROR("Failed to create recovery file, because %s.  "
+			"Aborting\n", strerror(errno));
+		
+	if(write_bytes(recoverfd, header, RECOVER_ID_SIZE) == -1)
+		BAD_ERROR("Failed to write recovery file, because %s\n",
+			strerror(errno));
+
+	if(write_bytes(recoverfd, sBlk, sizeof(struct squashfs_super_block)) == -1)
+		BAD_ERROR("Failed to write recovery file, because %s\n",
+			strerror(errno));
+
+	if(write_bytes(recoverfd, metadata, bytes) == -1)
+		BAD_ERROR("Failed to write recovery file, because %s\n",
+			strerror(errno));
+
+	close(recoverfd);
+	free(metadata);
+	
+	printf("Recovery file \"%s\" written\n", recovery_file);
+	printf("If Mksquashfs aborts abnormally (i.e. power failure), run\n");
+	printf("mksquashfs dummy %s -recover %s\n", destination_file,
+		recovery_file);
+	printf("to restore filesystem\n\n");
+}
+
+
+void read_recovery_data(char *recovery_file, char *destination_file)
+{
+	int fd, recoverfd, bytes;
+	struct squashfs_super_block orig_sBlk, sBlk;
+	char *metadata;
+	int res;
+	struct stat buf;
+	char header[] = RECOVER_ID;
+	char header2[RECOVER_ID_SIZE];
+
+	recoverfd = open(recovery_file, O_RDONLY);
+	if(recoverfd == -1)
+		BAD_ERROR("Failed to open recovery file because %s\n",
+			strerror(errno));
+
+	if(stat(destination_file, &buf) == -1)
+		BAD_ERROR("Failed to stat destination file, because %s\n",
+			strerror(errno));
+
+	fd = open(destination_file, O_RDWR);
+	if(fd == -1)
+		BAD_ERROR("Failed to open destination file because %s\n",
+			strerror(errno));
+
+	res = read_bytes(recoverfd, header2, RECOVER_ID_SIZE);
+	if(res == -1)
+		BAD_ERROR("Failed to read recovery file, because %s\n",
+			strerror(errno));
+	if(res < RECOVER_ID_SIZE)
+		BAD_ERROR("Recovery file appears to be truncated\n");
+	if(strncmp(header, header2, RECOVER_ID_SIZE) !=0 )
+		BAD_ERROR("Not a recovery file\n");
+
+	res = read_bytes(recoverfd, &sBlk, sizeof(struct squashfs_super_block));
+	if(res == -1)
+		BAD_ERROR("Failed to read recovery file, because %s\n",
+			strerror(errno));
+	if(res < sizeof(struct squashfs_super_block))
+		BAD_ERROR("Recovery file appears to be truncated\n");
+
+	res = read_fs_bytes(fd, 0, sizeof(struct squashfs_super_block), &orig_sBlk);
+	if(res == 0) {
+		ERROR("Failed to read superblock from output filesystem\n");
+		BAD_ERROR("Output filesystem is empty!\n");
+	}
+
+	if(memcmp(((char *) &sBlk) + 4, ((char *) &orig_sBlk) + 4,
+			sizeof(struct squashfs_super_block) - 4) != 0)
+		BAD_ERROR("Recovery file and destination file do not seem to "
+			"match\n");
+
+	bytes = sBlk.bytes_used - sBlk.inode_table_start;
+
+	metadata = malloc(bytes);
+	if(metadata == NULL)
+		MEM_ERROR();
+
+	res = read_bytes(recoverfd, metadata, bytes);
+	if(res == -1)
+		BAD_ERROR("Failed to read recovery file, because %s\n",
+			strerror(errno));
+	if(res < bytes)
+		BAD_ERROR("Recovery file appears to be truncated\n");
+
+	write_destination(fd, 0, sizeof(struct squashfs_super_block), &sBlk);
+
+	write_destination(fd, sBlk.inode_table_start, bytes, metadata);
+
+	close(recoverfd);
+	close(fd);
+
+	printf("Successfully wrote recovery file \"%s\".  Exiting\n",
+		recovery_file);
+	
+	exit(0);
+}
+
+
+void write_filesystem_tables(struct squashfs_super_block *sBlk, int nopad)
+{
+	int i;
+
+	sBlk->fragments = fragments;
+	sBlk->no_ids = id_count;
+	sBlk->inode_table_start = write_inodes();
+	sBlk->directory_table_start = write_directories();
+	sBlk->fragment_table_start = write_fragment_table();
+	sBlk->lookup_table_start = exportable ? write_inode_lookup_table() :
+		SQUASHFS_INVALID_BLK;
+	sBlk->id_table_start = write_id_table();
+	sBlk->xattr_id_table_start = write_xattrs();
+
+	TRACE("sBlk->inode_table_start 0x%llx\n", sBlk->inode_table_start);
+	TRACE("sBlk->directory_table_start 0x%llx\n",
+		sBlk->directory_table_start);
+	TRACE("sBlk->fragment_table_start 0x%llx\n", sBlk->fragment_table_start);
+	if(exportable)
+		TRACE("sBlk->lookup_table_start 0x%llx\n",
+			sBlk->lookup_table_start);
+
+	sBlk->bytes_used = bytes;
+
+	sBlk->compression = comp->id;
+
+	SQUASHFS_INSWAP_SUPER_BLOCK(sBlk); 
+	write_destination(fd, SQUASHFS_START, sizeof(*sBlk), sBlk);
+
+	if(!nopad && (i = bytes & (4096 - 1))) {
+		char temp[4096] = {0};
+		write_destination(fd, bytes, 4096 - i, temp);
+	}
+
+	close(fd);
+
+	if(recovery_file)
+		unlink(recovery_file);
+
+	total_bytes += total_inode_bytes + total_directory_bytes +
+		sizeof(struct squashfs_super_block) + total_xattr_bytes;
+
+	printf("\n%sSquashfs %d.%d filesystem, %s compressed, data block size"
+		" %d\n", exportable ? "Exportable " : "", SQUASHFS_MAJOR,
+		SQUASHFS_MINOR, comp->name, block_size);
+	printf("\t%s data, %s metadata, %s fragments, %s xattrs\n",
+		noD ? "uncompressed" : "compressed", noI ?  "uncompressed" :
+		"compressed", no_fragments ? "no" : noF ? "uncompressed" :
+		"compressed", no_xattrs ? "no" : noX ? "uncompressed" :
+		"compressed");
+	printf("\tduplicates are %sremoved\n", duplicate_checking ? "" :
+		"not ");
+	printf("Filesystem size %.2f Kbytes (%.2f Mbytes)\n", bytes / 1024.0,
+		bytes / (1024.0 * 1024.0));
+	printf("\t%.2f%% of uncompressed filesystem size (%.2f Kbytes)\n",
+		((float) bytes / total_bytes) * 100.0, total_bytes / 1024.0);
+	printf("Inode table size %d bytes (%.2f Kbytes)\n",
+		inode_bytes, inode_bytes / 1024.0);
+	printf("\t%.2f%% of uncompressed inode table size (%d bytes)\n",
+		((float) inode_bytes / total_inode_bytes) * 100.0,
+		total_inode_bytes);
+	printf("Directory table size %d bytes (%.2f Kbytes)\n",
+		directory_bytes, directory_bytes / 1024.0);
+	printf("\t%.2f%% of uncompressed directory table size (%d bytes)\n",
+		((float) directory_bytes / total_directory_bytes) * 100.0,
+		total_directory_bytes);
+	if(total_xattr_bytes) {
+		printf("Xattr table size %d bytes (%.2f Kbytes)\n",
+			xattr_bytes, xattr_bytes / 1024.0);
+		printf("\t%.2f%% of uncompressed xattr table size (%d bytes)\n",
+			((float) xattr_bytes / total_xattr_bytes) * 100.0,
+			total_xattr_bytes);
+	}
+	if(duplicate_checking)
+		printf("Number of duplicate files found %d\n", file_count -
+			dup_files);
+	else
+		printf("No duplicate files removed\n");
+	printf("Number of inodes %d\n", inode_count);
+	printf("Number of files %d\n", file_count);
+	if(!no_fragments)
+		printf("Number of fragments %d\n", fragments);
+	printf("Number of symbolic links  %d\n", sym_count);
+	printf("Number of device nodes %d\n", dev_count);
+	printf("Number of fifo nodes %d\n", fifo_count);
+	printf("Number of socket nodes %d\n", sock_count);
+	printf("Number of directories %d\n", dir_count);
+	printf("Number of ids (unique uids + gids) %d\n", id_count);
+	printf("Number of uids %d\n", uid_count);
+
+	for(i = 0; i < id_count; i++) {
+		if(id_table[i]->flags & ISA_UID) {
+			struct passwd *user = getpwuid(id_table[i]->id);
+			printf("\t%s (%d)\n", user == NULL ? "unknown" :
+				user->pw_name, id_table[i]->id);
+		}
+	}
+
+	printf("Number of gids %d\n", guid_count);
+
+	for(i = 0; i < id_count; i++) {
+		if(id_table[i]->flags & ISA_GID) {
+			struct group *group = getgrgid(id_table[i]->id);
+			printf("\t%s (%d)\n", group == NULL ? "unknown" :
+				group->gr_name, id_table[i]->id);
+		}
+	}
+}
+
+
+int parse_numberll(char *start, long long *res, int size)
+{
+	char *end;
+	long long number;
+
+	errno = 0; /* To distinguish success/failure after call */
+
+	number = strtoll(start, &end, 10);
+
+	/*
+	 * check for strtoll underflow or overflow in conversion, and other
+	 * errors.
+	 */
+	if((errno == ERANGE && (number == LLONG_MIN || number == LLONG_MAX)) ||
+			(errno != 0 && number == 0))
+		return 0;
+
+	/* reject negative numbers as invalid */
+	if(number < 0)
+		return 0;
+
+	if(size) {
+		/*
+		 * Check for multiplier and trailing junk.
+		 * But first check that a number exists before the
+		 * multiplier
+		 */
+		if(end == start)
+			return 0;
+
+		switch(end[0]) {
+		case 'g':
+		case 'G':
+			if(multiply_overflowll(number, 1073741824))
+				return 0;
+			number *= 1073741824;
+
+			if(end[1] != '\0')
+				/* trailing junk after multiplier, but
+				 * allow it to be "bytes" */
+				if(strcmp(end + 1, "bytes"))
+					return 0;
+
+			break;
+		case 'm':
+		case 'M':
+			if(multiply_overflowll(number, 1048576))
+				return 0;
+			number *= 1048576;
+
+			if(end[1] != '\0')
+				/* trailing junk after multiplier, but
+				 * allow it to be "bytes" */
+				if(strcmp(end + 1, "bytes"))
+					return 0;
+
+			break;
+		case 'k':
+		case 'K':
+			if(multiply_overflowll(number, 1024))
+				return 0;
+			number *= 1024;
+
+			if(end[1] != '\0')
+				/* trailing junk after multiplier, but
+				 * allow it to be "bytes" */
+				if(strcmp(end + 1, "bytes"))
+					return 0;
+
+			break;
+		case '\0':
+			break;
+		default:
+			/* trailing junk after number */
+			return 0;
+		}
+	} else if(end[0] != '\0')
+		/* trailing junk after number */
+		return 0;
+
+	*res = number;
+	return 1;
+}
+
+
+int parse_number(char *start, int *res, int size)
+{
+	long long number;
+
+	if(!parse_numberll(start, &number, size))
+		return 0;
+	
+	/* check if long result will overflow signed int */
+	if(number > INT_MAX)
+		return 0;
+
+	*res = (int) number;
+	return 1;
+}
+
+
+int parse_num(char *arg, int *res)
+{
+	return parse_number(arg, res, 0);
+}
+
+
+int get_physical_memory()
+{
+	int phys_mem;
+#ifndef linux
+	#ifdef HW_MEMSIZE
+		#define SYSCTL_PHYSMEM HW_MEMSIZE
+	#elif defined(HW_PHYSMEM64)
+		#define SYSCTL_PHYSMEM HW_PHYSMEM64
+	#else
+		#define SYSCTL_PHYSMEM HW_PHYSMEM
+	#endif
+
+	int mib[2];
+	uint64_t sysctl_physmem = 0;
+	size_t sysctl_len = sizeof(sysctl_physmem);
+
+	mib[0] = CTL_HW;
+	mib[1] = SYSCTL_PHYSMEM;
+
+	if(sysctl(mib, 2, &sysctl_physmem, &sysctl_len, NULL, 0) == 0) {
+		/* some systems use 32-bit values, work with what we're given */
+		if (sysctl_len == 4)
+			sysctl_physmem = *(uint32_t*)&sysctl_physmem;
+		phys_mem = sysctl_physmem >> 20;
+	} else {
+		ERROR_START("Failed to get amount of available "
+			"memory.");
+		ERROR_EXIT("  Defaulting to least viable amount\n");
+		phys_mem = SQUASHFS_LOWMEM;
+	}
+  #undef SYSCTL_PHYSMEM
+#else
+	/* Long longs are used here because with PAE, a 32-bit
+	  machine can have more than 4GB of physical memory */
+
+	long long num_pages = sysconf(_SC_PHYS_PAGES);
+	long long page_size = sysconf(_SC_PAGESIZE);
+	phys_mem = num_pages * page_size >> 20;
+	if(num_pages == -1 || page_size == -1)
+		return 0;
+
+#endif
+
+	if(phys_mem < SQUASHFS_LOWMEM)
+		BAD_ERROR("Mksquashfs requires more physical memory than is "
+			"available!\n");
+
+	return phys_mem;
+}
+
+
+void check_usable_phys_mem(int total_mem)
+{
+	/*
+	 * We want to allow users to use as much of their physical
+	 * memory as they wish.  However, for practical reasons there are
+	 * limits which need to be imposed, to protect users from themselves
+	 * and to prevent people from using Mksquashfs as a DOS attack by using
+	 * all physical memory.   Mksquashfs uses memory to cache data from disk
+	 * to optimise performance.  It is pointless to ask it to use more
+	 * than 75% of physical memory, as this causes thrashing and it is thus
+	 * self-defeating.
+	 */
+	int mem = get_physical_memory();
+
+	mem = (mem >> 1) + (mem >> 2); /* 75% */
+						
+	if(total_mem > mem && mem) {
+		ERROR("Total memory requested is more than 75%% of physical "
+						"memory.\n");
+		ERROR("Mksquashfs uses memory to cache data from disk to "
+						"optimise performance.\n");
+		ERROR("It is pointless to ask it to use more than this amount "
+						"of memory, as this\n");
+		ERROR("causes thrashing and it is thus self-defeating.\n");
+		BAD_ERROR("Requested memory size too large\n");
+	}
+
+	if(sizeof(void *) == 4 && total_mem > 2048) {
+		/*
+		 * If we're running on a kernel with PAE or on a 64-bit kernel,
+		 * then the 75% physical memory limit can still easily exceed
+		 * the addressable memory by this process.
+		 *
+		 * Due to the typical kernel/user-space split (1GB/3GB, or
+		 * 2GB/2GB), we have to conservatively assume the 32-bit
+		 * processes can only address 2-3GB.  So refuse if the user
+		 * tries to allocate more than 2GB.
+		 */
+		ERROR("Total memory requested may exceed maximum "
+				"addressable memory by this process\n");
+		BAD_ERROR("Requested memory size too large\n");
+	}
+}
+
+
+int get_default_phys_mem()
+{
+	/*
+	 * get_physical_memory() relies on /proc being mounted.
+	 * If it fails, issue a warning, and use
+	 * SQUASHFS_LOWMEM / SQUASHFS_TAKE as default,
+	 * and allow a larger value to be set with -mem.
+	 */
+	int mem = get_physical_memory();
+
+	if(mem == 0) {
+		mem = SQUASHFS_LOWMEM / SQUASHFS_TAKE;
+
+		ERROR("Warning: Cannot get size of physical memory, probably "
+				"because /proc is missing.\n");
+		ERROR("Warning: Defaulting to minimal use of %d Mbytes, use "
+				"-mem to set a better value,\n", mem);
+		ERROR("Warning: or fix /proc.\n");
+	} else
+		mem /= SQUASHFS_TAKE;
+
+	if(sizeof(void *) == 4 && mem > 640) {
+		/*
+		 * If we're running on a kernel with PAE or on a 64-bit kernel,
+		 * the default memory usage can exceed the addressable
+		 * memory by this process.
+		 * Due to the typical kernel/user-space split (1GB/3GB, or
+		 * 2GB/2GB), we have to conservatively assume the 32-bit
+		 * processes can only address 2-3GB.  So limit the  default
+		 * usage to 640M, which gives room for other data.
+		 */
+		mem = 640;
+	}
+
+	return mem;
+}
+
+
+void calculate_queue_sizes(int mem, int *readq, int *fragq, int *bwriteq,
+							int *fwriteq)
+{
+	*readq = mem / SQUASHFS_READQ_MEM;
+	*bwriteq = mem / SQUASHFS_BWRITEQ_MEM;
+	*fwriteq = mem / SQUASHFS_FWRITEQ_MEM;
+	*fragq = mem - *readq - *bwriteq - *fwriteq;
+}
+
+
+#define VERSION() \
+	printf("mksquashfs version 4.3-git (2014/09/12)\n");\
+	printf("copyright (C) 2014 Phillip Lougher "\
+		"<phillip@squashfs.org.uk>\n\n"); \
+	printf("This program is free software; you can redistribute it and/or"\
+		"\n");\
+	printf("modify it under the terms of the GNU General Public License"\
+		"\n");\
+	printf("as published by the Free Software Foundation; either version "\
+		"2,\n");\
+	printf("or (at your option) any later version.\n\n");\
+	printf("This program is distributed in the hope that it will be "\
+		"useful,\n");\
+	printf("but WITHOUT ANY WARRANTY; without even the implied warranty "\
+		"of\n");\
+	printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the"\
+		"\n");\
+	printf("GNU General Public License for more details.\n");
+int main(int argc, char *argv[])
+{
+	struct stat buf, source_buf;
+	int res, i;
+	char *b, *root_name = NULL;
+	int keep_as_directory = FALSE;
+	squashfs_inode inode;
+	int readq;
+	int fragq;
+	int bwriteq;
+	int fwriteq;
+	int total_mem = get_default_phys_mem();
+	int progress = TRUE;
+	int force_progress = FALSE;
+	struct file_buffer **fragment = NULL;
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+	const char *fs_config_file = NULL;
+#endif
+/* ANDROID CHANGES END */
+
+	if(argc > 1 && strcmp(argv[1], "-version") == 0) {
+		VERSION();
+		exit(0);
+	}
+
+	block_log = slog(block_size);
+	calculate_queue_sizes(total_mem, &readq, &fragq, &bwriteq, &fwriteq);
+
+        for(i = 1; i < argc && argv[i][0] != '-'; i++);
+	if(i < 3)
+		goto printOptions;
+	source_path = argv + 1;
+	source = i - 2;
+
+	/*
+	 * Scan the command line for -comp xxx option, this is to ensure
+	 * any -X compressor specific options are passed to the
+	 * correct compressor
+	 */
+	for(; i < argc; i++) {
+		struct compressor *prev_comp = comp;
+		
+		if(strcmp(argv[i], "-comp") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -comp missing compression type\n",
+					argv[0]);
+				exit(1);
+			}
+			comp = lookup_compressor(argv[i]);
+			if(!comp->supported) {
+				ERROR("%s: Compressor \"%s\" is not supported!"
+					"\n", argv[0], argv[i]);
+				ERROR("%s: Compressors available:\n", argv[0]);
+				display_compressors("", COMP_DEFAULT);
+				exit(1);
+			}
+			if(prev_comp != NULL && prev_comp != comp) {
+				ERROR("%s: -comp multiple conflicting -comp"
+					" options specified on command line"
+					", previously %s, now %s\n", argv[0],
+					prev_comp->name, comp->name);
+				exit(1);
+			}
+			compressor_opt_parsed = 1;
+
+		} else if(strcmp(argv[i], "-e") == 0)
+			break;
+		else if(strcmp(argv[i], "-root-becomes") == 0 ||
+				strcmp(argv[i], "-ef") == 0 ||
+				strcmp(argv[i], "-pf") == 0 ||
+				strcmp(argv[i], "-vaf") == 0 ||
+				strcmp(argv[i], "-comp") == 0)
+			i++;
+	}
+
+	/*
+	 * if no -comp option specified lookup default compressor.  Note the
+	 * Makefile ensures the default compressor has been built, and so we
+	 * don't need to to check for failure here
+	 */
+	if(comp == NULL)
+		comp = lookup_compressor(COMP_DEFAULT);
+
+	for(i = source + 2; i < argc; i++) {
+		if(strcmp(argv[i], "-action") == 0 ||
+				strcmp(argv[i], "-a") ==0) {
+			if(++i == argc) {
+				ERROR("%s: %s missing action\n",
+					argv[0], argv[i - 1]);
+				exit(1);
+			}
+			res = parse_action(argv[i], ACTION_LOG_NONE);
+			if(res == 0)
+				exit(1);
+
+		} else if(strcmp(argv[i], "-verbose-action") == 0 ||
+				strcmp(argv[i], "-va") ==0) {
+			if(++i == argc) {
+				ERROR("%s: %s missing action\n",
+					argv[0], argv[i - 1]);
+				exit(1);
+			}
+			res = parse_action(argv[i], ACTION_LOG_VERBOSE);
+			if(res == 0)
+				exit(1);
+
+		} else if(strcmp(argv[i], "-true-action") == 0 ||
+				strcmp(argv[i], "-ta") ==0) {
+			if(++i == argc) {
+				ERROR("%s: %s missing action\n",
+					argv[0], argv[i - 1]);
+				exit(1);
+			}
+			res = parse_action(argv[i], ACTION_LOG_TRUE);
+			if(res == 0)
+				exit(1);
+
+		} else if(strcmp(argv[i], "-false-action") == 0 ||
+				strcmp(argv[i], "-fa") ==0) {
+			if(++i == argc) {
+				ERROR("%s: %s missing action\n",
+					argv[0], argv[i - 1]);
+				exit(1);
+			}
+			res = parse_action(argv[i], ACTION_LOG_FALSE);
+			if(res == 0)
+				exit(1);
+
+		} else if(strcmp(argv[i], "-action-file") == 0 ||
+				strcmp(argv[i], "-af") ==0) {
+			if(++i == argc) {
+				ERROR("%s: %s missing filename\n", argv[0],
+							argv[i - 1]);
+				exit(1);
+			}
+			if(read_action_file(argv[i], ACTION_LOG_NONE) == FALSE)
+				exit(1);
+
+		} else if(strcmp(argv[i], "-verbose-action-file") == 0 ||
+				strcmp(argv[i], "-vaf") ==0) {
+			if(++i == argc) {
+				ERROR("%s: %s missing filename\n", argv[0],
+							argv[i - 1]);
+				exit(1);
+			}
+			if(read_action_file(argv[i], ACTION_LOG_VERBOSE) == FALSE)
+				exit(1);
+
+		} else if(strcmp(argv[i], "-true-action-file") == 0 ||
+				strcmp(argv[i], "-taf") ==0) {
+			if(++i == argc) {
+				ERROR("%s: %s missing filename\n", argv[0],
+							argv[i - 1]);
+				exit(1);
+			}
+			if(read_action_file(argv[i], ACTION_LOG_TRUE) == FALSE)
+				exit(1);
+
+		} else if(strcmp(argv[i], "-false-action-file") == 0 ||
+				strcmp(argv[i], "-faf") ==0) {
+			if(++i == argc) {
+				ERROR("%s: %s missing filename\n", argv[0],
+							argv[i - 1]);
+				exit(1);
+			}
+			if(read_action_file(argv[i], ACTION_LOG_FALSE) == FALSE)
+				exit(1);
+
+		} else if(strcmp(argv[i], "-comp") == 0)
+			/* parsed previously */
+			i++;
+
+		else if(strncmp(argv[i], "-X", 2) == 0) {
+			int args;
+
+			if(strcmp(argv[i] + 2, "help") == 0)
+				goto print_compressor_options;
+
+			args = compressor_options(comp, argv + i, argc - i);
+			if(args < 0) {
+				if(args == -1) {
+					ERROR("%s: Unrecognised compressor"
+						" option %s\n", argv[0],
+						argv[i]);
+					if(!compressor_opt_parsed)
+						ERROR("%s: Did you forget to"
+							" specify -comp?\n",
+							argv[0]);
+print_compressor_options:
+					ERROR("%s: selected compressor \"%s\""
+						".  Options supported: %s\n",
+						argv[0], comp->name,
+						comp->usage ? "" : "none");
+					if(comp->usage)
+						comp->usage();
+				}
+				exit(1);
+			}
+			i += args;
+
+		} else if(strcmp(argv[i], "-pf") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -pf missing filename\n", argv[0]);
+				exit(1);
+			}
+			if(read_pseudo_file(argv[i]) == FALSE)
+				exit(1);
+		} else if(strcmp(argv[i], "-p") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -p missing pseudo file definition\n",
+					argv[0]);
+				exit(1);
+			}
+			if(read_pseudo_def(argv[i]) == FALSE)
+				exit(1);
+		} else if(strcmp(argv[i], "-recover") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -recover missing recovery file\n",
+					argv[0]);
+				exit(1);
+			}
+			read_recovery_data(argv[i], argv[source + 1]);
+		} else if(strcmp(argv[i], "-no-recovery") == 0)
+			recover = FALSE;
+		else if(strcmp(argv[i], "-wildcards") == 0) {
+			old_exclude = FALSE;
+			use_regex = FALSE;
+		} else if(strcmp(argv[i], "-regex") == 0) {
+			old_exclude = FALSE;
+			use_regex = TRUE;
+		} else if(strcmp(argv[i], "-no-sparse") == 0)
+			sparse_files = FALSE;
+		else if(strcmp(argv[i], "-no-progress") == 0)
+			progress = FALSE;
+		else if(strcmp(argv[i], "-progress") == 0)
+			force_progress = TRUE;
+		else if(strcmp(argv[i], "-no-exports") == 0)
+			exportable = FALSE;
+		else if(strcmp(argv[i], "-processors") == 0) {
+			if((++i == argc) || !parse_num(argv[i], &processors)) {
+				ERROR("%s: -processors missing or invalid "
+					"processor number\n", argv[0]);
+				exit(1);
+			}
+			if(processors < 1) {
+				ERROR("%s: -processors should be 1 or larger\n",
+					argv[0]);
+				exit(1);
+			}
+		} else if(strcmp(argv[i], "-read-queue") == 0) {
+			if((++i == argc) || !parse_num(argv[i], &readq)) {
+				ERROR("%s: -read-queue missing or invalid "
+					"queue size\n", argv[0]);
+				exit(1);
+			}
+			if(readq < 1) {
+				ERROR("%s: -read-queue should be 1 megabyte or "
+					"larger\n", argv[0]);
+				exit(1);
+			}
+		} else if(strcmp(argv[i], "-write-queue") == 0) {
+			if((++i == argc) || !parse_num(argv[i], &bwriteq)) {
+				ERROR("%s: -write-queue missing or invalid "
+					"queue size\n", argv[0]);
+				exit(1);
+			}
+			if(bwriteq < 2) {
+				ERROR("%s: -write-queue should be 2 megabytes "
+					"or larger\n", argv[0]);
+				exit(1);
+			}
+			fwriteq = bwriteq >> 1;
+			bwriteq -= fwriteq;
+		} else if(strcmp(argv[i], "-fragment-queue") == 0) {
+			if((++i == argc) || !parse_num(argv[i], &fragq)) {
+				ERROR("%s: -fragment-queue missing or invalid "
+					"queue size\n", argv[0]);
+				exit(1);
+			}
+			if(fragq < 1) {
+				ERROR("%s: -fragment-queue should be 1 "
+					"megabyte or larger\n", argv[0]);
+				exit(1);
+			}
+		} else if(strcmp(argv[i], "-mem") == 0) {
+			long long number;
+
+			if((++i == argc) ||
+					!parse_numberll(argv[i], &number, 1)) {
+				ERROR("%s: -mem missing or invalid mem size\n",
+					 argv[0]);
+				exit(1);
+			}
+
+			/*
+			 * convert from bytes to Mbytes, ensuring the value
+			 * does not overflow a signed int
+			 */
+			if(number >= (1LL << 51)) {
+				ERROR("%s: -mem invalid mem size\n", argv[0]);
+				exit(1);
+			}
+
+			total_mem = number / 1048576;
+			if(total_mem < (SQUASHFS_LOWMEM / SQUASHFS_TAKE)) {
+				ERROR("%s: -mem should be %d Mbytes or "
+					"larger\n", argv[0],
+					SQUASHFS_LOWMEM / SQUASHFS_TAKE);
+				exit(1);
+			}
+			calculate_queue_sizes(total_mem, &readq, &fragq,
+				&bwriteq, &fwriteq);
+		} else if(strcmp(argv[i], "-b") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -b missing block size\n", argv[0]);
+				exit(1);
+			}
+			if(!parse_number(argv[i], &block_size, 1)) {
+				ERROR("%s: -b invalid block size\n", argv[0]);
+				exit(1);
+			}
+			if((block_log = slog(block_size)) == 0) {
+				ERROR("%s: -b block size not power of two or "
+					"not between 4096 and 1Mbyte\n",
+					argv[0]);
+				exit(1);
+			}
+		} else if(strcmp(argv[i], "-ef") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -ef missing filename\n", argv[0]);
+				exit(1);
+			}
+		} else if(strcmp(argv[i], "-no-duplicates") == 0)
+			duplicate_checking = FALSE;
+
+		else if(strcmp(argv[i], "-no-fragments") == 0)
+			no_fragments = TRUE;
+
+		 else if(strcmp(argv[i], "-always-use-fragments") == 0)
+			always_use_fragments = TRUE;
+
+		 else if(strcmp(argv[i], "-sort") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -sort missing filename\n", argv[0]);
+				exit(1);
+			}
+		} else if(strcmp(argv[i], "-all-root") == 0 ||
+				strcmp(argv[i], "-root-owned") == 0)
+			global_uid = global_gid = 0;
+
+		else if(strcmp(argv[i], "-force-uid") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -force-uid missing uid or user\n",
+					argv[0]);
+				exit(1);
+			}
+			if((global_uid = strtoll(argv[i], &b, 10)), *b =='\0') {
+				if(global_uid < 0 || global_uid >
+						(((long long) 1 << 32) - 1)) {
+					ERROR("%s: -force-uid uid out of range"
+						"\n", argv[0]);
+					exit(1);
+				}
+			} else {
+				struct passwd *uid = getpwnam(argv[i]);
+				if(uid)
+					global_uid = uid->pw_uid;
+				else {
+					ERROR("%s: -force-uid invalid uid or "
+						"unknown user\n", argv[0]);
+					exit(1);
+				}
+			}
+		} else if(strcmp(argv[i], "-force-gid") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -force-gid missing gid or group\n",
+					argv[0]);
+				exit(1);
+			}
+			if((global_gid = strtoll(argv[i], &b, 10)), *b =='\0') {
+				if(global_gid < 0 || global_gid >
+						(((long long) 1 << 32) - 1)) {
+					ERROR("%s: -force-gid gid out of range"
+						"\n", argv[0]);
+					exit(1);
+				}
+			} else {
+				struct group *gid = getgrnam(argv[i]);
+				if(gid)
+					global_gid = gid->gr_gid;
+				else {
+					ERROR("%s: -force-gid invalid gid or "
+						"unknown group\n", argv[0]);
+					exit(1);
+				}
+			}
+		} else if(strcmp(argv[i], "-noI") == 0 ||
+				strcmp(argv[i], "-noInodeCompression") == 0)
+			noI = TRUE;
+
+		else if(strcmp(argv[i], "-noD") == 0 ||
+				strcmp(argv[i], "-noDataCompression") == 0)
+			noD = TRUE;
+
+		else if(strcmp(argv[i], "-noF") == 0 ||
+				strcmp(argv[i], "-noFragmentCompression") == 0)
+			noF = TRUE;
+
+		else if(strcmp(argv[i], "-noX") == 0 ||
+				strcmp(argv[i], "-noXattrCompression") == 0)
+			noX = TRUE;
+
+		else if(strcmp(argv[i], "-no-xattrs") == 0)
+			no_xattrs = TRUE;
+
+		else if(strcmp(argv[i], "-xattrs") == 0)
+			no_xattrs = FALSE;
+
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+		else if(strcmp(argv[i], "-context-file") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -context-file: missing file name\n",
+					argv[0]);
+				exit(1);
+			}
+			context_file = argv[i];
+		}
+		else if(strcmp(argv[i], "-fs-config-file") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -fs-config-file: missing file name\n",
+					argv[0]);
+				exit(1);
+			}
+			fs_config_file = argv[i];
+		}
+#endif
+/* ANDROID CHANGES END */
+		else if(strcmp(argv[i], "-nopad") == 0)
+			nopad = TRUE;
+
+		else if(strcmp(argv[i], "-info") == 0)
+			silent = FALSE;
+
+		else if(strcmp(argv[i], "-e") == 0)
+			break;
+
+		else if(strcmp(argv[i], "-noappend") == 0)
+			delete = TRUE;
+
+		else if(strcmp(argv[i], "-keep-as-directory") == 0)
+			keep_as_directory = TRUE;
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+		else if(strcmp(argv[i], "-android-fs-config") == 0)
+			android_config = TRUE;
+		else if(strcmp(argv[i], "-mount-point") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -mount-point: missing mount point name\n",
+					argv[0]);
+				exit(1);
+			}
+			mount_point = argv[i];
+		}
+		else if(strcmp(argv[i], "-product-out") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -product-out: missing path name\n",
+					argv[0]);
+				exit(1);
+			}
+			target_out_path = argv[i];
+		}
+#endif
+/* ANDROID CHANGES END */
+
+		else if(strcmp(argv[i], "-exit-on-error") == 0)
+			exit_on_error = TRUE;
+
+		else if(strcmp(argv[i], "-root-becomes") == 0) {
+			if(++i == argc) {
+				ERROR("%s: -root-becomes: missing name\n",
+					argv[0]);
+				exit(1);
+			}	
+			root_name = argv[i];
+		} else if(strcmp(argv[i], "-version") == 0) {
+			VERSION();
+		} else {
+			ERROR("%s: invalid option\n\n", argv[0]);
+printOptions:
+			ERROR("SYNTAX:%s source1 source2 ...  dest [options] "
+				"[-e list of exclude\ndirs/files]\n", argv[0]);
+			ERROR("\nFilesystem build options:\n");
+			ERROR("-comp <comp>\t\tselect <comp> compression\n");
+			ERROR("\t\t\tCompressors available:\n");
+			display_compressors("\t\t\t", COMP_DEFAULT);
+			ERROR("-b <block_size>\t\tset data block to "
+				"<block_size>.  Default 128 Kbytes\n");
+			ERROR("\t\t\tOptionally a suffix of K or M can be"
+				" given to specify\n\t\t\tKbytes or Mbytes"
+				" respectively\n");
+			ERROR("-no-exports\t\tdon't make the filesystem "
+				"exportable via NFS\n");
+			ERROR("-no-sparse\t\tdon't detect sparse files\n");
+			ERROR("-no-xattrs\t\tdon't store extended attributes"
+				NOXOPT_STR "\n");
+			ERROR("-xattrs\t\t\tstore extended attributes" XOPT_STR
+				"\n");
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+			ERROR("-context-file <file>\tApply selinux security "
+				"xattrs from context-file instead\n\t\t\t"
+				"of reading xattrs from file system\n");
+			ERROR("-fs-config-file <file>\tAndroid specific "
+				"filesystem config file\n");
+#endif
+/* ANDROID CHANGES END */
+			ERROR("-noI\t\t\tdo not compress inode table\n");
+			ERROR("-noD\t\t\tdo not compress data blocks\n");
+			ERROR("-noF\t\t\tdo not compress fragment blocks\n");
+			ERROR("-noX\t\t\tdo not compress extended "
+				"attributes\n");
+			ERROR("-no-fragments\t\tdo not use fragments\n");
+			ERROR("-always-use-fragments\tuse fragment blocks for "
+				"files larger than block size\n");
+			ERROR("-no-duplicates\t\tdo not perform duplicate "
+				"checking\n");
+			ERROR("-all-root\t\tmake all files owned by root\n");
+			ERROR("-force-uid uid\t\tset all file uids to uid\n");
+			ERROR("-force-gid gid\t\tset all file gids to gid\n");
+			ERROR("-nopad\t\t\tdo not pad filesystem to a multiple "
+				"of 4K\n");
+			ERROR("-keep-as-directory\tif one source directory is "
+				"specified, create a root\n");
+			ERROR("\t\t\tdirectory containing that directory, "
+				"rather than the\n");
+			ERROR("\t\t\tcontents of the directory\n");
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+			ERROR("-android-fs-config\tuse android fs config "
+				"for mode, uid, and gids of inodes\n");
+			ERROR("-mount-point <name>\tNeed to be provided when "
+				"android-fs-config or context-file\n\t\t\tare "
+				"enabled and source directory is not mount point\n");
+			ERROR("-product-out <path>\tPRODUCT_OUT directory to "
+                                "read device specific FS rules files from\n");
+#endif
+/* ANDROID CHANGES END */
+			ERROR("\nFilesystem filter options:\n");
+			ERROR("-p <pseudo-definition>\tAdd pseudo file "
+				"definition\n");
+			ERROR("-pf <pseudo-file>\tAdd list of pseudo file "
+				"definitions\n");
+			ERROR("-sort <sort_file>\tsort files according to "
+				"priorities in <sort_file>.  One\n");
+			ERROR("\t\t\tfile or dir with priority per line.  "
+				"Priority -32768 to\n");
+			ERROR("\t\t\t32767, default priority 0\n");
+			ERROR("-ef <exclude_file>\tlist of exclude dirs/files."
+				"  One per line\n");
+			ERROR("-wildcards\t\tAllow extended shell wildcards "
+				"(globbing) to be used in\n\t\t\texclude "
+				"dirs/files\n");
+			ERROR("-regex\t\t\tAllow POSIX regular expressions to "
+				"be used in exclude\n\t\t\tdirs/files\n");
+			ERROR("\nFilesystem append options:\n");
+			ERROR("-noappend\t\tdo not append to existing "
+				"filesystem\n");
+			ERROR("-root-becomes <name>\twhen appending source "
+				"files/directories, make the\n");
+			ERROR("\t\t\toriginal root become a subdirectory in "
+				"the new root\n");
+			ERROR("\t\t\tcalled <name>, rather than adding the new "
+				"source items\n");
+			ERROR("\t\t\tto the original root\n");
+			ERROR("\nMksquashfs runtime options:\n");
+			ERROR("-version\t\tprint version, licence and "
+				"copyright message\n");
+			ERROR("-exit-on-error\t\ttreat normally ignored errors "
+				"as fatal\n");
+			ERROR("-recover <name>\t\trecover filesystem data "
+				"using recovery file <name>\n");
+			ERROR("-no-recovery\t\tdon't generate a recovery "
+				"file\n");
+			ERROR("-info\t\t\tprint files written to filesystem\n");
+			ERROR("-no-progress\t\tdon't display the progress "
+				"bar\n");
+			ERROR("-progress\t\tdisplay progress bar when using "
+				"the -info option\n");
+			ERROR("-processors <number>\tUse <number> processors."
+				"  By default will use number of\n");
+			ERROR("\t\t\tprocessors available\n");
+			ERROR("-mem <size>\t\tUse <size> physical memory.  "
+				"Currently set to %dM\n", total_mem);
+			ERROR("\t\t\tOptionally a suffix of K, M or G can be"
+				" given to specify\n\t\t\tKbytes, Mbytes or"
+				" Gbytes respectively\n");
+			ERROR("\nMiscellaneous options:\n");
+			ERROR("-root-owned\t\talternative name for -all-root"
+				"\n");
+			ERROR("-noInodeCompression\talternative name for -noI"
+				"\n");
+			ERROR("-noDataCompression\talternative name for -noD"
+				"\n");
+			ERROR("-noFragmentCompression\talternative name for "
+				"-noF\n");
+			ERROR("-noXattrCompression\talternative name for "
+				"-noX\n");
+			ERROR("\n-Xhelp\t\t\tprint compressor options for"
+				" selected compressor\n");
+			ERROR("\nCompressors available and compressor specific "
+				"options:\n");
+			display_compressor_usage(COMP_DEFAULT);
+			exit(1);
+		}
+	}
+
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+	if (fs_config_file) {
+		if (load_canned_fs_config(fs_config_file) < 0) {
+			fprintf(stderr, "failed to load %s\n", fs_config_file);
+			exit(1);
+		}
+		fs_config_func = canned_fs_config;
+	} else if (mount_point) {
+		fs_config_func = fs_config;
+	}
+#endif
+/* ANDROID CHANGES END */
+
+	/*
+	 * Some compressors may need the options to be checked for validity
+	 * once all the options have been processed
+	 */
+	res = compressor_options_post(comp, block_size);
+	if(res)
+		EXIT_MKSQUASHFS();
+
+	/*
+	 * If the -info option has been selected then disable the
+	 * progress bar unless it has been explicitly enabled with
+	 * the -progress option
+	 */
+	if(!silent)
+		progress = force_progress;
+		
+#ifdef SQUASHFS_TRACE
+	/*
+	 * Disable progress bar if full debug tracing is enabled.
+	 * The progress bar in this case just gets in the way of the
+	 * debug trace output
+	 */
+	progress = FALSE;
+#endif
+
+	for(i = 0; i < source; i++)
+		if(lstat(source_path[i], &source_buf) == -1) {
+			fprintf(stderr, "Cannot stat source directory \"%s\" "
+				"because %s\n", source_path[i],
+				strerror(errno));
+			EXIT_MKSQUASHFS();
+		}
+
+	destination_file = argv[source + 1];
+	if(stat(argv[source + 1], &buf) == -1) {
+		if(errno == ENOENT) { /* Does not exist */
+			fd = open(argv[source + 1], O_CREAT | O_TRUNC | O_RDWR,
+				S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+			if(fd == -1) {
+				perror("Could not create destination file");
+				exit(1);
+			}
+			delete = TRUE;
+		} else {
+			perror("Could not stat destination file");
+			exit(1);
+		}
+
+	} else {
+		if(S_ISBLK(buf.st_mode)) {
+			if((fd = open(argv[source + 1], O_RDWR)) == -1) {
+				perror("Could not open block device as "
+					"destination");
+				exit(1);
+			}
+			block_device = 1;
+
+		} else if(S_ISREG(buf.st_mode))	 {
+			fd = open(argv[source + 1], (delete ? O_TRUNC : 0) |
+				O_RDWR);
+			if(fd == -1) {
+				perror("Could not open regular file for "
+					"writing as destination");
+				exit(1);
+			}
+		}
+		else {
+			ERROR("Destination not block device or regular file\n");
+			exit(1);
+		}
+
+	}
+
+	/*
+	 * process the exclude files - must be done afer destination file has
+	 * been possibly created
+	 */
+	for(i = source + 2; i < argc; i++)
+		if(strcmp(argv[i], "-ef") == 0)
+			/*
+			 * Note presence of filename arg has already
+			 * been checked
+			 */
+			process_exclude_file(argv[++i]);
+		else if(strcmp(argv[i], "-e") == 0)
+			break;
+		else if(strcmp(argv[i], "-root-becomes") == 0 ||
+				strcmp(argv[i], "-sort") == 0 ||
+				strcmp(argv[i], "-pf") == 0 ||
+				strcmp(argv[i], "-af") == 0 ||
+				strcmp(argv[i], "-vaf") == 0 ||
+				strcmp(argv[i], "-comp") == 0)
+			i++;
+
+	if(i != argc) {
+		if(++i == argc) {
+			ERROR("%s: -e missing arguments\n", argv[0]);
+			EXIT_MKSQUASHFS();
+		}
+		while(i < argc)
+			if(old_exclude)
+				old_add_exclude(argv[i++]);
+			else
+				add_exclude(argv[i++]);
+	}
+
+	/* process the sort files - must be done afer the exclude files  */
+	for(i = source + 2; i < argc; i++)
+		if(strcmp(argv[i], "-sort") == 0) {
+			int res = read_sort_file(argv[++i], source,
+								source_path);
+			if(res == FALSE)
+				BAD_ERROR("Failed to read sort file\n");
+			sorted ++;
+		} else if(strcmp(argv[i], "-e") == 0)
+			break;
+		else if(strcmp(argv[i], "-root-becomes") == 0 ||
+				strcmp(argv[i], "-ef") == 0 ||
+				strcmp(argv[i], "-pf") == 0 ||
+				strcmp(argv[i], "-af") == 0 ||
+				strcmp(argv[i], "-vaf") == 0 ||
+				strcmp(argv[i], "-comp") == 0)
+			i++;
+
+	if(!delete) {
+	        comp = read_super(fd, &sBlk, argv[source + 1]);
+	        if(comp == NULL) {
+			ERROR("Failed to read existing filesystem - will not "
+				"overwrite - ABORTING!\n");
+			ERROR("To force Mksquashfs to write to this block "
+				"device or file use -noappend\n");
+			EXIT_MKSQUASHFS();
+		}
+
+		block_log = slog(block_size = sBlk.block_size);
+		noI = SQUASHFS_UNCOMPRESSED_INODES(sBlk.flags);
+		noD = SQUASHFS_UNCOMPRESSED_DATA(sBlk.flags);
+		noF = SQUASHFS_UNCOMPRESSED_FRAGMENTS(sBlk.flags);
+		noX = SQUASHFS_UNCOMPRESSED_XATTRS(sBlk.flags);
+		no_fragments = SQUASHFS_NO_FRAGMENTS(sBlk.flags);
+		always_use_fragments = SQUASHFS_ALWAYS_FRAGMENTS(sBlk.flags);
+		duplicate_checking = SQUASHFS_DUPLICATES(sBlk.flags);
+		exportable = SQUASHFS_EXPORTABLE(sBlk.flags);
+		no_xattrs = SQUASHFS_NO_XATTRS(sBlk.flags);
+		comp_opts = SQUASHFS_COMP_OPTS(sBlk.flags);
+	}
+
+	initialise_threads(readq, fragq, bwriteq, fwriteq, delete,
+		destination_file);
+
+	res = compressor_init(comp, &stream, SQUASHFS_METADATA_SIZE, 0);
+	if(res)
+		BAD_ERROR("compressor_init failed\n");
+
+	if(delete) {
+		int size;
+		void *comp_data = compressor_dump_options(comp, block_size,
+			&size);
+
+		printf("Creating %d.%d filesystem on %s, block size %d.\n",
+			SQUASHFS_MAJOR, SQUASHFS_MINOR, argv[source + 1], block_size);
+
+		/*
+		 * store any compressor specific options after the superblock,
+		 * and set the COMP_OPT flag to show that the filesystem has
+		 * compressor specfic options
+		 */
+		if(comp_data) {
+			unsigned short c_byte = size | SQUASHFS_COMPRESSED_BIT;
+	
+			SQUASHFS_INSWAP_SHORTS(&c_byte, 1);
+			write_destination(fd, sizeof(struct squashfs_super_block),
+				sizeof(c_byte), &c_byte);
+			write_destination(fd, sizeof(struct squashfs_super_block) +
+				sizeof(c_byte), size, comp_data);
+			bytes = sizeof(struct squashfs_super_block) + sizeof(c_byte)
+				+ size;
+			comp_opts = TRUE;
+		} else			
+			bytes = sizeof(struct squashfs_super_block);
+	} else {
+		unsigned int last_directory_block, inode_dir_offset,
+			inode_dir_file_size, root_inode_size,
+			inode_dir_start_block, uncompressed_data,
+			compressed_data, inode_dir_inode_number,
+			inode_dir_parent_inode;
+		unsigned int root_inode_start =
+			SQUASHFS_INODE_BLK(sBlk.root_inode),
+			root_inode_offset =
+			SQUASHFS_INODE_OFFSET(sBlk.root_inode);
+
+		if((bytes = read_filesystem(root_name, fd, &sBlk, &inode_table,
+				&data_cache, &directory_table,
+				&directory_data_cache, &last_directory_block,
+				&inode_dir_offset, &inode_dir_file_size,
+				&root_inode_size, &inode_dir_start_block,
+				&file_count, &sym_count, &dev_count, &dir_count,
+				&fifo_count, &sock_count, &total_bytes,
+				&total_inode_bytes, &total_directory_bytes,
+				&inode_dir_inode_number,
+				&inode_dir_parent_inode, add_old_root_entry,
+				&fragment_table, &inode_lookup_table)) == 0) {
+			ERROR("Failed to read existing filesystem - will not "
+				"overwrite - ABORTING!\n");
+			ERROR("To force Mksquashfs to write to this block "
+				"device or file use -noappend\n");
+			EXIT_MKSQUASHFS();
+		}
+		if((append_fragments = fragments = sBlk.fragments)) {
+			fragment_table = realloc((char *) fragment_table,
+				((fragments + FRAG_SIZE - 1) & ~(FRAG_SIZE - 1))
+				 * sizeof(struct squashfs_fragment_entry)); 
+			if(fragment_table == NULL)
+				BAD_ERROR("Out of memory in save filesystem state\n");
+		}
+
+		printf("Appending to existing %d.%d filesystem on %s, block "
+			"size %d\n", SQUASHFS_MAJOR, SQUASHFS_MINOR, argv[source + 1],
+			block_size);
+		printf("All -b, -noI, -noD, -noF, -noX, no-duplicates, no-fragments, "
+			"-always-use-fragments,\n-exportable and -comp options "
+			"ignored\n");
+		printf("\nIf appending is not wanted, please re-run with "
+			"-noappend specified!\n\n");
+
+		compressed_data = (inode_dir_offset + inode_dir_file_size) &
+			~(SQUASHFS_METADATA_SIZE - 1);
+		uncompressed_data = (inode_dir_offset + inode_dir_file_size) &
+			(SQUASHFS_METADATA_SIZE - 1);
+		
+		/* save original filesystem state for restoring ... */
+		sfragments = fragments;
+		sbytes = bytes;
+		sinode_count = sBlk.inodes;
+		scache_bytes = root_inode_offset + root_inode_size;
+		sdirectory_cache_bytes = uncompressed_data;
+		sdata_cache = malloc(scache_bytes);
+		if(sdata_cache == NULL)
+			BAD_ERROR("Out of memory in save filesystem state\n");
+		sdirectory_data_cache = malloc(sdirectory_cache_bytes);
+		if(sdirectory_data_cache == NULL)
+			BAD_ERROR("Out of memory in save filesystem state\n");
+		memcpy(sdata_cache, data_cache, scache_bytes);
+		memcpy(sdirectory_data_cache, directory_data_cache +
+			compressed_data, sdirectory_cache_bytes);
+		sinode_bytes = root_inode_start;
+		stotal_bytes = total_bytes;
+		stotal_inode_bytes = total_inode_bytes;
+		stotal_directory_bytes = total_directory_bytes +
+			compressed_data;
+		sfile_count = file_count;
+		ssym_count = sym_count;
+		sdev_count = dev_count;
+		sdir_count = dir_count + 1;
+		sfifo_count = fifo_count;
+		ssock_count = sock_count;
+		sdup_files = dup_files;
+		sid_count = id_count;
+		write_recovery_data(&sBlk);
+		save_xattrs();
+		appending = TRUE;
+
+		/*
+		 * set the filesystem state up to be able to append to the
+		 * original filesystem.  The filesystem state differs depending
+		 * on whether we're appending to the original root directory, or
+		 * if the original root directory becomes a sub-directory
+		 * (root-becomes specified on command line, here root_name !=
+		 * NULL)
+		 */
+		inode_bytes = inode_size = root_inode_start;
+		directory_size = last_directory_block;
+		cache_size = root_inode_offset + root_inode_size;
+		directory_cache_size = inode_dir_offset + inode_dir_file_size;
+		if(root_name) {
+			sdirectory_bytes = last_directory_block;
+			sdirectory_compressed_bytes = 0;
+			root_inode_number = inode_dir_parent_inode;
+			inode_no = sBlk.inodes + 2;
+			directory_bytes = last_directory_block;
+			directory_cache_bytes = uncompressed_data;
+			memmove(directory_data_cache, directory_data_cache +
+				compressed_data, uncompressed_data);
+			cache_bytes = root_inode_offset + root_inode_size;
+			add_old_root_entry(root_name, sBlk.root_inode,
+				inode_dir_inode_number, SQUASHFS_DIR_TYPE);
+			total_directory_bytes += compressed_data;
+			dir_count ++;
+		} else {
+			sdirectory_compressed_bytes = last_directory_block -
+				inode_dir_start_block;
+			sdirectory_compressed =
+				malloc(sdirectory_compressed_bytes);
+			if(sdirectory_compressed == NULL)
+				BAD_ERROR("Out of memory in save filesystem "
+					"state\n");
+			memcpy(sdirectory_compressed, directory_table +
+				inode_dir_start_block,
+				sdirectory_compressed_bytes); 
+			sdirectory_bytes = inode_dir_start_block;
+			root_inode_number = inode_dir_inode_number;
+			inode_no = sBlk.inodes + 1;
+			directory_bytes = inode_dir_start_block;
+			directory_cache_bytes = inode_dir_offset;
+			cache_bytes = root_inode_offset;
+		}
+
+		inode_count = file_count + dir_count + sym_count + dev_count +
+			fifo_count + sock_count;
+	}
+
+	if(path)
+		paths = add_subdir(paths, path);
+
+	dump_actions(); 
+	dump_pseudos();
+
+	if(delete && !keep_as_directory && source == 1 &&
+			S_ISDIR(source_buf.st_mode))
+		dir_scan(&inode, source_path[0], scan1_readdir, progress);
+	else if(!keep_as_directory && source == 1 &&
+			S_ISDIR(source_buf.st_mode))
+		dir_scan(&inode, source_path[0], scan1_single_readdir, progress);
+	else
+		dir_scan(&inode, "", scan1_encomp_readdir, progress);
+	sBlk.root_inode = inode;
+	sBlk.inodes = inode_count;
+	sBlk.s_magic = SQUASHFS_MAGIC;
+	sBlk.s_major = SQUASHFS_MAJOR;
+	sBlk.s_minor = SQUASHFS_MINOR;
+	sBlk.block_size = block_size;
+	sBlk.block_log = block_log;
+	sBlk.flags = SQUASHFS_MKFLAGS(noI, noD, noF, noX, no_fragments,
+		always_use_fragments, duplicate_checking, exportable,
+		no_xattrs, comp_opts);
+	sBlk.mkfs_time = time(NULL);
+
+	disable_info();
+
+	while((fragment = get_frag_action(fragment)))
+		write_fragment(*fragment);
+	unlock_fragments();
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+	pthread_mutex_lock(&fragment_mutex);
+	while(fragments_outstanding) {
+		pthread_mutex_unlock(&fragment_mutex);
+		sched_yield();
+		pthread_mutex_lock(&fragment_mutex);
+	}
+	pthread_cleanup_pop(1);
+
+	queue_put(to_writer, NULL);
+	if(queue_get(from_writer) != 0)
+		EXIT_MKSQUASHFS();
+
+	set_progressbar_state(FALSE);
+	write_filesystem_tables(&sBlk, nopad);
+
+	return 0;
+}
diff --git a/squashfs-tools/squashfs-tools/mksquashfs.h b/squashfs-tools/squashfs-tools/mksquashfs.h
new file mode 100644
index 0000000..bfbf0bf
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/mksquashfs.h
@@ -0,0 +1,165 @@
+#ifndef MKSQUASHFS_H
+#define MKSQUASHFS_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
+ * 2012, 2013, 2014
+ * 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.
+ *
+ * mksquashfs.h
+ *
+ */
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+#include <stdint.h>
+#endif
+/* ANDROID CHANGES END */
+#include <pthread.h>
+
+struct dir_info {
+	char			*pathname;
+	char			*subpath;
+	unsigned int		count;
+	unsigned int		directory_count;
+	int			depth;
+	unsigned int		excluded;
+	char			dir_is_ldir;
+	struct dir_ent		*dir_ent;
+	struct dir_ent		*list;
+	DIR			*linuxdir;
+};
+
+struct dir_ent {
+	char			*name;
+	char			*source_name;
+	char			*nonstandard_pathname;
+	struct inode_info	*inode;
+	struct dir_info		*dir;
+	struct dir_info		*our_dir;
+	struct dir_ent		*next;
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+	uint64_t capabilities;
+#endif
+/* ANDROID CHANGES END */
+};
+
+struct inode_info {
+	struct stat		buf;
+	struct inode_info	*next;
+	squashfs_inode		inode;
+	unsigned int		inode_number;
+	unsigned int		nlink;
+	int			pseudo_id;
+	char			type;
+	char			read;
+	char			root_entry;
+	char			pseudo_file;
+	char			no_fragments;
+	char			always_use_fragments;
+	char			noD;
+	char			noF;
+	char			symlink[0];
+};
+
+/* in memory file info */
+struct file_info {
+	long long		file_size;
+	long long		bytes;
+	long long		start;
+	unsigned int		*block_list;
+	struct file_info	*next;
+	struct fragment		*fragment;
+	unsigned short		checksum;
+	unsigned short		fragment_checksum;
+	char			have_frag_checksum;
+	char			have_checksum;
+};
+
+/* fragment block data structures */
+struct fragment {
+	unsigned int		index;
+	int			offset;
+	int			size;
+};
+
+/* in memory uid tables */
+#define ID_ENTRIES 256
+#define ID_HASH(id) (id & (ID_ENTRIES - 1))
+#define ISA_UID 1
+#define ISA_GID 2
+
+struct id {
+	unsigned int id;
+	int	index;
+	char	flags;
+	struct id *next;
+};
+
+/* fragment to file mapping used when appending */
+struct append_file {
+	struct file_info *file;
+	struct append_file *next;
+};
+
+#define PSEUDO_FILE_OTHER	1
+#define PSEUDO_FILE_PROCESS	2
+
+#define IS_PSEUDO(a)		((a)->pseudo_file)
+#define IS_PSEUDO_PROCESS(a)	((a)->pseudo_file & PSEUDO_FILE_PROCESS)
+#define IS_PSEUDO_OTHER(a)	((a)->pseudo_file & PSEUDO_FILE_OTHER)
+
+/*
+ * Amount of physical memory to use by default, and the default queue
+ * ratios
+ */
+#define SQUASHFS_TAKE 4
+#define SQUASHFS_READQ_MEM 4
+#define SQUASHFS_BWRITEQ_MEM 4
+#define SQUASHFS_FWRITEQ_MEM 4
+
+/*
+ * Lowest amount of physical memory considered viable for Mksquashfs
+ * to run in Mbytes
+ */
+#define SQUASHFS_LOWMEM 64
+
+/* offset of data in compressed metadata blocks (allowing room for
+ * compressed size */
+#define BLOCK_OFFSET 2
+
+extern struct cache *reader_buffer, *fragment_buffer, *reserve_cache;
+struct cache *bwriter_buffer, *fwriter_buffer;
+extern struct queue *to_reader, *to_deflate, *to_writer, *from_writer,
+	*to_frag, *locked_fragment, *to_process_frag;
+extern struct append_file **file_mapping;
+extern struct seq_queue *to_main;
+extern pthread_mutex_t fragment_mutex, dup_mutex;
+extern struct squashfs_fragment_entry *fragment_table;
+extern struct compressor *comp;
+extern int block_size;
+extern struct file_info *dupl[];
+extern int read_fs_bytes(int, long long, int, void *);
+extern void add_file(long long, long long, long long, unsigned int *, int,
+	unsigned int, int, int);
+extern struct id *create_id(unsigned int);
+extern unsigned int get_uid(unsigned int);
+extern unsigned int get_guid(unsigned int);
+extern int read_bytes(int, void *, int);
+extern unsigned short get_checksum_mem(char *, int);
+#endif
diff --git a/squashfs-tools/squashfs-tools/process_fragments.c b/squashfs-tools/squashfs-tools/process_fragments.c
new file mode 100644
index 0000000..bba6f5a
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/process_fragments.c
@@ -0,0 +1,370 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2014
+ * 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.
+ *
+ * process_fragments.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "caches-queues-lists.h"
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "error.h"
+#include "progressbar.h"
+#include "info.h"
+#include "compressor.h"
+#include "process_fragments.h"
+
+#define FALSE 0
+#define TRUE 1
+
+extern struct queue *to_process_frag;
+extern struct seq_queue *to_main;
+extern int sparse_files;
+
+/*
+ * Compute 16 bit BSD checksum over the data, and check for sparseness
+ */
+static int checksum_sparse(struct file_buffer *file_buffer)
+{
+	unsigned char *b = (unsigned char *) file_buffer->data;
+	unsigned short chksum = 0;
+	int bytes = file_buffer->size, sparse = TRUE, value;
+
+	while(bytes --) {
+		chksum = (chksum & 1) ? (chksum >> 1) | 0x8000 : chksum >> 1;
+		value = *b++;
+		if(value) {
+			sparse = FALSE;
+			chksum += value;
+		}
+	}
+
+	file_buffer->checksum = chksum;
+	return sparse;
+}
+
+
+static int read_filesystem(int fd, long long byte, int bytes, void *buff)
+{
+	off_t off = byte;
+
+	TRACE("read_filesystem: reading from position 0x%llx, bytes %d\n",
+		byte, bytes);
+
+	if(lseek(fd, off, SEEK_SET) == -1) {
+		ERROR("read_filesystem: Lseek on destination failed because %s, "
+			"offset=0x%llx\n", strerror(errno), off);
+		return 0;
+	} else if(read_bytes(fd, buff, bytes) < bytes) {
+		ERROR("Read on destination failed\n");
+		return 0;
+	}
+
+	return 1;
+}
+
+
+static struct file_buffer *get_fragment(struct fragment *fragment,
+	char *data_buffer, int fd)
+{
+	struct squashfs_fragment_entry *disk_fragment;
+	struct file_buffer *buffer, *compressed_buffer;
+	long long start_block;
+	int res, size, index = fragment->index;
+	char locked;
+
+	/*
+	 * Lookup fragment block in cache.
+	 * If the fragment block doesn't exist, then get the compressed version
+	 * from the writer cache or off disk, and decompress it.
+	 *
+	 * This routine has two things which complicate the code:
+	 *
+	 *	1. Multiple threads can simultaneously lookup/create the
+	 *	   same buffer.  This means a buffer needs to be "locked"
+	 *	   when it is being filled in, to prevent other threads from
+	 *	   using it when it is not ready.  This is because we now do
+	 *	   fragment duplicate checking in parallel.
+	 *	2. We have two caches which need to be checked for the
+	 *	   presence of fragment blocks: the normal fragment cache
+	 *	   and a "reserve" cache.  The reserve cache is used to
+	 *	   prevent an unnecessary pipeline stall when the fragment cache
+	 *	   is full of fragments waiting to be compressed.
+	 */
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+	pthread_mutex_lock(&dup_mutex);
+
+again:
+	buffer = cache_lookup_nowait(fragment_buffer, index, &locked);
+	if(buffer) {
+		pthread_mutex_unlock(&dup_mutex);
+		if(locked)
+			/* got a buffer being filled in.  Wait for it */
+			cache_wait_unlock(buffer);
+		goto finished;
+	}
+
+	/* not in fragment cache, is it in the reserve cache? */
+	buffer = cache_lookup_nowait(reserve_cache, index, &locked);
+	if(buffer) {
+		pthread_mutex_unlock(&dup_mutex);
+		if(locked)
+			/* got a buffer being filled in.  Wait for it */
+			cache_wait_unlock(buffer);
+		goto finished;
+	}
+
+	/* in neither cache, try to get it from the fragment cache */
+	buffer = cache_get_nowait(fragment_buffer, index);
+	if(!buffer) {
+		/*
+		 * no room, get it from the reserve cache, this is
+		 * dimensioned so it will always have space (no more than
+		 * processors + 1 can have an outstanding reserve buffer)
+		 */
+		buffer = cache_get_nowait(reserve_cache, index);
+		if(!buffer) {
+			/* failsafe */
+			ERROR("no space in reserve cache\n");
+			goto again;
+		}
+	}
+
+	pthread_mutex_unlock(&dup_mutex);
+
+	compressed_buffer = cache_lookup(fwriter_buffer, index);
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+	pthread_mutex_lock(&fragment_mutex);
+	disk_fragment = &fragment_table[index];
+	size = SQUASHFS_COMPRESSED_SIZE_BLOCK(disk_fragment->size);
+	start_block = disk_fragment->start_block;
+	pthread_cleanup_pop(1);
+
+	if(SQUASHFS_COMPRESSED_BLOCK(disk_fragment->size)) {
+		int error;
+		char *data;
+
+		if(compressed_buffer)
+			data = compressed_buffer->data;
+		else {
+			res = read_filesystem(fd, start_block, size, data_buffer);
+			if(res == 0) {
+				ERROR("Failed to read fragment from output"
+					" filesystem\n");
+				BAD_ERROR("Output filesystem corrupted?\n");
+			}
+			data = data_buffer;
+		}
+
+		res = compressor_uncompress(comp, buffer->data, data, size,
+			block_size, &error);
+		if(res == -1)
+			BAD_ERROR("%s uncompress failed with error code %d\n",
+				comp->name, error);
+	} else if(compressed_buffer)
+		memcpy(buffer->data, compressed_buffer->data, size);
+	else {
+		res = read_filesystem(fd, start_block, size, buffer->data);
+		if(res == 0) {
+			ERROR("Failed to read fragment from output "
+				"filesystem\n");
+			BAD_ERROR("Output filesystem corrupted?\n");
+		}
+	}
+
+	cache_unlock(buffer);
+	cache_block_put(compressed_buffer);
+
+finished:
+	pthread_cleanup_pop(0);
+
+	return buffer;
+}
+
+
+struct file_buffer *get_fragment_cksum(struct file_info *file,
+	char *data_buffer, int fd, unsigned short *checksum)
+{
+	struct file_buffer *frag_buffer;
+	struct append_file *append;
+	int index = file->fragment->index;
+
+	frag_buffer = get_fragment(file->fragment, data_buffer, fd);
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+
+	for(append = file_mapping[index]; append; append = append->next) {
+		int offset = append->file->fragment->offset;
+		int size = append->file->fragment->size;
+		char *data = frag_buffer->data + offset;
+		unsigned short cksum = get_checksum_mem(data, size);
+
+		if(file == append->file)
+			*checksum = cksum;
+
+		pthread_mutex_lock(&dup_mutex);
+		append->file->fragment_checksum = cksum;
+		append->file->have_frag_checksum = TRUE;
+		pthread_mutex_unlock(&dup_mutex);
+	}
+
+	pthread_cleanup_pop(0);
+
+	return frag_buffer;
+}
+
+
+void *frag_thrd(void *destination_file)
+{
+	sigset_t sigmask, old_mask;
+	char *data_buffer;
+	int fd;
+
+	sigemptyset(&sigmask);
+	sigaddset(&sigmask, SIGINT);
+	sigaddset(&sigmask, SIGTERM);
+	sigaddset(&sigmask, SIGUSR1);
+	pthread_sigmask(SIG_BLOCK, &sigmask, &old_mask);
+
+	fd = open(destination_file, O_RDONLY);
+	if(fd == -1)
+		BAD_ERROR("frag_thrd: can't open destination for reading\n");
+
+	data_buffer = malloc(SQUASHFS_FILE_MAX_SIZE);
+	if(data_buffer == NULL)
+		MEM_ERROR();
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+
+	while(1) {
+		struct file_buffer *file_buffer = queue_get(to_process_frag);
+		struct file_buffer *buffer;
+		int sparse = checksum_sparse(file_buffer);
+		struct file_info *dupl_ptr;
+		long long file_size;
+		unsigned short checksum;
+		char flag;
+		int res;
+
+		if(sparse_files && sparse) {
+			file_buffer->c_byte = 0;
+			file_buffer->fragment = FALSE;
+		} else
+			file_buffer->c_byte = file_buffer->size;
+
+		/*
+		 * Specutively pull into the fragment cache any fragment blocks
+		 * which contain fragments which *this* fragment may be
+		 * be a duplicate.
+		 *
+		 * By ensuring the fragment block is in cache ahead of time
+		 * should eliminate the parallelisation stall when the
+		 * main thread needs to read the fragment block to do a
+		 * duplicate check on it.
+		 *
+		 * If this is a fragment belonging to a larger file
+		 * (with additional blocks) then ignore it.  Here we're
+		 * interested in the "low hanging fruit" of files which
+		 * consist of only a fragment
+		 */
+		if(file_buffer->file_size != file_buffer->size) {
+			seq_queue_put(to_main, file_buffer);
+			continue;
+		}
+
+		file_size = file_buffer->file_size;
+
+		pthread_mutex_lock(&dup_mutex);
+		dupl_ptr = dupl[DUP_HASH(file_size)];
+		pthread_mutex_unlock(&dup_mutex);
+
+		file_buffer->dupl_start = dupl_ptr;
+		file_buffer->duplicate = FALSE;
+
+		for(; dupl_ptr; dupl_ptr = dupl_ptr->next) {
+			if(file_size != dupl_ptr->file_size ||
+					file_size != dupl_ptr->fragment->size)
+				continue;
+
+			pthread_mutex_lock(&dup_mutex);
+			flag = dupl_ptr->have_frag_checksum;
+			checksum = dupl_ptr->fragment_checksum;
+			pthread_mutex_unlock(&dup_mutex);
+
+			/*
+			 * If we have the checksum and it matches then
+			 * read in the fragment block.
+			 *
+			 * If we *don't* have the checksum, then we are
+			 * appending, and the fragment block is on the
+			 * "old" filesystem.  Read it in and checksum
+			 * the entire fragment buffer
+			 */
+			if(!flag) {
+				buffer = get_fragment_cksum(dupl_ptr,
+					data_buffer, fd, &checksum);
+				if(checksum != file_buffer->checksum) {
+					cache_block_put(buffer);
+					continue;
+				}
+			} else if(checksum == file_buffer->checksum)
+				buffer = get_fragment(dupl_ptr->fragment,
+					data_buffer, fd);
+			else
+				continue;
+
+			res = memcmp(file_buffer->data, buffer->data +
+				dupl_ptr->fragment->offset, file_size);
+			cache_block_put(buffer);
+			if(res == 0) {
+				struct file_buffer *dup = malloc(sizeof(*dup));
+				if(dup == NULL)
+					MEM_ERROR();
+				memcpy(dup, file_buffer, sizeof(*dup));
+				cache_block_put(file_buffer);
+				dup->dupl_start = dupl_ptr;
+				dup->duplicate = TRUE;
+				file_buffer = dup;
+				break;
+			}
+		}
+
+		seq_queue_put(to_main, file_buffer);
+	}
+
+	pthread_cleanup_pop(0);
+}
diff --git a/squashfs-tools/squashfs-tools/process_fragments.h b/squashfs-tools/squashfs-tools/process_fragments.h
new file mode 100644
index 0000000..6f6bdf2
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/process_fragments.h
@@ -0,0 +1,30 @@
+#ifndef PROCESS_FRAGMENTS_H
+#define PROCESS_FRAGMENTS_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2014
+ * 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.
+ *
+ * process_fragments.h
+ */
+
+#define DUP_HASH(a) (a & 0xffff)
+
+extern void *frag_thrd(void *);
+#endif
diff --git a/squashfs-tools/squashfs-tools/progressbar.c b/squashfs-tools/squashfs-tools/progressbar.c
new file mode 100644
index 0000000..987a45b
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/progressbar.c
@@ -0,0 +1,259 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2012, 2013, 2014
+ * 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.
+ *
+ * progressbar.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "error.h"
+
+#define FALSE 0
+#define TRUE 1
+
+/* flag whether progressbar display is enabled or not */
+int display_progress_bar = FALSE;
+
+/* flag whether the progress bar is temporarily disbled */
+int temp_disabled = FALSE;
+
+int rotate = 0;
+int cur_uncompressed = 0, estimated_uncompressed = 0;
+int columns;
+
+pthread_t progress_thread;
+pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+
+static void sigwinch_handler()
+{
+	struct winsize winsize;
+
+	if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
+		if(isatty(STDOUT_FILENO))
+			ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
+				"columns\n");
+		columns = 80;
+	} else
+		columns = winsize.ws_col;
+}
+
+
+static void sigalrm_handler()
+{
+	rotate = (rotate + 1) % 4;
+}
+
+
+void inc_progress_bar()
+{
+	cur_uncompressed ++;
+}
+
+
+void dec_progress_bar(int count)
+{
+	cur_uncompressed -= count;
+}
+
+
+void progress_bar_size(int count)
+{
+	estimated_uncompressed += count;
+}
+
+
+static void progress_bar(long long current, long long max, int columns)
+{
+	char rotate_list[] = { '|', '/', '-', '\\' };
+	int max_digits, used, hashes, spaces;
+	static int tty = -1;
+
+	if(max == 0)
+		return;
+
+	max_digits = floor(log10(max)) + 1;
+	used = max_digits * 2 + 11;
+	hashes = (current * (columns - used)) / max;
+	spaces = columns - used - hashes;
+
+	if((current > max) || (columns - used < 0))
+		return;
+
+	if(tty == -1)
+		tty = isatty(STDOUT_FILENO);
+	if(!tty) {
+		static long long previous = -1;
+
+		/* Updating much more frequently than this results in huge
+		 * log files. */
+		if((current % 100) != 0 && current != max)
+			return;
+		/* Don't update just to rotate the spinner. */
+		if(current == previous)
+			return;
+		previous = current;
+	}
+
+	printf("\r[");
+
+	while (hashes --)
+		putchar('=');
+
+	putchar(rotate_list[rotate]);
+
+	while(spaces --)
+		putchar(' ');
+
+	printf("] %*lld/%*lld", max_digits, current, max_digits, max);
+	printf(" %3lld%%", current * 100 / max);
+	fflush(stdout);
+}
+
+
+void enable_progress_bar()
+{
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+	pthread_mutex_lock(&progress_mutex);
+	if(display_progress_bar)
+		progress_bar(cur_uncompressed, estimated_uncompressed, columns);
+	temp_disabled = FALSE;
+	pthread_cleanup_pop(1);
+}
+
+
+void disable_progress_bar()
+{
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+	pthread_mutex_lock(&progress_mutex);
+	if(display_progress_bar)
+		printf("\n");
+	temp_disabled = TRUE;
+	pthread_cleanup_pop(1);
+}
+
+
+void set_progressbar_state(int state)
+{
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+	pthread_mutex_lock(&progress_mutex);
+	if(display_progress_bar != state) {
+		if(display_progress_bar && !temp_disabled) {
+			progress_bar(cur_uncompressed, estimated_uncompressed,
+				columns);
+			printf("\n");
+		}
+		display_progress_bar = state;
+	}
+	pthread_cleanup_pop(1);
+}
+
+
+void *progress_thrd(void *arg)
+{
+	struct timespec requested_time, remaining;
+	struct itimerval itimerval;
+	struct winsize winsize;
+
+	if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
+		if(isatty(STDOUT_FILENO))
+			ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
+				"columns\n");
+		columns = 80;
+	} else
+		columns = winsize.ws_col;
+	signal(SIGWINCH, sigwinch_handler);
+	signal(SIGALRM, sigalrm_handler);
+
+	itimerval.it_value.tv_sec = 0;
+	itimerval.it_value.tv_usec = 250000;
+	itimerval.it_interval.tv_sec = 0;
+	itimerval.it_interval.tv_usec = 250000;
+	setitimer(ITIMER_REAL, &itimerval, NULL);
+
+	requested_time.tv_sec = 0;
+	requested_time.tv_nsec = 250000000;
+
+	while(1) {
+		int res = nanosleep(&requested_time, &remaining);
+
+		if(res == -1 && errno != EINTR)
+			BAD_ERROR("nanosleep failed in progress thread\n");
+
+		pthread_mutex_lock(&progress_mutex);
+		if(display_progress_bar && !temp_disabled)
+			progress_bar(cur_uncompressed, estimated_uncompressed,
+				columns);
+		pthread_mutex_unlock(&progress_mutex);
+	}
+}
+
+
+void init_progress_bar()
+{
+	pthread_create(&progress_thread, NULL, progress_thrd, NULL);
+}
+
+
+void progressbar_error(char *fmt, ...)
+{
+	va_list ap;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+	pthread_mutex_lock(&progress_mutex);
+
+	if(display_progress_bar && !temp_disabled)
+		fprintf(stderr, "\n");
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	pthread_cleanup_pop(1);
+}
+
+
+void progressbar_info(char *fmt, ...)
+{
+	va_list ap;
+
+	pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+	pthread_mutex_lock(&progress_mutex);
+
+	if(display_progress_bar && !temp_disabled)
+		printf("\n");
+
+	va_start(ap, fmt);
+	vprintf(fmt, ap);
+	va_end(ap);
+
+	pthread_cleanup_pop(1);
+}
+
diff --git a/squashfs-tools/squashfs-tools/progressbar.h b/squashfs-tools/squashfs-tools/progressbar.h
new file mode 100644
index 0000000..b33b367
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/progressbar.h
@@ -0,0 +1,34 @@
+#ifndef PROGRESSBAR_H
+#define PROGRESSBAR_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2012, 2013, 2014
+ * 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.
+ *
+ * progressbar.h
+ */
+
+extern void inc_progress_bar();
+extern void dec_progress_bar(int count);
+extern void progress_bar_size(int count);
+extern void enable_progress_bar();
+extern void disable_progress_bar();
+extern void init_progress_bar();
+extern void set_progressbar_state(int);
+#endif
diff --git a/squashfs-tools/squashfs-tools/pseudo.c b/squashfs-tools/squashfs-tools/pseudo.c
new file mode 100644
index 0000000..e12d399
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/pseudo.c
@@ -0,0 +1,529 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2012, 2014
+ * 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.
+ *
+ * pseudo.c
+ */
+
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <ctype.h>
+
+#include "pseudo.h"
+#include "error.h"
+#include "progressbar.h"
+
+#define TRUE 1
+#define FALSE 0
+
+extern int read_file(char *filename, char *type, int (parse_line)(char *));
+
+struct pseudo_dev **pseudo_file = NULL;
+struct pseudo *pseudo = NULL;
+int pseudo_count = 0;
+
+static char *get_component(char *target, char **targname)
+{
+	char *start;
+
+	while(*target == '/')
+		target ++;
+
+	start = target;
+	while(*target != '/' && *target != '\0')
+		target ++;
+
+	*targname = strndup(start, target - start);
+
+	while(*target == '/')
+		target ++;
+
+	return target;
+}
+
+
+/*
+ * Add pseudo device target to the set of pseudo devices.  Pseudo_dev
+ * describes the pseudo device attributes.
+ */
+struct pseudo *add_pseudo(struct pseudo *pseudo, struct pseudo_dev *pseudo_dev,
+	char *target, char *alltarget)
+{
+	char *targname;
+	int i;
+
+	target = get_component(target, &targname);
+
+	if(pseudo == NULL) {
+		pseudo = malloc(sizeof(struct pseudo));
+		if(pseudo == NULL)
+			MEM_ERROR();
+
+		pseudo->names = 0;
+		pseudo->count = 0;
+		pseudo->name = NULL;
+	}
+
+	for(i = 0; i < pseudo->names; i++)
+		if(strcmp(pseudo->name[i].name, targname) == 0)
+			break;
+
+	if(i == pseudo->names) {
+		/* allocate new name entry */
+		pseudo->names ++;
+		pseudo->name = realloc(pseudo->name, (i + 1) *
+			sizeof(struct pseudo_entry));
+		if(pseudo->name == NULL)
+			MEM_ERROR();
+		pseudo->name[i].name = targname;
+
+		if(target[0] == '\0') {
+			/* at leaf pathname component */
+			pseudo->name[i].pseudo = NULL;
+			pseudo->name[i].pathname = strdup(alltarget);
+			pseudo->name[i].dev = pseudo_dev;
+		} else {
+			/* recurse adding child components */
+			pseudo->name[i].dev = NULL;
+			pseudo->name[i].pseudo = add_pseudo(NULL, pseudo_dev,
+				target, alltarget);
+		}
+	} else {
+		/* existing matching entry */
+		free(targname);
+
+		if(pseudo->name[i].pseudo == NULL) {
+			/* No sub-directory which means this is the leaf
+			 * component of a pre-existing pseudo file.
+			 */
+			if(target[0] != '\0') {
+				/*
+				 * entry must exist as either a 'd' type or
+				 * 'm' type pseudo file
+				 */
+				if(pseudo->name[i].dev->type == 'd' ||
+					pseudo->name[i].dev->type == 'm')
+					/* recurse adding child components */
+					pseudo->name[i].pseudo =
+						add_pseudo(NULL, pseudo_dev,
+						target, alltarget);
+				else {
+					ERROR_START("%s already exists as a "
+						"non directory.",
+						pseudo->name[i].name);
+					ERROR_EXIT(".  Ignoring %s!\n",
+						alltarget);
+				}
+			} else if(memcmp(pseudo_dev, pseudo->name[i].dev,
+					sizeof(struct pseudo_dev)) != 0) {
+				ERROR_START("%s already exists as a different "
+					"pseudo definition.", alltarget);
+				ERROR_EXIT("  Ignoring!\n");
+			} else {
+				ERROR_START("%s already exists as an identical "
+					"pseudo definition!", alltarget);
+				ERROR_EXIT("  Ignoring!\n");
+			}
+		} else {
+			if(target[0] == '\0') {
+				/*
+				 * sub-directory exists, which means we can only
+				 * add a pseudo file of type 'd' or type 'm'
+				 */
+				if(pseudo->name[i].dev == NULL &&
+						(pseudo_dev->type == 'd' ||
+						pseudo_dev->type == 'm')) {
+					pseudo->name[i].pathname =
+						strdup(alltarget);
+					pseudo->name[i].dev = pseudo_dev;
+				} else {
+					ERROR_START("%s already exists as a "
+						"different pseudo definition.",
+						pseudo->name[i].name);
+					ERROR_EXIT("  Ignoring %s!\n",
+						alltarget);
+				}
+			} else
+				/* recurse adding child components */
+				add_pseudo(pseudo->name[i].pseudo, pseudo_dev,
+					target, alltarget);
+		}
+	}
+
+	return pseudo;
+}
+
+
+/*
+ * Find subdirectory in pseudo directory referenced by pseudo, matching
+ * filename.  If filename doesn't exist or if filename is a leaf file
+ * return NULL
+ */
+struct pseudo *pseudo_subdir(char *filename, struct pseudo *pseudo)
+{
+	int i;
+
+	if(pseudo == NULL)
+		return NULL;
+
+	for(i = 0; i < pseudo->names; i++)
+		if(strcmp(filename, pseudo->name[i].name) == 0)
+			return pseudo->name[i].pseudo;
+
+	return NULL;
+}
+
+
+struct pseudo_entry *pseudo_readdir(struct pseudo *pseudo)
+{
+	if(pseudo == NULL)
+		return NULL;
+
+	while(pseudo->count < pseudo->names) {
+		if(pseudo->name[pseudo->count].dev != NULL)
+			return &pseudo->name[pseudo->count++];
+		else
+			pseudo->count++;
+	}
+
+	return NULL;
+}
+
+
+int pseudo_exec_file(struct pseudo_dev *dev, int *child)
+{
+	int res, pipefd[2];
+
+	res = pipe(pipefd);
+	if(res == -1) {
+		ERROR("Executing dynamic pseudo file, pipe failed\n");
+		return 0;
+	}
+
+	*child = fork();
+	if(*child == -1) {
+		ERROR("Executing dynamic pseudo file, fork failed\n");
+		goto failed;
+	}
+
+	if(*child == 0) {
+		close(pipefd[0]);
+		close(STDOUT_FILENO);
+		res = dup(pipefd[1]);
+		if(res == -1)
+			exit(EXIT_FAILURE);
+
+		execl("/bin/sh", "sh", "-c", dev->command, (char *) NULL);
+		exit(EXIT_FAILURE);
+	}
+
+	close(pipefd[1]);
+	return pipefd[0];
+
+failed:
+	close(pipefd[0]);
+	close(pipefd[1]);
+	return 0;
+}
+
+
+void add_pseudo_file(struct pseudo_dev *dev)
+{
+	pseudo_file = realloc(pseudo_file, (pseudo_count + 1) *
+		sizeof(struct pseudo_dev *));
+	if(pseudo_file == NULL)
+		MEM_ERROR();
+
+	dev->pseudo_id = pseudo_count;
+	pseudo_file[pseudo_count ++] = dev;
+}
+
+
+struct pseudo_dev *get_pseudo_file(int pseudo_id)
+{
+	return pseudo_file[pseudo_id];
+}
+
+
+int read_pseudo_def(char *def)
+{
+	int n, bytes;
+	unsigned int major = 0, minor = 0, mode;
+	char type, *ptr;
+	char suid[100], sgid[100]; /* overflow safe */
+	char *filename, *name;
+	char *orig_def = def;
+	long long uid, gid;
+	struct pseudo_dev *dev;
+
+	/*
+	 * Scan for filename, don't use sscanf() and "%s" because
+	 * that can't handle filenames with spaces
+	 */
+	filename = malloc(strlen(def) + 1);
+	if(filename == NULL)
+		MEM_ERROR();
+
+	for(name = filename; !isspace(*def) && *def != '\0';) {
+		if(*def == '\\') {
+			def ++;
+			if (*def == '\0')
+				break;
+		}
+		*name ++ = *def ++;
+	}
+	*name = '\0';
+
+	if(*filename == '\0') {
+		ERROR("Not enough or invalid arguments in pseudo file "
+			"definition \"%s\"\n", orig_def);
+		goto error;
+	}
+
+	n = sscanf(def, " %c %o %99s %99s %n", &type, &mode, suid, sgid,
+		&bytes);
+	def += bytes;
+
+	if(n < 4) {
+		ERROR("Not enough or invalid arguments in pseudo file "
+			"definition \"%s\"\n", orig_def);
+		switch(n) {
+		case -1:
+			/* FALLTHROUGH */
+		case 0:
+			ERROR("Read filename, but failed to read or match "
+				"type\n");
+			break;
+		case 1:
+			ERROR("Read filename and type, but failed to read or "
+				"match octal mode\n");
+			break;
+		case 2:
+			ERROR("Read filename, type and mode, but failed to "
+				"read or match uid\n");
+			break;
+		default:
+			ERROR("Read filename, type, mode and uid, but failed "
+				"to read or match gid\n");
+			break; 
+		}
+		goto error;
+	}
+
+	switch(type) {
+	case 'b':
+		/* FALLTHROUGH */
+	case 'c':
+		n = sscanf(def, "%u %u %n", &major, &minor, &bytes);
+		def += bytes;
+
+		if(n < 2) {
+			ERROR("Not enough or invalid arguments in %s device "
+				"pseudo file definition \"%s\"\n", type == 'b' ?
+				"block" : "character", orig_def);
+			if(n < 1)
+				ERROR("Read filename, type, mode, uid and gid, "
+					"but failed to read or match major\n");
+			else
+				ERROR("Read filename, type, mode, uid, gid "
+					"and major, but failed to read  or "
+					"match minor\n");
+			goto error;
+		}	
+		
+		if(major > 0xfff) {
+			ERROR("Major %d out of range\n", major);
+			goto error;
+		}
+
+		if(minor > 0xfffff) {
+			ERROR("Minor %d out of range\n", minor);
+			goto error;
+		}
+		/* FALLTHROUGH */
+	case 'd':
+		/* FALLTHROUGH */
+	case 'm':
+		/*
+		 * Check for trailing junk after expected arguments
+		 */
+		if(def[0] != '\0') {
+			ERROR("Unexpected tailing characters in pseudo file "
+				"definition \"%s\"\n", orig_def);
+			goto error;
+		}
+		break;
+	case 'f':
+		if(def[0] == '\0') {
+			ERROR("Not enough arguments in dynamic file pseudo "
+				"definition \"%s\"\n", orig_def);
+			ERROR("Expected command, which can be an executable "
+				"or a piece of shell script\n");
+			goto error;
+		}	
+		break;
+	default:
+		ERROR("Unsupported type %c\n", type);
+		goto error;
+	}
+
+
+	if(mode > 07777) {
+		ERROR("Mode %o out of range\n", mode);
+		goto error;
+	}
+
+	uid = strtoll(suid, &ptr, 10);
+	if(*ptr == '\0') {
+		if(uid < 0 || uid > ((1LL << 32) - 1)) {
+			ERROR("Uid %s out of range\n", suid);
+			goto error;
+		}
+	} else {
+		struct passwd *pwuid = getpwnam(suid);
+		if(pwuid)
+			uid = pwuid->pw_uid;
+		else {
+			ERROR("Uid %s invalid uid or unknown user\n", suid);
+			goto error;
+		}
+	}
+		
+	gid = strtoll(sgid, &ptr, 10);
+	if(*ptr == '\0') {
+		if(gid < 0 || gid > ((1LL << 32) - 1)) {
+			ERROR("Gid %s out of range\n", sgid);
+			goto error;
+		}
+	} else {
+		struct group *grgid = getgrnam(sgid);
+		if(grgid)
+			gid = grgid->gr_gid;
+		else {
+			ERROR("Gid %s invalid uid or unknown user\n", sgid);
+			goto error;
+		}
+	}
+
+	switch(type) {
+	case 'b':
+		mode |= S_IFBLK;
+		break;
+	case 'c':
+		mode |= S_IFCHR;
+		break;
+	case 'd':
+		mode |= S_IFDIR;
+		break;
+	case 'f':
+		mode |= S_IFREG;
+		break;
+	}
+
+	dev = malloc(sizeof(struct pseudo_dev));
+	if(dev == NULL)
+		MEM_ERROR();
+
+	dev->type = type;
+	dev->mode = mode;
+	dev->uid = uid;
+	dev->gid = gid;
+	dev->major = major;
+	dev->minor = minor;
+	if(type == 'f') {
+		dev->command = strdup(def);
+		add_pseudo_file(dev);
+	}
+
+	pseudo = add_pseudo(pseudo, dev, filename, filename);
+
+	free(filename);
+	return TRUE;
+
+error:
+	ERROR("Pseudo definitions should be of format\n");
+	ERROR("\tfilename d mode uid gid\n");
+	ERROR("\tfilename m mode uid gid\n");
+	ERROR("\tfilename b mode uid gid major minor\n");
+	ERROR("\tfilename c mode uid gid major minor\n");
+	ERROR("\tfilename f mode uid command\n");
+	free(filename);
+	return FALSE;
+}
+
+
+int read_pseudo_file(char *filename)
+{
+	return read_file(filename, "pseudo", read_pseudo_def);
+}
+
+
+struct pseudo *get_pseudo()
+{
+	return pseudo;
+}
+
+
+#ifdef SQUASHFS_TRACE
+static void dump_pseudo(struct pseudo *pseudo, char *string)
+{
+	int i, res;
+	char *path;
+
+	for(i = 0; i < pseudo->names; i++) {
+		struct pseudo_entry *entry = &pseudo->name[i];
+		if(string) {
+			res = asprintf(&path, "%s/%s", string, entry->name);
+			if(res == -1)
+				BAD_ERROR("asprintf failed in dump_pseudo\n");
+		} else
+			path = entry->name;
+		if(entry->dev)
+			ERROR("%s %c 0%o %d %d %d %d\n", path, entry->dev->type,
+				entry->dev->mode & ~S_IFMT, entry->dev->uid,
+				entry->dev->gid, entry->dev->major,
+				entry->dev->minor);
+		if(entry->pseudo)
+			dump_pseudo(entry->pseudo, path);
+		if(string)
+			free(path);
+	}
+}
+
+
+void dump_pseudos()
+{
+    if (pseudo)
+        dump_pseudo(pseudo, NULL);
+}
+#else
+void dump_pseudos()
+{
+}
+#endif
diff --git a/squashfs-tools/squashfs-tools/pseudo.h b/squashfs-tools/squashfs-tools/pseudo.h
new file mode 100644
index 0000000..c0d9f6f
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/pseudo.h
@@ -0,0 +1,58 @@
+#ifndef PSEUDO_H
+#define PSEUDO_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2014
+ * 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.
+ *
+ * pseudo.h
+ */
+struct pseudo_dev {
+	char		type;
+	unsigned int	mode;
+	unsigned int	uid;
+	unsigned int	gid;
+	unsigned int	major;
+	unsigned int	minor;
+	int		pseudo_id;
+	char		*command;
+};
+
+struct pseudo_entry {
+	char			*name;
+	char			*pathname;
+	struct pseudo		*pseudo;
+	struct pseudo_dev	*dev;
+};
+	
+struct pseudo {
+	int			names;
+	int			count;
+	struct pseudo_entry	*name;
+};
+
+extern int read_pseudo_def(char *);
+extern int read_pseudo_file(char *);
+extern struct pseudo *pseudo_subdir(char *, struct pseudo *);
+extern struct pseudo_entry *pseudo_readdir(struct pseudo *);
+extern struct pseudo_dev *get_pseudo_file(int);
+extern int pseudo_exec_file(struct pseudo_dev *, int *);
+extern struct pseudo *get_pseudo();
+extern void dump_pseudos();
+#endif
diff --git a/squashfs-tools/squashfs-tools/read_file.c b/squashfs-tools/squashfs-tools/read_file.c
new file mode 100644
index 0000000..22705a0
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/read_file.c
@@ -0,0 +1,150 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2012
+ * 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_file.c
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "error.h"
+
+#define TRUE 1
+#define FALSE 0
+#define MAX_LINE 16384
+
+/*
+ * Read file, passing each line to parse_line() for
+ * parsing.
+ *
+ * Lines can be split across multiple lines using "\".
+ * 
+ * Blank lines and comment lines indicated by # are supported.
+ */
+int read_file(char *filename, char *type, int (parse_line)(char *))
+{
+	FILE *fd;
+	char *def, *err, *line = NULL;
+	int res, size = 0;
+
+	fd = fopen(filename, "r");
+	if(fd == NULL) {
+		ERROR("Could not open %s device file \"%s\" because %s\n",
+			type, filename, strerror(errno));
+		return FALSE;
+	}
+
+	while(1) {
+		int total = 0;
+
+		while(1) {
+			int len;
+
+			if(total + (MAX_LINE + 1) > size) {
+				line = realloc(line, size += (MAX_LINE + 1));
+				if(line == NULL)
+					MEM_ERROR();
+			}
+
+			err = fgets(line + total, MAX_LINE + 1, fd);
+			if(err == NULL)
+				break;
+
+			len = strlen(line + total);
+			total += len;
+
+			if(len == MAX_LINE && line[total - 1] != '\n') {
+				/* line too large */
+				ERROR("Line too long when reading "
+					"%s file \"%s\", larger than "
+					"%d bytes\n", type, filename, MAX_LINE);
+				goto failed;
+			}
+
+			/*
+			 * Remove '\n' terminator if it exists (the last line
+			 * in the file may not be '\n' terminated)
+			 */
+			if(len && line[total - 1] == '\n') {
+				line[-- total] = '\0';
+				len --;
+			}
+
+			/*
+			 * If no line continuation then jump out to
+			 * process line.  Note, we have to be careful to
+			 * check for "\\" (backslashed backslash) and to
+			 * ensure we don't look at the previous line
+			 */
+			if(len == 0 || line[total - 1] != '\\' || (len >= 2 &&
+					strcmp(line + total - 2, "\\\\") == 0))
+				break;
+			else
+				total --;
+		}	
+
+		if(err == NULL) {
+			if(ferror(fd)) {
+                		ERROR("Reading %s file \"%s\" failed "
+					"because %s\n", type, filename,
+					strerror(errno));
+				goto failed;
+			}
+
+			/*
+			 * At EOF, normally we'll be finished, but, have to
+			 * check for special case where we had "\" line
+			 * continuation and then hit EOF immediately afterwards
+			 */
+			if(total == 0)
+				break;
+			else
+				line[total] = '\0';
+		}
+
+		/* Skip any leading whitespace */
+		for(def = line; isspace(*def); def ++);
+
+		/* if line is now empty after skipping characters, skip it */
+		if(*def == '\0')
+			continue;
+
+		/* if comment line, skip */
+		if(*def == '#')
+			continue;
+
+		res = parse_line(def);
+		if(res == FALSE)
+			goto failed;
+	}
+
+	fclose(fd);
+	free(line);
+	return TRUE;
+
+failed:
+	fclose(fd);
+	free(line);
+	return FALSE;
+}
diff --git a/squashfs-tools/squashfs-tools/read_fs.c b/squashfs-tools/squashfs-tools/read_fs.c
new file mode 100644
index 0000000..ca84460
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/read_fs.c
@@ -0,0 +1,980 @@
+/*
+ * 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
+ * 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>
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#include <stdlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_swap.h"
+#include "compressor.h"
+#include "xattr.h"
+#include "error.h"
+#include "mksquashfs.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))
+
+int 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, unsigned char **inode_table, unsigned int *root_inode_block,
+	unsigned int *root_inode_size, long long *uncompressed_file,
+	unsigned int *uncompressed_directory, int *file_count, int *sym_count,
+	int *dev_count, int *dir_count, int *fifo_count, int *sock_count,
+	unsigned int *id_table)
+{
+	unsigned char *cur_ptr;
+	int byte, files = 0;
+	unsigned int directory_start_block, bytes = 0, size = 0;
+	struct squashfs_base_inode_header base;
+
+	TRACE("scan_inode_table: start 0x%llx, end 0x%llx, root_inode_start "
+		"0x%llx\n", start, end, root_inode_start);
+
+	*root_inode_block = UINT_MAX;
+	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
+				+= SQUASHFS_METADATA_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 == UINT_MAX)
+		goto corrupted;
+
+	/*
+	 * The number of bytes available after the root inode medata 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 last inode entry which is the root directory inode, and obtain
+	 * the last directory start block index.  This is used when calculating
+	 * the total 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;
+	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;
+
+	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 ++) {
+		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);
+
+		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;
+
+			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);
+
+			if(NO_BYTES(blocks * sizeof(unsigned int)))
+				/* corrupted filesystem */
+				goto corrupted;
+
+			block_list = malloc(blocks * sizeof(unsigned int));
+			if(block_list == NULL)
+				MEM_ERROR();
+
+			cur_ptr += sizeof(inode);
+			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;
+
+			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);
+
+			if(NO_BYTES(blocks * sizeof(unsigned int)))
+				/* corrupted filesystem */
+				goto corrupted;
+
+			block_list = malloc(blocks * sizeof(unsigned int));
+			if(block_list == NULL)
+				MEM_ERROR();
+
+			cur_ptr += sizeof(inode);
+			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;
+
+			if(NO_INODE_BYTES(squashfs_symlink_inode_header))
+				/* corrupted filesystem */
+				goto corrupted;
+
+			SQUASHFS_SWAP_SYMLINK_INODE_HEADER(cur_ptr, &inode);
+
+			(*sym_count) ++;
+
+			if (inode.inode_type == SQUASHFS_LSYMLINK_TYPE) {
+				if(NO_BYTES(inode.symlink_size +
+							sizeof(unsigned int)))
+					/* corrupted filesystem */
+					goto corrupted;
+				cur_ptr += sizeof(inode) + inode.symlink_size +
+							sizeof(unsigned int);
+			} else {
+				if(NO_BYTES(inode.symlink_size))
+					/* corrupted filesystem */
+					goto corrupted;
+				cur_ptr += sizeof(inode) + inode.symlink_size;
+			}
+			break;
+		}
+		case SQUASHFS_DIR_TYPE: {
+			struct squashfs_dir_inode_header dir_inode;
+
+			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;
+
+			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);
+
+			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);
+
+				if(NO_BYTES(index.size + 1))
+					/* corrupted filesystem */
+					goto corrupted;
+
+				cur_ptr += sizeof(index) + index.size + 1;
+			}
+			break;
+		}
+	 	case SQUASHFS_BLKDEV_TYPE:
+	 	case SQUASHFS_CHRDEV_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:
+			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:
+			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:
+			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:
+			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:
+			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;
+		}
+	}
+	
+	printf("Read existing filesystem, %d inodes scanned\n", files);
+	return TRUE;
+
+corrupted:
+	ERROR("scan_inode_table: filesystem corruption detected in "
+		"scanning metadata\n");
+	free(*inode_table);
+	return FALSE;
+}
+
+
+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("", "");
+		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 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;
+	}
+
+	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 %d\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;
+}
+
+
+unsigned char *squashfs_readdir(int fd, int root_entries,
+	unsigned int directory_start_block, int offset, int size,
+	unsigned int *last_directory_block, struct squashfs_super_block *sBlk,
+	void (push_directory_entry)(char *, squashfs_inode, 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, bytes = 0, dir_count;
+	long long start = sBlk->directory_table_start + directory_start_block,
+		last_start_block = start; 
+
+	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 %d\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;
+		TRACE("squashfs_readdir: Read directory header @ byte position "
+			"0x%x, 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);
+
+			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;
+}
+
+
+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;
+}
+
+
+int read_fragment_table(int fd, struct squashfs_super_block *sBlk,
+	struct squashfs_fragment_entry **fragment_table)
+{
+	int res, i;
+	int bytes = SQUASHFS_FRAGMENT_BYTES(sBlk->fragments);
+	int indexes = SQUASHFS_FRAGMENT_INDEXES(sBlk->fragments);
+	long long fragment_table_index[indexes];
+
+	TRACE("read_fragment_table: %d fragments, reading %d fragment indexes "
+		"from 0x%llx\n", sBlk->fragments, indexes,
+		sBlk->fragment_table_start);
+
+	if(sBlk->fragments == 0)
+		return 1;
+
+	*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 0;
+	}
+
+	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 0;
+		}
+	}
+
+	for(i = 0; i < sBlk->fragments; i++)
+		SQUASHFS_INSWAP_FRAGMENT_ENTRY(&(*fragment_table)[i]);
+
+	return 1;
+}
+
+
+int read_inode_lookup_table(int fd, struct squashfs_super_block *sBlk,
+	squashfs_inode **inode_lookup_table)
+{
+	int lookup_bytes = SQUASHFS_LOOKUP_BYTES(sBlk->inodes);
+	int indexes = SQUASHFS_LOOKUP_BLOCKS(sBlk->inodes);
+	long long index[indexes];
+	int res, i;
+
+	if(sBlk->lookup_table_start == SQUASHFS_INVALID_BLK)
+		return 1;
+
+	*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 0;
+	}
+
+	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 0;
+		}
+	}
+
+	SQUASHFS_INSWAP_LONG_LONGS(*inode_lookup_table, sBlk->inodes);
+
+	return 1;
+}
+
+
+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,
+	unsigned int *inode_dir_offset, unsigned int *inode_dir_file_size,
+	unsigned int *root_inode_size, unsigned int *inode_dir_start_block,
+	int *file_count, int *sym_count, int *dev_count, int *dir_count,
+	int *fifo_count, int *sock_count, long long *uncompressed_file,
+	unsigned int *uncompressed_inode, unsigned int *uncompressed_directory,
+	unsigned int *inode_dir_inode_number,
+	unsigned int *inode_dir_parent_inode,
+	void (push_directory_entry)(char *, squashfs_inode, 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);
+	unsigned int root_inode_block;
+	union squashfs_inode_header inode;
+	unsigned int *id_table = NULL;
+	int res;
+
+	printf("Scanning existing filesystem...\n");
+
+	if(get_xattrs(fd, sBlk) == 0)
+		goto error;
+
+	if(read_fragment_table(fd, sBlk, fragment_table) == 0)
+		goto error;
+
+	if(read_inode_lookup_table(fd, sBlk, inode_lookup_table) == 0)
+		goto error;
+
+	id_table = read_id_table(fd, sBlk);
+	if(id_table == NULL)
+		goto error;
+
+	res = scan_inode_table(fd, start, end, root_inode_start,
+		root_inode_offset, sBlk, &inode, &inode_table,
+		&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(res == 0)
+		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;
+}
diff --git a/squashfs-tools/squashfs-tools/read_fs.h b/squashfs-tools/squashfs-tools/read_fs.h
new file mode 100644
index 0000000..9ad32c0
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/read_fs.h
@@ -0,0 +1,34 @@
+#ifndef READ_FS_H
+#define READ_FS_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013
+ * 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.h
+ *
+ */
+extern struct compressor *read_super(int, struct squashfs_super_block *,
+	char *);
+extern long long read_filesystem(char *, int, struct squashfs_super_block *,
+char **, char **, char **, char **, unsigned int *, unsigned int *,
+unsigned int *, unsigned int *, unsigned int *, int *, int *, int *, int *,
+int *, int *, long long *, unsigned int *, unsigned int *, unsigned int *,
+unsigned int *, void (push_directory_entry)(char *, squashfs_inode, int, int),
+struct squashfs_fragment_entry **, squashfs_inode **);
+#endif
diff --git a/squashfs-tools/squashfs-tools/read_xattrs.c b/squashfs-tools/squashfs-tools/read_xattrs.c
new file mode 100644
index 0000000..837d3fb
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/read_xattrs.c
@@ -0,0 +1,390 @@
+/*
+ * Read a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2010, 2012, 2013
+ * 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_xattrs.c
+ */
+
+/*
+ * Common xattr read code shared between mksquashfs and unsquashfs
+ */
+
+#define TRUE 1
+#define FALSE 0
+#include <stdio.h>
+#include <string.h>
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#include <stdlib.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_swap.h"
+#include "xattr.h"
+#include "error.h"
+
+extern int read_fs_bytes(int, long long, int, void *);
+extern int read_block(int, long long, long long *, int, void *);
+
+static struct hash_entry {
+	long long		start;
+	unsigned int		offset;
+	struct hash_entry	*next;
+} *hash_table[65536];
+
+static struct squashfs_xattr_id *xattr_ids;
+static void *xattrs = NULL;
+static long long xattr_table_start;
+
+/*
+ * Prefix lookup table, storing mapping to/from prefix string and prefix id
+ */
+struct prefix prefix_table[] = {
+	{ "user.", SQUASHFS_XATTR_USER },
+	{ "trusted.", SQUASHFS_XATTR_TRUSTED },
+	{ "security.", SQUASHFS_XATTR_SECURITY },
+	{ "", -1 }
+};
+
+/*
+ * store mapping from location of compressed block in fs ->
+ * location of uncompressed block in memory
+ */
+static void save_xattr_block(long long start, int offset)
+{
+	struct hash_entry *hash_entry = malloc(sizeof(*hash_entry));
+	int hash = start & 0xffff;
+
+	TRACE("save_xattr_block: start %lld, offset %d\n", start, offset);
+
+	if(hash_entry == NULL)
+		MEM_ERROR();
+
+	hash_entry->start = start;
+	hash_entry->offset = offset;
+	hash_entry->next = hash_table[hash];
+	hash_table[hash] = hash_entry;
+}
+
+
+/*
+ * map from location of compressed block in fs ->
+ * location of uncompressed block in memory
+ */
+static int get_xattr_block(long long start)
+{
+	int hash = start & 0xffff;
+	struct hash_entry *hash_entry = hash_table[hash];
+
+	for(; hash_entry; hash_entry = hash_entry->next)
+		if(hash_entry->start == start)
+			break;
+
+	TRACE("get_xattr_block: start %lld, offset %d\n", start,
+		hash_entry ? hash_entry->offset : -1);
+
+	return hash_entry ? hash_entry->offset : -1;
+}
+
+
+/*
+ * construct the xattr_list entry from the fs xattr, including
+ * mapping name and prefix into a full name
+ */
+static int read_xattr_entry(struct xattr_list *xattr,
+	struct squashfs_xattr_entry *entry, void *name)
+{
+	int i, len, type = entry->type & XATTR_PREFIX_MASK;
+
+	for(i = 0; prefix_table[i].type != -1; i++)
+		if(prefix_table[i].type == type)
+			break;
+
+	if(prefix_table[i].type == -1) {
+		ERROR("Unrecognised type in read_xattr_entry\n");
+		return 0;
+	}
+
+	len = strlen(prefix_table[i].prefix);
+	xattr->full_name = malloc(len + entry->size + 1);
+	if(xattr->full_name == NULL)
+		MEM_ERROR();
+
+	memcpy(xattr->full_name, prefix_table[i].prefix, len);
+	memcpy(xattr->full_name + len, name, entry->size);
+	xattr->full_name[len + entry->size] = '\0';
+	xattr->name = xattr->full_name + len;
+	xattr->size = entry->size;
+	xattr->type = type;
+
+	return 1;
+}
+
+
+/*
+ * Read and decompress the xattr id table and the xattr metadata.
+ * This is cached in memory for later use by get_xattr()
+ */
+int read_xattrs_from_disk(int fd, struct squashfs_super_block *sBlk)
+{
+	int res, bytes, i, indexes, index_bytes, ids;
+	long long *index, start, end;
+	struct squashfs_xattr_table id_table;
+
+	TRACE("read_xattrs_from_disk\n");
+
+	if(sBlk->xattr_id_table_start == SQUASHFS_INVALID_BLK)
+		return SQUASHFS_INVALID_BLK;
+
+	/*
+	 * Read xattr id table, containing start of xattr metadata and the
+	 * number of xattrs in the file system
+	 */
+	res = read_fs_bytes(fd, sBlk->xattr_id_table_start, sizeof(id_table),
+		&id_table);
+	if(res == 0)
+		return 0;
+
+	SQUASHFS_INSWAP_XATTR_TABLE(&id_table);
+
+	/*
+	 * Allocate and read the index to the xattr id table metadata
+	 * blocks
+	 */
+	ids = id_table.xattr_ids;
+	xattr_table_start = id_table.xattr_table_start;
+	index_bytes = SQUASHFS_XATTR_BLOCK_BYTES(ids);
+	indexes = SQUASHFS_XATTR_BLOCKS(ids);
+	index = malloc(index_bytes);
+	if(index == NULL)
+		MEM_ERROR();
+
+	res = read_fs_bytes(fd, sBlk->xattr_id_table_start + sizeof(id_table),
+		index_bytes, index);
+	if(res ==0)
+		goto failed1;
+
+	SQUASHFS_INSWAP_LONG_LONGS(index, indexes);
+
+	/*
+	 * Allocate enough space for the uncompressed xattr id table, and
+	 * read and decompress it
+	 */
+	bytes = SQUASHFS_XATTR_BYTES(ids);
+	xattr_ids = malloc(bytes);
+	if(xattr_ids == NULL)
+		MEM_ERROR();
+
+	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 *) xattr_ids) +
+			(i * SQUASHFS_METADATA_SIZE));
+		TRACE("Read xattr id table block %d, from 0x%llx, length "
+			"%d\n", i, index[i], length);
+		if(length == 0) {
+			ERROR("Failed to read xattr id table block %d, "
+				"from 0x%llx, length %d\n", i, index[i],
+				length);
+			goto failed2;
+		}
+	}
+
+	/*
+	 * Read and decompress the xattr metadata
+	 *
+	 * Note the first xattr id table metadata block is immediately after
+	 * the last xattr metadata block, so we can use index[0] to work out
+	 * the end of the xattr metadata
+	 */
+	start = xattr_table_start;
+	end = index[0];
+	for(i = 0; start < end; i++) {
+		int length;
+		xattrs = realloc(xattrs, (i + 1) * SQUASHFS_METADATA_SIZE);
+		if(xattrs == NULL)
+			MEM_ERROR();
+
+		/* store mapping from location of compressed block in fs ->
+		 * location of uncompressed block in memory */
+		save_xattr_block(start, i * SQUASHFS_METADATA_SIZE);
+
+		length = read_block(fd, start, &start, 0,
+			((unsigned char *) xattrs) +
+			(i * SQUASHFS_METADATA_SIZE));
+		TRACE("Read xattr block %d, length %d\n", i, length);
+		if(length == 0) {
+			ERROR("Failed to read xattr block %d\n", i);
+			goto failed3;
+		}
+
+		/*
+		 * If this is not the last metadata block in the xattr metadata
+		 * 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 && length != SQUASHFS_METADATA_SIZE) {
+			ERROR("Xattr block %d should be %d bytes in length, "
+				"it is %d bytes\n", i, SQUASHFS_METADATA_SIZE,
+				length);
+			goto failed3;
+		}
+	}
+
+	/* swap if necessary the xattr id entries */
+	for(i = 0; i < ids; i++)
+		SQUASHFS_INSWAP_XATTR_ID(&xattr_ids[i]);
+
+	free(index);
+
+	return ids;
+
+failed3:
+	free(xattrs);
+failed2:
+	free(xattr_ids);
+failed1:
+	free(index);
+
+	return 0;
+}
+
+
+void free_xattr(struct xattr_list *xattr_list, int count)
+{
+	int i;
+
+	for(i = 0; i < count; i++)
+		free(xattr_list[i].full_name);
+
+	free(xattr_list);
+}
+
+
+/*
+ * Construct and return the list of xattr name:value pairs for the passed xattr
+ * id
+ *
+ * There are two users for get_xattr(), Mksquashfs uses it to read the
+ * xattrs from the filesystem on appending, and Unsquashfs uses it
+ * to retrieve the xattrs for writing to disk.
+ *
+ * Unfortunately, the two users disagree on what to do with unknown
+ * xattr prefixes, Mksquashfs wants to treat this as fatal otherwise
+ * this will cause xattrs to be be lost on appending.  Unsquashfs
+ * on the otherhand wants to retrieve the xattrs which are known and
+ * to ignore the rest, this allows Unsquashfs to cope more gracefully
+ * with future versions which may have unknown xattrs, as long as the
+ * general xattr structure is adhered to, Unsquashfs should be able
+ * to safely ignore unknown xattrs, and to write the ones it knows about,
+ * this is better than completely refusing to retrieve all the xattrs.
+ *
+ * If ignore is TRUE then don't treat unknown xattr prefixes as
+ * a failure to read the xattr.  
+ */
+struct xattr_list *get_xattr(int i, unsigned int *count, int ignore)
+{
+	long long start;
+	struct xattr_list *xattr_list = NULL;
+	unsigned int offset;
+	void *xptr;
+	int j = 0, res = 1;
+
+	TRACE("get_xattr\n");
+
+	*count = xattr_ids[i].count;
+	start = SQUASHFS_XATTR_BLK(xattr_ids[i].xattr) + xattr_table_start;
+	offset = SQUASHFS_XATTR_OFFSET(xattr_ids[i].xattr);
+	xptr = xattrs + get_xattr_block(start) + offset;
+
+	TRACE("get_xattr: xattr_id %d, count %d, start %lld, offset %d\n", i,
+			*count, start, offset);
+
+	while(j < *count) {
+		struct squashfs_xattr_entry entry;
+		struct squashfs_xattr_val val;
+
+		if(res != 0) {
+			xattr_list = realloc(xattr_list, (j + 1) *
+						sizeof(struct xattr_list));
+			if(xattr_list == NULL)
+				MEM_ERROR();
+		}
+			
+		SQUASHFS_SWAP_XATTR_ENTRY(xptr, &entry);
+		xptr += sizeof(entry);
+
+		res = read_xattr_entry(&xattr_list[j], &entry, xptr);
+		if(ignore && res == 0) {
+			/* unknown prefix, but ignore flag is set */
+			(*count) --;
+			continue;
+		}
+
+		if(res != 1)
+			goto failed;
+
+		xptr += entry.size;
+			
+		TRACE("get_xattr: xattr %d, type %d, size %d, name %s\n", j,
+			entry.type, entry.size, xattr_list[j].full_name); 
+
+		if(entry.type & SQUASHFS_XATTR_VALUE_OOL) {
+			long long xattr;
+			void *ool_xptr;
+
+			xptr += sizeof(val);
+			SQUASHFS_SWAP_LONG_LONGS(xptr, &xattr, 1);
+			xptr += sizeof(xattr);	
+			start = SQUASHFS_XATTR_BLK(xattr) + xattr_table_start;
+			offset = SQUASHFS_XATTR_OFFSET(xattr);
+			ool_xptr = xattrs + get_xattr_block(start) + offset;
+			SQUASHFS_SWAP_XATTR_VAL(ool_xptr, &val);
+			xattr_list[j].value = ool_xptr + sizeof(val);
+		} else {
+			SQUASHFS_SWAP_XATTR_VAL(xptr, &val);
+			xattr_list[j].value = xptr + sizeof(val);
+			xptr += sizeof(val) + val.vsize;
+		}
+
+		TRACE("get_xattr: xattr %d, vsize %d\n", j, val.vsize);
+
+		xattr_list[j ++].vsize = val.vsize;
+	}
+
+	if(*count == 0)
+		goto failed;
+
+	return xattr_list;
+
+failed:
+	free_xattr(xattr_list, j);
+
+	return NULL;
+}
diff --git a/squashfs-tools/squashfs-tools/restore.c b/squashfs-tools/squashfs-tools/restore.c
new file mode 100644
index 0000000..5e336b3
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/restore.c
@@ -0,0 +1,155 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * 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.
+ *
+ * restore.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "caches-queues-lists.h"
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "error.h"
+#include "progressbar.h"
+#include "info.h"
+
+#define FALSE 0
+#define TRUE 1
+
+extern pthread_t reader_thread, writer_thread, main_thread;
+extern pthread_t *deflator_thread, *frag_deflator_thread, *frag_thread;
+extern struct queue *to_deflate, *to_writer, *to_frag, *to_process_frag;
+extern struct seq_queue *to_main;
+extern void restorefs();
+extern int processors;
+
+static int interrupted = 0;
+static pthread_t restore_thread;
+
+void *restore_thrd(void *arg)
+{
+	sigset_t sigmask, old_mask;
+	int i, sig;
+
+	sigemptyset(&sigmask);
+	sigaddset(&sigmask, SIGINT);
+	sigaddset(&sigmask, SIGTERM);
+	sigaddset(&sigmask, SIGUSR1);
+	pthread_sigmask(SIG_BLOCK, &sigmask, &old_mask);
+
+	while(1) {
+		sigwait(&sigmask, &sig);
+
+		if((sig == SIGINT || sig == SIGTERM) && !interrupted) {
+			ERROR("Interrupting will restore original "
+				"filesystem!\n");
+                	ERROR("Interrupt again to quit\n");
+			interrupted = TRUE;
+			continue;
+		}
+
+		/* kill main thread/worker threads and restore */
+		set_progressbar_state(FALSE);
+		disable_info();
+
+		/* first kill the reader thread */
+		pthread_cancel(reader_thread);
+		pthread_join(reader_thread, NULL);
+
+		/*
+		 * then flush the reader to deflator thread(s) output queue.
+		 * The deflator thread(s) will idle
+		 */
+		queue_flush(to_deflate);
+
+		/* now kill the deflator thread(s) */
+		for(i = 0; i < processors; i++)
+			pthread_cancel(deflator_thread[i]);
+		for(i = 0; i < processors; i++)
+			pthread_join(deflator_thread[i], NULL);
+
+		/*
+		 * then flush the reader to process fragment thread(s) output
+		 * queue.  The process fragment thread(s) will idle
+		 */
+		queue_flush(to_process_frag);
+
+		/* now kill the process fragment thread(s) */
+		for(i = 0; i < processors; i++)
+			pthread_cancel(frag_thread[i]);
+		for(i = 0; i < processors; i++)
+			pthread_join(frag_thread[i], NULL);
+
+		/*
+		 * then flush the reader/deflator/process fragment to main
+		 * thread output queue.  The main thread will idle
+		 */
+		seq_queue_flush(to_main);
+
+		/* now kill the main thread */
+		pthread_cancel(main_thread);
+		pthread_join(main_thread, NULL);
+
+		/* then flush the main thread to fragment deflator thread(s)
+		 * queue.  The fragment deflator thread(s) will idle
+		 */
+		queue_flush(to_frag);
+
+		/* now kill the fragment deflator thread(s) */
+		for(i = 0; i < processors; i++)
+			pthread_cancel(frag_deflator_thread[i]);
+		for(i = 0; i < processors; i++)
+			pthread_join(frag_deflator_thread[i], NULL);
+
+		/*
+		 * then flush the main thread/fragment deflator thread(s)
+		 * to writer thread queue.  The writer thread will idle
+		 */
+		queue_flush(to_writer);
+
+		/* now kill the writer thread */
+		pthread_cancel(writer_thread);
+		pthread_join(writer_thread, NULL);
+
+		TRACE("All threads cancelled\n");
+
+		restorefs();
+	}
+}
+
+
+pthread_t *init_restore_thread()
+{
+	pthread_create(&restore_thread, NULL, restore_thrd, NULL);
+	return &restore_thread;
+}
diff --git a/squashfs-tools/squashfs-tools/restore.h b/squashfs-tools/squashfs-tools/restore.h
new file mode 100644
index 0000000..35129f0
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/restore.h
@@ -0,0 +1,28 @@
+#ifndef RESTORE_H
+#define RESTORE_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * 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.
+ *
+ * restore.h
+ */
+
+extern pthread_t *init_restore_thread();
+#endif
diff --git a/squashfs-tools/squashfs-tools/sort.c b/squashfs-tools/squashfs-tools/sort.c
new file mode 100644
index 0000000..89df9e4
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/sort.c
@@ -0,0 +1,363 @@
+/*
+ * Create 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
+ * 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.
+ *
+ * sort.c
+ */
+
+#define TRUE 1
+#define FALSE 0
+#define MAX_LINE 16384
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "sort.h"
+#include "error.h"
+#include "progressbar.h"
+
+int mkisofs_style = -1;
+
+struct sort_info {
+	dev_t			st_dev;
+	ino_t			st_ino;
+	int			priority;
+	struct sort_info	*next;
+};
+
+struct sort_info *sort_info_list[65536];
+
+struct priority_entry *priority_list[65536];
+
+extern int silent;
+extern void write_file(squashfs_inode *inode, struct dir_ent *dir_ent,
+	int *c_size);
+extern char *pathname(struct dir_ent *dir_ent);
+
+
+void add_priority_list(struct dir_ent *dir, int priority)
+{
+	struct priority_entry *new_priority_entry;
+
+	priority += 32768;
+	new_priority_entry = malloc(sizeof(struct priority_entry));
+	if(new_priority_entry == NULL)
+		MEM_ERROR();
+
+	new_priority_entry->dir = dir;;
+	new_priority_entry->next = priority_list[priority];
+	priority_list[priority] = new_priority_entry;
+}
+
+
+int get_priority(char *filename, struct stat *buf, int priority)
+{
+	int hash = buf->st_ino & 0xffff;
+	struct sort_info *s;
+
+	for(s = sort_info_list[hash]; s; s = s->next)
+		if((s->st_dev == buf->st_dev) && (s->st_ino == buf->st_ino)) {
+			TRACE("returning priority %d (%s)\n", s->priority,
+				filename);
+			return s->priority;
+		}
+	TRACE("returning priority %d (%s)\n", priority, filename);
+	return priority;
+}
+
+
+#define ADD_ENTRY(buf, priority) {\
+	int hash = buf.st_ino & 0xffff;\
+	struct sort_info *s;\
+	if((s = malloc(sizeof(struct sort_info))) == NULL) \
+		MEM_ERROR(); \
+	s->st_dev = buf.st_dev;\
+	s->st_ino = buf.st_ino;\
+	s->priority = priority;\
+	s->next = sort_info_list[hash];\
+	sort_info_list[hash] = s;\
+	}
+int add_sort_list(char *path, int priority, int source, char *source_path[])
+{
+	int i, n;
+	struct stat buf;
+
+	TRACE("add_sort_list: filename %s, priority %d\n", path, priority);
+	if(strlen(path) > 1 && strcmp(path + strlen(path) - 2, "/*") == 0)
+		path[strlen(path) - 2] = '\0';
+
+	TRACE("add_sort_list: filename %s, priority %d\n", path, priority);
+re_read:
+	if(path[0] == '/' || strncmp(path, "./", 2) == 0 ||
+			strncmp(path, "../", 3) == 0 || mkisofs_style == 1) {
+		if(lstat(path, &buf) == -1)
+			goto error;
+		TRACE("adding filename %s, priority %d, st_dev %d, st_ino "
+			"%lld\n", path, priority, (int) buf.st_dev,
+			(long long) buf.st_ino);
+		ADD_ENTRY(buf, priority);
+		return TRUE;
+	}
+
+	for(i = 0, n = 0; i < source; i++) {
+		char *filename;
+		int res = asprintf(&filename, "%s/%s", source_path[i], path);
+		if(res == -1)
+			BAD_ERROR("asprintf failed in add_sort_list\n");
+		res = lstat(filename, &buf);
+		free(filename);
+		if(res == -1) {
+			if(!(errno == ENOENT || errno == ENOTDIR))
+				goto error;
+			continue;
+		}
+		ADD_ENTRY(buf, priority);
+		n ++;
+	}
+
+	if(n == 0 && mkisofs_style == -1 && lstat(path, &buf) != -1) {
+		ERROR("WARNING: Mkisofs style sortlist detected! This is "
+			"supported but please\n");
+		ERROR("convert to mksquashfs style sortlist! A sortlist entry");
+	        ERROR(" should be\neither absolute (starting with ");
+		ERROR("'/') start with './' or '../' (taken to be\nrelative to "
+			"$PWD), otherwise it ");
+		ERROR("is assumed the entry is relative to one\nof the source "
+			"directories, i.e. with ");
+		ERROR("\"mksquashfs test test.sqsh\",\nthe sortlist ");
+		ERROR("entry \"file\" is assumed to be inside the directory "
+			"test.\n\n");
+		mkisofs_style = 1;
+		goto re_read;
+	}
+
+	mkisofs_style = 0;
+
+	if(n == 1)
+		return TRUE;
+	if(n > 1) {
+		ERROR(" Ambiguous sortlist entry \"%s\"\n\nIt maps to more "
+			"than one source entry!  Please use an absolute path."
+			"\n", path);
+		return FALSE;
+	}
+
+error:
+        ERROR_START("Cannot stat sortlist entry \"%s\"\n", path);
+        ERROR("This is probably because you're using the wrong file\n");
+        ERROR("path relative to the source directories.");
+	ERROR_EXIT("  Ignoring");
+	/*
+	 * Historical note
+	 * Failure to stat a sortlist entry is deliberately ignored, even
+	 * though it is an error.  Squashfs release 2.2 changed the behaviour
+	 * to treat it as a fatal error, but it was changed back to
+	 * the original behaviour to ignore it in release 2.2-r2 following
+	 * feedback from users at the time.
+	 */
+        return TRUE;
+}
+
+
+void generate_file_priorities(struct dir_info *dir, int priority,
+	struct stat *buf)
+{
+	struct dir_ent *dir_ent = dir->list;
+
+	priority = get_priority(dir->pathname, buf, priority);
+
+	for(; dir_ent; dir_ent = dir_ent->next) {
+		struct stat *buf = &dir_ent->inode->buf;
+		if(dir_ent->inode->root_entry)
+			continue;
+
+		switch(buf->st_mode & S_IFMT) {
+			case S_IFREG:
+				add_priority_list(dir_ent,
+					get_priority(pathname(dir_ent), buf,
+					priority));
+				break;
+			case S_IFDIR:
+				generate_file_priorities(dir_ent->dir,
+					priority, buf);
+				break;
+		}
+	}
+}
+
+
+int read_sort_file(char *filename, int source, char *source_path[])
+{
+	FILE *fd;
+	char line_buffer[MAX_LINE + 1]; /* overflow safe */
+	char sort_filename[MAX_LINE + 1]; /* overflow safe */
+	char *line, *name;
+	int n, priority, res;
+
+	if((fd = fopen(filename, "r")) == NULL) {
+		ERROR("Failed to open sort file \"%s\" because %s\n",
+			filename, strerror(errno));
+		return FALSE;
+	}
+
+	while(fgets(line = line_buffer, MAX_LINE + 1, fd) != NULL) {
+		int len = strlen(line);
+
+		if(len == MAX_LINE && line[len - 1] != '\n') {
+			/* line too large */
+			ERROR("Line too long when reading "
+				"sort file \"%s\", larger than %d "
+				"bytes\n", filename, MAX_LINE);
+			goto failed;
+		}
+
+		/*
+		 * Remove '\n' terminator if it exists (the last line
+		 * in the file may not be '\n' terminated)
+		 */
+		if(len && line[len - 1] == '\n')
+			line[len - 1] = '\0';
+
+		/* Skip any leading whitespace */
+		while(isspace(*line))
+			line ++;
+
+		/* if comment line, skip */
+		if(*line == '#')
+			continue;
+
+		/*
+		 * Scan for filename, don't use sscanf() and "%s" because
+		 * that can't handle filenames with spaces
+		 */
+		for(name = sort_filename; !isspace(*line) && *line != '\0';) {
+			if(*line == '\\') {
+				line ++;
+				if (*line == '\0')
+					break;
+			}
+			*name ++ = *line ++;
+		}
+		*name = '\0';
+
+		/*
+		 * if filename empty, then line was empty of anything but
+		 * whitespace or a backslash character.  Skip empy lines
+		 */
+		if(sort_filename[0] == '\0')
+			continue;
+
+		/*
+		 * Scan the rest of the line, we expect a decimal number
+		 * which is the filename priority
+		 */
+		errno = 0;
+		res = sscanf(line, "%d%n", &priority, &n);
+
+		if((res < 1 || errno) && errno != ERANGE) {
+			if(errno == 0)
+				/* No error, assume EOL or match failure */
+				ERROR("Sort file \"%s\", can't find priority "
+					"in entry \"%s\", EOL or match "
+					"failure\n", filename, line_buffer);
+			else
+				/* Some other failure not ERANGE */
+				ERROR("Sscanf failed reading sort file \"%s\" "
+					"because %s\n", filename,
+					strerror(errno));
+			goto failed;
+		} else if((errno == ERANGE) ||
+				(priority < -32768 || priority > 32767)) {
+			ERROR("Sort file \"%s\", entry \"%s\" has priority "
+				"outside range of -32767:32768.\n", filename,
+				line_buffer);
+			goto failed;
+		}
+
+		/* Skip any trailing whitespace */
+		line += n;
+		while(isspace(*line))
+			line ++;
+
+		if(*line != '\0') {
+			ERROR("Sort file \"%s\", trailing characters after "
+				"priority in entry \"%s\"\n", filename,
+				line_buffer);
+			goto failed;
+		}
+
+		res = add_sort_list(sort_filename, priority, source,
+			source_path);
+		if(res == FALSE)
+			goto failed;
+	}
+
+	if(ferror(fd)) {
+		ERROR("Reading sort file \"%s\" failed because %s\n", filename,
+			strerror(errno));
+		goto failed;
+	}
+
+	fclose(fd);
+	return TRUE;
+
+failed:
+	fclose(fd);
+	return FALSE;
+}
+
+
+void sort_files_and_write(struct dir_info *dir)
+{
+	int i;
+	struct priority_entry *entry;
+	squashfs_inode inode;
+	int duplicate_file;
+
+	for(i = 65535; i >= 0; i--)
+		for(entry = priority_list[i]; entry; entry = entry->next) {
+			TRACE("%d: %s\n", i - 32768, pathname(entry->dir));
+			if(entry->dir->inode->inode == SQUASHFS_INVALID_BLK) {
+				write_file(&inode, entry->dir, &duplicate_file);
+				INFO("file %s, uncompressed size %lld bytes %s"
+					"\n", pathname(entry->dir),
+					(long long)
+					entry->dir->inode->buf.st_size,
+					duplicate_file ? "DUPLICATE" : "");
+				entry->dir->inode->inode = inode;
+				entry->dir->inode->type = SQUASHFS_FILE_TYPE;
+			} else
+				INFO("file %s, uncompressed size %lld bytes "
+					"LINK\n", pathname(entry->dir),
+					(long long)
+					entry->dir->inode->buf.st_size);
+		}
+}
diff --git a/squashfs-tools/squashfs-tools/sort.h b/squashfs-tools/squashfs-tools/sort.h
new file mode 100644
index 0000000..98db62c
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/sort.h
@@ -0,0 +1,37 @@
+#ifndef SORT_H 
+#define SORT_H
+
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013
+ * 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.
+ *
+ * sort.h
+ */
+
+struct priority_entry {
+	struct dir_ent *dir;
+	struct priority_entry *next;
+};
+
+extern int read_sort_file(char *, int, char *[]);
+extern void sort_files_and_write(struct dir_info *);
+extern void generate_file_priorities(struct dir_info *, int priority,
+	struct stat *);
+extern struct  priority_entry *priority_list[65536];
+#endif
diff --git a/squashfs-tools/squashfs-tools/squashfs_compat.h b/squashfs-tools/squashfs-tools/squashfs_compat.h
new file mode 100644
index 0000000..83b0278
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/squashfs_compat.h
@@ -0,0 +1,818 @@
+#ifndef SQUASHFS_COMPAT
+#define SQUASHFS_COMPAT
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2014
+ * 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.
+ *
+ * squashfs_compat.h
+ */
+
+/*
+ * definitions for structures on disk - layout 3.x
+ */
+
+#define SQUASHFS_CHECK			2
+#define SQUASHFS_CHECK_DATA(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_CHECK)
+
+/* Max number of uids and gids */
+#define SQUASHFS_UIDS			256
+#define SQUASHFS_GUIDS			255
+
+struct squashfs_super_block_3 {
+	unsigned int		s_magic;
+	unsigned int		inodes;
+	unsigned int		bytes_used_2;
+	unsigned int		uid_start_2;
+	unsigned int		guid_start_2;
+	unsigned int		inode_table_start_2;
+	unsigned int		directory_table_start_2;
+	unsigned int		s_major:16;
+	unsigned int		s_minor:16;
+	unsigned int		block_size_1:16;
+	unsigned int		block_log:16;
+	unsigned int		flags:8;
+	unsigned int		no_uids:8;
+	unsigned int		no_guids:8;
+	int			mkfs_time /* time of filesystem creation */;
+	squashfs_inode		root_inode;
+	unsigned int		block_size;
+	unsigned int		fragments;
+	unsigned int		fragment_table_start_2;
+	long long		bytes_used;
+	long long		uid_start;
+	long long		guid_start;
+	long long		inode_table_start;
+	long long		directory_table_start;
+	long long		fragment_table_start;
+	long long		lookup_table_start;
+} __attribute__ ((packed));
+
+struct squashfs_dir_index_3 {
+	unsigned int		index;
+	unsigned int		start_block;
+	unsigned char		size;
+	unsigned char		name[0];
+} __attribute__ ((packed));
+
+struct squashfs_base_inode_header_3 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12;
+	unsigned int		uid:8;
+	unsigned int		guid:8;
+	int			mtime;
+	unsigned int 		inode_number;
+} __attribute__ ((packed));
+
+struct squashfs_ipc_inode_header_3 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12;
+	unsigned int		uid:8;
+	unsigned int		guid:8;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+} __attribute__ ((packed));
+
+struct squashfs_dev_inode_header_3 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12;
+	unsigned int		uid:8;
+	unsigned int		guid:8;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	unsigned short		rdev;
+} __attribute__ ((packed));
+	
+struct squashfs_symlink_inode_header_3 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12;
+	unsigned int		uid:8;
+	unsigned int		guid:8;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	unsigned short		symlink_size;
+	char			symlink[0];
+} __attribute__ ((packed));
+
+struct squashfs_reg_inode_header_3 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12;
+	unsigned int		uid:8;
+	unsigned int		guid:8;
+	int			mtime;
+	unsigned int 		inode_number;
+	squashfs_block		start_block;
+	unsigned int		fragment;
+	unsigned int		offset;
+	unsigned int		file_size;
+	unsigned short		block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_lreg_inode_header_3 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12;
+	unsigned int		uid:8;
+	unsigned int		guid:8;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	squashfs_block		start_block;
+	unsigned int		fragment;
+	unsigned int		offset;
+	long long		file_size;
+	unsigned short		block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_inode_header_3 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12;
+	unsigned int		uid:8;
+	unsigned int		guid:8;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	unsigned int		file_size:19;
+	unsigned int		offset:13;
+	unsigned int		start_block;
+	unsigned int		parent_inode;
+} __attribute__  ((packed));
+
+struct squashfs_ldir_inode_header_3 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12;
+	unsigned int		uid:8;
+	unsigned int		guid:8;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	unsigned int		file_size:27;
+	unsigned int		offset:13;
+	unsigned int		start_block;
+	unsigned int		i_count:16;
+	unsigned int		parent_inode;
+	struct squashfs_dir_index_3	index[0];
+} __attribute__  ((packed));
+
+union squashfs_inode_header_3 {
+	struct squashfs_base_inode_header_3	base;
+	struct squashfs_dev_inode_header_3	dev;
+	struct squashfs_symlink_inode_header_3	symlink;
+	struct squashfs_reg_inode_header_3	reg;
+	struct squashfs_lreg_inode_header_3	lreg;
+	struct squashfs_dir_inode_header_3	dir;
+	struct squashfs_ldir_inode_header_3	ldir;
+	struct squashfs_ipc_inode_header_3	ipc;
+};
+	
+struct squashfs_dir_entry_3 {
+	unsigned int		offset:13;
+	unsigned int		type:3;
+	unsigned int		size:8;
+	int			inode_number:16;
+	char			name[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_header_3 {
+	unsigned int		count:8;
+	unsigned int		start_block;
+	unsigned int		inode_number;
+} __attribute__ ((packed));
+
+struct squashfs_fragment_entry_3 {
+	long long		start_block;
+	unsigned int		size;
+	unsigned int		pending;
+} __attribute__ ((packed));
+
+
+typedef struct squashfs_super_block_3 squashfs_super_block_3;
+typedef struct squashfs_dir_index_3 squashfs_dir_index_3;
+typedef struct squashfs_base_inode_header_3 squashfs_base_inode_header_3;
+typedef struct squashfs_ipc_inode_header_3 squashfs_ipc_inode_header_3;
+typedef struct squashfs_dev_inode_header_3 squashfs_dev_inode_header_3;
+typedef struct squashfs_symlink_inode_header_3 squashfs_symlink_inode_header_3;
+typedef struct squashfs_reg_inode_header_3 squashfs_reg_inode_header_3;
+typedef struct squashfs_lreg_inode_header_3 squashfs_lreg_inode_header_3;
+typedef struct squashfs_dir_inode_header_3 squashfs_dir_inode_header_3;
+typedef struct squashfs_ldir_inode_header_3 squashfs_ldir_inode_header_3;
+typedef struct squashfs_dir_entry_3 squashfs_dir_entry_3;
+typedef struct squashfs_dir_header_3 squashfs_dir_header_3;
+typedef struct squashfs_fragment_entry_3 squashfs_fragment_entry_3;
+
+/*
+ * macros to convert each packed bitfield structure from little endian to big
+ * endian and vice versa.  These are needed when creating or using a filesystem
+ * on a machine with different byte ordering to the target architecture.
+ *
+ */
+
+#define SQUASHFS_SWAP_START \
+	int bits;\
+	int b_pos;\
+	unsigned long long val;\
+	unsigned char *s;\
+	unsigned char *d;
+
+#define SQUASHFS_SWAP_SUPER_BLOCK_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_super_block_3));\
+	SQUASHFS_SWAP((s)->s_magic, d, 0, 32);\
+	SQUASHFS_SWAP((s)->inodes, d, 32, 32);\
+	SQUASHFS_SWAP((s)->bytes_used_2, d, 64, 32);\
+	SQUASHFS_SWAP((s)->uid_start_2, d, 96, 32);\
+	SQUASHFS_SWAP((s)->guid_start_2, d, 128, 32);\
+	SQUASHFS_SWAP((s)->inode_table_start_2, d, 160, 32);\
+	SQUASHFS_SWAP((s)->directory_table_start_2, d, 192, 32);\
+	SQUASHFS_SWAP((s)->s_major, d, 224, 16);\
+	SQUASHFS_SWAP((s)->s_minor, d, 240, 16);\
+	SQUASHFS_SWAP((s)->block_size_1, d, 256, 16);\
+	SQUASHFS_SWAP((s)->block_log, d, 272, 16);\
+	SQUASHFS_SWAP((s)->flags, d, 288, 8);\
+	SQUASHFS_SWAP((s)->no_uids, d, 296, 8);\
+	SQUASHFS_SWAP((s)->no_guids, d, 304, 8);\
+	SQUASHFS_SWAP((s)->mkfs_time, d, 312, 32);\
+	SQUASHFS_SWAP((s)->root_inode, d, 344, 64);\
+	SQUASHFS_SWAP((s)->block_size, d, 408, 32);\
+	SQUASHFS_SWAP((s)->fragments, d, 440, 32);\
+	SQUASHFS_SWAP((s)->fragment_table_start_2, d, 472, 32);\
+	SQUASHFS_SWAP((s)->bytes_used, d, 504, 64);\
+	SQUASHFS_SWAP((s)->uid_start, d, 568, 64);\
+	SQUASHFS_SWAP((s)->guid_start, d, 632, 64);\
+	SQUASHFS_SWAP((s)->inode_table_start, d, 696, 64);\
+	SQUASHFS_SWAP((s)->directory_table_start, d, 760, 64);\
+	SQUASHFS_SWAP((s)->fragment_table_start, d, 824, 64);\
+	SQUASHFS_SWAP((s)->lookup_table_start, d, 888, 64);\
+}
+
+#define SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, n)\
+	SQUASHFS_MEMSET(s, d, n);\
+	SQUASHFS_SWAP((s)->inode_type, d, 0, 4);\
+	SQUASHFS_SWAP((s)->mode, d, 4, 12);\
+	SQUASHFS_SWAP((s)->uid, d, 16, 8);\
+	SQUASHFS_SWAP((s)->guid, d, 24, 8);\
+	SQUASHFS_SWAP((s)->mtime, d, 32, 32);\
+	SQUASHFS_SWAP((s)->inode_number, d, 64, 32);
+
+#define SQUASHFS_SWAP_BASE_INODE_HEADER_3(s, d, n) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, n)\
+}
+
+#define SQUASHFS_SWAP_IPC_INODE_HEADER_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+			sizeof(struct squashfs_ipc_inode_header_3))\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+}
+
+#define SQUASHFS_SWAP_DEV_INODE_HEADER_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+			sizeof(struct squashfs_dev_inode_header_3)); \
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->rdev, d, 128, 16);\
+}
+
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+			sizeof(struct squashfs_symlink_inode_header_3));\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->symlink_size, d, 128, 16);\
+}
+
+#define SQUASHFS_SWAP_REG_INODE_HEADER_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+			sizeof(struct squashfs_reg_inode_header_3));\
+	SQUASHFS_SWAP((s)->start_block, d, 96, 64);\
+	SQUASHFS_SWAP((s)->fragment, d, 160, 32);\
+	SQUASHFS_SWAP((s)->offset, d, 192, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 224, 32);\
+}
+
+#define SQUASHFS_SWAP_LREG_INODE_HEADER_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+			sizeof(struct squashfs_lreg_inode_header_3));\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 128, 64);\
+	SQUASHFS_SWAP((s)->fragment, d, 192, 32);\
+	SQUASHFS_SWAP((s)->offset, d, 224, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 256, 64);\
+}
+
+#define SQUASHFS_SWAP_DIR_INODE_HEADER_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+			sizeof(struct squashfs_dir_inode_header_3));\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 128, 19);\
+	SQUASHFS_SWAP((s)->offset, d, 147, 13);\
+	SQUASHFS_SWAP((s)->start_block, d, 160, 32);\
+	SQUASHFS_SWAP((s)->parent_inode, d, 192, 32);\
+}
+
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+			sizeof(struct squashfs_ldir_inode_header_3));\
+	SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 128, 27);\
+	SQUASHFS_SWAP((s)->offset, d, 155, 13);\
+	SQUASHFS_SWAP((s)->start_block, d, 168, 32);\
+	SQUASHFS_SWAP((s)->i_count, d, 200, 16);\
+	SQUASHFS_SWAP((s)->parent_inode, d, 216, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_INDEX_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_index_3));\
+	SQUASHFS_SWAP((s)->index, d, 0, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 32, 32);\
+	SQUASHFS_SWAP((s)->size, d, 64, 8);\
+}
+
+#define SQUASHFS_SWAP_DIR_HEADER_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_header_3));\
+	SQUASHFS_SWAP((s)->count, d, 0, 8);\
+	SQUASHFS_SWAP((s)->start_block, d, 8, 32);\
+	SQUASHFS_SWAP((s)->inode_number, d, 40, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_ENTRY_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_entry_3));\
+	SQUASHFS_SWAP((s)->offset, d, 0, 13);\
+	SQUASHFS_SWAP((s)->type, d, 13, 3);\
+	SQUASHFS_SWAP((s)->size, d, 16, 8);\
+	SQUASHFS_SWAP((s)->inode_number, d, 24, 16);\
+}
+
+#define SQUASHFS_SWAP_INODE_T_3(s, d) SQUASHFS_SWAP_LONG_LONGS_3(s, d, 1)
+
+#define SQUASHFS_SWAP_SHORTS_3(s, d, n) {\
+	int entry;\
+	int bit_position;\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, n * 2);\
+	for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+			16)\
+		SQUASHFS_SWAP(s[entry], d, bit_position, 16);\
+}
+
+#define SQUASHFS_SWAP_INTS_3(s, d, n) {\
+	int entry;\
+	int bit_position;\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, n * 4);\
+	for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+			32)\
+		SQUASHFS_SWAP(s[entry], d, bit_position, 32);\
+}
+
+#define SQUASHFS_SWAP_LONG_LONGS_3(s, d, n) {\
+	int entry;\
+	int bit_position;\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, n * 8);\
+	for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+			64)\
+		SQUASHFS_SWAP(s[entry], d, bit_position, 64);\
+}
+
+#define SQUASHFS_SWAP_DATA(s, d, n, bits) {\
+	int entry;\
+	int bit_position;\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, n * bits / 8);\
+	for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+			bits)\
+		SQUASHFS_SWAP(s[entry], d, bit_position, bits);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES_3(s, d, n) SQUASHFS_SWAP_LONG_LONGS_3(s, d, n)
+#define SQUASHFS_SWAP_LOOKUP_BLOCKS_3(s, d, n) SQUASHFS_SWAP_LONG_LONGS_3(s, d, n)
+
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY_3(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_fragment_entry_3));\
+	SQUASHFS_SWAP((s)->start_block, d, 0, 64);\
+	SQUASHFS_SWAP((s)->size, d, 64, 32);\
+}
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES_3(A)	((A) * sizeof(struct squashfs_fragment_entry_3))
+
+#define SQUASHFS_FRAGMENT_INDEX_3(A)	(SQUASHFS_FRAGMENT_BYTES_3(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET_3(A)	(SQUASHFS_FRAGMENT_BYTES_3(A) % \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES_3(A)	((SQUASHFS_FRAGMENT_BYTES_3(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES_3(A)	(SQUASHFS_FRAGMENT_INDEXES_3(A) *\
+						sizeof(long long))
+
+/*
+ * definitions for structures on disk - layout 1.x
+ */
+#define SQUASHFS_TYPES			5
+#define SQUASHFS_IPC_TYPE		0
+
+struct squashfs_base_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+} __attribute__ ((packed));
+
+struct squashfs_ipc_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	unsigned int		type:4;
+	unsigned int		offset:4;
+} __attribute__ ((packed));
+
+struct squashfs_dev_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	unsigned short		rdev;
+} __attribute__ ((packed));
+	
+struct squashfs_symlink_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	unsigned short		symlink_size;
+	char			symlink[0];
+} __attribute__ ((packed));
+
+struct squashfs_reg_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	int			mtime;
+	unsigned int		start_block;
+	unsigned int		file_size:32;
+	unsigned short		block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_inode_header_1 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:4; /* index into uid table */
+	unsigned int		guid:4; /* index into guid table */
+	unsigned int		file_size:19;
+	unsigned int		offset:13;
+	int			mtime;
+	unsigned int		start_block:24;
+} __attribute__  ((packed));
+
+union squashfs_inode_header_1 {
+	struct squashfs_base_inode_header_1	base;
+	struct squashfs_dev_inode_header_1	dev;
+	struct squashfs_symlink_inode_header_1	symlink;
+	struct squashfs_reg_inode_header_1	reg;
+	struct squashfs_dir_inode_header_1	dir;
+	struct squashfs_ipc_inode_header_1	ipc;
+};
+
+typedef struct squashfs_dir_index_1 squashfs_dir_index_1;
+typedef struct squashfs_base_inode_header_1 squashfs_base_inode_header_1;
+typedef struct squashfs_ipc_inode_header_1 squashfs_ipc_inode_header_1;
+typedef struct squashfs_dev_inode_header_1 squashfs_dev_inode_header_1;
+typedef struct squashfs_symlink_inode_header_1 squashfs_symlink_inode_header_1;
+typedef struct squashfs_reg_inode_header_1 squashfs_reg_inode_header_1;
+typedef struct squashfs_dir_inode_header_1 squashfs_dir_inode_header_1;
+
+#define SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, n) \
+	SQUASHFS_MEMSET(s, d, n);\
+	SQUASHFS_SWAP((s)->inode_type, d, 0, 4);\
+	SQUASHFS_SWAP((s)->mode, d, 4, 12);\
+	SQUASHFS_SWAP((s)->uid, d, 16, 4);\
+	SQUASHFS_SWAP((s)->guid, d, 20, 4);
+
+#define SQUASHFS_SWAP_BASE_INODE_HEADER_1(s, d, n) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, n)\
+}
+
+#define SQUASHFS_SWAP_IPC_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_ipc_inode_header_1));\
+	SQUASHFS_SWAP((s)->type, d, 24, 4);\
+	SQUASHFS_SWAP((s)->offset, d, 28, 4);\
+}
+
+#define SQUASHFS_SWAP_DEV_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_dev_inode_header_1));\
+	SQUASHFS_SWAP((s)->rdev, d, 24, 16);\
+}
+
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_symlink_inode_header_1));\
+	SQUASHFS_SWAP((s)->symlink_size, d, 24, 16);\
+}
+
+#define SQUASHFS_SWAP_REG_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_reg_inode_header_1));\
+	SQUASHFS_SWAP((s)->mtime, d, 24, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 56, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 88, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_INODE_HEADER_1(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+			sizeof(struct squashfs_dir_inode_header_1));\
+	SQUASHFS_SWAP((s)->file_size, d, 24, 19);\
+	SQUASHFS_SWAP((s)->offset, d, 43, 13);\
+	SQUASHFS_SWAP((s)->mtime, d, 56, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 88, 24);\
+}
+
+/*
+ * definitions for structures on disk - layout 2.x
+ */
+struct squashfs_dir_index_2 {
+	unsigned int		index:27;
+	unsigned int		start_block:29;
+	unsigned char		size;
+	unsigned char		name[0];
+} __attribute__ ((packed));
+
+struct squashfs_base_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+} __attribute__ ((packed));
+
+struct squashfs_ipc_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+} __attribute__ ((packed));
+
+struct squashfs_dev_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	unsigned short		rdev;
+} __attribute__ ((packed));
+	
+struct squashfs_symlink_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	unsigned short		symlink_size;
+	char			symlink[0];
+} __attribute__ ((packed));
+
+struct squashfs_reg_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	int			mtime;
+	unsigned int		start_block;
+	unsigned int		fragment;
+	unsigned int		offset;
+	unsigned int		file_size:32;
+	unsigned short		block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	unsigned int		file_size:19;
+	unsigned int		offset:13;
+	int			mtime;
+	unsigned int		start_block:24;
+} __attribute__  ((packed));
+
+struct squashfs_ldir_inode_header_2 {
+	unsigned int		inode_type:4;
+	unsigned int		mode:12; /* protection */
+	unsigned int		uid:8; /* index into uid table */
+	unsigned int		guid:8; /* index into guid table */
+	unsigned int		file_size:27;
+	unsigned int		offset:13;
+	int			mtime;
+	unsigned int		start_block:24;
+	unsigned int		i_count:16;
+	struct squashfs_dir_index_2	index[0];
+} __attribute__  ((packed));
+
+union squashfs_inode_header_2 {
+	struct squashfs_base_inode_header_2	base;
+	struct squashfs_dev_inode_header_2	dev;
+	struct squashfs_symlink_inode_header_2	symlink;
+	struct squashfs_reg_inode_header_2	reg;
+	struct squashfs_dir_inode_header_2	dir;
+	struct squashfs_ldir_inode_header_2	ldir;
+	struct squashfs_ipc_inode_header_2	ipc;
+};
+	
+struct squashfs_dir_header_2 {
+	unsigned int		count:8;
+	unsigned int		start_block:24;
+} __attribute__ ((packed));
+
+struct squashfs_dir_entry_2 {
+	unsigned int		offset:13;
+	unsigned int		type:3;
+	unsigned int		size:8;
+	char			name[0];
+} __attribute__ ((packed));
+
+struct squashfs_fragment_entry_2 {
+	unsigned int		start_block;
+	unsigned int		size;
+} __attribute__ ((packed));
+
+typedef struct squashfs_dir_index_2 squashfs_dir_index_2;
+typedef struct squashfs_base_inode_header_2 squashfs_base_inode_header_2;
+typedef struct squashfs_ipc_inode_header_2 squashfs_ipc_inode_header_2;
+typedef struct squashfs_dev_inode_header_2 squashfs_dev_inode_header_2;
+typedef struct squashfs_symlink_inode_header_2 squashfs_symlink_inode_header_2;
+typedef struct squashfs_reg_inode_header_2 squashfs_reg_inode_header_2;
+typedef struct squashfs_lreg_inode_header_2 squashfs_lreg_inode_header_2;
+typedef struct squashfs_dir_inode_header_2 squashfs_dir_inode_header_2;
+typedef struct squashfs_ldir_inode_header_2 squashfs_ldir_inode_header_2;
+typedef struct squashfs_dir_entry_2 squashfs_dir_entry_2;
+typedef struct squashfs_dir_header_2 squashfs_dir_header_2;
+typedef struct squashfs_fragment_entry_2 squashfs_fragment_entry_2;
+
+#define SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, n)\
+	SQUASHFS_MEMSET(s, d, n);\
+	SQUASHFS_SWAP((s)->inode_type, d, 0, 4);\
+	SQUASHFS_SWAP((s)->mode, d, 4, 12);\
+	SQUASHFS_SWAP((s)->uid, d, 16, 8);\
+	SQUASHFS_SWAP((s)->guid, d, 24, 8);\
+
+#define SQUASHFS_SWAP_BASE_INODE_HEADER_2(s, d, n) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, n)\
+}
+
+#define SQUASHFS_SWAP_IPC_INODE_HEADER_2(s, d) \
+	SQUASHFS_SWAP_BASE_INODE_HEADER_2(s, d, sizeof(struct squashfs_ipc_inode_header_2))
+
+#define SQUASHFS_SWAP_DEV_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_dev_inode_header_2)); \
+	SQUASHFS_SWAP((s)->rdev, d, 32, 16);\
+}
+
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_symlink_inode_header_2));\
+	SQUASHFS_SWAP((s)->symlink_size, d, 32, 16);\
+}
+
+#define SQUASHFS_SWAP_REG_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_reg_inode_header_2));\
+	SQUASHFS_SWAP((s)->mtime, d, 32, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 64, 32);\
+	SQUASHFS_SWAP((s)->fragment, d, 96, 32);\
+	SQUASHFS_SWAP((s)->offset, d, 128, 32);\
+	SQUASHFS_SWAP((s)->file_size, d, 160, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_dir_inode_header_2));\
+	SQUASHFS_SWAP((s)->file_size, d, 32, 19);\
+	SQUASHFS_SWAP((s)->offset, d, 51, 13);\
+	SQUASHFS_SWAP((s)->mtime, d, 64, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 96, 24);\
+}
+
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+			sizeof(struct squashfs_ldir_inode_header_2));\
+	SQUASHFS_SWAP((s)->file_size, d, 32, 27);\
+	SQUASHFS_SWAP((s)->offset, d, 59, 13);\
+	SQUASHFS_SWAP((s)->mtime, d, 72, 32);\
+	SQUASHFS_SWAP((s)->start_block, d, 104, 24);\
+	SQUASHFS_SWAP((s)->i_count, d, 128, 16);\
+}
+
+#define SQUASHFS_SWAP_DIR_INDEX_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_index_2));\
+	SQUASHFS_SWAP((s)->index, d, 0, 27);\
+	SQUASHFS_SWAP((s)->start_block, d, 27, 29);\
+	SQUASHFS_SWAP((s)->size, d, 56, 8);\
+}
+#define SQUASHFS_SWAP_DIR_HEADER_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_header_2));\
+	SQUASHFS_SWAP((s)->count, d, 0, 8);\
+	SQUASHFS_SWAP((s)->start_block, d, 8, 24);\
+}
+
+#define SQUASHFS_SWAP_DIR_ENTRY_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_entry_2));\
+	SQUASHFS_SWAP((s)->offset, d, 0, 13);\
+	SQUASHFS_SWAP((s)->type, d, 13, 3);\
+	SQUASHFS_SWAP((s)->size, d, 16, 8);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY_2(s, d) {\
+	SQUASHFS_SWAP_START\
+	SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_fragment_entry_2));\
+	SQUASHFS_SWAP((s)->start_block, d, 0, 32);\
+	SQUASHFS_SWAP((s)->size, d, 32, 32);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES_2(s, d, n) SQUASHFS_SWAP_INTS_3(s, d, n)
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES_2(A)	((A) * sizeof(struct squashfs_fragment_entry_2))
+
+#define SQUASHFS_FRAGMENT_INDEX_2(A)	(SQUASHFS_FRAGMENT_BYTES_2(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET_2(A)	(SQUASHFS_FRAGMENT_BYTES_2(A) % \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES_2(A)	((SQUASHFS_FRAGMENT_BYTES_2(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES_2(A)	(SQUASHFS_FRAGMENT_INDEXES_2(A) *\
+						sizeof(int))
+/*
+ * macros used to swap each structure entry, taking into account
+ * bitfields and different bitfield placing conventions on differing architectures
+ */
+#if __BYTE_ORDER == __BIG_ENDIAN
+	/* convert from big endian to little endian */
+#define SQUASHFS_SWAP(value, p, pos, tbits) _SQUASHFS_SWAP(value, p, pos, tbits, b_pos)
+#else
+	/* convert from little endian to big endian */ 
+#define SQUASHFS_SWAP(value, p, pos, tbits) _SQUASHFS_SWAP(value, p, pos, tbits, 64 - tbits - b_pos)
+#endif
+
+#define _SQUASHFS_SWAP(value, p, pos, tbits, SHIFT) {\
+	b_pos = pos % 8;\
+	val = 0;\
+	s = (unsigned char *)p + (pos / 8);\
+	d = ((unsigned char *) &val) + 7;\
+	for(bits = 0; bits < (tbits + b_pos); bits += 8) \
+		*d-- = *s++;\
+	value = (val >> (SHIFT))/* & ((1 << tbits) - 1)*/;\
+}
+#define SQUASHFS_MEMSET(s, d, n)	memset(s, 0, n);
+#endif
diff --git a/squashfs-tools/squashfs-tools/squashfs_fs.h b/squashfs-tools/squashfs-tools/squashfs_fs.h
new file mode 100644
index 0000000..791fe12
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/squashfs_fs.h
@@ -0,0 +1,491 @@
+#ifndef SQUASHFS_FS
+#define SQUASHFS_FS
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012,
+ * 2013, 2014
+ * 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.
+ *
+ * squashfs_fs.h
+ */
+
+#define SQUASHFS_CACHED_FRAGMENTS	CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE	
+#define SQUASHFS_MAJOR			4
+#define SQUASHFS_MINOR			0
+#define SQUASHFS_MAGIC			0x73717368
+#define SQUASHFS_MAGIC_SWAP		0x68737173
+#define SQUASHFS_START			0
+
+/* size of metadata (inode and directory) blocks */
+#define SQUASHFS_METADATA_SIZE		8192
+#define SQUASHFS_METADATA_LOG		13
+
+/* default size of data blocks */
+#define SQUASHFS_FILE_SIZE		131072
+
+#define SQUASHFS_FILE_MAX_SIZE		1048576
+#define SQUASHFS_FILE_MAX_LOG		20
+
+/* Max number of uids and gids */
+#define SQUASHFS_IDS			65536
+
+/* Max length of filename (not 255) */
+#define SQUASHFS_NAME_LEN		256
+
+#define SQUASHFS_INVALID		((long long) 0xffffffffffff)
+#define SQUASHFS_INVALID_FRAG		((unsigned int) 0xffffffff)
+#define SQUASHFS_INVALID_XATTR		((unsigned int) 0xffffffff)
+#define SQUASHFS_INVALID_BLK		((long long) -1)
+#define SQUASHFS_USED_BLK		((long long) -2)
+
+/* Filesystem flags */
+#define SQUASHFS_NOI			0
+#define SQUASHFS_NOD			1
+#define SQUASHFS_CHECK			2
+#define SQUASHFS_NOF			3
+#define SQUASHFS_NO_FRAG		4
+#define SQUASHFS_ALWAYS_FRAG		5
+#define SQUASHFS_DUPLICATE		6
+#define SQUASHFS_EXPORT			7
+#define SQUASHFS_NOX			8
+#define SQUASHFS_NO_XATTR		9
+#define SQUASHFS_COMP_OPT		10
+
+#define SQUASHFS_BIT(flag, bit)		((flag >> bit) & 1)
+
+#define SQUASHFS_UNCOMPRESSED_INODES(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOI)
+
+#define SQUASHFS_UNCOMPRESSED_DATA(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOD)
+
+#define SQUASHFS_UNCOMPRESSED_FRAGMENTS(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOF)
+
+#define SQUASHFS_NO_FRAGMENTS(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_NO_FRAG)
+
+#define SQUASHFS_ALWAYS_FRAGMENTS(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_ALWAYS_FRAG)
+
+#define SQUASHFS_DUPLICATES(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_DUPLICATE)
+
+#define SQUASHFS_EXPORTABLE(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_EXPORT)
+
+#define SQUASHFS_UNCOMPRESSED_XATTRS(flags)	SQUASHFS_BIT(flags, \
+						SQUASHFS_NOX)
+
+#define SQUASHFS_NO_XATTRS(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_NO_XATTR)
+
+#define SQUASHFS_COMP_OPTS(flags)		SQUASHFS_BIT(flags, \
+						SQUASHFS_COMP_OPT)
+
+#define SQUASHFS_MKFLAGS(noi, nod, nof, nox, no_frag, always_frag, \
+		duplicate_checking, exportable, no_xattr, comp_opt) (noi | \
+		(nod << 1) | (nof << 3) | (no_frag << 4) | \
+		(always_frag << 5) | (duplicate_checking << 6) | \
+		(exportable << 7) | (nox << 8) | (no_xattr << 9) | \
+		(comp_opt << 10))
+
+/* Max number of types and file types */
+#define SQUASHFS_DIR_TYPE		1
+#define SQUASHFS_FILE_TYPE		2
+#define SQUASHFS_SYMLINK_TYPE		3
+#define SQUASHFS_BLKDEV_TYPE		4
+#define SQUASHFS_CHRDEV_TYPE		5
+#define SQUASHFS_FIFO_TYPE		6
+#define SQUASHFS_SOCKET_TYPE		7
+#define SQUASHFS_LDIR_TYPE		8
+#define SQUASHFS_LREG_TYPE		9
+#define SQUASHFS_LSYMLINK_TYPE		10
+#define SQUASHFS_LBLKDEV_TYPE		11
+#define SQUASHFS_LCHRDEV_TYPE		12
+#define SQUASHFS_LFIFO_TYPE		13
+#define SQUASHFS_LSOCKET_TYPE		14
+
+/* Xattr types */
+#define SQUASHFS_XATTR_USER		0
+#define SQUASHFS_XATTR_TRUSTED		1
+#define SQUASHFS_XATTR_SECURITY		2
+#define SQUASHFS_XATTR_VALUE_OOL	256
+#define SQUASHFS_XATTR_PREFIX_MASK	0xff
+
+/* Flag whether block is compressed or uncompressed, bit is set if block is
+ * uncompressed */
+#define SQUASHFS_COMPRESSED_BIT		(1 << 15)
+
+#define SQUASHFS_COMPRESSED_SIZE(B)	(((B) & ~SQUASHFS_COMPRESSED_BIT) ? \
+		(B) & ~SQUASHFS_COMPRESSED_BIT :  SQUASHFS_COMPRESSED_BIT)
+
+#define SQUASHFS_COMPRESSED(B)		(!((B) & SQUASHFS_COMPRESSED_BIT))
+
+#define SQUASHFS_COMPRESSED_BIT_BLOCK		(1 << 24)
+
+#define SQUASHFS_COMPRESSED_SIZE_BLOCK(B)	((B) & \
+	~SQUASHFS_COMPRESSED_BIT_BLOCK)
+
+#define SQUASHFS_COMPRESSED_BLOCK(B)	(!((B) & SQUASHFS_COMPRESSED_BIT_BLOCK))
+
+/*
+ * Inode number ops.  Inodes consist of a compressed block number, and an
+ * uncompressed  offset within that block
+ */
+#define SQUASHFS_INODE_BLK(a)		((unsigned int) ((a) >> 16))
+
+#define SQUASHFS_INODE_OFFSET(a)	((unsigned int) ((a) & 0xffff))
+
+#define SQUASHFS_MKINODE(A, B)		((squashfs_inode)(((squashfs_inode) (A)\
+					<< 16) + (B)))
+
+/* Compute 32 bit VFS inode number from squashfs inode number */
+#define SQUASHFS_MK_VFS_INODE(a, b)	((unsigned int) (((a) << 8) + \
+					((b) >> 2) + 1))
+
+/* Translate between VFS mode and squashfs mode */
+#define SQUASHFS_MODE(a)		((a) & 0xfff)
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES(A)	((A) * \
+					sizeof(struct squashfs_fragment_entry))
+
+#define SQUASHFS_FRAGMENT_INDEX(A)	(SQUASHFS_FRAGMENT_BYTES(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET(A)	(SQUASHFS_FRAGMENT_BYTES(A) % \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES(A)	((SQUASHFS_FRAGMENT_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES(A)	(SQUASHFS_FRAGMENT_INDEXES(A) *\
+						sizeof(long long))
+
+/* inode lookup table defines */
+#define SQUASHFS_LOOKUP_BYTES(A)	((A) * sizeof(squashfs_inode))
+
+#define SQUASHFS_LOOKUP_BLOCK(A)		(SQUASHFS_LOOKUP_BYTES(A) / \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_OFFSET(A)		(SQUASHFS_LOOKUP_BYTES(A) % \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCKS(A)	((SQUASHFS_LOOKUP_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_BYTES(A)	(SQUASHFS_LOOKUP_BLOCKS(A) *\
+					sizeof(long long))
+
+/* uid lookup table defines */
+#define SQUASHFS_ID_BYTES(A)	((A) * sizeof(unsigned int))
+
+#define SQUASHFS_ID_BLOCK(A)		(SQUASHFS_ID_BYTES(A) / \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCK_OFFSET(A)		(SQUASHFS_ID_BYTES(A) % \
+						SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCKS(A)	((SQUASHFS_ID_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCK_BYTES(A)	(SQUASHFS_ID_BLOCKS(A) *\
+					sizeof(long long))
+
+/* xattr id lookup table defines */
+#define SQUASHFS_XATTR_BYTES(A)		((A) * sizeof(struct squashfs_xattr_id))
+
+#define SQUASHFS_XATTR_BLOCK(A)		(SQUASHFS_XATTR_BYTES(A) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_XATTR_BLOCK_OFFSET(A)	(SQUASHFS_XATTR_BYTES(A) % \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_XATTR_BLOCKS(A)	((SQUASHFS_XATTR_BYTES(A) + \
+					SQUASHFS_METADATA_SIZE - 1) / \
+					SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_XATTR_BLOCK_BYTES(A)	(SQUASHFS_XATTR_BLOCKS(A) *\
+					sizeof(long long))
+
+#define SQUASHFS_XATTR_BLK(A)		((unsigned int) ((A) >> 16))
+
+#define SQUASHFS_XATTR_OFFSET(A)	((unsigned int) ((A) & 0xffff))
+
+/* cached data constants for filesystem */
+#define SQUASHFS_CACHED_BLKS		8
+
+#define SQUASHFS_MAX_FILE_SIZE_LOG	64
+
+#define SQUASHFS_MAX_FILE_SIZE		((long long) 1 << \
+					(SQUASHFS_MAX_FILE_SIZE_LOG - 2))
+
+#define SQUASHFS_MARKER_BYTE		0xff
+
+/* meta index cache */
+#define SQUASHFS_META_INDEXES	(SQUASHFS_METADATA_SIZE / sizeof(unsigned int))
+#define SQUASHFS_META_ENTRIES	31
+#define SQUASHFS_META_NUMBER	8
+#define SQUASHFS_SLOTS		4
+
+struct meta_entry {
+	long long		data_block;
+	unsigned int		index_block;
+	unsigned short		offset;
+	unsigned short		pad;
+};
+
+struct meta_index {
+	unsigned int		inode_number;
+	unsigned int		offset;
+	unsigned short		entries;
+	unsigned short		skip;
+	unsigned short		locked;
+	unsigned short		pad;
+	struct meta_entry	meta_entry[SQUASHFS_META_ENTRIES];
+};
+
+
+/*
+ * definitions for structures on disk
+ */
+
+typedef long long		squashfs_block;
+typedef long long		squashfs_inode;
+
+#define ZLIB_COMPRESSION	1
+#define LZMA_COMPRESSION	2
+#define LZO_COMPRESSION		3
+#define XZ_COMPRESSION		4
+#define LZ4_COMPRESSION		5
+
+struct squashfs_super_block {
+	unsigned int		s_magic;
+	unsigned int		inodes;
+	int			mkfs_time /* time of filesystem creation */;
+	unsigned int		block_size;
+	unsigned int		fragments;
+	unsigned short		compression;
+	unsigned short		block_log;
+	unsigned short		flags;
+	unsigned short		no_ids;
+	unsigned short		s_major;
+	unsigned short		s_minor;
+	squashfs_inode		root_inode;
+	long long		bytes_used;
+	long long		id_table_start;
+	long long		xattr_id_table_start;
+	long long		inode_table_start;
+	long long		directory_table_start;
+	long long		fragment_table_start;
+	long long		lookup_table_start;
+};
+
+struct squashfs_dir_index {
+	unsigned int		index;
+	unsigned int		start_block;
+	unsigned int		size;
+	unsigned char		name[0];
+};
+
+struct squashfs_base_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+};
+
+struct squashfs_ipc_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+};
+
+struct squashfs_lipc_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	unsigned int		xattr;
+};
+
+struct squashfs_dev_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	unsigned int		rdev;
+};
+	
+struct squashfs_ldev_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	unsigned int		rdev;
+	unsigned int		xattr;
+};
+	
+struct squashfs_symlink_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	unsigned int		symlink_size;
+	char			symlink[0];
+};
+
+struct squashfs_reg_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		start_block;
+	unsigned int		fragment;
+	unsigned int		offset;
+	unsigned int		file_size;
+	unsigned int		block_list[0];
+};
+
+struct squashfs_lreg_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+	squashfs_block		start_block;
+	long long		file_size;
+	long long		sparse;
+	unsigned int		nlink;
+	unsigned int		fragment;
+	unsigned int		offset;
+	unsigned int		xattr;
+	unsigned int		block_list[0];
+};
+
+struct squashfs_dir_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		start_block;
+	unsigned int		nlink;
+	unsigned short		file_size;
+	unsigned short		offset;
+	unsigned int		parent_inode;
+};
+
+struct squashfs_ldir_inode_header {
+	unsigned short		inode_type;
+	unsigned short		mode;
+	unsigned short		uid;
+	unsigned short		guid;
+	int			mtime;
+	unsigned int 		inode_number;
+	unsigned int		nlink;
+	unsigned int		file_size;
+	unsigned int		start_block;
+	unsigned int		parent_inode;
+	unsigned short		i_count;
+	unsigned short		offset;
+	unsigned int		xattr;
+	struct squashfs_dir_index	index[0];
+};
+
+union squashfs_inode_header {
+	struct squashfs_base_inode_header	base;
+	struct squashfs_dev_inode_header	dev;
+	struct squashfs_ldev_inode_header	ldev;
+	struct squashfs_symlink_inode_header	symlink;
+	struct squashfs_reg_inode_header	reg;
+	struct squashfs_lreg_inode_header	lreg;
+	struct squashfs_dir_inode_header	dir;
+	struct squashfs_ldir_inode_header	ldir;
+	struct squashfs_ipc_inode_header	ipc;
+	struct squashfs_lipc_inode_header	lipc;
+};
+	
+struct squashfs_dir_entry {
+	unsigned short		offset;
+	short			inode_number;
+	unsigned short		type;
+	unsigned short		size;
+	char			name[0];
+};
+
+struct squashfs_dir_header {
+	unsigned int		count;
+	unsigned int		start_block;
+	unsigned int		inode_number;
+};
+
+struct squashfs_fragment_entry {
+	long long		start_block;
+	unsigned int		size;
+	unsigned int		unused;
+};
+
+struct squashfs_xattr_entry {
+	unsigned short		type;
+	unsigned short		size;
+};
+
+struct squashfs_xattr_val {
+	unsigned int		vsize;
+};
+
+struct squashfs_xattr_id {
+	long long		xattr;
+	unsigned int		count;
+	unsigned int		size;
+};
+
+struct squashfs_xattr_table {
+	long long		xattr_table_start;
+	unsigned int		xattr_ids;
+	unsigned int		unused;
+};
+
+#endif
diff --git a/squashfs-tools/squashfs-tools/squashfs_swap.h b/squashfs-tools/squashfs-tools/squashfs_swap.h
new file mode 100644
index 0000000..f1becfc
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/squashfs_swap.h
@@ -0,0 +1,424 @@
+#ifndef SQUASHFS_SWAP_H
+#define SQUASHFS_SWAP_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2008, 2009, 2010, 2013, 2014
+ * 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.
+ *
+ * squashfs_swap.h
+ */
+
+/*
+ * macros to convert each stucture from big endian to little endian
+ */
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#include <stddef.h>
+extern void swap_le16(void *, void *);
+extern void swap_le32(void *, void *);
+extern void swap_le64(void *, void *);
+extern void swap_le16_num(void *, void *, int);
+extern void swap_le32_num(void *, void *, int);
+extern void swap_le64_num(void *, void *, int);
+extern unsigned short inswap_le16(unsigned short);
+extern unsigned int inswap_le32(unsigned int);
+extern long long inswap_le64(long long);
+extern void inswap_le16_num(unsigned short *, int);
+extern void inswap_le32_num(unsigned int *, int);
+extern void inswap_le64_num(long long *, int);
+
+#define _SQUASHFS_SWAP_SUPER_BLOCK(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(32, s, d, s_magic, struct squashfs_super_block);\
+	SWAP_FUNC(32, s, d, inodes, struct squashfs_super_block);\
+	SWAP_FUNC##S(32, s, d, mkfs_time, struct squashfs_super_block);\
+	SWAP_FUNC(32, s, d, block_size, struct squashfs_super_block);\
+	SWAP_FUNC(32, s, d, fragments, struct squashfs_super_block);\
+	SWAP_FUNC(16, s, d, compression, struct squashfs_super_block);\
+	SWAP_FUNC(16, s, d, block_log, struct squashfs_super_block);\
+	SWAP_FUNC(16, s, d, flags, struct squashfs_super_block);\
+	SWAP_FUNC(16, s, d, no_ids, struct squashfs_super_block);\
+	SWAP_FUNC(16, s, d, s_major, struct squashfs_super_block);\
+	SWAP_FUNC(16, s, d, s_minor, struct squashfs_super_block);\
+	SWAP_FUNC(64, s, d, root_inode, struct squashfs_super_block);\
+	SWAP_FUNC(64, s, d, bytes_used, struct squashfs_super_block);\
+	SWAP_FUNC(64, s, d, id_table_start, struct squashfs_super_block);\
+	SWAP_FUNC(64, s, d, xattr_id_table_start, struct squashfs_super_block);\
+	SWAP_FUNC(64, s, d, inode_table_start, struct squashfs_super_block);\
+	SWAP_FUNC(64, s, d, directory_table_start, struct squashfs_super_block);\
+	SWAP_FUNC(64, s, d, fragment_table_start, struct squashfs_super_block);\
+	SWAP_FUNC(64, s, d, lookup_table_start, struct squashfs_super_block);\
+}
+
+#define _SQUASHFS_SWAP_DIR_INDEX(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(32, s, d, index, struct squashfs_dir_index);\
+	SWAP_FUNC(32, s, d, start_block, struct squashfs_dir_index);\
+	SWAP_FUNC(32, s, d, size, struct squashfs_dir_index);\
+}
+
+#define _SQUASHFS_SWAP_BASE_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_base_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_base_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_base_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_base_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_base_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_base_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_IPC_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_ipc_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_ipc_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_ipc_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_ipc_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_ipc_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_ipc_inode_header);\
+	SWAP_FUNC(32, s, d, nlink, struct squashfs_ipc_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_LIPC_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_lipc_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_lipc_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_lipc_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_lipc_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_lipc_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_lipc_inode_header);\
+	SWAP_FUNC(32, s, d, nlink, struct squashfs_lipc_inode_header);\
+	SWAP_FUNC(32, s, d, xattr, struct squashfs_lipc_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_DEV_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_dev_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_dev_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_dev_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_dev_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_dev_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_dev_inode_header);\
+	SWAP_FUNC(32, s, d, nlink, struct squashfs_dev_inode_header);\
+	SWAP_FUNC(32, s, d, rdev, struct squashfs_dev_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_LDEV_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_ldev_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_ldev_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_ldev_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_ldev_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_ldev_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_ldev_inode_header);\
+	SWAP_FUNC(32, s, d, nlink, struct squashfs_ldev_inode_header);\
+	SWAP_FUNC(32, s, d, rdev, struct squashfs_ldev_inode_header);\
+	SWAP_FUNC(32, s, d, xattr, struct squashfs_ldev_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_symlink_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_symlink_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_symlink_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_symlink_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_symlink_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_symlink_inode_header);\
+	SWAP_FUNC(32, s, d, nlink, struct squashfs_symlink_inode_header);\
+	SWAP_FUNC(32, s, d, symlink_size, struct squashfs_symlink_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_REG_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_reg_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_reg_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_reg_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_reg_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_reg_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_reg_inode_header);\
+	SWAP_FUNC(32, s, d, start_block, struct squashfs_reg_inode_header);\
+	SWAP_FUNC(32, s, d, fragment, struct squashfs_reg_inode_header);\
+	SWAP_FUNC(32, s, d, offset, struct squashfs_reg_inode_header);\
+	SWAP_FUNC(32, s, d, file_size, struct squashfs_reg_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_LREG_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(64, s, d, start_block, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(64, s, d, file_size, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(64, s, d, sparse, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(32, s, d, nlink, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(32, s, d, fragment, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(32, s, d, offset, struct squashfs_lreg_inode_header);\
+	SWAP_FUNC(32, s, d, xattr, struct squashfs_lreg_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_DIR_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_dir_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_dir_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_dir_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_dir_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_dir_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_dir_inode_header);\
+	SWAP_FUNC(32, s, d, start_block, struct squashfs_dir_inode_header);\
+	SWAP_FUNC(32, s, d, nlink, struct squashfs_dir_inode_header);\
+	SWAP_FUNC(16, s, d, file_size, struct squashfs_dir_inode_header);\
+	SWAP_FUNC(16, s, d, offset, struct squashfs_dir_inode_header);\
+	SWAP_FUNC(32, s, d, parent_inode, struct squashfs_dir_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_LDIR_INODE_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, inode_type, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(16, s, d, mode, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(16, s, d, uid, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(16, s, d, guid, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC##S(32, s, d, mtime, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(32, s, d, nlink, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(32, s, d, file_size, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(32, s, d, start_block, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(32, s, d, parent_inode, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(16, s, d, i_count, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(16, s, d, offset, struct squashfs_ldir_inode_header);\
+	SWAP_FUNC(32, s, d, xattr, struct squashfs_ldir_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_DIR_ENTRY(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, offset, struct squashfs_dir_entry);\
+	SWAP_FUNC##S(16, s, d, inode_number, struct squashfs_dir_entry);\
+	SWAP_FUNC(16, s, d, type, struct squashfs_dir_entry);\
+	SWAP_FUNC(16, s, d, size, struct squashfs_dir_entry);\
+}
+
+#define _SQUASHFS_SWAP_DIR_HEADER(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(32, s, d, count, struct squashfs_dir_header);\
+	SWAP_FUNC(32, s, d, start_block, struct squashfs_dir_header);\
+	SWAP_FUNC(32, s, d, inode_number, struct squashfs_dir_header);\
+}
+
+#define _SQUASHFS_SWAP_FRAGMENT_ENTRY(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(64, s, d, start_block, struct squashfs_fragment_entry);\
+	SWAP_FUNC(32, s, d, size, struct squashfs_fragment_entry);\
+}
+
+#define _SQUASHFS_SWAP_XATTR_ENTRY(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(16, s, d, type, struct squashfs_xattr_entry);\
+	SWAP_FUNC(16, s, d, size, struct squashfs_xattr_entry);\
+}
+
+#define _SQUASHFS_SWAP_XATTR_VAL(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(32, s, d, vsize, struct squashfs_xattr_val);\
+}
+
+#define _SQUASHFS_SWAP_XATTR_ID(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(64, s, d, xattr, struct squashfs_xattr_id);\
+	SWAP_FUNC(32, s, d, count, struct squashfs_xattr_id);\
+	SWAP_FUNC(32, s, d, size, struct squashfs_xattr_id);\
+}
+
+#define _SQUASHFS_SWAP_XATTR_TABLE(s, d, SWAP_FUNC) {\
+	SWAP_FUNC(64, s, d, xattr_table_start, struct squashfs_xattr_table);\
+	SWAP_FUNC(32, s, d, xattr_ids, struct squashfs_xattr_table);\
+}
+
+/* big endian architecture copy and swap macros */
+#define SQUASHFS_SWAP_SUPER_BLOCK(s, d)	\
+			_SQUASHFS_SWAP_SUPER_BLOCK(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DIR_INDEX(s, d) \
+			_SQUASHFS_SWAP_DIR_INDEX(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_BASE_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_BASE_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_IPC_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_IPC_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_LIPC_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_LIPC_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DEV_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_DEV_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_LDEV_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_LDEV_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_REG_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_REG_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_LREG_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_LREG_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DIR_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_DIR_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER(s, d) \
+			_SQUASHFS_SWAP_LDIR_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DIR_ENTRY(s, d) \
+			_SQUASHFS_SWAP_DIR_ENTRY(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DIR_HEADER(s, d) \
+			_SQUASHFS_SWAP_DIR_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY(s, d) \
+			_SQUASHFS_SWAP_FRAGMENT_ENTRY(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_XATTR_ENTRY(s, d) \
+			 _SQUASHFS_SWAP_XATTR_ENTRY(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_XATTR_VAL(s, d) \
+			_SQUASHFS_SWAP_XATTR_VAL(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_XATTR_ID(s, d) \
+			 _SQUASHFS_SWAP_XATTR_ID(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_XATTR_TABLE(s, d) \
+			_SQUASHFS_SWAP_XATTR_TABLE(s, d, SWAP_LE)
+#define SWAP_LE(bits, s, d, field, type) \
+			SWAP_LE##bits(((void *)(s)) + offsetof(type, field), \
+				((void *)(d)) + offsetof(type, field))
+#define SWAP_LES(bits, s, d, field, type) \
+			SWAP_LE(bits, s, d, field, type)
+#define SQUASHFS_SWAP_INODE_T(s, d) SQUASHFS_SWAP_LONG_LONGS(s, d, 1)
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES(s, d, n) \
+			SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+#define SQUASHFS_SWAP_LOOKUP_BLOCKS(s, d, n) SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+#define SQUASHFS_SWAP_ID_BLOCKS(s, d, n) SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+
+#define SQUASHFS_SWAP_SHORTS(s, d, n) swap_le16_num(s, d, n)
+#define SQUASHFS_SWAP_INTS(s, d, n) swap_le32_num(s, d, n)
+#define SQUASHFS_SWAP_LONG_LONGS(s, d, n) swap_le64_num(s, d, n)
+
+#define SWAP_LE16(s, d)		swap_le16(s, d)
+#define SWAP_LE32(s, d)		swap_le32(s, d)
+#define SWAP_LE64(s, d)		swap_le64(s, d)
+
+/* big endian architecture swap in-place macros */
+#define SQUASHFS_INSWAP_SUPER_BLOCK(s) \
+			_SQUASHFS_SWAP_SUPER_BLOCK(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DIR_INDEX(s) \
+			_SQUASHFS_SWAP_DIR_INDEX(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_BASE_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_BASE_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_IPC_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_IPC_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_LIPC_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_LIPC_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DEV_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_DEV_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_LDEV_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_LDEV_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_SYMLINK_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_REG_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_REG_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_LREG_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_LREG_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DIR_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_DIR_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_LDIR_INODE_HEADER(s) \
+			_SQUASHFS_SWAP_LDIR_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DIR_ENTRY(s) \
+			_SQUASHFS_SWAP_DIR_ENTRY(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DIR_HEADER(s) \
+			_SQUASHFS_SWAP_DIR_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_FRAGMENT_ENTRY(s) \
+			_SQUASHFS_SWAP_FRAGMENT_ENTRY(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_XATTR_ENTRY(s) \
+			 _SQUASHFS_SWAP_XATTR_ENTRY(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_XATTR_VAL(s) \
+			_SQUASHFS_SWAP_XATTR_VAL(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_XATTR_ID(s) \
+			 _SQUASHFS_SWAP_XATTR_ID(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_XATTR_TABLE(s) \
+			_SQUASHFS_SWAP_XATTR_TABLE(s, s, INSWAP_LE)
+#define INSWAP_LE(bits, s, d, field, type) \
+			(s)->field = inswap_le##bits((s)->field)
+#define INSWAP_LES(bits, s, d, field, type) \
+			(s)->field = INSWAP_LES##bits((s)->field)
+#define INSWAP_LES16(num) (short) inswap_le16((unsigned short) (num))
+#define INSWAP_LES32(num) (int) inswap_le32((unsigned int) (num))
+#define SQUASHFS_INSWAP_INODE_T(s) s = inswap_le64(s)
+#define SQUASHFS_INSWAP_FRAGMENT_INDEXES(s, n) inswap_le64_num(s, n)
+#define SQUASHFS_INSWAP_LOOKUP_BLOCKS(s, n) inswap_le64_num(s, n)
+#define SQUASHFS_INSWAP_ID_BLOCKS(s, n) inswap_le64_num(s, n)
+#define SQUASHFS_INSWAP_SHORTS(s, n) inswap_le16_num(s, n)
+#define SQUASHFS_INSWAP_INTS(s, n) inswap_le32_num(s, n)
+#define SQUASHFS_INSWAP_LONG_LONGS(s, n) inswap_le64_num(s, n)
+#else
+/* little endian architecture, just copy */
+#define SQUASHFS_SWAP_SUPER_BLOCK(s, d)	\
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_super_block))
+#define SQUASHFS_SWAP_DIR_INDEX(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dir_index))
+#define SQUASHFS_SWAP_BASE_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_base_inode_header))
+#define SQUASHFS_SWAP_IPC_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_ipc_inode_header))
+#define SQUASHFS_SWAP_LIPC_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_lipc_inode_header))
+#define SQUASHFS_SWAP_DEV_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dev_inode_header))
+#define SQUASHFS_SWAP_LDEV_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_ldev_inode_header))
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_symlink_inode_header))
+#define SQUASHFS_SWAP_REG_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_reg_inode_header))
+#define SQUASHFS_SWAP_LREG_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_lreg_inode_header))
+#define SQUASHFS_SWAP_DIR_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dir_inode_header))
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_ldir_inode_header))
+#define SQUASHFS_SWAP_DIR_ENTRY(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dir_entry))
+#define SQUASHFS_SWAP_DIR_HEADER(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dir_header))
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_fragment_entry))
+#define SQUASHFS_SWAP_XATTR_ENTRY(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_xattr_entry))
+#define SQUASHFS_SWAP_XATTR_VAL(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_xattr_val))
+#define SQUASHFS_SWAP_XATTR_ID(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_xattr_id))
+#define SQUASHFS_SWAP_XATTR_TABLE(s, d) \
+		SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_xattr_table))
+#define SQUASHFS_SWAP_INODE_T(s, d) SQUASHFS_SWAP_LONG_LONGS(s, d, 1)
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES(s, d, n) \
+			SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+#define SQUASHFS_SWAP_LOOKUP_BLOCKS(s, d, n) SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+#define SQUASHFS_SWAP_ID_BLOCKS(s, d, n) SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+
+#define SQUASHFS_MEMCPY(s, d, n)	memcpy(d, s, n)
+#define SQUASHFS_SWAP_SHORTS(s, d, n)	memcpy(d, s, n * sizeof(short))
+#define SQUASHFS_SWAP_INTS(s, d, n)	memcpy(d, s, n * sizeof(int))
+#define SQUASHFS_SWAP_LONG_LONGS(s, d, n) \
+					memcpy(d, s, n * sizeof(long long))
+
+/* little endian architecture, data already in place so do nothing */
+#define SQUASHFS_INSWAP_SUPER_BLOCK(s)
+#define SQUASHFS_INSWAP_DIR_INDEX(s)
+#define SQUASHFS_INSWAP_BASE_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_IPC_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_LIPC_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_DEV_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_LDEV_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_SYMLINK_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_REG_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_LREG_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_DIR_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_LDIR_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_DIR_ENTRY(s)
+#define SQUASHFS_INSWAP_DIR_HEADER(s)
+#define SQUASHFS_INSWAP_FRAGMENT_ENTRY(s)
+#define SQUASHFS_INSWAP_XATTR_ENTRY(s)
+#define SQUASHFS_INSWAP_XATTR_VAL(s)
+#define SQUASHFS_INSWAP_XATTR_ID(s)
+#define SQUASHFS_INSWAP_XATTR_TABLE(s)
+#define SQUASHFS_INSWAP_INODE_T(s)
+#define SQUASHFS_INSWAP_FRAGMENT_INDEXES(s, n)
+#define SQUASHFS_INSWAP_LOOKUP_BLOCKS(s, n)
+#define SQUASHFS_INSWAP_ID_BLOCKS(s, n)
+#define SQUASHFS_INSWAP_SHORTS(s, n)
+#define SQUASHFS_INSWAP_INTS(s, n)
+#define SQUASHFS_INSWAP_LONG_LONGS(s, n)
+#endif
+#endif
diff --git a/squashfs-tools/squashfs-tools/swap.c b/squashfs-tools/squashfs-tools/swap.c
new file mode 100644
index 0000000..45c8cbc
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/swap.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2009, 2010
+ * 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.
+ *
+ * swap.c
+ */
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+void swap_le16(void *src, void *dest)
+{
+	unsigned char *s = src;
+	unsigned char *d = dest;
+
+	d[0] = s[1];
+	d[1] = s[0];
+}
+
+
+void swap_le32(void *src, void *dest)
+{
+	unsigned char *s = src;
+	unsigned char *d = dest;
+
+	d[0] = s[3];
+	d[1] = s[2];
+	d[2] = s[1];
+	d[3] = s[0];
+}
+
+
+void swap_le64(void *src, void *dest)
+{
+	unsigned char *s = src;
+	unsigned char *d = dest;
+
+	d[0] = s[7];
+	d[1] = s[6];
+	d[2] = s[5];
+	d[3] = s[4];
+	d[4] = s[3];
+	d[5] = s[2];
+	d[6] = s[1];
+	d[7] = s[0];
+}
+
+
+unsigned short inswap_le16(unsigned short num)
+{
+	return (num >> 8) |
+		((num & 0xff) << 8);
+}
+
+
+unsigned int inswap_le32(unsigned int num)
+{
+	return (num >> 24) |
+		((num & 0xff0000) >> 8) |
+		((num & 0xff00) << 8) |
+		((num & 0xff) << 24);
+}
+
+
+long long inswap_le64(long long n)
+{
+	unsigned long long num = n;
+
+	return (num >> 56) |
+		((num & 0xff000000000000LL) >> 40) |
+		((num & 0xff0000000000LL) >> 24) |
+		((num & 0xff00000000LL) >> 8) |
+		((num & 0xff000000) << 8) |
+		((num & 0xff0000) << 24) |
+		((num & 0xff00) << 40) |
+		((num & 0xff) << 56);
+}
+
+
+#define SWAP_LE_NUM(BITS) \
+void swap_le##BITS##_num(void *s, void *d, int n) \
+{\
+	int i;\
+	for(i = 0; i < n; i++, s += BITS / 8, d += BITS / 8)\
+		swap_le##BITS(s, d);\
+}
+
+SWAP_LE_NUM(16)
+SWAP_LE_NUM(32)
+SWAP_LE_NUM(64)
+
+#define INSWAP_LE_NUM(BITS, TYPE) \
+void inswap_le##BITS##_num(TYPE *s, int n) \
+{\
+	int i;\
+	for(i = 0; i < n; i++)\
+		s[i] = inswap_le##BITS(s[i]);\
+}
+
+INSWAP_LE_NUM(16, unsigned short)
+INSWAP_LE_NUM(32, unsigned int)
+INSWAP_LE_NUM(64, long long)
+#endif
diff --git a/squashfs-tools/squashfs-tools/unsquash-1.c b/squashfs-tools/squashfs-tools/unsquash-1.c
new file mode 100644
index 0000000..c2e7d38
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/unsquash-1.c
@@ -0,0 +1,357 @@
+/*
+ * Unsquash a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2011, 2012
+ * 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.
+ *
+ * unsquash-1.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_compat.h"
+
+void read_block_list_1(unsigned int *block_list, char *block_ptr, int blocks)
+{
+	unsigned short block_size;
+	int i;
+
+	TRACE("read_block_list: blocks %d\n", blocks);
+
+	for(i = 0; i < blocks; i++, block_ptr += 2) {
+		if(swap) {
+			unsigned short sblock_size;
+			memcpy(&sblock_size, block_ptr, sizeof(unsigned short));
+			SQUASHFS_SWAP_SHORTS_3((&block_size), &sblock_size, 1);
+		} else
+			memcpy(&block_size, block_ptr, sizeof(unsigned short));
+		block_list[i] = SQUASHFS_COMPRESSED_SIZE(block_size) |
+			(SQUASHFS_COMPRESSED(block_size) ? 0 :
+			SQUASHFS_COMPRESSED_BIT_BLOCK);
+	}
+}
+
+
+int read_fragment_table_1(long long *directory_table_end)
+{
+	TRACE("read_fragment_table\n");
+	*directory_table_end = sBlk.s.fragment_table_start;
+	return TRUE;
+}
+
+
+struct inode *read_inode_1(unsigned int start_block, unsigned int offset)
+{
+	static union squashfs_inode_header_1 header;
+	long long start = sBlk.s.inode_table_start + start_block;
+	int bytes = lookup_entry(inode_table_hash, start);
+	char *block_ptr = inode_table + bytes + offset;
+	static struct inode i;
+
+	TRACE("read_inode: reading inode [%d:%d]\n", start_block,  offset);
+
+	if(bytes == -1)
+		EXIT_UNSQUASH("read_inode: inode table block %lld not found\n",
+			 start); 
+
+	if(swap) {
+		squashfs_base_inode_header_1 sinode;
+		memcpy(&sinode, block_ptr, sizeof(header.base));
+		SQUASHFS_SWAP_BASE_INODE_HEADER_1(&header.base, &sinode,
+			sizeof(squashfs_base_inode_header_1));
+	} else
+		memcpy(&header.base, block_ptr, sizeof(header.base));
+
+	i.uid = (uid_t) uid_table[(header.base.inode_type - 1) /
+		SQUASHFS_TYPES * 16 + header.base.uid];
+	if(header.base.inode_type == SQUASHFS_IPC_TYPE) {
+		squashfs_ipc_inode_header_1 *inodep = &header.ipc;
+
+		if(swap) {
+			squashfs_ipc_inode_header_1 sinodep;
+			memcpy(&sinodep, block_ptr, sizeof(sinodep));
+			SQUASHFS_SWAP_IPC_INODE_HEADER_1(inodep, &sinodep);
+		} else
+			memcpy(inodep, block_ptr, sizeof(*inodep));
+
+		if(inodep->type == SQUASHFS_SOCKET_TYPE) {
+			i.mode = S_IFSOCK | header.base.mode;
+			i.type = SQUASHFS_SOCKET_TYPE;
+		} else {
+			i.mode = S_IFIFO | header.base.mode;
+			i.type = SQUASHFS_FIFO_TYPE;
+		}
+		i.uid = (uid_t) uid_table[inodep->offset * 16 + inodep->uid];
+	} else {
+		i.mode = lookup_type[(header.base.inode_type - 1) %
+			SQUASHFS_TYPES + 1] | header.base.mode;
+		i.type = (header.base.inode_type - 1) % SQUASHFS_TYPES + 1;
+	}
+
+	i.xattr = SQUASHFS_INVALID_XATTR;
+	i.gid = header.base.guid == 15 ? i.uid :
+		(uid_t) guid_table[header.base.guid];
+	i.time = sBlk.s.mkfs_time;
+	i.inode_number = inode_number ++;
+
+	switch(i.type) {
+		case SQUASHFS_DIR_TYPE: {
+			squashfs_dir_inode_header_1 *inode = &header.dir;
+
+			if(swap) {
+				squashfs_dir_inode_header_1 sinode;
+				memcpy(&sinode, block_ptr, sizeof(header.dir));
+				SQUASHFS_SWAP_DIR_INODE_HEADER_1(inode,
+					&sinode);
+			} else
+			memcpy(inode, block_ptr, sizeof(header.dir));
+
+			i.data = inode->file_size;
+			i.offset = inode->offset;
+			i.start = inode->start_block;
+			i.time = inode->mtime;
+			break;
+		}
+		case SQUASHFS_FILE_TYPE: {
+			squashfs_reg_inode_header_1 *inode = &header.reg;
+
+			if(swap) {
+				squashfs_reg_inode_header_1 sinode;
+				memcpy(&sinode, block_ptr, sizeof(sinode));
+				SQUASHFS_SWAP_REG_INODE_HEADER_1(inode,
+					&sinode);
+			} else
+				memcpy(inode, block_ptr, sizeof(*inode));
+
+			i.data = inode->file_size;
+			i.time = inode->mtime;
+			i.blocks = (i.data + sBlk.s.block_size - 1) >>
+				sBlk.s.block_log;
+			i.start = inode->start_block;
+			i.block_ptr = block_ptr + sizeof(*inode);
+			i.fragment = 0;
+			i.frag_bytes = 0;
+			i.offset = 0;
+			i.sparse = 0;
+			break;
+		}	
+		case SQUASHFS_SYMLINK_TYPE: {
+			squashfs_symlink_inode_header_1 *inodep =
+				&header.symlink;
+
+			if(swap) {
+				squashfs_symlink_inode_header_1 sinodep;
+				memcpy(&sinodep, block_ptr, sizeof(sinodep));
+				SQUASHFS_SWAP_SYMLINK_INODE_HEADER_1(inodep,
+					&sinodep);
+			} else
+				memcpy(inodep, block_ptr, sizeof(*inodep));
+
+			i.symlink = malloc(inodep->symlink_size + 1);
+			if(i.symlink == NULL)
+				EXIT_UNSQUASH("read_inode: failed to malloc "
+					"symlink data\n");
+			strncpy(i.symlink, block_ptr +
+				sizeof(squashfs_symlink_inode_header_1),
+				inodep->symlink_size);
+			i.symlink[inodep->symlink_size] = '\0';
+			i.data = inodep->symlink_size;
+			break;
+		}
+ 		case SQUASHFS_BLKDEV_TYPE:
+	 	case SQUASHFS_CHRDEV_TYPE: {
+			squashfs_dev_inode_header_1 *inodep = &header.dev;
+
+			if(swap) {
+				squashfs_dev_inode_header_1 sinodep;
+				memcpy(&sinodep, block_ptr, sizeof(sinodep));
+				SQUASHFS_SWAP_DEV_INODE_HEADER_1(inodep,
+					&sinodep);
+			} else
+				memcpy(inodep, block_ptr, sizeof(*inodep));
+
+			i.data = inodep->rdev;
+			break;
+			}
+		case SQUASHFS_FIFO_TYPE:
+		case SQUASHFS_SOCKET_TYPE: {
+			i.data = 0;
+			break;
+			}
+		default:
+			EXIT_UNSQUASH("Unknown inode type %d in "
+				" read_inode_header_1!\n",
+				header.base.inode_type);
+	}
+	return &i;
+}
+
+
+struct dir *squashfs_opendir_1(unsigned int block_start, unsigned int offset,
+	struct inode **i)
+{
+	squashfs_dir_header_2 dirh;
+	char buffer[sizeof(squashfs_dir_entry_2) + SQUASHFS_NAME_LEN + 1]
+		__attribute__((aligned));
+	squashfs_dir_entry_2 *dire = (squashfs_dir_entry_2 *) buffer;
+	long long start;
+	int bytes;
+	int dir_count, size;
+	struct dir_ent *new_dir;
+	struct dir *dir;
+
+	TRACE("squashfs_opendir: inode start block %d, offset %d\n",
+		block_start, offset);
+
+	*i = s_ops.read_inode(block_start, offset);
+
+	dir = malloc(sizeof(struct dir));
+	if(dir == NULL)
+		EXIT_UNSQUASH("squashfs_opendir: malloc failed!\n");
+
+	dir->dir_count = 0;
+	dir->cur_entry = 0;
+	dir->mode = (*i)->mode;
+	dir->uid = (*i)->uid;
+	dir->guid = (*i)->gid;
+	dir->mtime = (*i)->time;
+	dir->xattr = (*i)->xattr;
+	dir->dirs = NULL;
+
+	if ((*i)->data == 0)
+		/*
+		 * if the directory is empty, skip the unnecessary
+		 * lookup_entry, this fixes the corner case with
+		 * completely empty filesystems where lookup_entry correctly
+		 * returning -1 is incorrectly treated as an error
+		 */
+		return dir;
+		
+	start = sBlk.s.directory_table_start + (*i)->start;
+	bytes = lookup_entry(directory_table_hash, start);
+	if(bytes == -1)
+		EXIT_UNSQUASH("squashfs_opendir: directory block %d not "
+			"found!\n", block_start);
+
+	bytes += (*i)->offset;
+	size = (*i)->data + bytes;
+
+	while(bytes < size) {			
+		if(swap) {
+			squashfs_dir_header_2 sdirh;
+			memcpy(&sdirh, directory_table + bytes, sizeof(sdirh));
+			SQUASHFS_SWAP_DIR_HEADER_2(&dirh, &sdirh);
+		} else
+			memcpy(&dirh, directory_table + bytes, sizeof(dirh));
+	
+		dir_count = dirh.count + 1;
+		TRACE("squashfs_opendir: Read directory header @ byte position "
+			"%d, %d directory entries\n", bytes, dir_count);
+		bytes += sizeof(dirh);
+
+		/* dir_count should never be larger than 256 */
+		if(dir_count > 256)
+			goto corrupted;
+
+		while(dir_count--) {
+			if(swap) {
+				squashfs_dir_entry_2 sdire;
+				memcpy(&sdire, directory_table + bytes,
+					sizeof(sdire));
+				SQUASHFS_SWAP_DIR_ENTRY_2(dire, &sdire);
+			} else
+				memcpy(dire, directory_table + bytes,
+					sizeof(*dire));
+			bytes += sizeof(*dire);
+
+			/* size should never be larger than SQUASHFS_NAME_LEN */
+			if(dire->size > SQUASHFS_NAME_LEN)
+				goto corrupted;
+
+			memcpy(dire->name, directory_table + bytes,
+				dire->size + 1);
+			dire->name[dire->size + 1] = '\0';
+			TRACE("squashfs_opendir: directory entry %s, inode "
+				"%d:%d, type %d\n", dire->name,
+				dirh.start_block, dire->offset, dire->type);
+			if((dir->dir_count % DIR_ENT_SIZE) == 0) {
+				new_dir = realloc(dir->dirs, (dir->dir_count +
+					DIR_ENT_SIZE) * sizeof(struct dir_ent));
+				if(new_dir == NULL)
+					EXIT_UNSQUASH("squashfs_opendir: "
+						"realloc failed!\n");
+				dir->dirs = new_dir;
+			}
+			strcpy(dir->dirs[dir->dir_count].name, dire->name);
+			dir->dirs[dir->dir_count].start_block =
+				dirh.start_block;
+			dir->dirs[dir->dir_count].offset = dire->offset;
+			dir->dirs[dir->dir_count].type = dire->type;
+			dir->dir_count ++;
+			bytes += dire->size + 1;
+		}
+	}
+
+	return dir;
+
+corrupted:
+	free(dir->dirs);
+	free(dir);
+	return NULL;
+}
+
+
+int read_uids_guids_1()
+{
+	int res;
+
+	TRACE("read_uids_guids: no_uids %d, no_guids %d\n", sBlk.no_uids,
+		sBlk.no_guids);
+
+	uid_table = malloc((sBlk.no_uids + sBlk.no_guids) *
+		sizeof(unsigned int));
+	if(uid_table == NULL) {
+		ERROR("read_uids_guids: failed to allocate uid/gid table\n");
+		return FALSE;
+	}
+
+	guid_table = uid_table + sBlk.no_uids;
+
+	if(swap) {
+		unsigned int suid_table[sBlk.no_uids + sBlk.no_guids];
+
+		res = read_fs_bytes(fd, sBlk.uid_start, (sBlk.no_uids +
+			sBlk.no_guids) * sizeof(unsigned int), suid_table);
+		if(res == FALSE) {
+			ERROR("read_uids_guids: failed to read uid/gid table"
+				"\n");
+			return FALSE;
+		}
+		SQUASHFS_SWAP_INTS_3(uid_table, suid_table,
+			sBlk.no_uids + sBlk.no_guids);
+	} else {
+		res = read_fs_bytes(fd, sBlk.uid_start, (sBlk.no_uids +
+			sBlk.no_guids) * sizeof(unsigned int), uid_table);
+		if(res == FALSE) {
+			ERROR("read_uids_guids: failed to read uid/gid table"
+				"\n");
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
diff --git a/squashfs-tools/squashfs-tools/unsquash-2.c b/squashfs-tools/squashfs-tools/unsquash-2.c
new file mode 100644
index 0000000..0f2bfe8
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/unsquash-2.c
@@ -0,0 +1,270 @@
+/*
+ * Unsquash a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2013
+ * 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.
+ *
+ * unsquash-2.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_compat.h"
+
+static squashfs_fragment_entry_2 *fragment_table;
+
+void read_block_list_2(unsigned int *block_list, char *block_ptr, int blocks)
+{
+	TRACE("read_block_list: blocks %d\n", blocks);
+
+	if(swap) {
+		unsigned int sblock_list[blocks];
+		memcpy(sblock_list, block_ptr, blocks * sizeof(unsigned int));
+		SQUASHFS_SWAP_INTS_3(block_list, sblock_list, blocks);
+	} else
+		memcpy(block_list, block_ptr, blocks * sizeof(unsigned int));
+}
+
+
+int read_fragment_table_2(long long *directory_table_end)
+{
+	int res, i;
+	int bytes = SQUASHFS_FRAGMENT_BYTES_2(sBlk.s.fragments);
+	int indexes = SQUASHFS_FRAGMENT_INDEXES_2(sBlk.s.fragments);
+	unsigned int fragment_table_index[indexes];
+
+	TRACE("read_fragment_table: %d fragments, reading %d fragment indexes "
+		"from 0x%llx\n", sBlk.s.fragments, indexes,
+		sBlk.s.fragment_table_start);
+
+	if(sBlk.s.fragments == 0) {
+		*directory_table_end = sBlk.s.fragment_table_start;
+		return TRUE;
+	}
+
+	fragment_table = malloc(bytes);
+	if(fragment_table == NULL)
+		EXIT_UNSQUASH("read_fragment_table: failed to allocate "
+			"fragment table\n");
+
+	if(swap) {
+		 unsigned int sfragment_table_index[indexes];
+
+		 res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
+			SQUASHFS_FRAGMENT_INDEX_BYTES_2(sBlk.s.fragments),
+			sfragment_table_index);
+		if(res == FALSE) {
+			ERROR("read_fragment_table: failed to read fragment "
+				"table index\n");
+			return FALSE;
+		}
+		SQUASHFS_SWAP_FRAGMENT_INDEXES_2(fragment_table_index,
+			sfragment_table_index, indexes);
+	} else {
+		res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
+			SQUASHFS_FRAGMENT_INDEX_BYTES_2(sBlk.s.fragments),
+			fragment_table_index);
+		if(res == FALSE) {
+			ERROR("read_fragment_table: failed to read fragment "
+				"table index\n");
+			return FALSE;
+		}
+	}
+
+	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, ((char *) fragment_table) + (i *
+			SQUASHFS_METADATA_SIZE));
+		TRACE("Read fragment table block %d, from 0x%x, length %d\n", i,
+			fragment_table_index[i], length);
+		if(length == FALSE) {
+			ERROR("read_fragment_table: failed to read fragment "
+				"table block\n");
+			return FALSE;
+		}
+	}
+
+	if(swap) {
+		squashfs_fragment_entry_2 sfragment;
+		for(i = 0; i < sBlk.s.fragments; i++) {
+			SQUASHFS_SWAP_FRAGMENT_ENTRY_2((&sfragment),
+				(&fragment_table[i]));
+			memcpy((char *) &fragment_table[i], (char *) &sfragment,
+				sizeof(squashfs_fragment_entry_2));
+		}
+	}
+
+	*directory_table_end = fragment_table_index[0];
+	return TRUE;
+}
+
+
+void read_fragment_2(unsigned int fragment, long long *start_block, int *size)
+{
+	TRACE("read_fragment: reading fragment %d\n", fragment);
+
+	squashfs_fragment_entry_2 *fragment_entry = &fragment_table[fragment];
+	*start_block = fragment_entry->start_block;
+	*size = fragment_entry->size;
+}
+
+
+struct inode *read_inode_2(unsigned int start_block, unsigned int offset)
+{
+	static union squashfs_inode_header_2 header;
+	long long start = sBlk.s.inode_table_start + start_block;
+	int bytes = lookup_entry(inode_table_hash, start);
+	char *block_ptr = inode_table + bytes + offset;
+	static struct inode i;
+
+	TRACE("read_inode: reading inode [%d:%d]\n", start_block,  offset);
+
+	if(bytes == -1)
+		EXIT_UNSQUASH("read_inode: inode table block %lld not found\n",
+			start); 
+
+	if(swap) {
+		squashfs_base_inode_header_2 sinode;
+		memcpy(&sinode, block_ptr, sizeof(header.base));
+		SQUASHFS_SWAP_BASE_INODE_HEADER_2(&header.base, &sinode,
+			sizeof(squashfs_base_inode_header_2));
+	} else
+		memcpy(&header.base, block_ptr, sizeof(header.base));
+
+	i.xattr = SQUASHFS_INVALID_XATTR;
+	i.uid = (uid_t) uid_table[header.base.uid];
+	i.gid = header.base.guid == SQUASHFS_GUIDS ? i.uid :
+		(uid_t) guid_table[header.base.guid];
+	i.mode = lookup_type[header.base.inode_type] | header.base.mode;
+	i.type = header.base.inode_type;
+	i.time = sBlk.s.mkfs_time;
+	i.inode_number = inode_number++;
+
+	switch(header.base.inode_type) {
+		case SQUASHFS_DIR_TYPE: {
+			squashfs_dir_inode_header_2 *inode = &header.dir;
+
+			if(swap) {
+				squashfs_dir_inode_header_2 sinode;
+				memcpy(&sinode, block_ptr, sizeof(header.dir));
+				SQUASHFS_SWAP_DIR_INODE_HEADER_2(&header.dir,
+					&sinode);
+			} else
+				memcpy(&header.dir, block_ptr,
+					sizeof(header.dir));
+
+			i.data = inode->file_size;
+			i.offset = inode->offset;
+			i.start = inode->start_block;
+			i.time = inode->mtime;
+			break;
+		}
+		case SQUASHFS_LDIR_TYPE: {
+			squashfs_ldir_inode_header_2 *inode = &header.ldir;
+
+			if(swap) {
+				squashfs_ldir_inode_header_2 sinode;
+				memcpy(&sinode, block_ptr, sizeof(header.ldir));
+				SQUASHFS_SWAP_LDIR_INODE_HEADER_2(&header.ldir,
+					&sinode);
+			} else
+				memcpy(&header.ldir, block_ptr,
+					sizeof(header.ldir));
+
+			i.data = inode->file_size;
+			i.offset = inode->offset;
+			i.start = inode->start_block;
+			i.time = inode->mtime;
+			break;
+		}
+		case SQUASHFS_FILE_TYPE: {
+			squashfs_reg_inode_header_2 *inode = &header.reg;
+
+			if(swap) {
+				squashfs_reg_inode_header_2 sinode;
+				memcpy(&sinode, block_ptr, sizeof(sinode));
+				SQUASHFS_SWAP_REG_INODE_HEADER_2(inode,
+					&sinode);
+			} else
+				memcpy(inode, block_ptr, sizeof(*inode));
+
+			i.data = inode->file_size;
+			i.time = inode->mtime;
+			i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+				?  0 : inode->file_size % sBlk.s.block_size;
+			i.fragment = inode->fragment;
+			i.offset = inode->offset;
+			i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+				(i.data + sBlk.s.block_size - 1) >>
+				sBlk.s.block_log : i.data >>
+				sBlk.s.block_log;
+			i.start = inode->start_block;
+			i.sparse = 0;
+			i.block_ptr = block_ptr + sizeof(*inode);
+			break;
+		}	
+		case SQUASHFS_SYMLINK_TYPE: {
+			squashfs_symlink_inode_header_2 *inodep =
+				&header.symlink;
+
+			if(swap) {
+				squashfs_symlink_inode_header_2 sinodep;
+				memcpy(&sinodep, block_ptr, sizeof(sinodep));
+				SQUASHFS_SWAP_SYMLINK_INODE_HEADER_2(inodep,
+					&sinodep);
+			} else
+				memcpy(inodep, block_ptr, sizeof(*inodep));
+
+			i.symlink = malloc(inodep->symlink_size + 1);
+			if(i.symlink == NULL)
+				EXIT_UNSQUASH("read_inode: failed to malloc "
+					"symlink data\n");
+			strncpy(i.symlink, block_ptr +
+				sizeof(squashfs_symlink_inode_header_2),
+				inodep->symlink_size);
+			i.symlink[inodep->symlink_size] = '\0';
+			i.data = inodep->symlink_size;
+			break;
+		}
+ 		case SQUASHFS_BLKDEV_TYPE:
+	 	case SQUASHFS_CHRDEV_TYPE: {
+			squashfs_dev_inode_header_2 *inodep = &header.dev;
+
+			if(swap) {
+				squashfs_dev_inode_header_2 sinodep;
+				memcpy(&sinodep, block_ptr, sizeof(sinodep));
+				SQUASHFS_SWAP_DEV_INODE_HEADER_2(inodep,
+					&sinodep);
+			} else
+				memcpy(inodep, block_ptr, sizeof(*inodep));
+
+			i.data = inodep->rdev;
+			break;
+			}
+		case SQUASHFS_FIFO_TYPE:
+		case SQUASHFS_SOCKET_TYPE:
+			i.data = 0;
+			break;
+		default:
+			EXIT_UNSQUASH("Unknown inode type %d in "
+				"read_inode_header_2!\n",
+				header.base.inode_type);
+	}
+	return &i;
+}
diff --git a/squashfs-tools/squashfs-tools/unsquash-3.c b/squashfs-tools/squashfs-tools/unsquash-3.c
new file mode 100644
index 0000000..d5af57a
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/unsquash-3.c
@@ -0,0 +1,393 @@
+/*
+ * Unsquash a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013
+ * 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.
+ *
+ * unsquash-3.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_compat.h"
+
+static squashfs_fragment_entry_3 *fragment_table;
+
+int read_fragment_table_3(long long *directory_table_end)
+{
+	int res, i;
+	int bytes = SQUASHFS_FRAGMENT_BYTES_3(sBlk.s.fragments);
+	int indexes = SQUASHFS_FRAGMENT_INDEXES_3(sBlk.s.fragments);
+	long long fragment_table_index[indexes];
+
+	TRACE("read_fragment_table: %d fragments, reading %d fragment indexes "
+		"from 0x%llx\n", sBlk.s.fragments, indexes,
+		sBlk.s.fragment_table_start);
+
+	if(sBlk.s.fragments == 0) {
+		*directory_table_end = sBlk.s.fragment_table_start;
+		return TRUE;
+	}
+
+	fragment_table = malloc(bytes);
+	if(fragment_table == NULL)
+		EXIT_UNSQUASH("read_fragment_table: failed to allocate "
+			"fragment table\n");
+
+	if(swap) {
+		long long sfragment_table_index[indexes];
+
+		res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
+			SQUASHFS_FRAGMENT_INDEX_BYTES_3(sBlk.s.fragments),
+			sfragment_table_index);
+		if(res == FALSE) {
+			ERROR("read_fragment_table: failed to read fragment "
+				"table index\n");       
+			return FALSE;
+		}
+		SQUASHFS_SWAP_FRAGMENT_INDEXES_3(fragment_table_index,
+			sfragment_table_index, indexes);
+	} else {
+		res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
+			SQUASHFS_FRAGMENT_INDEX_BYTES_3(sBlk.s.fragments),
+			fragment_table_index);
+		if(res == FALSE) {
+			ERROR("read_fragment_table: failed to read fragment "
+				"table index\n");       
+			return FALSE;
+		}
+	}
+
+	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, ((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 == FALSE) {
+			ERROR("read_fragment_table: failed to read fragment "
+				"table block\n");       
+			return FALSE;
+		}
+	}
+
+	if(swap) {
+		squashfs_fragment_entry_3 sfragment;
+		for(i = 0; i < sBlk.s.fragments; i++) {
+			SQUASHFS_SWAP_FRAGMENT_ENTRY_3((&sfragment),
+				(&fragment_table[i]));
+			memcpy((char *) &fragment_table[i], (char *) &sfragment,
+				sizeof(squashfs_fragment_entry_3));
+		}
+	}
+
+	*directory_table_end = fragment_table_index[0];
+	return TRUE;
+}
+
+
+void read_fragment_3(unsigned int fragment, long long *start_block, int *size)
+{
+	TRACE("read_fragment: reading fragment %d\n", fragment);
+
+	squashfs_fragment_entry_3 *fragment_entry = &fragment_table[fragment];
+	*start_block = fragment_entry->start_block;
+	*size = fragment_entry->size;
+}
+
+
+struct inode *read_inode_3(unsigned int start_block, unsigned int offset)
+{
+	static union squashfs_inode_header_3 header;
+	long long start = sBlk.s.inode_table_start + start_block;
+	int bytes = lookup_entry(inode_table_hash, start);
+	char *block_ptr = inode_table + bytes + offset;
+	static struct inode i;
+
+	TRACE("read_inode: reading inode [%d:%d]\n", start_block,  offset);
+
+	if(bytes == -1)
+		EXIT_UNSQUASH("read_inode: inode table block %lld not found\n",
+			start); 
+
+	if(swap) {
+		squashfs_base_inode_header_3 sinode;
+		memcpy(&sinode, block_ptr, sizeof(header.base));
+		SQUASHFS_SWAP_BASE_INODE_HEADER_3(&header.base, &sinode,
+			sizeof(squashfs_base_inode_header_3));
+	} else
+		memcpy(&header.base, block_ptr, sizeof(header.base));
+
+	i.xattr = SQUASHFS_INVALID_XATTR;
+	i.uid = (uid_t) uid_table[header.base.uid];
+	i.gid = header.base.guid == SQUASHFS_GUIDS ? i.uid :
+		(uid_t) guid_table[header.base.guid];
+	i.mode = lookup_type[header.base.inode_type] | header.base.mode;
+	i.type = header.base.inode_type;
+	i.time = header.base.mtime;
+	i.inode_number = header.base.inode_number;
+
+	switch(header.base.inode_type) {
+		case SQUASHFS_DIR_TYPE: {
+			squashfs_dir_inode_header_3 *inode = &header.dir;
+
+			if(swap) {
+				squashfs_dir_inode_header_3 sinode;
+				memcpy(&sinode, block_ptr, sizeof(header.dir));
+				SQUASHFS_SWAP_DIR_INODE_HEADER_3(&header.dir,
+					&sinode);
+			} else
+				memcpy(&header.dir, block_ptr,
+					sizeof(header.dir));
+
+			i.data = inode->file_size;
+			i.offset = inode->offset;
+			i.start = inode->start_block;
+			break;
+		}
+		case SQUASHFS_LDIR_TYPE: {
+			squashfs_ldir_inode_header_3 *inode = &header.ldir;
+
+			if(swap) {
+				squashfs_ldir_inode_header_3 sinode;
+				memcpy(&sinode, block_ptr, sizeof(header.ldir));
+				SQUASHFS_SWAP_LDIR_INODE_HEADER_3(&header.ldir,
+					&sinode);
+			} else
+				memcpy(&header.ldir, block_ptr,
+					sizeof(header.ldir));
+
+			i.data = inode->file_size;
+			i.offset = inode->offset;
+			i.start = inode->start_block;
+			break;
+		}
+		case SQUASHFS_FILE_TYPE: {
+			squashfs_reg_inode_header_3 *inode = &header.reg;
+
+			if(swap) {
+				squashfs_reg_inode_header_3 sinode;
+				memcpy(&sinode, block_ptr, sizeof(sinode));
+				SQUASHFS_SWAP_REG_INODE_HEADER_3(inode,
+					&sinode);
+			} else
+				memcpy(inode, block_ptr, sizeof(*inode));
+
+			i.data = inode->file_size;
+			i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+				?  0 : inode->file_size % sBlk.s.block_size;
+			i.fragment = inode->fragment;
+			i.offset = inode->offset;
+			i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+				(i.data + sBlk.s.block_size - 1) >>
+				sBlk.s.block_log :
+				i.data >> sBlk.s.block_log;
+			i.start = inode->start_block;
+			i.sparse = 1;
+			i.block_ptr = block_ptr + sizeof(*inode);
+			break;
+		}	
+		case SQUASHFS_LREG_TYPE: {
+			squashfs_lreg_inode_header_3 *inode = &header.lreg;
+
+			if(swap) {
+				squashfs_lreg_inode_header_3 sinode;
+				memcpy(&sinode, block_ptr, sizeof(sinode));
+				SQUASHFS_SWAP_LREG_INODE_HEADER_3(inode,
+					&sinode);
+			} else
+				memcpy(inode, block_ptr, sizeof(*inode));
+
+			i.data = inode->file_size;
+			i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+				?  0 : inode->file_size % sBlk.s.block_size;
+			i.fragment = inode->fragment;
+			i.offset = inode->offset;
+			i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+				(inode->file_size + sBlk.s.block_size - 1) >>
+				sBlk.s.block_log :
+				inode->file_size >> sBlk.s.block_log;
+			i.start = inode->start_block;
+			i.sparse = 1;
+			i.block_ptr = block_ptr + sizeof(*inode);
+			break;
+		}	
+		case SQUASHFS_SYMLINK_TYPE: {
+			squashfs_symlink_inode_header_3 *inodep =
+				&header.symlink;
+
+			if(swap) {
+				squashfs_symlink_inode_header_3 sinodep;
+				memcpy(&sinodep, block_ptr, sizeof(sinodep));
+				SQUASHFS_SWAP_SYMLINK_INODE_HEADER_3(inodep,
+					&sinodep);
+			} else
+				memcpy(inodep, block_ptr, sizeof(*inodep));
+
+			i.symlink = malloc(inodep->symlink_size + 1);
+			if(i.symlink == NULL)
+				EXIT_UNSQUASH("read_inode: failed to malloc "
+					"symlink data\n");
+			strncpy(i.symlink, block_ptr +
+				sizeof(squashfs_symlink_inode_header_3),
+				inodep->symlink_size);
+			i.symlink[inodep->symlink_size] = '\0';
+			i.data = inodep->symlink_size;
+			break;
+		}
+ 		case SQUASHFS_BLKDEV_TYPE:
+	 	case SQUASHFS_CHRDEV_TYPE: {
+			squashfs_dev_inode_header_3 *inodep = &header.dev;
+
+			if(swap) {
+				squashfs_dev_inode_header_3 sinodep;
+				memcpy(&sinodep, block_ptr, sizeof(sinodep));
+				SQUASHFS_SWAP_DEV_INODE_HEADER_3(inodep,
+					&sinodep);
+			} else
+				memcpy(inodep, block_ptr, sizeof(*inodep));
+
+			i.data = inodep->rdev;
+			break;
+			}
+		case SQUASHFS_FIFO_TYPE:
+		case SQUASHFS_SOCKET_TYPE:
+			i.data = 0;
+			break;
+		default:
+			EXIT_UNSQUASH("Unknown inode type %d in read_inode!\n",
+				header.base.inode_type);
+	}
+	return &i;
+}
+
+
+struct dir *squashfs_opendir_3(unsigned int block_start, unsigned int offset,
+	struct inode **i)
+{
+	squashfs_dir_header_3 dirh;
+	char buffer[sizeof(squashfs_dir_entry_3) + SQUASHFS_NAME_LEN + 1]
+		__attribute__((aligned));
+	squashfs_dir_entry_3 *dire = (squashfs_dir_entry_3 *) buffer;
+	long long start;
+	int bytes;
+	int dir_count, size;
+	struct dir_ent *new_dir;
+	struct dir *dir;
+
+	TRACE("squashfs_opendir: inode start block %d, offset %d\n",
+		block_start, offset);
+
+	*i = s_ops.read_inode(block_start, offset);
+
+	dir = malloc(sizeof(struct dir));
+	if(dir == NULL)
+		EXIT_UNSQUASH("squashfs_opendir: malloc failed!\n");
+
+	dir->dir_count = 0;
+	dir->cur_entry = 0;
+	dir->mode = (*i)->mode;
+	dir->uid = (*i)->uid;
+	dir->guid = (*i)->gid;
+	dir->mtime = (*i)->time;
+	dir->xattr = (*i)->xattr;
+	dir->dirs = NULL;
+
+	if ((*i)->data == 3)
+		/*
+		 * if the directory is empty, skip the unnecessary
+		 * lookup_entry, this fixes the corner case with
+		 * completely empty filesystems where lookup_entry correctly
+		 * returning -1 is incorrectly treated as an error
+		 */
+		return dir;
+
+	start = sBlk.s.directory_table_start + (*i)->start;
+	bytes = lookup_entry(directory_table_hash, start);
+
+	if(bytes == -1)
+		EXIT_UNSQUASH("squashfs_opendir: directory block %d not "
+			"found!\n", block_start);
+
+	bytes += (*i)->offset;
+	size = (*i)->data + bytes - 3;
+
+	while(bytes < size) {			
+		if(swap) {
+			squashfs_dir_header_3 sdirh;
+			memcpy(&sdirh, directory_table + bytes, sizeof(sdirh));
+			SQUASHFS_SWAP_DIR_HEADER_3(&dirh, &sdirh);
+		} else
+			memcpy(&dirh, directory_table + bytes, sizeof(dirh));
+	
+		dir_count = dirh.count + 1;
+		TRACE("squashfs_opendir: Read directory header @ byte position "
+			"%d, %d directory entries\n", bytes, dir_count);
+		bytes += sizeof(dirh);
+
+		/* dir_count should never be larger than 256 */
+		if(dir_count > 256)
+			goto corrupted;
+
+		while(dir_count--) {
+			if(swap) {
+				squashfs_dir_entry_3 sdire;
+				memcpy(&sdire, directory_table + bytes,
+					sizeof(sdire));
+				SQUASHFS_SWAP_DIR_ENTRY_3(dire, &sdire);
+			} else
+				memcpy(dire, directory_table + bytes,
+					sizeof(*dire));
+			bytes += sizeof(*dire);
+
+			/* size should never be larger than SQUASHFS_NAME_LEN */
+			if(dire->size > SQUASHFS_NAME_LEN)
+				goto corrupted;
+
+			memcpy(dire->name, directory_table + bytes,
+				dire->size + 1);
+			dire->name[dire->size + 1] = '\0';
+			TRACE("squashfs_opendir: directory entry %s, inode "
+				"%d:%d, type %d\n", dire->name,
+				dirh.start_block, dire->offset, dire->type);
+			if((dir->dir_count % DIR_ENT_SIZE) == 0) {
+				new_dir = realloc(dir->dirs, (dir->dir_count +
+					DIR_ENT_SIZE) * sizeof(struct dir_ent));
+				if(new_dir == NULL)
+					EXIT_UNSQUASH("squashfs_opendir: "
+						"realloc failed!\n");
+				dir->dirs = new_dir;
+			}
+			strcpy(dir->dirs[dir->dir_count].name, dire->name);
+			dir->dirs[dir->dir_count].start_block =
+				dirh.start_block;
+			dir->dirs[dir->dir_count].offset = dire->offset;
+			dir->dirs[dir->dir_count].type = dire->type;
+			dir->dir_count ++;
+			bytes += dire->size + 1;
+		}
+	}
+
+	return dir;
+
+corrupted:
+	free(dir->dirs);
+	free(dir);
+	return NULL;
+}
diff --git a/squashfs-tools/squashfs-tools/unsquash-4.c b/squashfs-tools/squashfs-tools/unsquash-4.c
new file mode 100644
index 0000000..ecdaac7
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/unsquash-4.c
@@ -0,0 +1,392 @@
+/*
+ * Unsquash a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013
+ * 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.
+ *
+ * unsquash-4.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_swap.h"
+
+static struct squashfs_fragment_entry *fragment_table;
+static unsigned int *id_table;
+
+int read_fragment_table_4(long long *directory_table_end)
+{
+	int res, i;
+	int bytes = SQUASHFS_FRAGMENT_BYTES(sBlk.s.fragments);
+	int  indexes = SQUASHFS_FRAGMENT_INDEXES(sBlk.s.fragments);
+	long long fragment_table_index[indexes];
+
+	TRACE("read_fragment_table: %d fragments, reading %d fragment indexes "
+		"from 0x%llx\n", sBlk.s.fragments, indexes,
+		sBlk.s.fragment_table_start);
+
+	if(sBlk.s.fragments == 0) {
+		*directory_table_end = sBlk.s.fragment_table_start;
+		return TRUE;
+	}
+
+	fragment_table = malloc(bytes);
+	if(fragment_table == NULL)
+		EXIT_UNSQUASH("read_fragment_table: failed to allocate "
+			"fragment table\n");
+
+	res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
+		SQUASHFS_FRAGMENT_INDEX_BYTES(sBlk.s.fragments),
+		fragment_table_index);
+	if(res == FALSE) {
+		ERROR("read_fragment_table: failed to read fragment table "
+			"index\n");
+		return FALSE;
+	}
+	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, ((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 == FALSE) {
+			ERROR("read_fragment_table: failed to read fragment "
+				"table index\n");
+			return FALSE;
+		}
+	}
+
+	for(i = 0; i < sBlk.s.fragments; i++) 
+		SQUASHFS_INSWAP_FRAGMENT_ENTRY(&fragment_table[i]);
+
+	*directory_table_end = fragment_table_index[0];
+	return TRUE;
+}
+
+
+void read_fragment_4(unsigned int fragment, long long *start_block, int *size)
+{
+	TRACE("read_fragment: reading fragment %d\n", fragment);
+
+	struct squashfs_fragment_entry *fragment_entry;
+
+	fragment_entry = &fragment_table[fragment];
+	*start_block = fragment_entry->start_block;
+	*size = fragment_entry->size;
+}
+
+
+struct inode *read_inode_4(unsigned int start_block, unsigned int offset)
+{
+	static union squashfs_inode_header header;
+	long long start = sBlk.s.inode_table_start + start_block;
+	int bytes = lookup_entry(inode_table_hash, start);
+	char *block_ptr = inode_table + bytes + offset;
+	static struct inode i;
+
+	TRACE("read_inode: reading inode [%d:%d]\n", start_block,  offset);
+
+	if(bytes == -1)
+		EXIT_UNSQUASH("read_inode: inode table block %lld not found\n",
+			start); 		
+
+	SQUASHFS_SWAP_BASE_INODE_HEADER(block_ptr, &header.base);
+
+	i.uid = (uid_t) id_table[header.base.uid];
+	i.gid = (uid_t) id_table[header.base.guid];
+	i.mode = lookup_type[header.base.inode_type] | header.base.mode;
+	i.type = header.base.inode_type;
+	i.time = header.base.mtime;
+	i.inode_number = header.base.inode_number;
+
+	switch(header.base.inode_type) {
+		case SQUASHFS_DIR_TYPE: {
+			struct squashfs_dir_inode_header *inode = &header.dir;
+
+			SQUASHFS_SWAP_DIR_INODE_HEADER(block_ptr, inode);
+
+			i.data = inode->file_size;
+			i.offset = inode->offset;
+			i.start = inode->start_block;
+			i.xattr = SQUASHFS_INVALID_XATTR;
+			break;
+		}
+		case SQUASHFS_LDIR_TYPE: {
+			struct squashfs_ldir_inode_header *inode = &header.ldir;
+
+			SQUASHFS_SWAP_LDIR_INODE_HEADER(block_ptr, inode);
+
+			i.data = inode->file_size;
+			i.offset = inode->offset;
+			i.start = inode->start_block;
+			i.xattr = inode->xattr;
+			break;
+		}
+		case SQUASHFS_FILE_TYPE: {
+			struct squashfs_reg_inode_header *inode = &header.reg;
+
+			SQUASHFS_SWAP_REG_INODE_HEADER(block_ptr, inode);
+
+			i.data = inode->file_size;
+			i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+				?  0 : inode->file_size % sBlk.s.block_size;
+			i.fragment = inode->fragment;
+			i.offset = inode->offset;
+			i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+				(i.data + sBlk.s.block_size - 1) >>
+				sBlk.s.block_log :
+				i.data >> sBlk.s.block_log;
+			i.start = inode->start_block;
+			i.sparse = 0;
+			i.block_ptr = block_ptr + sizeof(*inode);
+			i.xattr = SQUASHFS_INVALID_XATTR;
+			break;
+		}	
+		case SQUASHFS_LREG_TYPE: {
+			struct squashfs_lreg_inode_header *inode = &header.lreg;
+
+			SQUASHFS_SWAP_LREG_INODE_HEADER(block_ptr, inode);
+
+			i.data = inode->file_size;
+			i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+				?  0 : inode->file_size % sBlk.s.block_size;
+			i.fragment = inode->fragment;
+			i.offset = inode->offset;
+			i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+				(inode->file_size + sBlk.s.block_size - 1) >>
+				sBlk.s.block_log :
+				inode->file_size >> sBlk.s.block_log;
+			i.start = inode->start_block;
+			i.sparse = inode->sparse != 0;
+			i.block_ptr = block_ptr + sizeof(*inode);
+			i.xattr = inode->xattr;
+			break;
+		}	
+		case SQUASHFS_SYMLINK_TYPE:
+		case SQUASHFS_LSYMLINK_TYPE: {
+			struct squashfs_symlink_inode_header *inode = &header.symlink;
+
+			SQUASHFS_SWAP_SYMLINK_INODE_HEADER(block_ptr, inode);
+
+			i.symlink = malloc(inode->symlink_size + 1);
+			if(i.symlink == NULL)
+				EXIT_UNSQUASH("read_inode: failed to malloc "
+					"symlink data\n");
+			strncpy(i.symlink, block_ptr +
+				sizeof(struct squashfs_symlink_inode_header),
+				inode->symlink_size);
+			i.symlink[inode->symlink_size] = '\0';
+			i.data = inode->symlink_size;
+
+			if(header.base.inode_type == SQUASHFS_LSYMLINK_TYPE)
+				SQUASHFS_SWAP_INTS(block_ptr +
+					sizeof(struct squashfs_symlink_inode_header) +
+					inode->symlink_size, &i.xattr, 1);
+			else
+				i.xattr = SQUASHFS_INVALID_XATTR;
+			break;
+		}
+ 		case SQUASHFS_BLKDEV_TYPE:
+	 	case SQUASHFS_CHRDEV_TYPE: {
+			struct squashfs_dev_inode_header *inode = &header.dev;
+
+			SQUASHFS_SWAP_DEV_INODE_HEADER(block_ptr, inode);
+
+			i.data = inode->rdev;
+			i.xattr = SQUASHFS_INVALID_XATTR;
+			break;
+		}
+ 		case SQUASHFS_LBLKDEV_TYPE:
+	 	case SQUASHFS_LCHRDEV_TYPE: {
+			struct squashfs_ldev_inode_header *inode = &header.ldev;
+
+			SQUASHFS_SWAP_LDEV_INODE_HEADER(block_ptr, inode);
+
+			i.data = inode->rdev;
+			i.xattr = inode->xattr;
+			break;
+		}
+		case SQUASHFS_FIFO_TYPE:
+		case SQUASHFS_SOCKET_TYPE:
+			i.data = 0;
+			i.xattr = SQUASHFS_INVALID_XATTR;
+			break;
+		case SQUASHFS_LFIFO_TYPE:
+		case SQUASHFS_LSOCKET_TYPE: {
+			struct squashfs_lipc_inode_header *inode = &header.lipc;
+
+			SQUASHFS_SWAP_LIPC_INODE_HEADER(block_ptr, inode);
+
+			i.data = 0;
+			i.xattr = inode->xattr;
+			break;
+		}
+		default:
+			EXIT_UNSQUASH("Unknown inode type %d in read_inode!\n",
+				header.base.inode_type);
+	}
+	return &i;
+}
+
+
+struct dir *squashfs_opendir_4(unsigned int block_start, unsigned int offset,
+	struct inode **i)
+{
+	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;
+	long long start;
+	int bytes;
+	int dir_count, size;
+	struct dir_ent *new_dir;
+	struct dir *dir;
+
+	TRACE("squashfs_opendir: inode start block %d, offset %d\n",
+		block_start, offset);
+
+	*i = s_ops.read_inode(block_start, offset);
+
+	dir = malloc(sizeof(struct dir));
+	if(dir == NULL)
+		EXIT_UNSQUASH("squashfs_opendir: malloc failed!\n");
+
+	dir->dir_count = 0;
+	dir->cur_entry = 0;
+	dir->mode = (*i)->mode;
+	dir->uid = (*i)->uid;
+	dir->guid = (*i)->gid;
+	dir->mtime = (*i)->time;
+	dir->xattr = (*i)->xattr;
+	dir->dirs = NULL;
+
+	if ((*i)->data == 3)
+		/*
+		 * if the directory is empty, skip the unnecessary
+		 * lookup_entry, this fixes the corner case with
+		 * completely empty filesystems where lookup_entry correctly
+		 * returning -1 is incorrectly treated as an error
+		 */
+		return dir;
+
+	start = sBlk.s.directory_table_start + (*i)->start;
+	bytes = lookup_entry(directory_table_hash, start);
+
+	if(bytes == -1)
+		EXIT_UNSQUASH("squashfs_opendir: directory block %d not "
+			"found!\n", block_start);
+
+	bytes += (*i)->offset;
+	size = (*i)->data + bytes - 3;
+
+	while(bytes < size) {			
+		SQUASHFS_SWAP_DIR_HEADER(directory_table + bytes, &dirh);
+	
+		dir_count = dirh.count + 1;
+		TRACE("squashfs_opendir: Read directory header @ byte position "
+			"%d, %d directory entries\n", bytes, dir_count);
+		bytes += sizeof(dirh);
+
+		/* dir_count should never be larger than 256 */
+	 	if(dir_count > 256)
+			goto corrupted;
+
+		while(dir_count--) {
+			SQUASHFS_SWAP_DIR_ENTRY(directory_table + bytes, dire);
+
+			bytes += sizeof(*dire);
+
+			/* size should never be larger than SQUASHFS_NAME_LEN */
+			if(dire->size > SQUASHFS_NAME_LEN)
+				goto corrupted;
+
+			memcpy(dire->name, directory_table + bytes,
+				dire->size + 1);
+			dire->name[dire->size + 1] = '\0';
+			TRACE("squashfs_opendir: directory entry %s, inode "
+				"%d:%d, type %d\n", dire->name,
+				dirh.start_block, dire->offset, dire->type);
+			if((dir->dir_count % DIR_ENT_SIZE) == 0) {
+				new_dir = realloc(dir->dirs, (dir->dir_count +
+					DIR_ENT_SIZE) * sizeof(struct dir_ent));
+				if(new_dir == NULL)
+					EXIT_UNSQUASH("squashfs_opendir: "
+						"realloc failed!\n");
+				dir->dirs = new_dir;
+			}
+			strcpy(dir->dirs[dir->dir_count].name, dire->name);
+			dir->dirs[dir->dir_count].start_block =
+				dirh.start_block;
+			dir->dirs[dir->dir_count].offset = dire->offset;
+			dir->dirs[dir->dir_count].type = dire->type;
+			dir->dir_count ++;
+			bytes += dire->size + 1;
+		}
+	}
+
+	return dir;
+
+corrupted:
+	free(dir->dirs);
+	free(dir);
+	return NULL;
+}
+
+
+int read_uids_guids_4()
+{
+	int res, i;
+	int bytes = SQUASHFS_ID_BYTES(sBlk.s.no_ids);
+	int indexes = SQUASHFS_ID_BLOCKS(sBlk.s.no_ids);
+	long long id_index_table[indexes];
+
+	TRACE("read_uids_guids: no_ids %d\n", sBlk.s.no_ids);
+
+	id_table = malloc(bytes);
+	if(id_table == NULL) {
+		ERROR("read_uids_guids: failed to allocate id table\n");
+		return FALSE;
+	}
+
+	res = read_fs_bytes(fd, sBlk.s.id_table_start,
+		SQUASHFS_ID_BLOCK_BYTES(sBlk.s.no_ids), id_index_table);
+	if(res == FALSE) {
+		ERROR("read_uids_guids: failed to read id index table\n");
+		return FALSE;
+	}
+	SQUASHFS_INSWAP_ID_BLOCKS(id_index_table, indexes);
+
+	for(i = 0; i < indexes; i++) {
+		int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
+					bytes & (SQUASHFS_METADATA_SIZE - 1);
+		res = read_block(fd, id_index_table[i], NULL, expected,
+			((char *) id_table) + i * SQUASHFS_METADATA_SIZE);
+		if(res == FALSE) {
+			ERROR("read_uids_guids: failed to read id table block"
+				"\n");
+			return FALSE;
+		}
+	}
+
+	SQUASHFS_INSWAP_INTS(id_table, sBlk.s.no_ids);
+
+	return TRUE;
+}
diff --git a/squashfs-tools/squashfs-tools/unsquashfs.c b/squashfs-tools/squashfs-tools/unsquashfs.c
new file mode 100644
index 0000000..7f46968
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/unsquashfs.c
@@ -0,0 +1,2820 @@
+/*
+ * Unsquash a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011,
+ * 2012, 2013, 2014
+ * 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.
+ *
+ * unsquashfs.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_swap.h"
+#include "squashfs_compat.h"
+#include "compressor.h"
+#include "xattr.h"
+#include "unsquashfs_info.h"
+#include "stdarg.h"
+
+#ifndef linux
+#include <sys/sysctl.h>
+#else
+#include <sys/sysinfo.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <limits.h>
+#include <ctype.h>
+
+struct cache *fragment_cache, *data_cache;
+struct queue *to_reader, *to_inflate, *to_writer, *from_writer;
+pthread_t *thread, *inflator_thread;
+pthread_mutex_t	fragment_mutex;
+
+/* user options that control parallelisation */
+int processors = -1;
+
+struct super_block sBlk;
+squashfs_operations s_ops;
+struct compressor *comp;
+
+int bytes = 0, swap, file_count = 0, dir_count = 0, sym_count = 0,
+	dev_count = 0, fifo_count = 0;
+char *inode_table = NULL, *directory_table = NULL;
+struct hash_table_entry *inode_table_hash[65536], *directory_table_hash[65536];
+int fd;
+unsigned int *uid_table, *guid_table;
+unsigned int cached_frag = SQUASHFS_INVALID_FRAG;
+char *fragment_data;
+char *file_data;
+char *data;
+unsigned int block_size;
+unsigned int block_log;
+int lsonly = FALSE, info = FALSE, force = FALSE, short_ls = TRUE;
+int use_regex = FALSE;
+char **created_inode;
+int root_process;
+int columns;
+int rotate = 0;
+pthread_mutex_t	screen_mutex;
+int progress = TRUE, progress_enabled = FALSE;
+unsigned int total_blocks = 0, total_files = 0, total_inodes = 0;
+unsigned int cur_blocks = 0;
+int inode_number = 1;
+int no_xattrs = XATTR_DEF;
+int user_xattrs = FALSE;
+
+int lookup_type[] = {
+	0,
+	S_IFDIR,
+	S_IFREG,
+	S_IFLNK,
+	S_IFBLK,
+	S_IFCHR,
+	S_IFIFO,
+	S_IFSOCK,
+	S_IFDIR,
+	S_IFREG,
+	S_IFLNK,
+	S_IFBLK,
+	S_IFCHR,
+	S_IFIFO,
+	S_IFSOCK
+};
+
+struct test table[] = {
+	{ S_IFMT, S_IFSOCK, 0, 's' },
+	{ S_IFMT, S_IFLNK, 0, 'l' },
+	{ S_IFMT, S_IFBLK, 0, 'b' },
+	{ S_IFMT, S_IFDIR, 0, 'd' },
+	{ S_IFMT, S_IFCHR, 0, 'c' },
+	{ S_IFMT, S_IFIFO, 0, 'p' },
+	{ S_IRUSR, S_IRUSR, 1, 'r' },
+	{ S_IWUSR, S_IWUSR, 2, 'w' },
+	{ S_IRGRP, S_IRGRP, 4, 'r' },
+	{ S_IWGRP, S_IWGRP, 5, 'w' },
+	{ S_IROTH, S_IROTH, 7, 'r' },
+	{ S_IWOTH, S_IWOTH, 8, 'w' },
+	{ S_IXUSR | S_ISUID, S_IXUSR | S_ISUID, 3, 's' },
+	{ S_IXUSR | S_ISUID, S_ISUID, 3, 'S' },
+	{ S_IXUSR | S_ISUID, S_IXUSR, 3, 'x' },
+	{ S_IXGRP | S_ISGID, S_IXGRP | S_ISGID, 6, 's' },
+	{ S_IXGRP | S_ISGID, S_ISGID, 6, 'S' },
+	{ S_IXGRP | S_ISGID, S_IXGRP, 6, 'x' },
+	{ S_IXOTH | S_ISVTX, S_IXOTH | S_ISVTX, 9, 't' },
+	{ S_IXOTH | S_ISVTX, S_ISVTX, 9, 'T' },
+	{ S_IXOTH | S_ISVTX, S_IXOTH, 9, 'x' },
+	{ 0, 0, 0, 0}
+};
+
+void progress_bar(long long current, long long max, int columns);
+
+#define MAX_LINE 16384
+
+void prep_exit()
+{
+}
+
+
+void sigwinch_handler()
+{
+	struct winsize winsize;
+
+	if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
+		if(isatty(STDOUT_FILENO))
+			ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
+				"columns\n");
+		columns = 80;
+	} else
+		columns = winsize.ws_col;
+}
+
+
+void sigalrm_handler()
+{
+	rotate = (rotate + 1) % 4;
+}
+
+
+int add_overflow(int a, int b)
+{
+	return (INT_MAX - a) < b;
+}
+
+
+int shift_overflow(int a, int shift)
+{
+	return (INT_MAX >> shift) < a;
+}
+
+ 
+int multiply_overflow(int a, int multiplier)
+{
+	return (INT_MAX / multiplier) < a;
+}
+
+
+struct queue *queue_init(int size)
+{
+	struct queue *queue = malloc(sizeof(struct queue));
+
+	if(queue == NULL)
+		EXIT_UNSQUASH("Out of memory in queue_init\n");
+
+	if(add_overflow(size, 1) ||
+				multiply_overflow(size + 1, sizeof(void *)))
+		EXIT_UNSQUASH("Size too large in queue_init\n");
+
+	queue->data = malloc(sizeof(void *) * (size + 1));
+	if(queue->data == NULL)
+		EXIT_UNSQUASH("Out of memory in queue_init\n");
+
+	queue->size = size + 1;
+	queue->readp = queue->writep = 0;
+	pthread_mutex_init(&queue->mutex, NULL);
+	pthread_cond_init(&queue->empty, NULL);
+	pthread_cond_init(&queue->full, NULL);
+
+	return queue;
+}
+
+
+void queue_put(struct queue *queue, void *data)
+{
+	int nextp;
+
+	pthread_mutex_lock(&queue->mutex);
+
+	while((nextp = (queue->writep + 1) % queue->size) == queue->readp)
+		pthread_cond_wait(&queue->full, &queue->mutex);
+
+	queue->data[queue->writep] = data;
+	queue->writep = nextp;
+	pthread_cond_signal(&queue->empty);
+	pthread_mutex_unlock(&queue->mutex);
+}
+
+
+void *queue_get(struct queue *queue)
+{
+	void *data;
+	pthread_mutex_lock(&queue->mutex);
+
+	while(queue->readp == queue->writep)
+		pthread_cond_wait(&queue->empty, &queue->mutex);
+
+	data = queue->data[queue->readp];
+	queue->readp = (queue->readp + 1) % queue->size;
+	pthread_cond_signal(&queue->full);
+	pthread_mutex_unlock(&queue->mutex);
+
+	return data;
+}
+
+
+void dump_queue(struct queue *queue)
+{
+	pthread_mutex_lock(&queue->mutex);
+
+	printf("Max size %d, size %d%s\n", queue->size - 1,  
+		queue->readp <= queue->writep ? queue->writep - queue->readp :
+			queue->size - queue->readp + queue->writep,
+		queue->readp == queue->writep ? " (EMPTY)" :
+			((queue->writep + 1) % queue->size) == queue->readp ?
+			" (FULL)" : "");
+
+	pthread_mutex_unlock(&queue->mutex);
+}
+
+
+/* Called with the cache mutex held */
+void insert_hash_table(struct cache *cache, struct cache_entry *entry)
+{
+	int hash = CALCULATE_HASH(entry->block);
+
+	entry->hash_next = cache->hash_table[hash];
+	cache->hash_table[hash] = entry;
+	entry->hash_prev = NULL;
+	if(entry->hash_next)
+		entry->hash_next->hash_prev = entry;
+}
+
+
+/* Called with the cache mutex held */
+void remove_hash_table(struct cache *cache, struct cache_entry *entry)
+{
+	if(entry->hash_prev)
+		entry->hash_prev->hash_next = entry->hash_next;
+	else
+		cache->hash_table[CALCULATE_HASH(entry->block)] =
+			entry->hash_next;
+	if(entry->hash_next)
+		entry->hash_next->hash_prev = entry->hash_prev;
+
+	entry->hash_prev = entry->hash_next = NULL;
+}
+
+
+/* Called with the cache mutex held */
+void insert_free_list(struct cache *cache, struct cache_entry *entry)
+{
+	if(cache->free_list) {
+		entry->free_next = cache->free_list;
+		entry->free_prev = cache->free_list->free_prev;
+		cache->free_list->free_prev->free_next = entry;
+		cache->free_list->free_prev = entry;
+	} else {
+		cache->free_list = entry;
+		entry->free_prev = entry->free_next = entry;
+	}
+}
+
+
+/* Called with the cache mutex held */
+void remove_free_list(struct cache *cache, struct cache_entry *entry)
+{
+	if(entry->free_prev == NULL || entry->free_next == NULL)
+		/* not in free list */
+		return;
+	else if(entry->free_prev == entry && entry->free_next == entry) {
+		/* only this entry in the free list */
+		cache->free_list = NULL;
+	} else {
+		/* more than one entry in the free list */
+		entry->free_next->free_prev = entry->free_prev;
+		entry->free_prev->free_next = entry->free_next;
+		if(cache->free_list == entry)
+			cache->free_list = entry->free_next;
+	}
+
+	entry->free_prev = entry->free_next = NULL;
+}
+
+
+struct cache *cache_init(int buffer_size, int max_buffers)
+{
+	struct cache *cache = malloc(sizeof(struct cache));
+
+	if(cache == NULL)
+		EXIT_UNSQUASH("Out of memory in cache_init\n");
+
+	cache->max_buffers = max_buffers;
+	cache->buffer_size = buffer_size;
+	cache->count = 0;
+	cache->used = 0;
+	cache->free_list = NULL;
+	memset(cache->hash_table, 0, sizeof(struct cache_entry *) * 65536);
+	cache->wait_free = FALSE;
+	cache->wait_pending = FALSE;
+	pthread_mutex_init(&cache->mutex, NULL);
+	pthread_cond_init(&cache->wait_for_free, NULL);
+	pthread_cond_init(&cache->wait_for_pending, NULL);
+
+	return cache;
+}
+
+
+struct cache_entry *cache_get(struct cache *cache, long long block, int size)
+{
+	/*
+	 * Get a block out of the cache.  If the block isn't in the cache
+ 	 * it is added and queued to the reader() and inflate() threads for
+ 	 * reading off disk and decompression.  The cache grows until max_blocks
+ 	 * is reached, once this occurs existing discarded blocks on the free
+ 	 * list are reused
+ 	 */
+	int hash = CALCULATE_HASH(block);
+	struct cache_entry *entry;
+
+	pthread_mutex_lock(&cache->mutex);
+
+	for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next)
+		if(entry->block == block)
+			break;
+
+	if(entry) {
+		/*
+ 		 * found the block in the cache.  If the block is currently unused
+		 * remove it from the free list and increment cache used count.
+ 		 */
+		if(entry->used == 0) {
+			cache->used ++;
+			remove_free_list(cache, entry);
+		}
+		entry->used ++;
+		pthread_mutex_unlock(&cache->mutex);
+	} else {
+		/*
+ 		 * not in the cache
+		 *
+		 * first try to allocate new block
+		 */
+		if(cache->count < cache->max_buffers) {
+			entry = malloc(sizeof(struct cache_entry));
+			if(entry == NULL)
+				EXIT_UNSQUASH("Out of memory in cache_get\n");
+			entry->data = malloc(cache->buffer_size);
+			if(entry->data == NULL)
+				EXIT_UNSQUASH("Out of memory in cache_get\n");
+			entry->cache = cache;
+			entry->free_prev = entry->free_next = NULL;
+			cache->count ++;
+		} else {
+			/*
+			 * try to get from free list
+			 */
+			while(cache->free_list == NULL) {
+				cache->wait_free = TRUE;
+				pthread_cond_wait(&cache->wait_for_free,
+					&cache->mutex);
+			}
+			entry = cache->free_list;
+			remove_free_list(cache, entry);
+			remove_hash_table(cache, entry);
+		}
+
+		/*
+		 * Initialise block and insert into the hash table.
+		 * Increment used which tracks how many buffers in the
+		 * cache are actively in use (the other blocks, count - used,
+		 * are in the cache and available for lookup, but can also be
+		 * re-used).
+		 */
+		entry->block = block;
+		entry->size = size;
+		entry->used = 1;
+		entry->error = FALSE;
+		entry->pending = TRUE;
+		insert_hash_table(cache, entry);
+		cache->used ++;
+
+		/*
+		 * queue to read thread to read and ultimately (via the
+		 * decompress threads) decompress the buffer
+ 		 */
+		pthread_mutex_unlock(&cache->mutex);
+		queue_put(to_reader, entry);
+	}
+
+	return entry;
+}
+
+	
+void cache_block_ready(struct cache_entry *entry, int error)
+{
+	/*
+	 * mark cache entry as being complete, reading and (if necessary)
+ 	 * decompression has taken place, and the buffer is valid for use.
+ 	 * If an error occurs reading or decompressing, the buffer also 
+ 	 * becomes ready but with an error...
+ 	 */
+	pthread_mutex_lock(&entry->cache->mutex);
+	entry->pending = FALSE;
+	entry->error = error;
+
+	/*
+	 * if the wait_pending flag is set, one or more threads may be waiting
+	 * on this buffer
+	 */
+	if(entry->cache->wait_pending) {
+		entry->cache->wait_pending = FALSE;
+		pthread_cond_broadcast(&entry->cache->wait_for_pending);
+	}
+
+	pthread_mutex_unlock(&entry->cache->mutex);
+}
+
+
+void cache_block_wait(struct cache_entry *entry)
+{
+	/*
+	 * wait for this cache entry to become ready, when reading and (if
+	 * necessary) decompression has taken place
+	 */
+	pthread_mutex_lock(&entry->cache->mutex);
+
+	while(entry->pending) {
+		entry->cache->wait_pending = TRUE;
+		pthread_cond_wait(&entry->cache->wait_for_pending,
+			&entry->cache->mutex);
+	}
+
+	pthread_mutex_unlock(&entry->cache->mutex);
+}
+
+
+void cache_block_put(struct cache_entry *entry)
+{
+	/*
+	 * finished with this cache entry, once the usage count reaches zero it
+ 	 * can be reused and is put onto the free list.  As it remains
+ 	 * accessible via the hash table it can be found getting a new lease of
+ 	 * life before it is reused.
+ 	 */
+	pthread_mutex_lock(&entry->cache->mutex);
+
+	entry->used --;
+	if(entry->used == 0) {
+		insert_free_list(entry->cache, entry);
+		entry->cache->used --;
+
+		/*
+		 * if the wait_free flag is set, one or more threads may be
+		 * waiting on this buffer
+		 */
+		if(entry->cache->wait_free) {
+			entry->cache->wait_free = FALSE;
+			pthread_cond_broadcast(&entry->cache->wait_for_free);
+		}
+	}
+
+	pthread_mutex_unlock(&entry->cache->mutex);
+}
+
+
+void dump_cache(struct cache *cache)
+{
+	pthread_mutex_lock(&cache->mutex);
+
+	printf("Max buffers %d, Current size %d, Used %d,  %s\n",
+		cache->max_buffers, cache->count, cache->used,
+		cache->free_list ?  "Free buffers" : "No free buffers");
+
+	pthread_mutex_unlock(&cache->mutex);
+}
+
+
+char *modestr(char *str, int mode)
+{
+	int i;
+
+	strcpy(str, "----------");
+
+	for(i = 0; table[i].mask != 0; i++) {
+		if((mode & table[i].mask) == table[i].value)
+			str[table[i].position] = table[i].mode;
+	}
+
+	return str;
+}
+
+
+#define TOTALCHARS  25
+int print_filename(char *pathname, struct inode *inode)
+{
+	char str[11], dummy[12], dummy2[12]; /* overflow safe */
+	char *userstr, *groupstr;
+	int padchars;
+	struct passwd *user;
+	struct group *group;
+	struct tm *t;
+
+	if(short_ls) {
+		printf("%s\n", pathname);
+		return 1;
+	}
+
+	user = getpwuid(inode->uid);
+	if(user == NULL) {
+		int res = snprintf(dummy, 12, "%d", inode->uid);
+		if(res < 0)
+			EXIT_UNSQUASH("snprintf failed in print_filename()\n");
+		else if(res >= 12)
+			/* unsigned int shouldn't ever need more than 11 bytes
+			 * (including terminating '\0') to print in base 10 */
+			userstr = "*";
+		else
+			userstr = dummy;
+	} else
+		userstr = user->pw_name;
+		 
+	group = getgrgid(inode->gid);
+	if(group == NULL) {
+		int res = snprintf(dummy2, 12, "%d", inode->gid);
+		if(res < 0)
+			EXIT_UNSQUASH("snprintf failed in print_filename()\n");
+		else if(res >= 12)
+			/* unsigned int shouldn't ever need more than 11 bytes
+			 * (including terminating '\0') to print in base 10 */
+			groupstr = "*";
+		else
+			groupstr = dummy2;
+	} else
+		groupstr = group->gr_name;
+
+	printf("%s %s/%s ", modestr(str, inode->mode), userstr, groupstr);
+
+	switch(inode->mode & S_IFMT) {
+		case S_IFREG:
+		case S_IFDIR:
+		case S_IFSOCK:
+		case S_IFIFO:
+		case S_IFLNK:
+			padchars = TOTALCHARS - strlen(userstr) -
+				strlen(groupstr);
+
+			printf("%*lld ", padchars > 0 ? padchars : 0,
+				inode->data);
+			break;
+		case S_IFCHR:
+		case S_IFBLK:
+			padchars = TOTALCHARS - strlen(userstr) -
+				strlen(groupstr) - 7; 
+
+			printf("%*s%3d,%3d ", padchars > 0 ? padchars : 0, " ",
+				(int) inode->data >> 8, (int) inode->data &
+				0xff);
+			break;
+	}
+
+	t = localtime(&inode->time);
+
+	printf("%d-%02d-%02d %02d:%02d %s", t->tm_year + 1900, t->tm_mon + 1,
+		t->tm_mday, t->tm_hour, t->tm_min, pathname);
+	if((inode->mode & S_IFMT) == S_IFLNK)
+		printf(" -> %s", inode->symlink);
+	printf("\n");
+		
+	return 1;
+}
+	
+
+void add_entry(struct hash_table_entry *hash_table[], long long start,
+	int bytes)
+{
+	int hash = CALCULATE_HASH(start);
+	struct hash_table_entry *hash_table_entry;
+
+	hash_table_entry = malloc(sizeof(struct hash_table_entry));
+	if(hash_table_entry == NULL)
+		EXIT_UNSQUASH("Out of memory in add_entry\n");
+
+	hash_table_entry->start = start;
+	hash_table_entry->bytes = bytes;
+	hash_table_entry->next = hash_table[hash];
+	hash_table[hash] = hash_table_entry;
+}
+
+
+int lookup_entry(struct hash_table_entry *hash_table[], long long start)
+{
+	int hash = CALCULATE_HASH(start);
+	struct hash_table_entry *hash_table_entry;
+
+	for(hash_table_entry = hash_table[hash]; hash_table_entry;
+				hash_table_entry = hash_table_entry->next)
+
+		if(hash_table_entry->start == start)
+			return hash_table_entry->bytes;
+
+	return -1;
+}
+
+
+int read_fs_bytes(int fd, long long byte, int bytes, void *buff)
+{
+	off_t off = byte;
+	int res, count;
+
+	TRACE("read_bytes: reading from position 0x%llx, bytes %d\n", byte,
+		bytes);
+
+	if(lseek(fd, off, SEEK_SET) == -1) {
+		ERROR("Lseek failed because %s\n", strerror(errno));
+		return FALSE;
+	}
+
+	for(count = 0; count < bytes; count += res) {
+		res = read(fd, buff + count, bytes - count);
+		if(res < 1) {
+			if(res == 0) {
+				ERROR("Read on filesystem failed because "
+					"EOF\n");
+				return FALSE;
+			} else if(errno != EINTR) {
+				ERROR("Read on filesystem failed because %s\n",
+						strerror(errno));
+				return FALSE;
+			} else
+				res = 0;
+		}
+	}
+
+	return TRUE;
+}
+
+
+int read_block(int fd, long long start, long long *next, int expected,
+								void *block)
+{
+	unsigned short c_byte;
+	int offset = 2, res, compressed;
+	int outlen = expected ? expected : SQUASHFS_METADATA_SIZE;
+	
+	if(swap) {
+		if(read_fs_bytes(fd, start, 2, &c_byte) == FALSE)
+			goto failed;
+		c_byte = (c_byte >> 8) | ((c_byte & 0xff) << 8);
+	} else 
+		if(read_fs_bytes(fd, start, 2, &c_byte) == FALSE)
+			goto failed;
+
+	TRACE("read_block: block @0x%llx, %d %s bytes\n", start,
+		SQUASHFS_COMPRESSED_SIZE(c_byte), SQUASHFS_COMPRESSED(c_byte) ?
+		"compressed" : "uncompressed");
+
+	if(SQUASHFS_CHECK_DATA(sBlk.s.flags))
+		offset = 3;
+
+	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 + offset, c_byte, buffer);
+		if(res == FALSE)
+			goto failed;
+
+		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);
+			goto failed;
+		}
+	} else {
+		res = read_fs_bytes(fd, start + offset, c_byte, block);
+		if(res == FALSE)
+			goto failed;
+		res = c_byte;
+	}
+
+	if(next)
+		*next = start + offset + c_byte;
+
+	/*
+	 * if expected, then check the (uncompressed) return data
+	 * is of the expected size
+	 */
+	if(expected && expected != res)
+		return 0;
+	else
+		return res;
+
+failed:
+	ERROR("read_block: failed to read block @0x%llx\n", start);
+	return FALSE;
+}
+
+
+int read_data_block(long long start, unsigned int size, char *block)
+{
+	int error, res;
+	int c_byte = SQUASHFS_COMPRESSED_SIZE_BLOCK(size);
+
+	TRACE("read_data_block: block @0x%llx, %d %s bytes\n", start,
+		c_byte, SQUASHFS_COMPRESSED_BLOCK(size) ? "compressed" :
+		"uncompressed");
+
+	if(SQUASHFS_COMPRESSED_BLOCK(size)) {
+		if(read_fs_bytes(fd, start, c_byte, data) == FALSE)
+			goto failed;
+
+		res = compressor_uncompress(comp, block, data, c_byte,
+			block_size, &error);
+
+		if(res == -1) {
+			ERROR("%s uncompress failed with error code %d\n",
+				comp->name, error);
+			goto failed;
+		}
+
+		return res;
+	} else {
+		if(read_fs_bytes(fd, start, c_byte, block) == FALSE)
+			goto failed;
+
+		return c_byte;
+	}
+
+failed:
+	ERROR("read_data_block: failed to read block @0x%llx, size %d\n", start,
+		c_byte);
+	return FALSE;
+}
+
+
+int read_inode_table(long long start, long long end)
+{
+	int size = 0, bytes = 0, res;
+
+	TRACE("read_inode_table: start %lld, end %lld\n", start, end);
+
+	while(start < end) {
+		if(size - bytes < SQUASHFS_METADATA_SIZE) {
+			inode_table = realloc(inode_table, size +=
+				SQUASHFS_METADATA_SIZE);
+			if(inode_table == NULL) {
+				ERROR("Out of memory in read_inode_table");
+				goto failed;
+			}
+		}
+
+		add_entry(inode_table_hash, start, bytes);
+
+		res = read_block(fd, start, &start, 0, inode_table + bytes);
+		if(res == 0) {
+			ERROR("read_inode_table: failed to read block\n");
+			goto failed;
+		}
+		bytes += res;
+
+		/*
+		 * 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 && res != SQUASHFS_METADATA_SIZE) {
+			ERROR("read_inode_table: metadata block should be %d "
+				"bytes in length, it is %d bytes\n",
+				SQUASHFS_METADATA_SIZE, res);
+			
+			goto failed;
+		}
+	}
+
+	return TRUE;
+
+failed:
+	free(inode_table);
+	return FALSE;
+}
+
+
+int set_attributes(char *pathname, int mode, uid_t uid, gid_t guid, time_t time,
+	unsigned int xattr, unsigned int set_mode)
+{
+	struct utimbuf times = { time, time };
+
+	write_xattr(pathname, xattr);
+
+	if(utime(pathname, &times) == -1) {
+		ERROR("set_attributes: failed to set time on %s, because %s\n",
+			pathname, strerror(errno));
+		return FALSE;
+	}
+
+	if(root_process) {
+		if(chown(pathname, uid, guid) == -1) {
+			ERROR("set_attributes: failed to change uid and gids "
+				"on %s, because %s\n", pathname,
+				strerror(errno));
+			return FALSE;
+		}
+	} else
+		mode &= ~07000;
+
+	if((set_mode || (mode & 07000)) && chmod(pathname, (mode_t) mode) == -1) {
+		ERROR("set_attributes: failed to change mode %s, because %s\n",
+			pathname, strerror(errno));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+int write_bytes(int fd, char *buff, int bytes)
+{
+	int res, count;
+
+	for(count = 0; count < bytes; count += res) {
+		res = write(fd, buff + count, bytes - count);
+		if(res == -1) {
+			if(errno != EINTR) {
+				ERROR("Write on output file failed because "
+					"%s\n", strerror(errno));
+				return -1;
+			}
+			res = 0;
+		}
+	}
+
+	return 0;
+}
+
+
+int lseek_broken = FALSE;
+char *zero_data = NULL;
+
+int write_block(int file_fd, char *buffer, int size, long long hole, int sparse)
+{
+	off_t off = hole;
+
+	if(hole) {
+		if(sparse && lseek_broken == FALSE) {
+			 int error = lseek(file_fd, off, SEEK_CUR);
+			 if(error == -1)
+				/* failed to seek beyond end of file */
+				lseek_broken = TRUE;
+		}
+
+		if((sparse == FALSE || lseek_broken) && zero_data == NULL) {
+			if((zero_data = malloc(block_size)) == NULL)
+				EXIT_UNSQUASH("write_block: failed to alloc "
+					"zero data block\n");
+			memset(zero_data, 0, block_size);
+		}
+
+		if(sparse == FALSE || lseek_broken) {
+			int blocks = (hole + block_size -1) / block_size;
+			int avail_bytes, i;
+			for(i = 0; i < blocks; i++, hole -= avail_bytes) {
+				avail_bytes = hole > block_size ? block_size :
+					hole;
+				if(write_bytes(file_fd, zero_data, avail_bytes)
+						== -1)
+					goto failure;
+			}
+		}
+	}
+
+	if(write_bytes(file_fd, buffer, size) == -1)
+		goto failure;
+
+	return TRUE;
+
+failure:
+	return FALSE;
+}
+
+
+pthread_mutex_t open_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t open_empty = PTHREAD_COND_INITIALIZER;
+int open_unlimited, open_count;
+#define OPEN_FILE_MARGIN 10
+
+
+void open_init(int count)
+{
+	open_count = count;
+	open_unlimited = count == -1;
+}
+
+
+int open_wait(char *pathname, int flags, mode_t mode)
+{
+	if (!open_unlimited) {
+		pthread_mutex_lock(&open_mutex);
+		while (open_count == 0)
+			pthread_cond_wait(&open_empty, &open_mutex);
+		open_count --;
+		pthread_mutex_unlock(&open_mutex);
+	}
+
+	return open(pathname, flags, mode);
+}
+
+
+void close_wake(int fd)
+{
+	close(fd);
+
+	if (!open_unlimited) {
+		pthread_mutex_lock(&open_mutex);
+		open_count ++;
+		pthread_cond_signal(&open_empty);
+		pthread_mutex_unlock(&open_mutex);
+	}
+}
+
+
+void queue_file(char *pathname, int file_fd, struct inode *inode)
+{
+	struct squashfs_file *file = malloc(sizeof(struct squashfs_file));
+	if(file == NULL)
+		EXIT_UNSQUASH("queue_file: unable to malloc file\n");
+
+	file->fd = file_fd;
+	file->file_size = inode->data;
+	file->mode = inode->mode;
+	file->gid = inode->gid;
+	file->uid = inode->uid;
+	file->time = inode->time;
+	file->pathname = strdup(pathname);
+	file->blocks = inode->blocks + (inode->frag_bytes > 0);
+	file->sparse = inode->sparse;
+	file->xattr = inode->xattr;
+	queue_put(to_writer, file);
+}
+
+
+void queue_dir(char *pathname, struct dir *dir)
+{
+	struct squashfs_file *file = malloc(sizeof(struct squashfs_file));
+	if(file == NULL)
+		EXIT_UNSQUASH("queue_dir: unable to malloc file\n");
+
+	file->fd = -1;
+	file->mode = dir->mode;
+	file->gid = dir->guid;
+	file->uid = dir->uid;
+	file->time = dir->mtime;
+	file->pathname = strdup(pathname);
+	file->xattr = dir->xattr;
+	queue_put(to_writer, file);
+}
+
+
+int write_file(struct inode *inode, char *pathname)
+{
+	unsigned int file_fd, i;
+	unsigned int *block_list;
+	int file_end = inode->data / block_size;
+	long long start = inode->start;
+
+	TRACE("write_file: regular file, blocks %d\n", inode->blocks);
+
+	file_fd = open_wait(pathname, O_CREAT | O_WRONLY |
+		(force ? O_TRUNC : 0), (mode_t) inode->mode & 0777);
+	if(file_fd == -1) {
+		ERROR("write_file: failed to create file %s, because %s\n",
+			pathname, strerror(errno));
+		return FALSE;
+	}
+
+	block_list = malloc(inode->blocks * sizeof(unsigned int));
+	if(block_list == NULL)
+		EXIT_UNSQUASH("write_file: unable to malloc block list\n");
+
+	s_ops.read_block_list(block_list, inode->block_ptr, inode->blocks);
+
+	/*
+	 * the writer thread is queued a squashfs_file structure describing the
+ 	 * file.  If the file has one or more blocks or a fragment they are
+ 	 * queued separately (references to blocks in the cache).
+ 	 */
+	queue_file(pathname, file_fd, inode);
+
+	for(i = 0; i < inode->blocks; i++) {
+		int c_byte = SQUASHFS_COMPRESSED_SIZE_BLOCK(block_list[i]);
+		struct file_entry *block = malloc(sizeof(struct file_entry));
+
+		if(block == NULL)
+			EXIT_UNSQUASH("write_file: unable to malloc file\n");
+		block->offset = 0;
+		block->size = i == file_end ? inode->data & (block_size - 1) :
+			block_size;
+		if(block_list[i] == 0) /* sparse block */
+			block->buffer = NULL;
+		else {
+			block->buffer = cache_get(data_cache, start,
+				block_list[i]);
+			start += c_byte;
+		}
+		queue_put(to_writer, block);
+	}
+
+	if(inode->frag_bytes) {
+		int size;
+		long long start;
+		struct file_entry *block = malloc(sizeof(struct file_entry));
+
+		if(block == NULL)
+			EXIT_UNSQUASH("write_file: unable to malloc file\n");
+		s_ops.read_fragment(inode->fragment, &start, &size);
+		block->buffer = cache_get(fragment_cache, start, size);
+		block->offset = inode->offset;
+		block->size = inode->frag_bytes;
+		queue_put(to_writer, block);
+	}
+
+	free(block_list);
+	return TRUE;
+}
+
+
+int create_inode(char *pathname, struct inode *i)
+{
+	TRACE("create_inode: pathname %s\n", pathname);
+
+	if(created_inode[i->inode_number - 1]) {
+		TRACE("create_inode: hard link\n");
+		if(force)
+			unlink(pathname);
+
+		if(link(created_inode[i->inode_number - 1], pathname) == -1) {
+			ERROR("create_inode: failed to create hardlink, "
+				"because %s\n", strerror(errno));
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	switch(i->type) {
+		case SQUASHFS_FILE_TYPE:
+		case SQUASHFS_LREG_TYPE:
+			TRACE("create_inode: regular file, file_size %lld, "
+				"blocks %d\n", i->data, i->blocks);
+
+			if(write_file(i, pathname))
+				file_count ++;
+			break;
+		case SQUASHFS_SYMLINK_TYPE:
+		case SQUASHFS_LSYMLINK_TYPE:
+			TRACE("create_inode: symlink, symlink_size %lld\n",
+				i->data);
+
+			if(force)
+				unlink(pathname);
+
+			if(symlink(i->symlink, pathname) == -1) {
+				ERROR("create_inode: failed to create symlink "
+					"%s, because %s\n", pathname,
+					strerror(errno));
+				break;
+			}
+
+			write_xattr(pathname, i->xattr);
+	
+			if(root_process) {
+				if(lchown(pathname, i->uid, i->gid) == -1)
+					ERROR("create_inode: failed to change "
+						"uid and gids on %s, because "
+						"%s\n", pathname,
+						strerror(errno));
+			}
+
+			sym_count ++;
+			break;
+ 		case SQUASHFS_BLKDEV_TYPE:
+	 	case SQUASHFS_CHRDEV_TYPE:
+ 		case SQUASHFS_LBLKDEV_TYPE:
+	 	case SQUASHFS_LCHRDEV_TYPE: {
+			int chrdev = i->type == SQUASHFS_CHRDEV_TYPE;
+			TRACE("create_inode: dev, rdev 0x%llx\n", i->data);
+
+			if(root_process) {
+				if(force)
+					unlink(pathname);
+
+				if(mknod(pathname, chrdev ? S_IFCHR : S_IFBLK,
+						makedev((i->data >> 8) & 0xff,
+						i->data & 0xff)) == -1) {
+					ERROR("create_inode: failed to create "
+						"%s device %s, because %s\n",
+						chrdev ? "character" : "block",
+						pathname, strerror(errno));
+					break;
+				}
+				set_attributes(pathname, i->mode, i->uid,
+					i->gid, i->time, i->xattr, TRUE);
+				dev_count ++;
+			} else
+				ERROR("create_inode: could not create %s "
+					"device %s, because you're not "
+					"superuser!\n", chrdev ? "character" :
+					"block", pathname);
+			break;
+		}
+		case SQUASHFS_FIFO_TYPE:
+		case SQUASHFS_LFIFO_TYPE:
+			TRACE("create_inode: fifo\n");
+
+			if(force)
+				unlink(pathname);
+
+			if(mknod(pathname, S_IFIFO, 0) == -1) {
+				ERROR("create_inode: failed to create fifo %s, "
+					"because %s\n", pathname,
+					strerror(errno));
+				break;
+			}
+			set_attributes(pathname, i->mode, i->uid, i->gid,
+				i->time, i->xattr, TRUE);
+			fifo_count ++;
+			break;
+		case SQUASHFS_SOCKET_TYPE:
+		case SQUASHFS_LSOCKET_TYPE:
+			TRACE("create_inode: socket\n");
+			ERROR("create_inode: socket %s ignored\n", pathname);
+			break;
+		default:
+			ERROR("Unknown inode type %d in create_inode_table!\n",
+				i->type);
+			return FALSE;
+	}
+
+	created_inode[i->inode_number - 1] = strdup(pathname);
+
+	return TRUE;
+}
+
+
+int read_directory_table(long long start, long long end)
+{
+	int bytes = 0, size = 0, res;
+
+	TRACE("read_directory_table: start %lld, end %lld\n", start, end);
+
+	while(start < end) {
+		if(size - bytes < SQUASHFS_METADATA_SIZE) {
+			directory_table = realloc(directory_table, size +=
+				SQUASHFS_METADATA_SIZE);
+			if(directory_table == NULL) {
+				ERROR("Out of memory in "
+						"read_directory_table\n");
+				goto failed;
+			}
+		}
+
+		add_entry(directory_table_hash, start, bytes);
+
+		res = read_block(fd, start, &start, 0, directory_table + bytes);
+		if(res == 0) {
+			ERROR("read_directory_table: failed to read block\n");
+			goto failed;
+		}
+
+		bytes += res;
+
+		/*
+		 * If this is not the last metadata block in the directory 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 && res != SQUASHFS_METADATA_SIZE) {
+			ERROR("read_directory_table: metadata block "
+				"should be %d bytes in length, it is %d "
+				"bytes\n", SQUASHFS_METADATA_SIZE, res);
+			goto failed;
+		}
+	}
+
+	return TRUE;
+
+failed:
+	free(directory_table);
+	return FALSE;
+}
+
+
+int squashfs_readdir(struct dir *dir, char **name, unsigned int *start_block,
+unsigned int *offset, unsigned int *type)
+{
+	if(dir->cur_entry == dir->dir_count)
+		return FALSE;
+
+	*name = dir->dirs[dir->cur_entry].name;
+	*start_block = dir->dirs[dir->cur_entry].start_block;
+	*offset = dir->dirs[dir->cur_entry].offset;
+	*type = dir->dirs[dir->cur_entry].type;
+	dir->cur_entry ++;
+
+	return TRUE;
+}
+
+
+void squashfs_closedir(struct dir *dir)
+{
+	free(dir->dirs);
+	free(dir);
+}
+
+
+char *get_component(char *target, char **targname)
+{
+	char *start;
+
+	while(*target == '/')
+		target ++;
+
+	start = target;
+	while(*target != '/' && *target != '\0')
+		target ++;
+
+	*targname = strndup(start, target - start);
+
+	while(*target == '/')
+		target ++;
+
+	return target;
+}
+
+
+void free_path(struct pathname *paths)
+{
+	int i;
+
+	for(i = 0; i < paths->names; i++) {
+		if(paths->name[i].paths)
+			free_path(paths->name[i].paths);
+		free(paths->name[i].name);
+		if(paths->name[i].preg) {
+			regfree(paths->name[i].preg);
+			free(paths->name[i].preg);
+		}
+	}
+
+	free(paths);
+}
+
+
+struct pathname *add_path(struct pathname *paths, char *target, char *alltarget)
+{
+	char *targname;
+	int i, error;
+
+	TRACE("add_path: adding \"%s\" extract file\n", target);
+
+	target = get_component(target, &targname);
+
+	if(paths == NULL) {
+		paths = malloc(sizeof(struct pathname));
+		if(paths == NULL)
+			EXIT_UNSQUASH("failed to allocate paths\n");
+
+		paths->names = 0;
+		paths->name = NULL;
+	}
+
+	for(i = 0; i < paths->names; i++)
+		if(strcmp(paths->name[i].name, targname) == 0)
+			break;
+
+	if(i == paths->names) {
+		/*
+		 * allocate new name entry
+		 */
+		paths->names ++;
+		paths->name = realloc(paths->name, (i + 1) *
+			sizeof(struct path_entry));
+		if(paths->name == NULL)
+			EXIT_UNSQUASH("Out of memory in add_path\n");	
+		paths->name[i].name = targname;
+		paths->name[i].paths = NULL;
+		if(use_regex) {
+			paths->name[i].preg = malloc(sizeof(regex_t));
+			if(paths->name[i].preg == NULL)
+				EXIT_UNSQUASH("Out of memory in add_path\n");
+			error = regcomp(paths->name[i].preg, targname,
+				REG_EXTENDED|REG_NOSUB);
+			if(error) {
+				char str[1024]; /* overflow safe */
+
+				regerror(error, paths->name[i].preg, str, 1024);
+				EXIT_UNSQUASH("invalid regex %s in export %s, "
+					"because %s\n", targname, alltarget,
+					str);
+			}
+		} else
+			paths->name[i].preg = NULL;
+
+		if(target[0] == '\0')
+			/*
+			 * at leaf pathname component
+			*/
+			paths->name[i].paths = NULL;
+		else
+			/*
+			 * recurse adding child components
+			 */
+			paths->name[i].paths = add_path(NULL, target, alltarget);
+	} else {
+		/*
+		 * existing matching entry
+		 */
+		free(targname);
+
+		if(paths->name[i].paths == NULL) {
+			/*
+			 * No sub-directory which means this is the leaf
+			 * component of a pre-existing extract which subsumes
+			 * the extract currently being added, in which case stop
+			 * adding components
+			 */
+		} else if(target[0] == '\0') {
+			/*
+			 * at leaf pathname component and child components exist
+			 * from more specific extracts, delete as they're
+			 * subsumed by this extract
+			 */
+			free_path(paths->name[i].paths);
+			paths->name[i].paths = NULL;
+		} else
+			/*
+			 * recurse adding child components
+			 */
+			add_path(paths->name[i].paths, target, alltarget);
+	}
+
+	return paths;
+}
+
+
+struct pathnames *init_subdir()
+{
+	struct pathnames *new = malloc(sizeof(struct pathnames));
+	if(new == NULL)
+		EXIT_UNSQUASH("Out of memory in init_subdir\n");
+	new->count = 0;
+	return new;
+}
+
+
+struct pathnames *add_subdir(struct pathnames *paths, struct pathname *path)
+{
+	if(paths->count % PATHS_ALLOC_SIZE == 0) {
+		paths = realloc(paths, sizeof(struct pathnames *) +
+			(paths->count + PATHS_ALLOC_SIZE) *
+			sizeof(struct pathname *));
+		if(paths == NULL)
+			EXIT_UNSQUASH("Out of memory in add_subdir\n");
+	}
+
+	paths->path[paths->count++] = path;
+	return paths;
+}
+
+
+void free_subdir(struct pathnames *paths)
+{
+	free(paths);
+}
+
+
+int matches(struct pathnames *paths, char *name, struct pathnames **new)
+{
+	int i, n;
+
+	if(paths == NULL) {
+		*new = NULL;
+		return TRUE;
+	}
+
+	*new = init_subdir();
+
+	for(n = 0; n < paths->count; n++) {
+		struct pathname *path = paths->path[n];
+		for(i = 0; i < path->names; i++) {
+			int match = use_regex ?
+				regexec(path->name[i].preg, name, (size_t) 0,
+				NULL, 0) == 0 : fnmatch(path->name[i].name,
+				name, FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) ==
+				0;
+			if(match && path->name[i].paths == NULL)
+				/*
+				 * match on a leaf component, any subdirectories
+				 * will implicitly match, therefore return an
+				 * empty new search set
+				 */
+				goto empty_set;
+
+			if(match)
+				/*
+				 * match on a non-leaf component, add any
+				 * subdirectories to the new set of
+				 * subdirectories to scan for this name
+				 */
+				*new = add_subdir(*new, path->name[i].paths);
+		}
+	}
+
+	if((*new)->count == 0) {
+		/*
+		 * no matching names found, delete empty search set, and return
+		 * FALSE
+		 */
+		free_subdir(*new);
+		*new = NULL;
+		return FALSE;
+	}
+
+	/*
+	 * one or more matches with sub-directories found (no leaf matches),
+	 * return new search set and return TRUE
+	 */
+	return TRUE;
+
+empty_set:
+	/*
+	 * found matching leaf exclude, return empty search set and return TRUE
+	 */
+	free_subdir(*new);
+	*new = NULL;
+	return TRUE;
+}
+
+
+void pre_scan(char *parent_name, unsigned int start_block, unsigned int offset,
+	struct pathnames *paths)
+{
+	unsigned int type;
+	char *name;
+	struct pathnames *new;
+	struct inode *i;
+	struct dir *dir = s_ops.squashfs_opendir(start_block, offset, &i);
+
+	if(dir == NULL)
+		return;
+
+	while(squashfs_readdir(dir, &name, &start_block, &offset, &type)) {
+		struct inode *i;
+		char *pathname;
+		int res;
+
+		TRACE("pre_scan: name %s, start_block %d, offset %d, type %d\n",
+			name, start_block, offset, type);
+
+		if(!matches(paths, name, &new))
+			continue;
+
+		res = asprintf(&pathname, "%s/%s", parent_name, name);
+		if(res == -1)
+			EXIT_UNSQUASH("asprintf failed in dir_scan\n");
+
+		if(type == SQUASHFS_DIR_TYPE)
+			pre_scan(parent_name, start_block, offset, new);
+		else if(new == NULL) {
+			if(type == SQUASHFS_FILE_TYPE ||
+					type == SQUASHFS_LREG_TYPE) {
+				i = s_ops.read_inode(start_block, offset);
+				if(created_inode[i->inode_number - 1] == NULL) {
+					created_inode[i->inode_number - 1] =
+						(char *) i;
+					total_blocks += (i->data +
+						(block_size - 1)) >> block_log;
+				}
+				total_files ++;
+			}
+			total_inodes ++;
+		}
+
+		free_subdir(new);
+		free(pathname);
+	}
+
+	squashfs_closedir(dir);
+}
+
+
+void dir_scan(char *parent_name, unsigned int start_block, unsigned int offset,
+	struct pathnames *paths)
+{
+	unsigned int type;
+	char *name;
+	struct pathnames *new;
+	struct inode *i;
+	struct dir *dir = s_ops.squashfs_opendir(start_block, offset, &i);
+
+	if(dir == NULL) {
+		ERROR("dir_scan: failed to read directory %s, skipping\n",
+			parent_name);
+		return;
+	}
+
+	if(lsonly || info)
+		print_filename(parent_name, i);
+
+	if(!lsonly) {
+		/*
+		 * Make directory with default User rwx permissions rather than
+		 * the permissions from the filesystem, as these may not have
+		 * write/execute permission.  These are fixed up later in
+		 * set_attributes().
+		 */
+		int res = mkdir(parent_name, S_IRUSR|S_IWUSR|S_IXUSR);
+		if(res == -1) {
+			/*
+			 * Skip directory if mkdir fails, unless we're
+			 * forcing and the error is -EEXIST
+			 */
+			if(!force || errno != EEXIST) {
+				ERROR("dir_scan: failed to make directory %s, "
+					"because %s\n", parent_name,
+					strerror(errno));
+				squashfs_closedir(dir);
+				return;
+			} 
+
+			/*
+			 * Try to change permissions of existing directory so
+			 * that we can write to it
+			 */
+			res = chmod(parent_name, S_IRUSR|S_IWUSR|S_IXUSR);
+			if (res == -1)
+				ERROR("dir_scan: failed to change permissions "
+					"for directory %s, because %s\n",
+					parent_name, strerror(errno));
+		}
+	}
+
+	while(squashfs_readdir(dir, &name, &start_block, &offset, &type)) {
+		char *pathname;
+		int res;
+
+		TRACE("dir_scan: name %s, start_block %d, offset %d, type %d\n",
+			name, start_block, offset, type);
+
+
+		if(!matches(paths, name, &new))
+			continue;
+
+		res = asprintf(&pathname, "%s/%s", parent_name, name);
+		if(res == -1)
+			EXIT_UNSQUASH("asprintf failed in dir_scan\n");
+
+		if(type == SQUASHFS_DIR_TYPE) {
+			dir_scan(pathname, start_block, offset, new);
+			free(pathname);
+		} else if(new == NULL) {
+			update_info(pathname);
+
+			i = s_ops.read_inode(start_block, offset);
+
+			if(lsonly || info)
+				print_filename(pathname, i);
+
+			if(!lsonly)
+				create_inode(pathname, i);
+
+			if(i->type == SQUASHFS_SYMLINK_TYPE ||
+					i->type == SQUASHFS_LSYMLINK_TYPE)
+				free(i->symlink);
+		} else
+			free(pathname);
+
+		free_subdir(new);
+	}
+
+	if(!lsonly)
+		queue_dir(parent_name, dir);
+
+	squashfs_closedir(dir);
+	dir_count ++;
+}
+
+
+void squashfs_stat(char *source)
+{
+	time_t mkfs_time = (time_t) sBlk.s.mkfs_time;
+	char *mkfs_str = ctime(&mkfs_time);
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+	printf("Found a valid %sSQUASHFS %d:%d superblock on %s.\n",
+		sBlk.s.s_major == 4 ? "" : swap ? "little endian " :
+		"big endian ", sBlk.s.s_major, sBlk.s.s_minor, source);
+#else
+	printf("Found a valid %sSQUASHFS %d:%d superblock on %s.\n",
+		sBlk.s.s_major == 4 ? "" : swap ? "big endian " :
+		"little endian ", sBlk.s.s_major, sBlk.s.s_minor, source);
+#endif
+
+	printf("Creation or last append time %s", mkfs_str ? mkfs_str :
+		"failed to get time\n");
+	printf("Filesystem size %.2f Kbytes (%.2f Mbytes)\n",
+		sBlk.s.bytes_used / 1024.0, sBlk.s.bytes_used /
+		(1024.0 * 1024.0));
+
+	if(sBlk.s.s_major == 4) {
+		printf("Compression %s\n", comp->name);
+
+		if(SQUASHFS_COMP_OPTS(sBlk.s.flags)) {
+			char buffer[SQUASHFS_METADATA_SIZE] __attribute__ ((aligned));
+			int bytes;
+
+			bytes = read_block(fd, sizeof(sBlk.s), NULL, 0, buffer);
+			if(bytes == 0) {
+				ERROR("Failed to read compressor options\n");
+				return;
+			}
+
+			compressor_display_options(comp, buffer, bytes);
+		}
+	}
+
+	printf("Block size %d\n", sBlk.s.block_size);
+	printf("Filesystem is %sexportable via NFS\n",
+		SQUASHFS_EXPORTABLE(sBlk.s.flags) ? "" : "not ");
+	printf("Inodes are %scompressed\n",
+		SQUASHFS_UNCOMPRESSED_INODES(sBlk.s.flags) ? "un" : "");
+	printf("Data is %scompressed\n",
+		SQUASHFS_UNCOMPRESSED_DATA(sBlk.s.flags) ? "un" : "");
+
+	if(sBlk.s.s_major > 1) {
+		if(SQUASHFS_NO_FRAGMENTS(sBlk.s.flags))
+			printf("Fragments are not stored\n");
+		else {
+			printf("Fragments are %scompressed\n",
+				SQUASHFS_UNCOMPRESSED_FRAGMENTS(sBlk.s.flags) ?
+				"un" : "");
+			printf("Always-use-fragments option is %sspecified\n",
+				SQUASHFS_ALWAYS_FRAGMENTS(sBlk.s.flags) ? "" :
+				"not ");
+		}
+	}
+
+	if(sBlk.s.s_major == 4) {
+		if(SQUASHFS_NO_XATTRS(sBlk.s.flags))
+			printf("Xattrs are not stored\n");
+		else
+			printf("Xattrs are %scompressed\n",
+				SQUASHFS_UNCOMPRESSED_XATTRS(sBlk.s.flags) ?
+				"un" : "");
+	}
+
+	if(sBlk.s.s_major < 4)
+			printf("Check data is %spresent in the filesystem\n",
+				SQUASHFS_CHECK_DATA(sBlk.s.flags) ? "" :
+				"not ");
+
+	if(sBlk.s.s_major > 1)
+		printf("Duplicates are %sremoved\n",
+			SQUASHFS_DUPLICATES(sBlk.s.flags) ? "" : "not ");
+	else
+		printf("Duplicates are removed\n");
+
+	if(sBlk.s.s_major > 1)
+		printf("Number of fragments %d\n", sBlk.s.fragments);
+
+	printf("Number of inodes %d\n", sBlk.s.inodes);
+
+	if(sBlk.s.s_major == 4)
+		printf("Number of ids %d\n", sBlk.s.no_ids);
+	else {
+		printf("Number of uids %d\n", sBlk.no_uids);
+		printf("Number of gids %d\n", sBlk.no_guids);
+	}
+
+	TRACE("sBlk.s.inode_table_start 0x%llx\n", sBlk.s.inode_table_start);
+	TRACE("sBlk.s.directory_table_start 0x%llx\n",
+		sBlk.s.directory_table_start);
+
+	if(sBlk.s.s_major > 1)
+		TRACE("sBlk.s.fragment_table_start 0x%llx\n\n",
+			sBlk.s.fragment_table_start);
+
+	if(sBlk.s.s_major > 2)
+		TRACE("sBlk.s.lookup_table_start 0x%llx\n\n",
+			sBlk.s.lookup_table_start);
+
+	if(sBlk.s.s_major == 4) {
+		TRACE("sBlk.s.id_table_start 0x%llx\n", sBlk.s.id_table_start);
+		TRACE("sBlk.s.xattr_id_table_start 0x%llx\n",
+			sBlk.s.xattr_id_table_start);
+	} else {
+		TRACE("sBlk.uid_start 0x%llx\n", sBlk.uid_start);
+		TRACE("sBlk.guid_start 0x%llx\n", sBlk.guid_start);
+	}
+}
+
+
+int check_compression(struct compressor *comp)
+{
+	int res, bytes = 0;
+	char buffer[SQUASHFS_METADATA_SIZE] __attribute__ ((aligned));
+
+	if(!comp->supported) {
+		ERROR("Filesystem uses %s compression, this is "
+			"unsupported by this version\n", comp->name);
+		ERROR("Decompressors available:\n");
+		display_compressors("", "");
+		return 0;
+	}
+
+	/*
+	 * Read compression options from disk if present, and pass to
+	 * the compressor to ensure we know how to decompress a filesystem
+	 * compressed with these compression options.
+	 *
+	 * Note, even if there is no compression options we still call the
+	 * compressor because some compression options may be mandatory
+	 * for some compressors.
+	 */
+	if(SQUASHFS_COMP_OPTS(sBlk.s.flags)) {
+		bytes = read_block(fd, sizeof(sBlk.s), NULL, 0, buffer);
+		if(bytes == 0) {
+			ERROR("Failed to read compressor options\n");
+			return 0;
+		}
+	}
+
+	res = compressor_check_options(comp, sBlk.s.block_size, buffer, bytes);
+
+	return res != -1;
+}
+
+
+int read_super(char *source)
+{
+	squashfs_super_block_3 sBlk_3;
+	struct squashfs_super_block sBlk_4;
+
+	/*
+	 * Try to read a Squashfs 4 superblock
+	 */
+	read_fs_bytes(fd, SQUASHFS_START, sizeof(struct squashfs_super_block),
+		&sBlk_4);
+	swap = sBlk_4.s_magic != SQUASHFS_MAGIC;
+	SQUASHFS_INSWAP_SUPER_BLOCK(&sBlk_4);
+
+	if(sBlk_4.s_magic == SQUASHFS_MAGIC && sBlk_4.s_major == 4 &&
+			sBlk_4.s_minor == 0) {
+		s_ops.squashfs_opendir = squashfs_opendir_4;
+		s_ops.read_fragment = read_fragment_4;
+		s_ops.read_fragment_table = read_fragment_table_4;
+		s_ops.read_block_list = read_block_list_2;
+		s_ops.read_inode = read_inode_4;
+		s_ops.read_uids_guids = read_uids_guids_4;
+		memcpy(&sBlk, &sBlk_4, sizeof(sBlk_4));
+
+		/*
+		 * Check the compression type
+		 */
+		comp = lookup_compressor_id(sBlk.s.compression);
+		return TRUE;
+	}
+
+	/*
+ 	 * Not a Squashfs 4 superblock, try to read a squashfs 3 superblock
+ 	 * (compatible with 1 and 2 filesystems)
+ 	 */
+	read_fs_bytes(fd, SQUASHFS_START, sizeof(squashfs_super_block_3),
+		&sBlk_3);
+
+	/*
+	 * Check it is a SQUASHFS superblock
+	 */
+	swap = 0;
+	if(sBlk_3.s_magic != SQUASHFS_MAGIC) {
+		if(sBlk_3.s_magic == SQUASHFS_MAGIC_SWAP) {
+			squashfs_super_block_3 sblk;
+			ERROR("Reading a different endian SQUASHFS filesystem "
+				"on %s\n", source);
+			SQUASHFS_SWAP_SUPER_BLOCK_3(&sblk, &sBlk_3);
+			memcpy(&sBlk_3, &sblk, sizeof(squashfs_super_block_3));
+			swap = 1;
+		} else  {
+			ERROR("Can't find a SQUASHFS superblock on %s\n",
+				source);
+			goto failed_mount;
+		}
+	}
+
+	sBlk.s.s_magic = sBlk_3.s_magic;
+	sBlk.s.inodes = sBlk_3.inodes;
+	sBlk.s.mkfs_time = sBlk_3.mkfs_time;
+	sBlk.s.block_size = sBlk_3.block_size;
+	sBlk.s.fragments = sBlk_3.fragments;
+	sBlk.s.block_log = sBlk_3.block_log;
+	sBlk.s.flags = sBlk_3.flags;
+	sBlk.s.s_major = sBlk_3.s_major;
+	sBlk.s.s_minor = sBlk_3.s_minor;
+	sBlk.s.root_inode = sBlk_3.root_inode;
+	sBlk.s.bytes_used = sBlk_3.bytes_used;
+	sBlk.s.inode_table_start = sBlk_3.inode_table_start;
+	sBlk.s.directory_table_start = sBlk_3.directory_table_start;
+	sBlk.s.fragment_table_start = sBlk_3.fragment_table_start;
+	sBlk.s.lookup_table_start = sBlk_3.lookup_table_start;
+	sBlk.no_uids = sBlk_3.no_uids;
+	sBlk.no_guids = sBlk_3.no_guids;
+	sBlk.uid_start = sBlk_3.uid_start;
+	sBlk.guid_start = sBlk_3.guid_start;
+	sBlk.s.xattr_id_table_start = SQUASHFS_INVALID_BLK;
+
+	/* Check the MAJOR & MINOR versions */
+	if(sBlk.s.s_major == 1 || sBlk.s.s_major == 2) {
+		sBlk.s.bytes_used = sBlk_3.bytes_used_2;
+		sBlk.uid_start = sBlk_3.uid_start_2;
+		sBlk.guid_start = sBlk_3.guid_start_2;
+		sBlk.s.inode_table_start = sBlk_3.inode_table_start_2;
+		sBlk.s.directory_table_start = sBlk_3.directory_table_start_2;
+		
+		if(sBlk.s.s_major == 1) {
+			sBlk.s.block_size = sBlk_3.block_size_1;
+			sBlk.s.fragment_table_start = sBlk.uid_start;
+			s_ops.squashfs_opendir = squashfs_opendir_1;
+			s_ops.read_fragment_table = read_fragment_table_1;
+			s_ops.read_block_list = read_block_list_1;
+			s_ops.read_inode = read_inode_1;
+			s_ops.read_uids_guids = read_uids_guids_1;
+		} else {
+			sBlk.s.fragment_table_start =
+				sBlk_3.fragment_table_start_2;
+			s_ops.squashfs_opendir = squashfs_opendir_1;
+			s_ops.read_fragment = read_fragment_2;
+			s_ops.read_fragment_table = read_fragment_table_2;
+			s_ops.read_block_list = read_block_list_2;
+			s_ops.read_inode = read_inode_2;
+			s_ops.read_uids_guids = read_uids_guids_1;
+		}
+	} else if(sBlk.s.s_major == 3) {
+		s_ops.squashfs_opendir = squashfs_opendir_3;
+		s_ops.read_fragment = read_fragment_3;
+		s_ops.read_fragment_table = read_fragment_table_3;
+		s_ops.read_block_list = read_block_list_2;
+		s_ops.read_inode = read_inode_3;
+		s_ops.read_uids_guids = read_uids_guids_1;
+	} else {
+		ERROR("Filesystem on %s is (%d:%d), ", source, sBlk.s.s_major,
+			sBlk.s.s_minor);
+		ERROR("which is a later filesystem version than I support!\n");
+		goto failed_mount;
+	}
+
+	/*
+	 * 1.x, 2.x and 3.x filesystems use gzip compression.
+	 */
+	comp = lookup_compressor("gzip");
+	return TRUE;
+
+failed_mount:
+	return FALSE;
+}
+
+
+struct pathname *process_extract_files(struct pathname *path, char *filename)
+{
+	FILE *fd;
+	char buffer[MAX_LINE + 1]; /* overflow safe */
+	char *name;
+
+	fd = fopen(filename, "r");
+	if(fd == NULL)
+		EXIT_UNSQUASH("Failed to open extract file \"%s\" because %s\n",
+			filename, strerror(errno));
+
+	while(fgets(name = buffer, MAX_LINE + 1, fd) != NULL) {
+		int len = strlen(name);
+
+		if(len == MAX_LINE && name[len - 1] != '\n')
+			/* line too large */
+			EXIT_UNSQUASH("Line too long when reading "
+				"extract file \"%s\", larger than %d "
+				"bytes\n", filename, MAX_LINE);
+
+		/*
+		 * Remove '\n' terminator if it exists (the last line
+		 * in the file may not be '\n' terminated)
+		 */
+		if(len && name[len - 1] == '\n')
+			name[len - 1] = '\0';
+
+		/* Skip any leading whitespace */
+		while(isspace(*name))
+			name ++;
+
+		/* if comment line, skip */
+		if(*name == '#')
+			continue;
+
+		/* check for initial backslash, to accommodate
+		 * filenames with leading space or leading # character
+		 */
+		if(*name == '\\')
+			name ++;
+
+		/* if line is now empty after skipping characters, skip it */
+		if(*name == '\0')
+			continue;
+
+		path = add_path(path, name, name);
+	}
+
+	if(ferror(fd))
+		EXIT_UNSQUASH("Reading extract file \"%s\" failed because %s\n",
+			filename, strerror(errno));
+
+	fclose(fd);
+	return path;
+}
+		
+
+/*
+ * reader thread.  This thread processes read requests queued by the
+ * cache_get() routine.
+ */
+void *reader(void *arg)
+{
+	while(1) {
+		struct cache_entry *entry = queue_get(to_reader);
+		int res = read_fs_bytes(fd, entry->block,
+			SQUASHFS_COMPRESSED_SIZE_BLOCK(entry->size),
+			entry->data);
+
+		if(res && SQUASHFS_COMPRESSED_BLOCK(entry->size))
+			/*
+			 * queue successfully read block to the inflate
+			 * thread(s) for further processing
+ 			 */
+			queue_put(to_inflate, entry);
+		else
+			/*
+			 * block has either been successfully read and is
+			 * uncompressed, or an error has occurred, clear pending
+			 * flag, set error appropriately, and wake up any
+			 * threads waiting on this buffer
+			 */
+			cache_block_ready(entry, !res);
+	}
+}
+
+
+/*
+ * writer thread.  This processes file write requests queued by the
+ * write_file() routine.
+ */
+void *writer(void *arg)
+{
+	int i;
+
+	while(1) {
+		struct squashfs_file *file = queue_get(to_writer);
+		int file_fd;
+		long long hole = 0;
+		int failed = FALSE;
+		int error;
+
+		if(file == NULL) {
+			queue_put(from_writer, NULL);
+			continue;
+		} else if(file->fd == -1) {
+			/* write attributes for directory file->pathname */
+			set_attributes(file->pathname, file->mode, file->uid,
+				file->gid, file->time, file->xattr, TRUE);
+			free(file->pathname);
+			free(file);
+			continue;
+		}
+
+		TRACE("writer: regular file, blocks %d\n", file->blocks);
+
+		file_fd = file->fd;
+
+		for(i = 0; i < file->blocks; i++, cur_blocks ++) {
+			struct file_entry *block = queue_get(to_writer);
+
+			if(block->buffer == 0) { /* sparse file */
+				hole += block->size;
+				free(block);
+				continue;
+			}
+
+			cache_block_wait(block->buffer);
+
+			if(block->buffer->error)
+				failed = TRUE;
+
+			if(failed)
+				continue;
+
+			error = write_block(file_fd, block->buffer->data +
+				block->offset, block->size, hole, file->sparse);
+
+			if(error == FALSE) {
+				ERROR("writer: failed to write data block %d\n",
+					i);
+				failed = TRUE;
+			}
+
+			hole = 0;
+			cache_block_put(block->buffer);
+			free(block);
+		}
+
+		if(hole && failed == FALSE) {
+			/*
+			 * corner case for hole extending to end of file
+			 */
+			if(file->sparse == FALSE ||
+					lseek(file_fd, hole, SEEK_CUR) == -1) {
+				/*
+				 * for files which we don't want to write
+				 * sparsely, or for broken lseeks which cannot
+				 * seek beyond end of file, write_block will do
+				 * the right thing
+				 */
+				hole --;
+				if(write_block(file_fd, "\0", 1, hole,
+						file->sparse) == FALSE) {
+					ERROR("writer: failed to write sparse "
+						"data block\n");
+					failed = TRUE;
+				}
+			} else if(ftruncate(file_fd, file->file_size) == -1) {
+				ERROR("writer: failed to write sparse data "
+					"block\n");
+				failed = TRUE;
+			}
+		}
+
+		close_wake(file_fd);
+		if(failed == FALSE)
+			set_attributes(file->pathname, file->mode, file->uid,
+				file->gid, file->time, file->xattr, force);
+		else {
+			ERROR("Failed to write %s, skipping\n", file->pathname);
+			unlink(file->pathname);
+		}
+		free(file->pathname);
+		free(file);
+
+	}
+}
+
+
+/*
+ * decompress thread.  This decompresses buffers queued by the read thread
+ */
+void *inflator(void *arg)
+{
+	char tmp[block_size];
+
+	while(1) {
+		struct cache_entry *entry = queue_get(to_inflate);
+		int error, res;
+
+		res = compressor_uncompress(comp, tmp, entry->data,
+			SQUASHFS_COMPRESSED_SIZE_BLOCK(entry->size), block_size,
+			&error);
+
+		if(res == -1)
+			ERROR("%s uncompress failed with error code %d\n",
+				comp->name, error);
+		else
+			memcpy(entry->data, tmp, res);
+
+		/*
+		 * block has been either successfully decompressed, or an error
+ 		 * occurred, clear pending flag, set error appropriately and
+ 		 * wake up any threads waiting on this block
+ 		 */ 
+		cache_block_ready(entry, res == -1);
+	}
+}
+
+
+void *progress_thread(void *arg)
+{
+	struct timespec requested_time, remaining;
+	struct itimerval itimerval;
+	struct winsize winsize;
+
+	if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
+		if(isatty(STDOUT_FILENO))
+			ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
+				"columns\n");
+		columns = 80;
+	} else
+		columns = winsize.ws_col;
+	signal(SIGWINCH, sigwinch_handler);
+	signal(SIGALRM, sigalrm_handler);
+
+	itimerval.it_value.tv_sec = 0;
+	itimerval.it_value.tv_usec = 250000;
+	itimerval.it_interval.tv_sec = 0;
+	itimerval.it_interval.tv_usec = 250000;
+	setitimer(ITIMER_REAL, &itimerval, NULL);
+
+	requested_time.tv_sec = 0;
+	requested_time.tv_nsec = 250000000;
+
+	while(1) {
+		int res = nanosleep(&requested_time, &remaining);
+
+		if(res == -1 && errno != EINTR)
+			EXIT_UNSQUASH("nanosleep failed in progress thread\n");
+
+		if(progress_enabled) {
+			pthread_mutex_lock(&screen_mutex);
+			progress_bar(sym_count + dev_count +
+				fifo_count + cur_blocks, total_inodes -
+				total_files + total_blocks, columns);
+			pthread_mutex_unlock(&screen_mutex);
+		}
+	}
+}
+
+
+void initialise_threads(int fragment_buffer_size, int data_buffer_size)
+{
+	struct rlimit rlim;
+	int i, max_files, res;
+	sigset_t sigmask, old_mask;
+
+	/* block SIGQUIT and SIGHUP, these are handled by the info thread */
+	sigemptyset(&sigmask);
+	sigaddset(&sigmask, SIGQUIT);
+	sigaddset(&sigmask, SIGHUP);
+	sigaddset(&sigmask, SIGALRM);
+	if(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == -1)
+		EXIT_UNSQUASH("Failed to set signal mask in initialise_threads"
+			"\n");
+
+	/*
+	 * temporarily block these signals so the created sub-threads will
+	 * ignore them, ensuring the main thread handles them
+	 */
+	sigemptyset(&sigmask);
+	sigaddset(&sigmask, SIGINT);
+	sigaddset(&sigmask, SIGTERM);
+	if(pthread_sigmask(SIG_BLOCK, &sigmask, &old_mask) == -1)
+		EXIT_UNSQUASH("Failed to set signal mask in initialise_threads"
+			"\n");
+
+	if(processors == -1) {
+#ifndef linux
+		int mib[2];
+		size_t len = sizeof(processors);
+
+		mib[0] = CTL_HW;
+#ifdef HW_AVAILCPU
+		mib[1] = HW_AVAILCPU;
+#else
+		mib[1] = HW_NCPU;
+#endif
+
+		if(sysctl(mib, 2, &processors, &len, NULL, 0) == -1) {
+			ERROR("Failed to get number of available processors.  "
+				"Defaulting to 1\n");
+			processors = 1;
+		}
+#else
+		processors = sysconf(_SC_NPROCESSORS_ONLN);
+#endif
+	}
+
+	if(add_overflow(processors, 3) ||
+			multiply_overflow(processors + 3, sizeof(pthread_t)))
+		EXIT_UNSQUASH("Processors too large\n");
+
+	thread = malloc((3 + processors) * sizeof(pthread_t));
+	if(thread == NULL)
+		EXIT_UNSQUASH("Out of memory allocating thread descriptors\n");
+	inflator_thread = &thread[3];
+
+	/*
+	 * dimensioning the to_reader and to_inflate queues.  The size of
+	 * these queues is directly related to the amount of block
+	 * read-ahead possible.  To_reader queues block read requests to
+	 * the reader thread and to_inflate queues block decompression
+	 * requests to the inflate thread(s) (once the block has been read by
+	 * the reader thread).  The amount of read-ahead is determined by
+	 * the combined size of the data_block and fragment caches which
+	 * determine the total number of blocks which can be "in flight"
+	 * at any one time (either being read or being decompressed)
+	 *
+	 * The maximum file open limit, however, affects the read-ahead
+	 * possible, in that for normal sizes of the fragment and data block
+	 * caches, where the incoming files have few data blocks or one fragment
+	 * only, the file open limit is likely to be reached before the
+	 * caches are full.  This means the worst case sizing of the combined
+	 * sizes of the caches is unlikely to ever be necessary.  However, is is
+	 * obvious read-ahead up to the data block cache size is always possible
+	 * irrespective of the file open limit, because a single file could
+	 * contain that number of blocks.
+	 *
+	 * Choosing the size as "file open limit + data block cache size" seems
+	 * to be a reasonable estimate.  We can reasonably assume the maximum
+	 * likely read-ahead possible is data block cache size + one fragment
+	 * per open file.
+	 *
+	 * dimensioning the to_writer queue.  The size of this queue is
+	 * directly related to the amount of block read-ahead possible.
+	 * However, unlike the to_reader and to_inflate queues, this is
+	 * complicated by the fact the to_writer queue not only contains
+	 * entries for fragments and data_blocks but it also contains
+	 * file entries, one per open file in the read-ahead.
+	 *
+	 * Choosing the size as "2 * (file open limit) +
+	 * data block cache size" seems to be a reasonable estimate.
+	 * We can reasonably assume the maximum likely read-ahead possible
+	 * is data block cache size + one fragment per open file, and then
+	 * we will have a file_entry for each open file.
+	 */
+	res = getrlimit(RLIMIT_NOFILE, &rlim);
+	if (res == -1) {
+		ERROR("failed to get open file limit!  Defaulting to 1\n");
+		rlim.rlim_cur = 1;
+	}
+
+	if (rlim.rlim_cur != RLIM_INFINITY) {
+		/*
+		 * leave OPEN_FILE_MARGIN free (rlim_cur includes fds used by
+		 * stdin, stdout, stderr and filesystem fd
+		 */
+		if (rlim.rlim_cur <= OPEN_FILE_MARGIN)
+			/* no margin, use minimum possible */
+			max_files = 1;
+		else
+			max_files = rlim.rlim_cur - OPEN_FILE_MARGIN;
+	} else
+		max_files = -1;
+
+	/* set amount of available files for use by open_wait and close_wake */
+	open_init(max_files);
+
+	/*
+	 * allocate to_reader, to_inflate and to_writer queues.  Set based on
+	 * open file limit and cache size, unless open file limit is unlimited,
+	 * in which case set purely based on cache limits
+	 *
+	 * In doing so, check that the user supplied values do not overflow
+	 * a signed int
+	 */
+	if (max_files != -1) {
+		if(add_overflow(data_buffer_size, max_files) ||
+				add_overflow(data_buffer_size, max_files * 2))
+			EXIT_UNSQUASH("Data queue size is too large\n");
+
+		to_reader = queue_init(max_files + data_buffer_size);
+		to_inflate = queue_init(max_files + data_buffer_size);
+		to_writer = queue_init(max_files * 2 + data_buffer_size);
+	} else {
+		int all_buffers_size;
+
+		if(add_overflow(fragment_buffer_size, data_buffer_size))
+			EXIT_UNSQUASH("Data and fragment queues combined are"
+							" too large\n");
+
+		all_buffers_size = fragment_buffer_size + data_buffer_size;
+
+		if(add_overflow(all_buffers_size, all_buffers_size))
+			EXIT_UNSQUASH("Data and fragment queues combined are"
+							" too large\n");
+
+		to_reader = queue_init(all_buffers_size);
+		to_inflate = queue_init(all_buffers_size);
+		to_writer = queue_init(all_buffers_size * 2);
+	}
+
+	from_writer = queue_init(1);
+
+	fragment_cache = cache_init(block_size, fragment_buffer_size);
+	data_cache = cache_init(block_size, data_buffer_size);
+	pthread_create(&thread[0], NULL, reader, NULL);
+	pthread_create(&thread[1], NULL, writer, NULL);
+	pthread_create(&thread[2], NULL, progress_thread, NULL);
+	init_info();
+	pthread_mutex_init(&fragment_mutex, NULL);
+
+	for(i = 0; i < processors; i++) {
+		if(pthread_create(&inflator_thread[i], NULL, inflator, NULL) !=
+				 0)
+			EXIT_UNSQUASH("Failed to create thread\n");
+	}
+
+	printf("Parallel unsquashfs: Using %d processor%s\n", processors,
+			processors == 1 ? "" : "s");
+
+	if(pthread_sigmask(SIG_SETMASK, &old_mask, NULL) == -1)
+		EXIT_UNSQUASH("Failed to set signal mask in initialise_threads"
+			"\n");
+}
+
+
+void enable_progress_bar()
+{
+	pthread_mutex_lock(&screen_mutex);
+	progress_enabled = progress;
+	pthread_mutex_unlock(&screen_mutex);
+}
+
+
+void disable_progress_bar()
+{
+	pthread_mutex_lock(&screen_mutex);
+	if(progress_enabled) {
+		progress_bar(sym_count + dev_count + fifo_count + cur_blocks,
+			total_inodes - total_files + total_blocks, columns);
+		printf("\n");
+	}
+	progress_enabled = FALSE;
+	pthread_mutex_unlock(&screen_mutex);
+}
+
+
+void progressbar_error(char *fmt, ...)
+{
+	va_list ap;
+
+	pthread_mutex_lock(&screen_mutex);
+
+	if(progress_enabled)
+		fprintf(stderr, "\n");
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	pthread_mutex_unlock(&screen_mutex);
+}
+
+
+void progressbar_info(char *fmt, ...)
+{
+	va_list ap;
+
+	pthread_mutex_lock(&screen_mutex);
+
+	if(progress_enabled)
+		printf("\n");
+
+	va_start(ap, fmt);
+	vprintf(fmt, ap);
+	va_end(ap);
+
+	pthread_mutex_unlock(&screen_mutex);
+}
+
+void progress_bar(long long current, long long max, int columns)
+{
+	char rotate_list[] = { '|', '/', '-', '\\' };
+	int max_digits, used, hashes, spaces;
+	static int tty = -1;
+
+	if(max == 0)
+		return;
+
+	max_digits = floor(log10(max)) + 1;
+	used = max_digits * 2 + 11;
+	hashes = (current * (columns - used)) / max;
+	spaces = columns - used - hashes;
+
+	if((current > max) || (columns - used < 0))
+		return;
+
+	if(tty == -1)
+		tty = isatty(STDOUT_FILENO);
+	if(!tty) {
+		static long long previous = -1;
+
+		/*
+		 * Updating much more frequently than this results in huge
+		 * log files.
+		 */
+		if((current % 100) != 0 && current != max)
+			return;
+		/* Don't update just to rotate the spinner. */
+		if(current == previous)
+			return;
+		previous = current;
+	}
+
+	printf("\r[");
+
+	while (hashes --)
+		putchar('=');
+
+	putchar(rotate_list[rotate]);
+
+	while(spaces --)
+		putchar(' ');
+
+	printf("] %*lld/%*lld", max_digits, current, max_digits, max);
+	printf(" %3lld%%", current * 100 / max);
+	fflush(stdout);
+}
+
+
+int parse_number(char *arg, int *res)
+{
+	char *b;
+	long number = strtol(arg, &b, 10);
+
+	/* check for trailing junk after number */
+	if(*b != '\0')
+		return 0;
+
+	/*
+	 * check for strtol underflow or overflow in conversion.
+	 * Note: strtol can validly return LONG_MIN and LONG_MAX
+	 * if the user entered these values, but, additional code
+	 * to distinguish this scenario is unnecessary, because for
+	 * our purposes LONG_MIN and LONG_MAX are too large anyway
+	 */
+	if(number == LONG_MIN || number == LONG_MAX)
+		return 0;
+
+	/* reject negative numbers as invalid */
+	if(number < 0)
+		return 0;
+
+	/* check if long result will overflow signed int */
+	if(number > INT_MAX)
+		return 0;
+
+	*res = number;
+	return 1;
+}
+
+
+#define VERSION() \
+	printf("unsquashfs version 4.3 (2014/05/12)\n");\
+	printf("copyright (C) 2014 Phillip Lougher "\
+		"<phillip@squashfs.org.uk>\n\n");\
+    	printf("This program is free software; you can redistribute it and/or"\
+		"\n");\
+	printf("modify it under the terms of the GNU General Public License"\
+		"\n");\
+	printf("as published by the Free Software Foundation; either version "\
+		"2,\n");\
+	printf("or (at your option) any later version.\n\n");\
+	printf("This program is distributed in the hope that it will be "\
+		"useful,\n");\
+	printf("but WITHOUT ANY WARRANTY; without even the implied warranty of"\
+		"\n");\
+	printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the"\
+		"\n");\
+	printf("GNU General Public License for more details.\n");
+int main(int argc, char *argv[])
+{
+	char *dest = "squashfs-root";
+	int i, stat_sys = FALSE, version = FALSE;
+	int n;
+	struct pathnames *paths = NULL;
+	struct pathname *path = NULL;
+	long long directory_table_end;
+	int fragment_buffer_size = FRAGMENT_BUFFER_DEFAULT;
+	int data_buffer_size = DATA_BUFFER_DEFAULT;
+
+	pthread_mutex_init(&screen_mutex, NULL);
+	root_process = geteuid() == 0;
+	if(root_process)
+		umask(0);
+	
+	for(i = 1; i < argc; i++) {
+		if(*argv[i] != '-')
+			break;
+		if(strcmp(argv[i], "-version") == 0 ||
+				strcmp(argv[i], "-v") == 0) {
+			VERSION();
+			version = TRUE;
+		} else if(strcmp(argv[i], "-info") == 0 ||
+				strcmp(argv[i], "-i") == 0)
+			info = TRUE;
+		else if(strcmp(argv[i], "-ls") == 0 ||
+				strcmp(argv[i], "-l") == 0)
+			lsonly = TRUE;
+		else if(strcmp(argv[i], "-no-progress") == 0 ||
+				strcmp(argv[i], "-n") == 0)
+			progress = FALSE;
+		else if(strcmp(argv[i], "-no-xattrs") == 0 ||
+				strcmp(argv[i], "-no") == 0)
+			no_xattrs = TRUE;
+		else if(strcmp(argv[i], "-xattrs") == 0 ||
+				strcmp(argv[i], "-x") == 0)
+			no_xattrs = FALSE;
+		else if(strcmp(argv[i], "-user-xattrs") == 0 ||
+				strcmp(argv[i], "-u") == 0) {
+			user_xattrs = TRUE;
+			no_xattrs = FALSE;
+		} else if(strcmp(argv[i], "-dest") == 0 ||
+				strcmp(argv[i], "-d") == 0) {
+			if(++i == argc) {
+				fprintf(stderr, "%s: -dest missing filename\n",
+					argv[0]);
+				exit(1);
+			}
+			dest = argv[i];
+		} else if(strcmp(argv[i], "-processors") == 0 ||
+				strcmp(argv[i], "-p") == 0) {
+			if((++i == argc) || 
+					!parse_number(argv[i],
+						&processors)) {
+				ERROR("%s: -processors missing or invalid "
+					"processor number\n", argv[0]);
+				exit(1);
+			}
+			if(processors < 1) {
+				ERROR("%s: -processors should be 1 or larger\n",
+					argv[0]);
+				exit(1);
+			}
+		} else if(strcmp(argv[i], "-data-queue") == 0 ||
+					 strcmp(argv[i], "-da") == 0) {
+			if((++i == argc) ||
+					!parse_number(argv[i],
+						&data_buffer_size)) {
+				ERROR("%s: -data-queue missing or invalid "
+					"queue size\n", argv[0]);
+				exit(1);
+			}
+			if(data_buffer_size < 1) {
+				ERROR("%s: -data-queue should be 1 Mbyte or "
+					"larger\n", argv[0]);
+				exit(1);
+			}
+		} else if(strcmp(argv[i], "-frag-queue") == 0 ||
+					strcmp(argv[i], "-fr") == 0) {
+			if((++i == argc) ||
+					!parse_number(argv[i],
+						&fragment_buffer_size)) {
+				ERROR("%s: -frag-queue missing or invalid "
+					"queue size\n", argv[0]);
+				exit(1);
+			}
+			if(fragment_buffer_size < 1) {
+				ERROR("%s: -frag-queue should be 1 Mbyte or "
+					"larger\n", argv[0]);
+				exit(1);
+			}
+		} else if(strcmp(argv[i], "-force") == 0 ||
+				strcmp(argv[i], "-f") == 0)
+			force = TRUE;
+		else if(strcmp(argv[i], "-stat") == 0 ||
+				strcmp(argv[i], "-s") == 0)
+			stat_sys = TRUE;
+		else if(strcmp(argv[i], "-lls") == 0 ||
+				strcmp(argv[i], "-ll") == 0) {
+			lsonly = TRUE;
+			short_ls = FALSE;
+		} else if(strcmp(argv[i], "-linfo") == 0 ||
+				strcmp(argv[i], "-li") == 0) {
+			info = TRUE;
+			short_ls = FALSE;
+		} else if(strcmp(argv[i], "-ef") == 0 ||
+				strcmp(argv[i], "-e") == 0) {
+			if(++i == argc) {
+				fprintf(stderr, "%s: -ef missing filename\n",
+					argv[0]);
+				exit(1);
+			}
+			path = process_extract_files(path, argv[i]);
+		} else if(strcmp(argv[i], "-regex") == 0 ||
+				strcmp(argv[i], "-r") == 0)
+			use_regex = TRUE;
+		else
+			goto options;
+	}
+
+	if(lsonly || info)
+		progress = FALSE;
+
+#ifdef SQUASHFS_TRACE
+	/*
+	 * Disable progress bar if full debug tracing is enabled.
+	 * The progress bar in this case just gets in the way of the
+	 * debug trace output
+	 */
+	progress = FALSE;
+#endif
+
+	if(i == argc) {
+		if(!version) {
+options:
+			ERROR("SYNTAX: %s [options] filesystem [directories or "
+				"files to extract]\n", argv[0]);
+			ERROR("\t-v[ersion]\t\tprint version, licence and "
+				"copyright information\n");
+			ERROR("\t-d[est] <pathname>\tunsquash to <pathname>, "
+				"default \"squashfs-root\"\n");
+			ERROR("\t-n[o-progress]\t\tdon't display the progress "
+				"bar\n");
+			ERROR("\t-no[-xattrs]\t\tdon't extract xattrs in file system"
+				NOXOPT_STR"\n");
+			ERROR("\t-x[attrs]\t\textract xattrs in file system"
+				XOPT_STR "\n");
+			ERROR("\t-u[ser-xattrs]\t\tonly extract user xattrs in "
+				"file system.\n\t\t\t\tEnables extracting "
+				"xattrs\n");
+			ERROR("\t-p[rocessors] <number>\tuse <number> "
+				"processors.  By default will use\n");
+			ERROR("\t\t\t\tnumber of processors available\n");
+			ERROR("\t-i[nfo]\t\t\tprint files as they are "
+				"unsquashed\n");
+			ERROR("\t-li[nfo]\t\tprint files as they are "
+				"unsquashed with file\n");
+			ERROR("\t\t\t\tattributes (like ls -l output)\n");
+			ERROR("\t-l[s]\t\t\tlist filesystem, but don't unsquash"
+				"\n");
+			ERROR("\t-ll[s]\t\t\tlist filesystem with file "
+				"attributes (like\n");
+			ERROR("\t\t\t\tls -l output), but don't unsquash\n");
+			ERROR("\t-f[orce]\t\tif file already exists then "
+				"overwrite\n");
+			ERROR("\t-s[tat]\t\t\tdisplay filesystem superblock "
+				"information\n");
+			ERROR("\t-e[f] <extract file>\tlist of directories or "
+				"files to extract.\n\t\t\t\tOne per line\n");
+			ERROR("\t-da[ta-queue] <size>\tSet data queue to "
+				"<size> Mbytes.  Default %d\n\t\t\t\tMbytes\n",
+				DATA_BUFFER_DEFAULT);
+			ERROR("\t-fr[ag-queue] <size>\tSet fragment queue to "
+				"<size> Mbytes.  Default\n\t\t\t\t%d Mbytes\n",
+				FRAGMENT_BUFFER_DEFAULT);
+			ERROR("\t-r[egex]\t\ttreat extract names as POSIX "
+				"regular expressions\n");
+			ERROR("\t\t\t\trather than use the default shell "
+				"wildcard\n\t\t\t\texpansion (globbing)\n");
+			ERROR("\nDecompressors available:\n");
+			display_compressors("", "");
+		}
+		exit(1);
+	}
+
+	for(n = i + 1; n < argc; n++)
+		path = add_path(path, argv[n], argv[n]);
+
+	if((fd = open(argv[i], O_RDONLY)) == -1) {
+		ERROR("Could not open %s, because %s\n", argv[i],
+			strerror(errno));
+		exit(1);
+	}
+
+	if(read_super(argv[i]) == FALSE)
+		exit(1);
+
+	if(stat_sys) {
+		squashfs_stat(argv[i]);
+		exit(0);
+	}
+
+	if(!check_compression(comp))
+		exit(1);
+
+	block_size = sBlk.s.block_size;
+	block_log = sBlk.s.block_log;
+
+	/*
+	 * Sanity check block size and block log.
+	 *
+	 * Check they're within correct limits
+	 */
+	if(block_size > SQUASHFS_FILE_MAX_SIZE ||
+					block_log > SQUASHFS_FILE_MAX_LOG)
+		EXIT_UNSQUASH("Block size or block_log too large."
+			"  File system is corrupt.\n");
+
+	/*
+	 * Check block_size and block_log match
+	 */
+	if(block_size != (1 << block_log))
+		EXIT_UNSQUASH("Block size and block_log do not match."
+			"  File system is corrupt.\n");
+
+	/*
+	 * convert from queue size in Mbytes to queue size in
+	 * blocks.
+	 *
+	 * In doing so, check that the user supplied values do not
+	 * overflow a signed int
+	 */
+	if(shift_overflow(fragment_buffer_size, 20 - block_log))
+		EXIT_UNSQUASH("Fragment queue size is too large\n");
+	else
+		fragment_buffer_size <<= 20 - block_log;
+
+	if(shift_overflow(data_buffer_size, 20 - block_log))
+		EXIT_UNSQUASH("Data queue size is too large\n");
+	else
+		data_buffer_size <<= 20 - block_log;
+
+	initialise_threads(fragment_buffer_size, data_buffer_size);
+
+	fragment_data = malloc(block_size);
+	if(fragment_data == NULL)
+		EXIT_UNSQUASH("failed to allocate fragment_data\n");
+
+	file_data = malloc(block_size);
+	if(file_data == NULL)
+		EXIT_UNSQUASH("failed to allocate file_data");
+
+	data = malloc(block_size);
+	if(data == NULL)
+		EXIT_UNSQUASH("failed to allocate data\n");
+
+	created_inode = malloc(sBlk.s.inodes * sizeof(char *));
+	if(created_inode == NULL)
+		EXIT_UNSQUASH("failed to allocate created_inode\n");
+
+	memset(created_inode, 0, sBlk.s.inodes * sizeof(char *));
+
+	if(s_ops.read_uids_guids() == FALSE)
+		EXIT_UNSQUASH("failed to uid/gid table\n");
+
+	if(s_ops.read_fragment_table(&directory_table_end) == FALSE)
+		EXIT_UNSQUASH("failed to read fragment table\n");
+
+	if(read_inode_table(sBlk.s.inode_table_start,
+				sBlk.s.directory_table_start) == FALSE)
+		EXIT_UNSQUASH("failed to read inode table\n");
+
+	if(read_directory_table(sBlk.s.directory_table_start,
+				directory_table_end) == FALSE)
+		EXIT_UNSQUASH("failed to read directory table\n");
+
+	if(no_xattrs)
+		sBlk.s.xattr_id_table_start = SQUASHFS_INVALID_BLK;
+
+	if(read_xattrs_from_disk(fd, &sBlk.s) == 0)
+		EXIT_UNSQUASH("failed to read the xattr table\n");
+
+	if(path) {
+		paths = init_subdir();
+		paths = add_subdir(paths, path);
+	}
+
+	pre_scan(dest, SQUASHFS_INODE_BLK(sBlk.s.root_inode),
+		SQUASHFS_INODE_OFFSET(sBlk.s.root_inode), paths);
+
+	memset(created_inode, 0, sBlk.s.inodes * sizeof(char *));
+	inode_number = 1;
+
+	printf("%d inodes (%d blocks) to write\n\n", total_inodes,
+		total_inodes - total_files + total_blocks);
+
+	enable_progress_bar();
+
+	dir_scan(dest, SQUASHFS_INODE_BLK(sBlk.s.root_inode),
+		SQUASHFS_INODE_OFFSET(sBlk.s.root_inode), paths);
+
+	queue_put(to_writer, NULL);
+	queue_get(from_writer);
+
+	disable_progress_bar();
+
+	if(!lsonly) {
+		printf("\n");
+		printf("created %d files\n", file_count);
+		printf("created %d directories\n", dir_count);
+		printf("created %d symlinks\n", sym_count);
+		printf("created %d devices\n", dev_count);
+		printf("created %d fifos\n", fifo_count);
+	}
+
+	return 0;
+}
diff --git a/squashfs-tools/squashfs-tools/unsquashfs.h b/squashfs-tools/squashfs-tools/unsquashfs.h
new file mode 100644
index 0000000..4836b8d
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/unsquashfs.h
@@ -0,0 +1,282 @@
+#ifndef UNSQUASHFS_H
+#define UNSQUASHFS_H
+/*
+ * Unsquash a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2013, 2014
+ * 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.
+ *
+ * unsquashfs.h
+ */
+
+#define TRUE 1
+#define FALSE 0
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <utime.h>
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#include <regex.h>
+#include <fnmatch.h>
+#include <signal.h>
+#include <pthread.h>
+#include <math.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#ifndef FNM_EXTMATCH /* glibc extension */
+    #define FNM_EXTMATCH 0
+#endif
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#include "squashfs_fs.h"
+#include "error.h"
+
+#define CALCULATE_HASH(start)	(start & 0xffff)
+
+/*
+ * Unified superblock containing fields for all superblocks
+ */
+struct super_block {
+	struct squashfs_super_block s;
+	/* fields only used by squashfs 3 and earlier layouts */
+	unsigned int		no_uids;
+	unsigned int		no_guids;
+	long long		uid_start;
+	long long		guid_start;
+};
+
+struct hash_table_entry {
+	long long	start;
+	int		bytes;
+	struct hash_table_entry *next;
+};
+
+struct inode {
+	int blocks;
+	char *block_ptr;
+	long long data;
+	int fragment;
+	int frag_bytes;
+	gid_t gid;
+	int inode_number;
+	int mode;
+	int offset;
+	long long start;
+	char *symlink;
+	time_t time;
+	int type;
+	uid_t uid;
+	char sparse;
+	unsigned int xattr;
+};
+
+typedef struct squashfs_operations {
+	struct dir *(*squashfs_opendir)(unsigned int block_start,
+		unsigned int offset, struct inode **i);
+	void (*read_fragment)(unsigned int fragment, long long *start_block,
+		int *size);
+	int (*read_fragment_table)(long long *);
+	void (*read_block_list)(unsigned int *block_list, char *block_ptr,
+		int blocks);
+	struct inode *(*read_inode)(unsigned int start_block,
+		unsigned int offset);
+	int (*read_uids_guids)();
+} squashfs_operations;
+
+struct test {
+	int mask;
+	int value;
+	int position;
+	char mode;
+};
+
+
+/* Cache status struct.  Caches are used to keep
+  track of memory buffers passed between different threads */
+struct cache {
+	int	max_buffers;
+	int	count;
+	int	used;
+	int	buffer_size;
+	int	wait_free;
+	int	wait_pending;
+	pthread_mutex_t	mutex;
+	pthread_cond_t wait_for_free;
+	pthread_cond_t wait_for_pending;
+	struct cache_entry *free_list;
+	struct cache_entry *hash_table[65536];
+};
+
+/* struct describing a cache entry passed between threads */
+struct cache_entry {
+	struct cache *cache;
+	long long block;
+	int	size;
+	int	used;
+	int error;
+	int	pending;
+	struct cache_entry *hash_next;
+	struct cache_entry *hash_prev;
+	struct cache_entry *free_next;
+	struct cache_entry *free_prev;
+	char *data;
+};
+
+/* struct describing queues used to pass data between threads */
+struct queue {
+	int	size;
+	int	readp;
+	int	writep;
+	pthread_mutex_t	mutex;
+	pthread_cond_t empty;
+	pthread_cond_t full;
+	void **data;
+};
+
+/* default size of fragment buffer in Mbytes */
+#define FRAGMENT_BUFFER_DEFAULT 256
+/* default size of data buffer in Mbytes */
+#define DATA_BUFFER_DEFAULT 256
+
+#define DIR_ENT_SIZE	16
+
+struct dir_ent	{
+	char		name[SQUASHFS_NAME_LEN + 1];
+	unsigned int	start_block;
+	unsigned int	offset;
+	unsigned int	type;
+};
+
+struct dir {
+	int		dir_count;
+	int 		cur_entry;
+	unsigned int	mode;
+	uid_t		uid;
+	gid_t		guid;
+	unsigned int	mtime;
+	unsigned int xattr;
+	struct dir_ent	*dirs;
+};
+
+struct file_entry {
+	int offset;
+	int size;
+	struct cache_entry *buffer;
+};
+
+
+struct squashfs_file {
+	int fd;
+	int blocks;
+	long long file_size;
+	int mode;
+	uid_t uid;
+	gid_t gid;
+	time_t time;
+	char *pathname;
+	char sparse;
+	unsigned int xattr;
+};
+
+struct path_entry {
+	char *name;
+	regex_t *preg;
+	struct pathname *paths;
+};
+
+struct pathname {
+	int names;
+	struct path_entry *name;
+};
+
+struct pathnames {
+	int count;
+	struct pathname *path[0];
+};
+#define PATHS_ALLOC_SIZE 10
+
+/* globals */
+extern struct super_block sBlk;
+extern squashfs_operations s_ops;
+extern int swap;
+extern char *inode_table, *directory_table;
+extern struct hash_table_entry *inode_table_hash[65536],
+	*directory_table_hash[65536];
+extern unsigned int *uid_table, *guid_table;
+extern pthread_mutex_t screen_mutex;
+extern int progress_enabled;
+extern int inode_number;
+extern int lookup_type[];
+extern int fd;
+extern struct queue *to_reader, *to_inflate, *to_writer;
+extern struct cache *fragment_cache, *data_cache;
+
+/* unsquashfs.c */
+extern int lookup_entry(struct hash_table_entry **, long long);
+extern int read_fs_bytes(int fd, long long, int, void *);
+extern int read_block(int, long long, long long *, int, void *);
+extern void enable_progress_bar();
+extern void disable_progress_bar();
+extern void dump_queue(struct queue *);
+extern void dump_cache(struct cache *);
+
+/* unsquash-1.c */
+extern void read_block_list_1(unsigned int *, char *, int);
+extern int read_fragment_table_1(long long *);
+extern struct inode *read_inode_1(unsigned int, unsigned int);
+extern struct dir *squashfs_opendir_1(unsigned int, unsigned int,
+	struct inode **);
+extern int read_uids_guids_1();
+
+/* unsquash-2.c */
+extern void read_block_list_2(unsigned int *, char *, int);
+extern int read_fragment_table_2(long long *);
+extern void read_fragment_2(unsigned int, long long *, int *);
+extern struct inode *read_inode_2(unsigned int, unsigned int);
+
+/* unsquash-3.c */
+extern int read_fragment_table_3(long long *);
+extern void read_fragment_3(unsigned int, long long *, int *);
+extern struct inode *read_inode_3(unsigned int, unsigned int);
+extern struct dir *squashfs_opendir_3(unsigned int, unsigned int,
+	struct inode **);
+
+/* unsquash-4.c */
+extern int read_fragment_table_4(long long *);
+extern void read_fragment_4(unsigned int, long long *, int *);
+extern struct inode *read_inode_4(unsigned int, unsigned int);
+extern struct dir *squashfs_opendir_4(unsigned int, unsigned int,
+	struct inode **);
+extern int read_uids_guids_4();
+#endif
diff --git a/squashfs-tools/squashfs-tools/unsquashfs_info.c b/squashfs-tools/squashfs-tools/unsquashfs_info.c
new file mode 100644
index 0000000..7d4f7af
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/unsquashfs_info.c
@@ -0,0 +1,140 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013
+ * 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.
+ *
+ * unsquashfs_info.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include "squashfs_fs.h"
+#include "unsquashfs.h"
+#include "error.h"
+
+static int silent = 0;
+char *pathname = NULL;
+
+pthread_t info_thread;
+
+
+void disable_info()
+{
+	if(pathname)
+		free(pathname);
+
+	pathname = NULL;
+}
+
+
+void update_info(char *name)
+{
+	if(pathname)
+		free(pathname);
+
+	pathname = name;
+}
+
+
+void dump_state()
+{
+	disable_progress_bar();
+
+	printf("Queue and cache status dump\n");
+	printf("===========================\n");
+
+	printf("file buffer read queue (main thread -> reader thread)\n");
+	dump_queue(to_reader);
+
+	printf("file buffer decompress queue (reader thread -> inflate"
+							" thread(s))\n");
+	dump_queue(to_inflate);
+
+	printf("file buffer write queue (main thread -> writer thread)\n");
+	dump_queue(to_writer);
+
+	printf("\nbuffer cache (uncompressed blocks and compressed blocks "
+							"'in flight')\n");
+	dump_cache(data_cache);
+
+	printf("fragment buffer cache (uncompressed frags and compressed"
+						" frags 'in flight')\n");
+	dump_cache(fragment_cache);
+
+	enable_progress_bar();
+}
+
+
+void *info_thrd(void *arg)
+{
+	sigset_t sigmask;
+	int sig, err, waiting = 0;
+
+	sigemptyset(&sigmask);
+	sigaddset(&sigmask, SIGQUIT);
+	sigaddset(&sigmask, SIGHUP);
+	sigaddset(&sigmask, SIGALRM);
+
+	while(1) {
+		err = sigwait(&sigmask, &sig);
+
+		if(err == -1) {
+			switch(errno) {
+			case EINTR:
+				continue;
+			default:
+				BAD_ERROR("sigwait failed "
+					"because %s\n", strerror(errno));
+			}
+		}
+
+		if(sig == SIGQUIT && !waiting) {
+			if(pathname)
+				INFO("%s\n", pathname);
+
+			/* set one second interval period, if ^\ received
+			   within then, dump queue and cache status */
+			waiting = 1;
+			alarm(1);
+		} else if (sig == SIGQUIT) {
+			dump_state();
+		} else if (sig == SIGALRM) {
+			waiting = 0;
+		}
+	}
+}
+
+
+void init_info()
+{
+	pthread_create(&info_thread, NULL, info_thrd, NULL);
+}
diff --git a/squashfs-tools/squashfs-tools/unsquashfs_info.h b/squashfs-tools/squashfs-tools/unsquashfs_info.h
new file mode 100644
index 0000000..f85efd1
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/unsquashfs_info.h
@@ -0,0 +1,30 @@
+#ifndef UNSQUASHFS_INFO_H
+#define UNSQUASHFS_INFO_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * 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.
+ *
+ * unsquashfs_info.h
+ */
+
+extern void disable_info();
+extern void update_info(char *);
+extern void init_info();
+#endif
diff --git a/squashfs-tools/squashfs-tools/unsquashfs_xattr.c b/squashfs-tools/squashfs-tools/unsquashfs_xattr.c
new file mode 100644
index 0000000..13f0e35
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/unsquashfs_xattr.c
@@ -0,0 +1,144 @@
+/*
+ * Unsquash a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2010, 2012
+ * 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.
+ *
+ * unsquashfs_xattr.c
+ */
+
+#include "unsquashfs.h"
+#include "xattr.h"
+
+#include <sys/xattr.h>
+
+#ifdef XATTR_NOFOLLOW /* Apple's xattrs */
+    #define lsetxattr(path_, name_, val_, sz_, flags_) \
+        setxattr(path_, name_, val_, sz_, 0, flags_ | XATTR_NOFOLLOW)
+#endif
+
+#define NOSPACE_MAX 10
+
+extern int root_process;
+extern int user_xattrs;
+
+void write_xattr(char *pathname, unsigned int xattr)
+{
+	unsigned int count;
+	struct xattr_list *xattr_list;
+	int i;
+	static int nonsuper_error = FALSE;
+	static int ignore_xattrs = FALSE;
+	static int nospace_error = 0;
+
+	if(ignore_xattrs || xattr == SQUASHFS_INVALID_XATTR ||
+			sBlk.s.xattr_id_table_start == SQUASHFS_INVALID_BLK)
+		return;
+
+	xattr_list = get_xattr(xattr, &count, 1);
+	if(xattr_list == NULL) {
+		ERROR("Failed to read xattrs for file %s\n", pathname);
+		return;
+	}
+
+	for(i = 0; i < count; i++) {
+		int prefix = xattr_list[i].type & SQUASHFS_XATTR_PREFIX_MASK;
+
+		if(user_xattrs && prefix != SQUASHFS_XATTR_USER)
+			continue;
+
+		if(root_process || prefix == SQUASHFS_XATTR_USER) {
+			int res = lsetxattr(pathname, xattr_list[i].full_name,
+				xattr_list[i].value, xattr_list[i].vsize, 0);
+
+			if(res == -1) {
+				if(errno == ENOTSUP) {
+					/*
+					 * If the destination filesystem cannot
+					 * suppport xattrs, print error, and
+					 * disable xattr output as this error is
+					 * unlikely to go away, and printing
+					 * screenfulls of the same error message
+					 * is rather annoying
+					 */
+					ERROR("write_xattr: failed to write "
+						"xattr %s for file %s because " 
+						"extended attributes are not "
+						"supported by the destination "
+						"filesystem\n",
+						xattr_list[i].full_name,
+						pathname);
+					ERROR("Ignoring xattrs in "
+								"filesystem\n");
+					ERROR("To avoid this error message, "
+						"specify -no-xattrs\n");
+					ignore_xattrs = TRUE;
+				} else if((errno == ENOSPC || errno == EDQUOT)
+						&& nospace_error < NOSPACE_MAX) {
+					/*
+					 * Many filesystems like ext2/3/4 have
+					 * limits on the amount of xattr
+					 * data that can be stored per file
+					 * (typically one block or 4K), so
+					 * we shouldn't disable xattr ouput,
+					 * as the error may be restriced to one
+					 * file only.  If we get a lot of these
+					 * then suppress the error messsage
+					 */
+					ERROR("write_xattr: failed to write "
+						"xattr %s for file %s because " 
+						"no extended attribute space "
+						"remaining (per file or "
+						"filesystem limit)\n",
+						xattr_list[i].full_name,
+						pathname);
+					if(++ nospace_error == NOSPACE_MAX)
+						ERROR("%d of these errors "
+							"printed, further error "
+							"messages of this type "
+							"are suppressed!\n",
+							NOSPACE_MAX);
+				} else
+					ERROR("write_xattr: failed to write "
+						"xattr %s for file %s because "
+						"%s\n", xattr_list[i].full_name,
+						pathname, strerror(errno));
+			}
+		} else if(nonsuper_error == FALSE) {
+			/*
+			 * if extract user xattrs only then
+			 * error message is suppressed, if not
+			 * print error, and then suppress further error
+			 * messages to avoid possible screenfulls of the
+			 * same error message!
+			 */
+			ERROR("write_xattr: could not write xattr %s "
+					"for file %s because you're not "
+					"superuser!\n",
+					xattr_list[i].full_name, pathname);
+			ERROR("write_xattr: to avoid this error message, either"
+				" specify -user-xattrs, -no-xattrs, or run as "
+				"superuser!\n");
+			ERROR("Further error messages of this type are "
+				"suppressed!\n");
+			nonsuper_error = TRUE;
+		}
+	}
+
+	free_xattr(xattr_list, count);
+}
diff --git a/squashfs-tools/squashfs-tools/xattr.c b/squashfs-tools/squashfs-tools/xattr.c
new file mode 100644
index 0000000..6cecb74
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/xattr.c
@@ -0,0 +1,819 @@
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2008, 2009, 2010, 2012, 2014
+ * 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.
+ *
+ * xattr.c
+ */
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#define TRUE 1
+#define FALSE 0
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/xattr.h>
+
+#ifdef XATTR_NOFOLLOW /* Apple's xattrs */
+    #define llistxattr(path_, buf_, sz_) \
+        listxattr(path_, buf_, sz_, XATTR_NOFOLLOW)
+    #define lgetxattr(path_, name_, val_, sz_) \
+        getxattr(path_, name_, val_, sz_, 0, XATTR_NOFOLLOW)
+#endif
+
+#include "squashfs_fs.h"
+#include "squashfs_swap.h"
+#include "mksquashfs.h"
+#include "xattr.h"
+#include "error.h"
+#include "progressbar.h"
+
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+#include "android.h"
+#ifdef __ANDROID__
+#include <linux/capability.h>
+#else
+#include <private/android_filesystem_capability.h>
+#endif
+static struct selabel_handle *sehnd = NULL;
+#endif
+/* ANDROID CHANGES END */
+
+/* compressed xattr table */
+static char *xattr_table = NULL;
+static unsigned int xattr_size = 0;
+
+/* cached uncompressed xattr data */
+static char *data_cache = NULL;
+static int cache_bytes = 0, cache_size = 0;
+
+/* cached uncompressed xattr id table */
+static struct squashfs_xattr_id *xattr_id_table = NULL;
+static int xattr_ids = 0;
+
+/* saved compressed xattr table */
+unsigned int sxattr_bytes = 0, stotal_xattr_bytes = 0;
+
+/* saved cached uncompressed xattr data */
+static char *sdata_cache = NULL;
+static int scache_bytes = 0;
+
+/* saved cached uncompressed xattr id table */
+static int sxattr_ids = 0;
+
+/* xattr hash table for value duplicate detection */
+static struct xattr_list *dupl_value[65536];
+
+/* xattr hash table for id duplicate detection */
+static struct dupl_id *dupl_id[65536];
+
+/* file system globals from mksquashfs.c */
+extern int no_xattrs, noX;
+extern long long bytes;
+extern int fd;
+extern unsigned int xattr_bytes, total_xattr_bytes;
+/* ANDROID CHANGES START*/
+extern char *context_file;
+extern char *mount_point;
+/* ANDROID CHANGES END */
+
+/* helper functions from mksquashfs.c */
+extern unsigned short get_checksum(char *, int, unsigned short);
+extern void write_destination(int, long long, int, void *);
+extern long long generic_write_table(int, void *, int, void *, int);
+extern int mangle(char *, char *, int, int, int, int);
+extern char *pathname(struct dir_ent *);
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+extern char *subpathname(struct dir_ent *);
+#endif
+/* ANDROID CHANGES END */
+
+/* helper functions and definitions from read_xattrs.c */
+extern int read_xattrs_from_disk(int, struct squashfs_super_block *);
+extern struct xattr_list *get_xattr(int, unsigned int *, int);
+extern struct prefix prefix_table[];
+
+
+static int get_prefix(struct xattr_list *xattr, char *name)
+{
+	int i;
+
+	xattr->full_name = strdup(name);
+
+	for(i = 0; prefix_table[i].type != -1; i++) {
+		struct prefix *p = &prefix_table[i];
+		if(strncmp(xattr->full_name, p->prefix, strlen(p->prefix)) == 0)
+			break;
+	}
+
+	if(prefix_table[i].type != -1) {
+		xattr->name = xattr->full_name + strlen(prefix_table[i].prefix);
+		xattr->size = strlen(xattr->name);
+	}
+
+	return prefix_table[i].type;
+}
+
+
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+static struct xattr_list *next_xattr_list(int *xattr_count, struct xattr_list **xattrs) {
+	struct xattr_list *x;
+	x = realloc(*xattrs, ++*xattr_count * sizeof(struct xattr_list));
+	if (x == NULL) MEM_ERROR();
+	*xattrs = x;
+	return &x[*xattr_count - 1];
+}
+
+static void read_selinux_xattr_from_sehnd(char *filename, int mode,
+	struct selabel_handle *sehnd, struct xattr_list *xattrs)
+{
+	char *attr_val;
+
+	xattrs->type = get_prefix(xattrs, "security.selinux");
+	attr_val = set_selabel(filename, mode, sehnd);
+	xattrs->value = (void *)attr_val;
+	xattrs->vsize = strlen(attr_val);
+}
+
+static void set_caps_xattr(uint64_t caps, struct xattr_list *xattrs)
+{
+	struct vfs_cap_data *attr_val;
+	attr_val = malloc(sizeof(*attr_val));
+	if (attr_val == NULL) MEM_ERROR();
+
+	xattrs->type = get_prefix(xattrs, "security.capability");
+	*attr_val = set_caps(caps);
+	xattrs->value = attr_val;
+	xattrs->vsize = sizeof(*attr_val);
+}
+#endif
+/* ANDROID CHANGES END */
+
+
+static int read_xattrs_from_system(char *filename, struct xattr_list **xattrs)
+{
+	ssize_t size, vsize;
+	char *xattr_names, *p;
+	int i;
+	struct xattr_list *xattr_list = NULL;
+
+	while(1) {
+		size = llistxattr(filename, NULL, 0);
+		if(size <= 0) {
+			if(size < 0 && errno != ENOTSUP) {
+				ERROR_START("llistxattr for %s failed in "
+					"read_attrs, because %s", filename,
+					strerror(errno));
+				ERROR_EXIT(".  Ignoring");
+			}
+			return 0;
+		}
+
+		xattr_names = malloc(size);
+		if(xattr_names == NULL)
+			MEM_ERROR();
+
+		size = llistxattr(filename, xattr_names, size);
+		if(size < 0) {
+			free(xattr_names);
+			if(errno == ERANGE)
+				/* xattr list grew?  Try again */
+				continue;
+			else {
+				ERROR_START("llistxattr for %s failed in "
+					"read_attrs, because %s", filename,
+					strerror(errno));
+				ERROR_EXIT(".  Ignoring");
+				return 0;
+			}
+		}
+
+		break;
+	}
+
+	for(i = 0, p = xattr_names; p < xattr_names + size; i++) {
+		struct xattr_list *x = realloc(xattr_list, (i + 1) *
+						sizeof(struct xattr_list));
+		if(x == NULL)
+			MEM_ERROR();
+		xattr_list = x;
+
+		xattr_list[i].type = get_prefix(&xattr_list[i], p);
+		p += strlen(p) + 1;
+		if(xattr_list[i].type == -1) {
+			ERROR("Unrecognised xattr prefix %s\n",
+				xattr_list[i].full_name);
+			free(xattr_list[i].full_name);
+			i--;
+			continue;
+		}
+
+		while(1) {
+			vsize = lgetxattr(filename, xattr_list[i].full_name,
+								NULL, 0);
+			if(vsize < 0) {
+				ERROR_START("lgetxattr failed for %s in "
+					"read_attrs, because %s", filename,
+					strerror(errno));
+				ERROR_EXIT(".  Ignoring");
+				free(xattr_list[i].full_name);
+				goto failed;
+			}
+
+			xattr_list[i].value = malloc(vsize);
+			if(xattr_list[i].value == NULL)
+				MEM_ERROR();
+
+			vsize = lgetxattr(filename, xattr_list[i].full_name,
+						xattr_list[i].value, vsize);
+			if(vsize < 0) {
+				free(xattr_list[i].value);
+				if(errno == ERANGE)
+					/* xattr grew?  Try again */
+					continue;
+				else {
+					ERROR_START("lgetxattr failed for %s "
+						"in read_attrs, because %s",
+						filename, strerror(errno));
+					ERROR_EXIT(".  Ignoring");
+					free(xattr_list[i].full_name);
+					goto failed;
+				}
+			}
+			
+			break;
+		}
+		xattr_list[i].vsize = vsize;
+
+		TRACE("read_xattrs_from_system: filename %s, xattr name %s,"
+			" vsize %d\n", filename, xattr_list[i].full_name,
+			xattr_list[i].vsize);
+	}
+	free(xattr_names);
+	*xattrs = xattr_list;
+	return i;
+
+failed:
+	while(--i >= 0) {
+		free(xattr_list[i].full_name);
+		free(xattr_list[i].value);
+	}
+	free(xattr_list);
+	free(xattr_names);
+	return 0;
+}
+
+
+static int get_xattr_size(struct xattr_list *xattr)
+{
+	int size = sizeof(struct squashfs_xattr_entry) +
+		sizeof(struct squashfs_xattr_val) + xattr->size;
+
+	if(xattr->type & XATTR_VALUE_OOL)
+		size += XATTR_VALUE_OOL_SIZE;
+	else
+		size += xattr->vsize;
+
+	return size;
+}
+
+
+static void *get_xattr_space(unsigned int req_size, long long *disk)
+{
+	int data_space;
+	unsigned short c_byte;
+
+	/*
+	 * Move and compress cached uncompressed data into xattr table.
+	 */
+	while(cache_bytes >= SQUASHFS_METADATA_SIZE) {
+		if((xattr_size - xattr_bytes) <
+				((SQUASHFS_METADATA_SIZE << 1)) + 2) {
+			xattr_table = realloc(xattr_table, xattr_size +
+				(SQUASHFS_METADATA_SIZE << 1) + 2);
+			if(xattr_table == NULL)
+				MEM_ERROR();
+			xattr_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+		}
+
+		c_byte = mangle(xattr_table + xattr_bytes + BLOCK_OFFSET,
+			data_cache, SQUASHFS_METADATA_SIZE,
+			SQUASHFS_METADATA_SIZE, noX, 0);
+		TRACE("Xattr block @ 0x%x, size %d\n", xattr_bytes, c_byte);
+		SQUASHFS_SWAP_SHORTS(&c_byte, xattr_table + xattr_bytes, 1);
+		xattr_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+		memmove(data_cache, data_cache + SQUASHFS_METADATA_SIZE,
+			cache_bytes - SQUASHFS_METADATA_SIZE);
+		cache_bytes -= SQUASHFS_METADATA_SIZE;
+	}
+
+	/*
+	 * Ensure there's enough space in the uncompressed data cache
+	 */
+	data_space = cache_size - cache_bytes;
+	if(data_space < req_size) {
+			int realloc_size = req_size - data_space;
+			data_cache = realloc(data_cache, cache_size +
+				realloc_size);
+			if(data_cache == NULL)
+				MEM_ERROR();
+			cache_size += realloc_size;
+	}
+
+	if(disk)
+		*disk = ((long long) xattr_bytes << 16) | cache_bytes;
+	cache_bytes += req_size;
+	return data_cache + cache_bytes - req_size;
+}
+
+
+static struct dupl_id *check_id_dupl(struct xattr_list *xattr_list, int xattrs)
+{
+	struct dupl_id *entry;
+	int i;
+	unsigned short checksum = 0;
+
+	/* compute checksum over all xattrs */
+	for(i = 0; i < xattrs; i++) {
+		struct xattr_list *xattr = &xattr_list[i];
+
+		checksum = get_checksum(xattr->full_name,
+					strlen(xattr->full_name), checksum);
+		checksum = get_checksum(xattr->value,
+					xattr->vsize, checksum);
+	}
+
+	for(entry = dupl_id[checksum]; entry; entry = entry->next) {
+		if (entry->xattrs != xattrs)
+			continue;
+
+		for(i = 0; i < xattrs; i++) {
+			struct xattr_list *xattr = &xattr_list[i];
+			struct xattr_list *dup_xattr = &entry->xattr_list[i];
+
+			if(strcmp(xattr->full_name, dup_xattr->full_name))
+				break;
+
+			if(memcmp(xattr->value, dup_xattr->value, xattr->vsize))
+				break;
+		}
+		
+		if(i == xattrs)
+			break;
+	}
+
+	if(entry == NULL) {
+		/* no duplicate exists */
+		entry = malloc(sizeof(*entry));
+		if(entry == NULL)
+			MEM_ERROR();
+		entry->xattrs = xattrs;
+		entry->xattr_list = xattr_list;
+		entry->xattr_id = SQUASHFS_INVALID_XATTR;
+		entry->next = dupl_id[checksum];
+		dupl_id[checksum] = entry;
+	}
+		
+	return entry;
+}
+
+
+static void check_value_dupl(struct xattr_list *xattr)
+{
+	struct xattr_list *entry;
+
+	if(xattr->vsize < XATTR_VALUE_OOL_SIZE)
+		return;
+
+	/* Check if this is a duplicate of an existing value */
+	xattr->vchecksum = get_checksum(xattr->value, xattr->vsize, 0);
+	for(entry = dupl_value[xattr->vchecksum]; entry; entry = entry->vnext) {
+		if(entry->vsize != xattr->vsize)
+			continue;
+		
+		if(memcmp(entry->value, xattr->value, xattr->vsize) == 0)
+			break;
+	}
+
+	if(entry == NULL) {
+		/*
+		 * No duplicate exists, add to hash table, and mark as
+		 * requiring writing
+		 */
+		xattr->vnext = dupl_value[xattr->vchecksum];
+		dupl_value[xattr->vchecksum] = xattr;
+		xattr->ool_value = SQUASHFS_INVALID_BLK;
+	} else {
+		/*
+		 * Duplicate exists, make type XATTR_VALUE_OOL, and
+		 * remember where the duplicate is
+		 */
+		xattr->type |= XATTR_VALUE_OOL;
+		xattr->ool_value = entry->ool_value;
+		/* on appending don't free duplicate values because the
+		 * duplicate value already points to the non-duplicate value */
+		if(xattr->value != entry->value) {
+			free(xattr->value);
+			xattr->value = entry->value;
+		}
+	}
+}
+
+
+static int get_xattr_id(int xattrs, struct xattr_list *xattr_list,
+		long long xattr_disk, struct dupl_id *xattr_dupl)
+{
+	int i, size = 0;
+	struct squashfs_xattr_id *xattr_id;
+
+	xattr_id_table = realloc(xattr_id_table, (xattr_ids + 1) *
+		sizeof(struct squashfs_xattr_id));
+	if(xattr_id_table == NULL)
+		MEM_ERROR();
+
+	/* get total uncompressed size of xattr data, needed for stat */
+	for(i = 0; i < xattrs; i++)
+		size += strlen(xattr_list[i].full_name) + 1 +
+			xattr_list[i].vsize;
+
+	xattr_id = &xattr_id_table[xattr_ids];
+	xattr_id->xattr = xattr_disk;
+	xattr_id->count = xattrs;
+	xattr_id->size = size;
+
+	/*
+	 * keep track of total uncompressed xattr data, needed for mksquashfs
+	 * file system summary
+	 */
+	total_xattr_bytes += size;
+
+	xattr_dupl->xattr_id = xattr_ids ++;
+	return xattr_dupl->xattr_id;
+}
+	
+
+long long write_xattrs()
+{
+	unsigned short c_byte;
+	int i, avail_bytes;
+	char *datap = data_cache;
+	long long start_bytes = bytes;
+	struct squashfs_xattr_table header;
+
+	if(xattr_ids == 0)
+		return SQUASHFS_INVALID_BLK;
+
+	/*
+	 * Move and compress cached uncompressed data into xattr table.
+	 */
+	while(cache_bytes) {
+		if((xattr_size - xattr_bytes) <
+				((SQUASHFS_METADATA_SIZE << 1)) + 2) {
+			xattr_table = realloc(xattr_table, xattr_size +
+				(SQUASHFS_METADATA_SIZE << 1) + 2);
+			if(xattr_table == NULL)
+				MEM_ERROR();
+			xattr_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+		}
+
+		avail_bytes = cache_bytes > SQUASHFS_METADATA_SIZE ?
+			SQUASHFS_METADATA_SIZE : cache_bytes;
+		c_byte = mangle(xattr_table + xattr_bytes + BLOCK_OFFSET, datap,
+			avail_bytes, SQUASHFS_METADATA_SIZE, noX, 0);
+		TRACE("Xattr block @ 0x%x, size %d\n", xattr_bytes, c_byte);
+		SQUASHFS_SWAP_SHORTS(&c_byte, xattr_table + xattr_bytes, 1);
+		xattr_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+		datap += avail_bytes;
+		cache_bytes -= avail_bytes;
+	}
+
+	/*
+	 * Write compressed xattr table to file system
+	 */
+	write_destination(fd, bytes, xattr_bytes, xattr_table);
+        bytes += xattr_bytes;
+
+	/*
+	 * Swap if necessary the xattr id table
+	 */
+	for(i = 0; i < xattr_ids; i++)
+		SQUASHFS_INSWAP_XATTR_ID(&xattr_id_table[i]);
+
+	header.xattr_ids = xattr_ids;
+	header.xattr_table_start = start_bytes;
+	SQUASHFS_INSWAP_XATTR_TABLE(&header);
+
+	return generic_write_table(xattr_ids * sizeof(struct squashfs_xattr_id),
+		xattr_id_table, sizeof(header), &header, noX);
+}
+
+
+int generate_xattrs(int xattrs, struct xattr_list *xattr_list)
+{
+	int total_size, i;
+	int xattr_value_max;
+	void *xp;
+	long long xattr_disk;
+	struct dupl_id *xattr_dupl;
+
+	/*
+	 * check if the file xattrs are a complete duplicate of a pre-existing
+	 * id
+	 */
+	xattr_dupl = check_id_dupl(xattr_list, xattrs);
+	if(xattr_dupl->xattr_id != SQUASHFS_INVALID_XATTR)
+		return xattr_dupl->xattr_id;
+	 
+	/*
+	 * Scan the xattr_list deciding which type to assign to each
+	 * xattr.  The choice is fairly straightforward, and depends on the
+	 * size of each xattr name/value and the overall size of the
+	 * resultant xattr list stored in the xattr metadata table.
+	 *
+	 * Choices are whether to store data inline or out of line.
+	 *
+	 * The overall goal is to optimise xattr scanning and lookup, and
+	 * to enable the file system layout to scale from a couple of
+	 * small xattr name/values to a large number of large xattr
+	 * names/values without affecting performance.  While hopefully
+	 * enabling the common case of a couple of small xattr name/values
+	 * to be stored efficiently
+	 *
+	 * Code repeatedly scans, doing the following
+	 *		move xattr data out of line if it exceeds
+	 *		xattr_value_max.  Where xattr_value_max is
+	 *		initially XATTR_INLINE_MAX.  If the final uncompressed
+	 *		xattr list is larger than XATTR_TARGET_MAX then more
+	 *		aggressively move xattr data out of line by repeatedly
+	 *	 	setting inline threshold to 1/2, then 1/4, 1/8 of
+	 *		XATTR_INLINE_MAX until target achieved or there's
+	 *		nothing left to move out of line
+	 */
+	xattr_value_max = XATTR_INLINE_MAX;
+	while(1) {
+		for(total_size = 0, i = 0; i < xattrs; i++) {
+			struct xattr_list *xattr = &xattr_list[i];
+			xattr->type &= XATTR_PREFIX_MASK; /* all inline */
+			if (xattr->vsize > xattr_value_max)
+				xattr->type |= XATTR_VALUE_OOL;
+
+			total_size += get_xattr_size(xattr);
+		}
+
+		/*
+		 * If the total size of the uncompressed xattr list is <=
+		 * XATTR_TARGET_MAX we're done
+		 */
+		if(total_size <= XATTR_TARGET_MAX)
+			break;
+
+		if(xattr_value_max == XATTR_VALUE_OOL_SIZE)
+			break;
+
+		/*
+		 * Inline target not yet at minimum and so reduce it, and
+		 * try again
+		 */
+		xattr_value_max /= 2;
+		if(xattr_value_max < XATTR_VALUE_OOL_SIZE)
+			xattr_value_max = XATTR_VALUE_OOL_SIZE;
+	}
+
+	/*
+	 * Check xattr values for duplicates
+	 */
+	for(i = 0; i < xattrs; i++) {
+		check_value_dupl(&xattr_list[i]);
+	}
+
+	/*
+	 * Add each out of line value to the file system xattr table
+	 * if it doesn't already exist as a duplicate
+	 */
+	for(i = 0; i < xattrs; i++) {
+		struct xattr_list *xattr = &xattr_list[i];
+
+		if((xattr->type & XATTR_VALUE_OOL) &&
+				(xattr->ool_value == SQUASHFS_INVALID_BLK)) {
+			struct squashfs_xattr_val val;
+			int size = sizeof(val) + xattr->vsize;
+			xp = get_xattr_space(size, &xattr->ool_value);
+			val.vsize = xattr->vsize;
+			SQUASHFS_SWAP_XATTR_VAL(&val, xp);
+			memcpy(xp + sizeof(val), xattr->value, xattr->vsize);
+		}
+	}
+
+	/*
+	 * Create xattr list and add to file system xattr table
+	 */
+	get_xattr_space(0, &xattr_disk);
+	for(i = 0; i < xattrs; i++) {
+		struct xattr_list *xattr = &xattr_list[i];
+		struct squashfs_xattr_entry entry;
+		struct squashfs_xattr_val val;
+
+		xp = get_xattr_space(sizeof(entry) + xattr->size, NULL);
+		entry.type = xattr->type;
+		entry.size = xattr->size;
+		SQUASHFS_SWAP_XATTR_ENTRY(&entry, xp);
+		memcpy(xp + sizeof(entry), xattr->name, xattr->size);
+
+		if(xattr->type & XATTR_VALUE_OOL) {
+			int size = sizeof(val) + XATTR_VALUE_OOL_SIZE;
+			xp = get_xattr_space(size, NULL);
+			val.vsize = XATTR_VALUE_OOL_SIZE;
+			SQUASHFS_SWAP_XATTR_VAL(&val, xp);
+			SQUASHFS_SWAP_LONG_LONGS(&xattr->ool_value, xp +
+				sizeof(val), 1);
+		} else {
+			int size = sizeof(val) + xattr->vsize;
+			xp = get_xattr_space(size, &xattr->ool_value);
+			val.vsize = xattr->vsize;
+			SQUASHFS_SWAP_XATTR_VAL(&val, xp);
+			memcpy(xp + sizeof(val), xattr->value, xattr->vsize);
+		}
+	}
+
+	/*
+	 * Add to xattr id lookup table
+	 */
+	return get_xattr_id(xattrs, xattr_list, xattr_disk, xattr_dupl);
+}
+
+
+int read_xattrs(void *d)
+{
+	struct dir_ent *dir_ent = d;
+	struct inode_info *inode = dir_ent->inode;
+	char *filename = pathname(dir_ent);
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+    // NOTE: xattr_list has to point to an array of xattr_list elements
+	struct xattr_list *xattr_list = NULL, *next_xattr = NULL;
+	int xattrs = 0;
+#else
+	struct xattr_list *xattr_list;
+	int xattrs;
+#endif
+/* ANDROID CHANGES END */
+
+	if(no_xattrs || IS_PSEUDO(inode) || inode->root_entry)
+		return SQUASHFS_INVALID_XATTR;
+
+/* ANDROID CHANGES START*/
+#ifdef ANDROID
+	if (context_file) {
+		if (sehnd == NULL)
+			sehnd = get_sehnd(context_file);
+		if (mount_point) {
+			char *mounted_path;
+			alloc_mounted_path(mount_point, subpathname(dir_ent), &mounted_path);
+			next_xattr = next_xattr_list(&xattrs, &xattr_list);
+			read_selinux_xattr_from_sehnd(mounted_path, inode->buf.st_mode,
+					sehnd, next_xattr);
+			free(mounted_path);
+		} else {
+			next_xattr = next_xattr_list(&xattrs, &xattr_list);
+			read_selinux_xattr_from_sehnd(filename, inode->buf.st_mode,
+					sehnd, next_xattr);
+		}
+	}
+	if (dir_ent->capabilities != 0) {
+		next_xattr = next_xattr_list(&xattrs, &xattr_list);
+		set_caps_xattr(dir_ent->capabilities, next_xattr);
+	}
+#else
+	xattrs = read_xattrs_from_system(filename, &xattr_list);
+#endif
+/* ANDROID CHANGES END */
+
+	if(xattrs == 0)
+		return SQUASHFS_INVALID_XATTR;
+
+	return generate_xattrs(xattrs, xattr_list);
+}
+
+
+/*
+ * Add the existing xattr ids and xattr metadata in the file system being
+ * appended to, to the in-memory xattr cache.  This allows duplicate checking to
+ * take place against the xattrs already in the file system being appended to,
+ * and ensures the pre-existing xattrs are written out along with any new xattrs
+ */
+int get_xattrs(int fd, struct squashfs_super_block *sBlk)
+{
+	int ids, res, i, id;
+	unsigned int count;
+
+	TRACE("get_xattrs\n");
+
+	res = read_xattrs_from_disk(fd, sBlk);
+	if(res == SQUASHFS_INVALID_BLK || res == 0)
+		goto done;
+	ids = res;
+
+	/*
+	 * for each xattr id read and construct its list of xattr
+	 * name:value pairs, and add them to the in-memory xattr cache
+	 */
+	for(i = 0; i < ids; i++) {
+		struct xattr_list *xattr_list = get_xattr(i, &count, 0);
+		if(xattr_list == NULL) {
+			res = 0;
+			goto done;
+		}
+		id = generate_xattrs(count, xattr_list);
+
+		/*
+		 * Sanity check, the new xattr id should be the same as the
+		 * xattr id in the original file system
+		 */
+		if(id != i) {
+			ERROR("BUG, different xattr_id in get_xattrs\n");
+			res = 0;
+			goto done;
+		}
+	}
+
+done:
+	return res;
+}
+
+
+/*
+ * Save current state of xattrs, needed for restoring state in the event of an
+ * abort in appending
+ */
+void save_xattrs()
+{
+	/* save the current state of the compressed xattr data */
+	sxattr_bytes = xattr_bytes;
+	stotal_xattr_bytes = total_xattr_bytes;
+
+	/*
+	 * save the current state of the cached uncompressed xattr data.
+	 * Note we have to save the contents of the data cache because future
+	 * operations will delete the current contents
+	 */
+	sdata_cache = malloc(cache_bytes);
+	if(sdata_cache == NULL)
+		MEM_ERROR();
+
+	memcpy(sdata_cache, data_cache, cache_bytes);
+	scache_bytes = cache_bytes;
+
+	/* save the current state of the xattr id table */
+	sxattr_ids = xattr_ids;
+}
+
+
+/*
+ * Restore xattrs in the event of an abort in appending
+ */
+void restore_xattrs()
+{
+	/* restore the state of the compressed xattr data */
+	xattr_bytes = sxattr_bytes;
+	total_xattr_bytes = stotal_xattr_bytes;
+
+	/* restore the state of the uncomoressed xattr data */
+	memcpy(data_cache, sdata_cache, scache_bytes);
+	cache_bytes = scache_bytes;
+
+	/* restore the state of the xattr id table */
+	xattr_ids = sxattr_ids;
+}
diff --git a/squashfs-tools/squashfs-tools/xattr.h b/squashfs-tools/squashfs-tools/xattr.h
new file mode 100644
index 0000000..9260255
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/xattr.h
@@ -0,0 +1,150 @@
+#ifndef XATTR_H
+#define XATTR_H
+/*
+ * Create a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2010, 2012, 2013, 2014
+ * 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.
+ *
+ * xattr.h
+ */
+
+#define XATTR_VALUE_OOL		SQUASHFS_XATTR_VALUE_OOL
+#define XATTR_PREFIX_MASK	SQUASHFS_XATTR_PREFIX_MASK
+
+#define XATTR_VALUE_OOL_SIZE	sizeof(long long)
+
+/* maximum size of xattr value data that will be inlined */
+#define XATTR_INLINE_MAX 	128
+
+/* the target size of an inode's xattr name:value list.  If it
+ * exceeds this, then xattr value data will be successively out of lined
+ * until it meets the target */
+#define XATTR_TARGET_MAX	65536
+
+#define IS_XATTR(a)		(a != SQUASHFS_INVALID_XATTR)
+
+struct xattr_list {
+	char			*name;
+	char			*full_name;
+	int			size;
+	int			vsize;
+	void			*value;
+	int			type;
+	long long		ool_value;
+	unsigned short		vchecksum;
+	struct xattr_list	*vnext;
+};
+
+struct dupl_id {
+	struct xattr_list	*xattr_list;
+	int			xattrs;
+	int			xattr_id;
+	struct dupl_id		*next;
+};
+
+struct prefix {
+	char			*prefix;
+	int			type;
+};
+
+extern int generate_xattrs(int, struct xattr_list *);
+
+#ifdef XATTR_SUPPORT
+extern int get_xattrs(int, struct squashfs_super_block *);
+extern int read_xattrs(void *);
+extern long long write_xattrs();
+extern void save_xattrs();
+extern void restore_xattrs();
+extern unsigned int xattr_bytes, total_xattr_bytes;
+extern void write_xattr(char *, unsigned int);
+extern int read_xattrs_from_disk(int, struct squashfs_super_block *);
+extern struct xattr_list *get_xattr(int, unsigned int *, int);
+extern void free_xattr(struct xattr_list *, int);
+#else
+static inline int get_xattrs(int fd, struct squashfs_super_block *sBlk)
+{
+	if(sBlk->xattr_id_table_start != SQUASHFS_INVALID_BLK) {
+		fprintf(stderr, "Xattrs in filesystem! These are not "
+			"supported on this version of Squashfs\n");
+		return 0;
+	} else
+		return SQUASHFS_INVALID_BLK;
+}
+
+
+static inline int read_xattrs(void *dir_ent)
+{
+	return SQUASHFS_INVALID_XATTR;
+}
+
+
+static inline long long write_xattrs()
+{
+	return SQUASHFS_INVALID_BLK;
+}
+
+
+static inline void save_xattrs()
+{
+}
+
+
+static inline void restore_xattrs()
+{
+}
+
+
+static inline void write_xattr(char *pathname, unsigned int xattr)
+{
+}
+
+
+static inline int read_xattrs_from_disk(int fd, struct squashfs_super_block *sBlk)
+{
+	if(sBlk->xattr_id_table_start != SQUASHFS_INVALID_BLK) {
+		fprintf(stderr, "Xattrs in filesystem! These are not "
+			"supported on this version of Squashfs\n");
+		return 0;
+	} else
+		return SQUASHFS_INVALID_BLK;
+}
+
+
+static inline struct xattr_list *get_xattr(int i, unsigned int *count, int j)
+{
+	return NULL;
+}
+#endif
+
+#ifdef XATTR_SUPPORT
+#ifdef XATTR_DEFAULT
+#define NOXOPT_STR
+#define XOPT_STR " (default)"
+#define XATTR_DEF 0
+#else
+#define NOXOPT_STR " (default)"
+#define XOPT_STR
+#define XATTR_DEF 1
+#endif
+#else
+#define NOXOPT_STR " (default)"
+#define XOPT_STR " (unsupported)"
+#define XATTR_DEF 1
+#endif
+#endif
diff --git a/squashfs-tools/squashfs-tools/xz_wrapper.c b/squashfs-tools/squashfs-tools/xz_wrapper.c
new file mode 100644
index 0000000..e77ec59
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/xz_wrapper.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright (c) 2010, 2011, 2012, 2013
+ * 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.
+ *
+ * xz_wrapper.c
+ *
+ * Support for XZ (LZMA2) compression using XZ Utils liblzma
+ * http://tukaani.org/xz/
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <lzma.h>
+
+#include "squashfs_fs.h"
+#include "xz_wrapper.h"
+#include "compressor.h"
+
+static struct bcj bcj[] = {
+	{ "x86", LZMA_FILTER_X86, 0 },
+	{ "powerpc", LZMA_FILTER_POWERPC, 0 },
+	{ "ia64", LZMA_FILTER_IA64, 0 },
+	{ "arm", LZMA_FILTER_ARM, 0 },
+	{ "armthumb", LZMA_FILTER_ARMTHUMB, 0 },
+	{ "sparc", LZMA_FILTER_SPARC, 0 },
+	{ NULL, LZMA_VLI_UNKNOWN, 0 }
+};
+
+static int filter_count = 1;
+static int dictionary_size = 0;
+static float dictionary_percent = 0;
+
+
+/*
+ * This function is called by the options parsing code in mksquashfs.c
+ * to parse any -X compressor option.
+ *
+ * Two specific options are supported:
+ *	-Xbcj
+ *	-Xdict-size
+ *
+ * This function returns:
+ *	>=0 (number of additional args parsed) on success
+ *	-1 if the option was unrecognised, or
+ *	-2 if the option was recognised, but otherwise bad in
+ *	   some way (e.g. invalid parameter)
+ *
+ * Note: this function sets internal compressor state, but does not
+ * pass back the results of the parsing other than success/failure.
+ * The xz_dump_options() function is called later to get the options in
+ * a format suitable for writing to the filesystem.
+ */
+static int xz_options(char *argv[], int argc)
+{
+	int i;
+	char *name;
+
+	if(strcmp(argv[0], "-Xbcj") == 0) {
+		if(argc < 2) {
+			fprintf(stderr, "xz: -Xbcj missing filter\n");
+			goto failed;
+		}
+
+		name = argv[1];
+		while(name[0] != '\0') {
+			for(i = 0; bcj[i].name; i++) {
+				int n = strlen(bcj[i].name);
+				if((strncmp(name, bcj[i].name, n) == 0) &&
+						(name[n] == '\0' ||
+						 name[n] == ',')) {
+					if(bcj[i].selected == 0) {
+				 		bcj[i].selected = 1;
+						filter_count++;
+					}
+					name += name[n] == ',' ? n + 1 : n;
+					break;
+				}
+			}
+			if(bcj[i].name == NULL) {
+				fprintf(stderr, "xz: -Xbcj unrecognised "
+					"filter\n");
+				goto failed;
+			}
+		}
+	
+		return 1;
+	} else if(strcmp(argv[0], "-Xdict-size") == 0) {
+		char *b;
+		float size;
+
+		if(argc < 2) {
+			fprintf(stderr, "xz: -Xdict-size missing dict-size\n");
+			goto failed;
+		}
+
+		size = strtof(argv[1], &b);
+		if(*b == '%') {
+			if(size <= 0 || size > 100) {
+				fprintf(stderr, "xz: -Xdict-size percentage "
+					"should be 0 < dict-size <= 100\n");
+				goto failed;
+			}
+
+			dictionary_percent = size;
+			dictionary_size = 0;
+		} else {
+			if((float) ((int) size) != size) {
+				fprintf(stderr, "xz: -Xdict-size can't be "
+					"fractional unless a percentage of the"
+					" block size\n");
+				goto failed;
+			}
+
+			dictionary_percent = 0;
+			dictionary_size = (int) size;
+
+			if(*b == 'k' || *b == 'K')
+				dictionary_size *= 1024;
+			else if(*b == 'm' || *b == 'M')
+				dictionary_size *= 1024 * 1024;
+			else if(*b != '\0') {
+				fprintf(stderr, "xz: -Xdict-size invalid "
+					"dict-size\n");
+				goto failed;
+			}
+		}
+
+		return 1;
+	}
+
+	return -1;
+	
+failed:
+	return -2;
+}
+
+
+/*
+ * This function is called after all options have been parsed.
+ * It is used to do post-processing on the compressor options using
+ * values that were not expected to be known at option parse time.
+ *
+ * In this case block_size may not be known until after -Xdict-size has
+ * been processed (in the case where -b is specified after -Xdict-size)
+ *
+ * This function returns 0 on successful post processing, or
+ *			-1 on error
+ */
+static int xz_options_post(int block_size)
+{
+	/*
+	 * if -Xdict-size has been specified use this to compute the datablock
+	 * dictionary size
+	 */
+	if(dictionary_size || dictionary_percent) {
+		int n;
+
+		if(dictionary_size) {
+			if(dictionary_size > block_size) {
+				fprintf(stderr, "xz: -Xdict-size is larger than"
+				" block_size\n");
+				goto failed;
+			}
+		} else
+			dictionary_size = block_size * dictionary_percent / 100;
+
+		if(dictionary_size < 8192) {
+			fprintf(stderr, "xz: -Xdict-size should be 8192 bytes "
+				"or larger\n");
+			goto failed;
+		}
+
+		/*
+		 * dictionary_size must be storable in xz header as either
+		 * 2^n or as  2^n+2^(n+1)
+	 	*/
+		n = ffs(dictionary_size) - 1;
+		if(dictionary_size != (1 << n) && 
+				dictionary_size != ((1 << n) + (1 << (n + 1)))) {
+			fprintf(stderr, "xz: -Xdict-size is an unsupported "
+				"value, dict-size must be storable in xz "
+				"header\n");
+			fprintf(stderr, "as either 2^n or as 2^n+2^(n+1).  "
+				"Example dict-sizes are 75%%, 50%%, 37.5%%, "
+				"25%%,\n");
+			fprintf(stderr, "or 32K, 16K, 8K etc.\n");
+			goto failed;
+		}
+
+	} else
+		/* No -Xdict-size specified, use defaults */
+		dictionary_size = block_size;
+
+	return 0;
+
+failed:
+	return -1;
+}
+
+
+/*
+ * This function is called by mksquashfs to dump the parsed
+ * compressor options in a format suitable for writing to the
+ * compressor options field in the filesystem (stored immediately
+ * after the superblock).
+ *
+ * This function returns a pointer to the compression options structure
+ * to be stored (and the size), or NULL if there are no compression
+ * options
+ */
+static void *xz_dump_options(int block_size, int *size)
+{
+	static struct comp_opts comp_opts;
+	int flags = 0, i;
+
+	/*
+	 * don't store compressor specific options in file system if the
+	 * default options are being used - no compressor options in the
+	 * file system means the default options are always assumed
+	 *
+	 * Defaults are:
+	 *  metadata dictionary size: SQUASHFS_METADATA_SIZE
+	 *  datablock dictionary size: block_size
+	 *  1 filter
+	 */
+	if(dictionary_size == block_size && filter_count == 1)
+		return NULL;
+
+	for(i = 0; bcj[i].name; i++)
+		flags |= bcj[i].selected << i;
+
+	comp_opts.dictionary_size = dictionary_size;
+	comp_opts.flags = flags;
+
+	SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
+
+	*size = sizeof(comp_opts);
+	return &comp_opts;
+}
+
+
+/*
+ * This function is a helper specifically for the append mode of
+ * mksquashfs.  Its purpose is to set the internal compressor state
+ * to the stored compressor options in the passed compressor options
+ * structure.
+ *
+ * In effect this function sets up the compressor options
+ * to the same state they were when the filesystem was originally
+ * generated, this is to ensure on appending, the compressor uses
+ * the same compression options that were used to generate the
+ * original filesystem.
+ *
+ * Note, even if there are no compressor options, this function is still
+ * called with an empty compressor structure (size == 0), to explicitly
+ * set the default options, this is to ensure any user supplied
+ * -X options on the appending mksquashfs command line are over-ridden
+ *
+ * This function returns 0 on sucessful extraction of options, and
+ *			-1 on error
+ */
+static int xz_extract_options(int block_size, void *buffer, int size)
+{
+	struct comp_opts *comp_opts = buffer;
+	int flags, i, n;
+
+	if(size == 0) {
+		/* set defaults */
+		dictionary_size = block_size;
+		flags = 0;
+	} else {
+		/* check passed comp opts struct is of the correct length */
+		if(size != sizeof(struct comp_opts))
+			goto failed;
+					 
+		SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+		dictionary_size = comp_opts->dictionary_size;
+		flags = comp_opts->flags;
+
+		/*
+		 * check that the dictionary size seems correct - the dictionary
+		 * size should 2^n or 2^n+2^(n+1)
+		 */
+		n = ffs(dictionary_size) - 1;
+		if(dictionary_size != (1 << n) && 
+				dictionary_size != ((1 << n) + (1 << (n + 1))))
+			goto failed;
+	}
+
+	filter_count = 1;
+	for(i = 0; bcj[i].name; i++) {
+		if((flags >> i) & 1) {
+			bcj[i].selected = 1;
+			filter_count ++;
+		} else
+			bcj[i].selected = 0;
+	}
+
+	return 0;
+
+failed:
+	fprintf(stderr, "xz: error reading stored compressor options from "
+		"filesystem!\n");
+
+	return -1;
+}
+
+
+void xz_display_options(void *buffer, int size)
+{
+	struct comp_opts *comp_opts = buffer;
+	int dictionary_size, flags, printed;
+	int i, n;
+
+	/* check passed comp opts struct is of the correct length */
+	if(size != sizeof(struct comp_opts))
+		goto failed;
+
+	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+	dictionary_size = comp_opts->dictionary_size;
+	flags = comp_opts->flags;
+
+	/*
+	 * check that the dictionary size seems correct - the dictionary
+	 * size should 2^n or 2^n+2^(n+1)
+	 */
+	n = ffs(dictionary_size) - 1;
+	if(dictionary_size != (1 << n) && 
+			dictionary_size != ((1 << n) + (1 << (n + 1))))
+		goto failed;
+
+	printf("\tDictionary size %d\n", dictionary_size);
+
+	printed = 0;
+	for(i = 0; bcj[i].name; i++) {
+		if((flags >> i) & 1) {
+			if(printed)
+				printf(", ");
+			else
+				printf("\tFilters selected: ");
+			printf("%s", bcj[i].name);
+			printed = 1;
+		}
+	}
+
+	if(!printed)
+		printf("\tNo filters specified\n");
+	else
+		printf("\n");
+
+	return;
+
+failed:
+	fprintf(stderr, "xz: error reading stored compressor options from "
+		"filesystem!\n");
+}	
+
+
+/*
+ * This function is called by mksquashfs to initialise the
+ * compressor, before compress() is called.
+ *
+ * This function returns 0 on success, and
+ *			-1 on error
+ */
+static int xz_init(void **strm, int block_size, int datablock)
+{
+	int i, j, filters = datablock ? filter_count : 1;
+	struct filter *filter = malloc(filters * sizeof(struct filter));
+	struct xz_stream *stream;
+
+	if(filter == NULL)
+		goto failed;
+
+	stream = *strm = malloc(sizeof(struct xz_stream));
+	if(stream == NULL)
+		goto failed2;
+
+	stream->filter = filter;
+	stream->filters = filters;
+
+	memset(filter, 0, filters * sizeof(struct filter));
+
+	stream->dictionary_size = datablock ? dictionary_size :
+		SQUASHFS_METADATA_SIZE;
+
+	filter[0].filter[0].id = LZMA_FILTER_LZMA2;
+	filter[0].filter[0].options = &stream->opt;
+	filter[0].filter[1].id = LZMA_VLI_UNKNOWN;
+
+	for(i = 0, j = 1; datablock && bcj[i].name; i++) {
+		if(bcj[i].selected) {
+			filter[j].buffer = malloc(block_size);
+			if(filter[j].buffer == NULL)
+				goto failed3;
+			filter[j].filter[0].id = bcj[i].id;
+			filter[j].filter[1].id = LZMA_FILTER_LZMA2;
+			filter[j].filter[1].options = &stream->opt;
+			filter[j].filter[2].id = LZMA_VLI_UNKNOWN;
+			j++;
+		}
+	}
+
+	return 0;
+
+failed3:
+	for(i = 1; i < filters; i++)
+		free(filter[i].buffer);
+	free(stream);
+
+failed2:
+	free(filter);
+
+failed:
+	return -1;
+}
+
+
+static int xz_compress(void *strm, void *dest, void *src,  int size,
+	int block_size, int *error)
+{
+	int i;
+        lzma_ret res = 0;
+	struct xz_stream *stream = strm;
+	struct filter *selected = NULL;
+
+	stream->filter[0].buffer = dest;
+
+	for(i = 0; i < stream->filters; i++) {
+		struct filter *filter = &stream->filter[i];
+
+        	if(lzma_lzma_preset(&stream->opt, LZMA_PRESET_DEFAULT))
+                	goto failed;
+
+		stream->opt.dict_size = stream->dictionary_size;
+
+		filter->length = 0;
+		res = lzma_stream_buffer_encode(filter->filter,
+			LZMA_CHECK_CRC32, NULL, src, size, filter->buffer,
+			&filter->length, block_size);
+	
+		if(res == LZMA_OK) {
+			if(!selected || selected->length > filter->length)
+				selected = filter;
+		} else if(res != LZMA_BUF_ERROR)
+			goto failed;
+	}
+
+	if(!selected)
+		/*
+	 	 * Output buffer overflow.  Return out of buffer space
+	 	 */
+		return 0;
+
+	if(selected->buffer != dest)
+		memcpy(dest, selected->buffer, selected->length);
+
+	return (int) selected->length;
+
+failed:
+	/*
+	 * All other errors return failure, with the compressor
+	 * specific error code in *error
+	 */
+	*error = res;
+	return -1;
+}
+
+
+static int xz_uncompress(void *dest, void *src, int size, int outsize,
+	int *error)
+{
+	size_t src_pos = 0;
+	size_t dest_pos = 0;
+	uint64_t memlimit = MEMLIMIT;
+
+	lzma_ret res = lzma_stream_buffer_decode(&memlimit, 0, NULL,
+			src, &src_pos, size, dest, &dest_pos, outsize);
+
+	if(res == LZMA_OK && size == (int) src_pos)
+		return (int) dest_pos;
+	else {
+		*error = res;
+		return -1;
+	}
+}
+
+
+void xz_usage()
+{
+	fprintf(stderr, "\t  -Xbcj filter1,filter2,...,filterN\n");
+	fprintf(stderr, "\t\tCompress using filter1,filter2,...,filterN in");
+	fprintf(stderr, " turn\n\t\t(in addition to no filter), and choose");
+	fprintf(stderr, " the best compression.\n");
+	fprintf(stderr, "\t\tAvailable filters: x86, arm, armthumb,");
+	fprintf(stderr, " powerpc, sparc, ia64\n");
+	fprintf(stderr, "\t  -Xdict-size <dict-size>\n");
+	fprintf(stderr, "\t\tUse <dict-size> as the XZ dictionary size.  The");
+	fprintf(stderr, " dictionary size\n\t\tcan be specified as a");
+	fprintf(stderr, " percentage of the block size, or as an\n\t\t");
+	fprintf(stderr, "absolute value.  The dictionary size must be less");
+	fprintf(stderr, " than or equal\n\t\tto the block size and 8192 bytes");
+	fprintf(stderr, " or larger.  It must also be\n\t\tstorable in the xz");
+	fprintf(stderr, " header as either 2^n or as 2^n+2^(n+1).\n\t\t");
+	fprintf(stderr, "Example dict-sizes are 75%%, 50%%, 37.5%%, 25%%, or");
+	fprintf(stderr, " 32K, 16K, 8K\n\t\tetc.\n");
+}
+
+
+struct compressor xz_comp_ops = {
+	.init = xz_init,
+	.compress = xz_compress,
+	.uncompress = xz_uncompress,
+	.options = xz_options,
+	.options_post = xz_options_post,
+	.dump_options = xz_dump_options,
+	.extract_options = xz_extract_options,
+	.display_options = xz_display_options,
+	.usage = xz_usage,
+	.id = XZ_COMPRESSION,
+	.name = "xz",
+	.supported = 1
+};
diff --git a/squashfs-tools/squashfs-tools/xz_wrapper.h b/squashfs-tools/squashfs-tools/xz_wrapper.h
new file mode 100644
index 0000000..ce2545c
--- /dev/null
+++ b/squashfs-tools/squashfs-tools/xz_wrapper.h
@@ -0,0 +1,71 @@
+#ifndef XZ_WRAPPER_H
+#define XZ_WRAPPER_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2010
+ * 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.
+ *
+ * xz_wrapper.h
+ *
+ */
+
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+extern unsigned int inswap_le32(unsigned int);
+
+#define SQUASHFS_INSWAP_COMP_OPTS(s) { \
+	(s)->dictionary_size = inswap_le32((s)->dictionary_size); \
+	(s)->flags = inswap_le32((s)->flags); \
+}
+#else
+#define SQUASHFS_INSWAP_COMP_OPTS(s)
+#endif
+
+#define MEMLIMIT (32 * 1024 * 1024)
+
+struct bcj {
+	char	 	*name;
+	lzma_vli	id;
+	int		selected;
+};
+
+struct filter {
+	void		*buffer;
+	lzma_filter	filter[3];
+	size_t		length;
+};
+
+struct xz_stream {
+	struct filter	*filter;
+	int		filters;
+	int		dictionary_size;
+	lzma_options_lzma opt;
+};
+
+struct comp_opts {
+	int dictionary_size;
+	int flags;
+};
+#endif