Radix cross Linux Build System

Cross-platform build system is designed to build distributions of different operating systems for a set of target devices

74 Commits   2 Branches   2 Tags
Index: create.patch.sh
===================================================================
--- create.patch.sh	(nonexistent)
+++ create.patch.sh	(revision 5)
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+VERSION=1.4.1
+
+tar --files-from=file.list -xzvf ../genext2fs-$VERSION.tar.gz
+mv genext2fs-$VERSION genext2fs-$VERSION-orig
+
+cp -rf ./genext2fs-$VERSION-new ./genext2fs-$VERSION
+
+diff -b --unified -Nr  genext2fs-$VERSION-orig  genext2fs-$VERSION > genext2fs-$VERSION-blocksize.patch
+
+mv genext2fs-$VERSION-blocksize.patch ../patches
+
+rm -rf ./genext2fs-$VERSION
+rm -rf ./genext2fs-$VERSION-orig

Property changes on: create.patch.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: file.list
===================================================================
--- file.list	(nonexistent)
+++ file.list	(revision 5)
@@ -0,0 +1,5 @@
+genext2fs-1.4.1/genext2fs.8
+genext2fs-1.4.1/genext2fs.c
+genext2fs-1.4.1/test-gen.lib
+genext2fs-1.4.1/test-mount.sh
+genext2fs-1.4.1/test.sh
Index: genext2fs-1.4.1-new/genext2fs.8
===================================================================
--- genext2fs-1.4.1-new/genext2fs.8	(nonexistent)
+++ genext2fs-1.4.1-new/genext2fs.8	(revision 5)
@@ -0,0 +1,178 @@
+.\"                                      Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH GENEXT2FS 8 "October 11, 2015"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh        disable hyphenation
+.\" .hy        enable hyphenation
+.\" .ad l      left justify
+.\" .ad b      justify to both left and right margins
+.\" .nf        disable filling
+.\" .fi        enable filling
+.\" .br        insert line break
+.\" .sp <n>    insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+genext2fs \- ext2 filesystem generator for embedded systems
+.SH SYNOPSIS
+.B genext2fs
+.RI "[ options ] [ output\-image ]"
+.SH DESCRIPTION
+\fBgenext2fs\fP generates an ext2 filesystem
+as a normal (non-root) user. It does not require you to mount
+the image file to copy files on it, nor does it require that
+you become the superuser to make device nodes.
+
+The filesystem image is created in the file \fIoutput-image\fP. If not
+specified, it is sent to stdout.
+
+By default, the maximum number of inodes in the filesystem is the minimum
+number required to accommodate the initial contents.
+In this way, a minimal filesystem (typically read-only) can be created with
+minimal free inodes.
+If required, free inodes can be added by passing the relevant options.
+The filesystem image size in blocks can be minimised by trial and error.
+.SH OPTIONS
+.TP
+.BI "\-x, \-\-starting\-image image"
+Use this image as a starting point.
+.TP
+.BI "\-d, \-\-root directory[:path]"
+Add the given directory and contents at a particular path (by default
+the root).
+.TP
+.BI "\-D, \-\-devtable spec\-file[:path]"
+Use \fBspec-file\fP to specify inodes to be added, at the given
+path (by default the root), including files, directories and
+special files like devices.
+If the specified files are already present in the image, their
+ownership and permission modes will be adjusted accordingly.
+Furthermore, you can use a single table entry to create many devices
+with a range of minor numbers (see examples below).
+All specified inodes receive the mtime of \fBspec-file\fP itself.
+.TP
+.BI "\-B, \-\-block\-size bytes"
+Size of the block (valid block sizes: 1024, 20148 or 4096 bytes).
+.TP
+.BI "\-b, \-\-size\-in\-blocks blocks"
+Size of the image in blocks.
+.TP
+.BI "\-N, \-\-number\-of\-inodes inodes"
+Maximum number of inodes.
+.TP
+.BI "\-i, \-\-bytes\-per\-inode ratio"
+Used to calculate the maximum number of inodes from the available blocks.
+.TP
+.BI "\-m, \-\-reserved\-percentage"
+Number of reserved blocks as a percentage of size. Reserving 0 blocks will prevent creation of the "lost+found" directory.
+.TP
+.BI "\-o, \-\-creator\-os OS"
+The default value for Creator OS ('linux', 'hurd', 'freebsd' or a numerical value: 0, 1, 3).
+.TP
+.BI "\-g, \-\-block\-map path"
+Generate a block map file for this path.
+.TP
+.BI "\-e, \-\-fill\-value value"
+Fill unallocated blocks with value.
+.TP
+.BI "\-z, \-\-allow\-holes"
+Make files with holes.
+.TP
+.BI "\-f, \-\-faketime"
+Use a timestamp of 0 for inode and filesystem creation, instead of the present. Useful for testing.
+.TP
+.BI "\-q, \-\-squash"
+Squash permissions and owners (same as -P -U).
+.TP
+.BI "\-U, \-\-squash\-uids"
+Squash ownership of inodes added using the -d option, making them all
+owned by root:root.
+.TP
+.BI "\-P, \-\-squash\-perms"
+Squash permissions of inodes added using the -d option. Analogous to
+"umask 077".
+.TP
+.BI "\-v, \-\-verbose"
+Print resulting filesystem structure.
+.TP
+.BI "\-V, \-\-version"
+Print genext2fs version.
+.TP
+.BI "\-h, \-\-help"
+Display help.
+.SH EXAMPLES
+
+.EX
+.B
+genext2fs -b 1440 -d src /dev/fd0
+.EE
+
+All files in the 
+.I src
+directory will be written to
+.B /dev/fd0
+as a new ext2 filesystem image. You can then mount the floppy as
+usual.
+
+.EX
+.B
+genext2fs -b 1024 -d src -D device_table.txt flashdisk.img
+.EE
+
+This example builds a filesystem from all the files in 
+.I src,
+then device nodes are created based on the contents of the file
+.I device_table.txt.
+Entries in the device table take the form of:
+
+<name> <type> <mode> <uid> <gid> <major> <minor> <start> <inc> <count>
+
+where name is the file name and type can be one of: 
+.RS
+.nf
+f	A regular file
+d	Directory
+c	Character special device file
+b	Block special device file
+p	Fifo (named pipe)
+.fi
+.RE
+uid is the user id for the target file, gid is the group id for the
+target file.  The rest of the entries (major, minor, etc) apply only 
+to device special files.
+
+An example device file follows:
+
+.RS
+.nf
+# name	type mode uid gid major minor start inc count
+
+/dev		d	755	0	0	-	-	-	-	-
+/dev/mem	c	640	0	0	1	1	0	0	-
+/dev/tty	c	666	0	0	5	0	0	0	-
+/dev/tty	c	666	0	0	4	0	0	1	6
+/dev/loop	b	640	0	0	7	0	0	1	2
+/dev/hda	b	640	0	0	3	0	0	0	-
+/dev/hda	b	640	0	0	3	1	1	1	16
+/dev/log	s	666	0	0	-	-	-	-	-
+.fi
+.RE
+
+This device table creates the /dev directory, a character device
+node /dev/mem (major 1, minor 1), and also creates /dev/tty, 
+/dev/tty[0-5], /dev/loop[0-1], /dev/hda, /dev/hda1 to /dev/hda15 and
+/dev/log socket.
+
+.SH SEE ALSO
+.BR mkfs(8),
+.BR genromfs(8),
+.BR mkisofs(8),
+.BR mkfs.jffs2(1)
+.br
+.SH AUTHOR
+This manual page was written by David Kimdon <dwhedon@debian.org>,
+for the Debian GNU/Linux system (but may be used by others).
+Examples provided by Erik Andersen <andersen@codepoet.org>.
Index: genext2fs-1.4.1-new/genext2fs.c
===================================================================
--- genext2fs-1.4.1-new/genext2fs.c	(nonexistent)
+++ genext2fs-1.4.1-new/genext2fs.c	(revision 5)
@@ -0,0 +1,2721 @@
+/* vi: set sw=8 ts=8: */
+// genext2fs.c
+//
+// ext2 filesystem generator for embedded systems
+// Copyright (C) 2000 Xavier Bestel <xavier.bestel@free.fr>
+//
+// Please direct support requests to genext2fs-devel@lists.sourceforge.net
+//
+// 'du' portions taken from coreutils/du.c in busybox:
+//	Copyright (C) 1999,2000 by Lineo, inc. and John Beppu
+//	Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org>
+//	Copyright (C) 2002  Edward Betts <edward@debian.org>
+//
+// 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; version
+// 2 of the License.
+//
+// Changes:
+// 	 3 Jun 2000	Initial release
+// 	 6 Jun 2000	Bugfix: fs size multiple of 8
+// 			Bugfix: fill blocks with inodes
+// 	14 Jun 2000	Bugfix: bad chdir() with -d option
+// 			Bugfix: removed size=8n constraint
+// 			Changed -d file to -f file
+// 			Added -e option
+// 	22 Jun 2000	Changed types for 64bits archs
+// 	24 Jun 2000	Added endianness swap
+// 			Bugfix: bad dir name lookup
+// 	03 Aug 2000	Bugfix: ind. blocks endian swap
+// 	09 Aug 2000	Bugfix: symlinks endian swap
+// 	01 Sep 2000	Bugfix: getopt returns int, not char	proski@gnu.org
+// 	10 Sep 2000	Bugfix: device nodes endianness		xavier.gueguen@col.bsf.alcatel.fr
+// 			Bugfix: getcwd values for Solaris	xavier.gueguen@col.bsf.alcatel.fr
+// 			Bugfix: ANSI scanf for non-GNU C	xavier.gueguen@col.bsf.alcatel.fr
+// 	28 Jun 2001	Bugfix: getcwd differs for Solaris/GNU	mike@sowbug.com
+// 	 8 Mar 2002	Bugfix: endianness swap of x-indirects
+// 	23 Mar 2002	Bugfix: test for IFCHR or IFBLK was flawed
+// 	10 Oct 2002	Added comments,makefile targets,	vsundar@ixiacom.com    
+// 			endianess swap assert check.  
+// 			Copyright (C) 2002 Ixia communications
+// 	12 Oct 2002	Added support for triple indirection	vsundar@ixiacom.com
+// 			Copyright (C) 2002 Ixia communications
+// 	14 Oct 2002	Added support for groups		vsundar@ixiacom.com
+// 			Copyright (C) 2002 Ixia communications
+// 	 5 Jan 2003	Bugfixes: reserved inodes should be set vsundar@usc.edu
+// 			only in the first group; directory names
+// 			need to be null padded at the end; and 
+// 			number of blocks per group should be a 
+// 			multiple of 8. Updated md5 values. 
+// 	 6 Jan 2003	Erik Andersen <andersee@debian.org> added
+// 			mkfs.jffs2 compatible device table support,
+// 			along with -q, -P, -U
+
+
+#include <config.h>
+#include <stdio.h>
+
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+
+#if MAJOR_IN_MKDEV
+# include <sys/mkdev.h>
+#elif MAJOR_IN_SYSMACROS
+# include <sys/sysmacros.h>
+#endif
+
+#if HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+# if HAVE_STDDEF_H
+#  include <stddef.h>
+# endif
+#endif
+
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+
+#if HAVE_STRINGS_H
+# include <strings.h>
+#endif
+
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#else
+# if HAVE_STDINT_H
+#  include <stdint.h>
+# endif
+#endif
+
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#if HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# if HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# if HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif
+
+#if HAVE_LIBGEN_H
+# include <libgen.h>
+#endif
+
+#include <stdarg.h>
+#include <assert.h>
+#include <time.h>
+#include <ctype.h>
+#include <errno.h>
+
+#if HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+
+#if HAVE_GETOPT_H
+# include <getopt.h>
+#endif
+
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+struct stats {
+	unsigned long nblocks;
+	unsigned long ninodes;
+};
+
+// block size
+
+static int blocksize = 1024;
+
+#define BLOCKSIZE         blocksize
+#define BLOCKS_PER_GROUP  8192
+#define INODES_PER_GROUP  8192
+/* Percentage of blocks that are reserved.*/
+#define RESERVED_BLOCKS       5/100
+#define MAX_RESERVED_BLOCKS  25/100
+
+/* The default value for s_creator_os. */
+#if defined(__GNU__)
+# define CREATOR_OS  1 /* Hurd */
+#elif defined(__FreeBSD__)
+# define CREATOR_OS  3 /* FreeBSD */
+#else
+# define CREATOR_OS  0 /* Linux */
+#endif
+
+
+// inode block size (why is it != BLOCKSIZE ?!?)
+/* The field i_blocks in the ext2 inode stores the number of data blocks
+   but in terms of 512 bytes. That is what INODE_BLOCKSIZE represents.
+   INOBLK is the number of such blocks in an actual disk block            */
+
+#define INODE_BLOCKSIZE   512
+#define INOBLK            (BLOCKSIZE / INODE_BLOCKSIZE)
+
+// reserved inodes
+
+#define EXT2_BAD_INO         1     // Bad blocks inode
+#define EXT2_ROOT_INO        2     // Root inode
+#define EXT2_ACL_IDX_INO     3     // ACL inode
+#define EXT2_ACL_DATA_INO    4     // ACL inode
+#define EXT2_BOOT_LOADER_INO 5     // Boot loader inode
+#define EXT2_UNDEL_DIR_INO   6     // Undelete directory inode
+#define EXT2_FIRST_INO       11    // First non reserved inode
+
+// magic number for ext2
+
+#define EXT2_MAGIC_NUMBER  0xEF53
+
+
+// direct/indirect block addresses
+
+#define EXT2_NDIR_BLOCKS   11                    // direct blocks
+#define EXT2_IND_BLOCK     12                    // indirect block
+#define EXT2_DIND_BLOCK    13                    // double indirect block
+#define EXT2_TIND_BLOCK    14                    // triple indirect block
+#define EXT2_INIT_BLOCK    0xFFFFFFFF            // just initialized (not really a block address)
+
+// end of a block walk
+
+#define WALK_END           0xFFFFFFFE
+
+// file modes
+
+#define FM_IFMT    0170000	// format mask
+#define FM_IFSOCK  0140000	// socket
+#define FM_IFLNK   0120000	// symbolic link
+#define FM_IFREG   0100000	// regular file
+
+#define FM_IFBLK   0060000	// block device
+#define FM_IFDIR   0040000	// directory
+#define FM_IFCHR   0020000	// character device
+#define FM_IFIFO   0010000	// fifo
+
+#define FM_IMASK   0007777	// *all* perms mask for everything below
+
+#define FM_ISUID   0004000	// SUID
+#define FM_ISGID   0002000	// SGID
+#define FM_ISVTX   0001000	// sticky bit
+
+#define FM_IRWXU   0000700	// entire "user" mask
+#define FM_IRUSR   0000400	// read
+#define FM_IWUSR   0000200	// write
+#define FM_IXUSR   0000100	// execute
+
+#define FM_IRWXG   0000070	// entire "group" mask
+#define FM_IRGRP   0000040	// read
+#define FM_IWGRP   0000020	// write
+#define FM_IXGRP   0000010	// execute
+
+#define FM_IRWXO   0000007	// entire "other" mask
+#define FM_IROTH   0000004	// read
+#define FM_IWOTH   0000002	// write
+#define FM_IXOTH   0000001	// execute
+
+// options
+
+#define OP_HOLES     0x01       // make files with holes
+
+/* Defines for accessing group details */
+
+// Number of groups in the filesystem
+#define GRP_NBGROUPS(fs) \
+	(((fs)->sb.s_blocks_count - fs->sb.s_first_data_block + \
+	  (fs)->sb.s_blocks_per_group - 1) / (fs)->sb.s_blocks_per_group)
+
+// Get group block bitmap (bbm) given the group number
+#define GRP_GET_GROUP_BBM(fs,grp) ( get_blk((fs), get_gd((fs),(grp))->bg_block_bitmap) )
+
+// Get group inode bitmap (ibm) given the group number
+#define GRP_GET_GROUP_IBM(fs,grp) ( get_blk((fs), get_gd((fs),(grp))->bg_inode_bitmap) )
+
+// Given an inode number find the group it belongs to
+#define GRP_GROUP_OF_INODE(fs,nod) ( ((nod)-1) / (fs)->sb.s_inodes_per_group)
+
+//Given an inode number get the inode bitmap that covers it
+#define GRP_GET_INODE_BITMAP(fs,nod) \
+	( GRP_GET_GROUP_IBM((fs),GRP_GROUP_OF_INODE((fs),(nod))) )
+
+//Given an inode number find its offset within the inode bitmap that covers it
+#define GRP_IBM_OFFSET(fs,nod) \
+	( (nod) - GRP_GROUP_OF_INODE((fs),(nod))*(fs)->sb.s_inodes_per_group )
+
+// Given a block number find the group it belongs to
+#define GRP_GROUP_OF_BLOCK(fs,blk) ( ((blk)-1) / (fs)->sb.s_blocks_per_group)
+	
+//Given a block number get the block bitmap that covers it
+#define GRP_GET_BLOCK_BITMAP(fs,blk) \
+	( GRP_GET_GROUP_BBM((fs),GRP_GROUP_OF_BLOCK((fs),(blk))) )
+
+//Given a block number find its offset within the block bitmap that covers it
+#define GRP_BBM_OFFSET(fs,blk) \
+	( (blk) - GRP_GROUP_OF_BLOCK((fs),(blk))*(fs)->sb.s_blocks_per_group )
+
+
+// used types
+
+typedef signed char int8;
+typedef unsigned char uint8;
+typedef signed short int16;
+typedef unsigned short uint16;
+typedef signed int int32;
+typedef unsigned int uint32;
+
+
+// the GNU C library has a wonderful scanf("%as", string) which will
+// allocate the string with the right size, good to avoid buffer
+// overruns. the following macros use it if available or use a
+// hacky workaround
+// moreover it will define a snprintf() like a sprintf(), i.e.
+// without the buffer overrun checking, to work around bugs in
+// older solaris. Note that this is still not very portable, in that
+// the return value cannot be trusted.
+
+#if 0 /* SCANF_CAN_MALLOC */
+/* C99 define "a" for floating point, so you can have
+   runtime surprise according the library versions    */
+# define SCANF_PREFIX "a"
+# define SCANF_STRING(s) (&s)
+#else
+# define SCANF_PREFIX "511"
+# define SCANF_STRING(s) (s = malloc(512))
+#endif /* SCANF_CAN_MALLOC */
+
+#if PREFER_PORTABLE_SNPRINTF
+static inline int
+portable_snprintf(char *str, size_t n, const char *fmt, ...)
+{
+	int ret;
+	va_list ap;
+	va_start(ap, fmt);
+	ret = vsprintf(str, fmt, ap);
+	va_end(ap);
+	return ret;
+}
+# define SNPRINTF portable_snprintf
+#else
+# define SNPRINTF snprintf
+#endif /* PREFER_PORTABLE_SNPRINTF */
+
+#if !HAVE_GETLINE
+// getline() replacement for Darwin and Solaris etc.
+// This code uses backward seeks (unless rchunk is set to 1) which can't work
+// on pipes etc. However, add2fs_from_file() only calls getline() for
+// regular files, so a larger rchunk and backward seeks are okay.
+
+ssize_t 
+getdelim(char **lineptr, size_t *n, int delim, FILE *stream)
+{
+	char *p;                    // reads stored here
+	size_t const rchunk = 512;  // number of bytes to read
+	size_t const mchunk = 512;  // number of extra bytes to malloc
+	size_t m = rchunk + 1;      // initial buffer size
+	
+	if (*lineptr) {
+		if (*n < m) {
+			*lineptr = (char*)realloc(*lineptr, m);
+			if (!*lineptr) return -1;
+			*n = m;
+		}
+	} else {
+		*lineptr = (char*)malloc(m);
+		if (!*lineptr) return -1;
+		*n = m;
+	}
+
+	m = 0; // record length including seperator
+
+	do {
+		size_t i;     // number of bytes read etc
+		size_t j = 0; // number of bytes searched
+
+		p = *lineptr + m;
+
+		i = fread(p, 1, rchunk, stream);
+		if (i < rchunk && ferror(stream))
+			return -1;
+		while (j < i) {
+			++j;
+			if (*p++ == (char)delim) {
+				*p = '\0';
+				if (j != i) {
+					if (fseek(stream, j - i, SEEK_CUR))
+						return -1;
+					if (feof(stream))
+						clearerr(stream);
+				}
+				m += j;
+				return m;
+			}
+		}
+
+		m += j;
+		if (feof(stream)) {
+			if (m) return m;
+			if (!i) return -1;
+		}
+
+		// allocate space for next read plus possible null terminator
+		i = ((m + (rchunk + 1 > mchunk ? rchunk + 1 : mchunk) +
+		      mchunk - 1) / mchunk) * mchunk;
+		if (i != *n) {
+			*lineptr = (char*)realloc(*lineptr, i);
+			if (!*lineptr) return -1;
+			*n = i;
+		}
+	} while (1);
+}
+#define getline(a,b,c) getdelim(a,b,'\n',c)
+#endif /* HAVE_GETLINE */
+
+// Convert a numerical string to a float, and multiply the result by an
+// IEC or SI multiplier if provided; supported multipliers are Ki, Mi, Gi, k, M
+// and G.
+
+float
+SI_atof(const char *nptr)
+{
+	float f = 0;
+	float m = 1;
+	char *suffixptr;
+
+#if HAVE_STRTOF
+	f = strtof(nptr, &suffixptr);
+#else
+	f = (float)strtod(nptr, &suffixptr);
+#endif /* HAVE_STRTOF */
+
+	if (*suffixptr) {
+		if (!strcmp(suffixptr, "Ki"))
+			m = 1 << 10;
+		else if (!strcmp(suffixptr, "Mi"))
+			m = 1 << 20;
+		else if (!strcmp(suffixptr, "Gi"))
+			m = 1 << 30;
+		else if (!strcmp(suffixptr, "k"))
+			m = 1000;
+		else if (!strcmp(suffixptr, "M"))
+			m = 1000 * 1000;
+		else if (!strcmp(suffixptr, "G"))
+			m = 1000 * 1000 * 1000;
+	}
+	return f * m;
+}
+
+// endianness swap
+
+static inline uint16
+swab16(uint16 val)
+{
+	return (val >> 8) | (val << 8);
+}
+
+static inline uint32
+swab32(uint32 val)
+{
+	return ((val>>24) | ((val>>8)&0xFF00) |
+			((val<<8)&0xFF0000) | (val<<24));
+}
+
+
+// on-disk structures
+// this trick makes me declare things only once
+// (once for the structures, once for the endianness swap)
+
+#define superblock_decl \
+	udecl32(s_inodes_count)        /* Count of inodes in the filesystem */ \
+	udecl32(s_blocks_count)        /* Count of blocks in the filesystem */ \
+	udecl32(s_r_blocks_count)      /* Count of the number of reserved blocks */ \
+	udecl32(s_free_blocks_count)   /* Count of the number of free blocks */ \
+	udecl32(s_free_inodes_count)   /* Count of the number of free inodes */ \
+	udecl32(s_first_data_block)    /* The first block which contains data */ \
+	udecl32(s_log_block_size)      /* Indicator of the block size */ \
+	decl32(s_log_frag_size)        /* Indicator of the size of the fragments */ \
+	udecl32(s_blocks_per_group)    /* Count of the number of blocks in each block group */ \
+	udecl32(s_frags_per_group)     /* Count of the number of fragments in each block group */ \
+	udecl32(s_inodes_per_group)    /* Count of the number of inodes in each block group */ \
+	udecl32(s_mtime)               /* The time that the filesystem was last mounted */ \
+	udecl32(s_wtime)               /* The time that the filesystem was last written to */ \
+	udecl16(s_mnt_count)           /* The number of times the file system has been mounted */ \
+	decl16(s_max_mnt_count)        /* The number of times the file system can be mounted */ \
+	udecl16(s_magic)               /* Magic number indicating ex2fs */ \
+	udecl16(s_state)               /* Flags indicating the current state of the filesystem */ \
+	udecl16(s_errors)              /* Flags indicating the procedures for error reporting */ \
+	udecl16(s_minor_rev_level)     /* The minor revision level of the filesystem */ \
+	udecl32(s_lastcheck)           /* The time that the filesystem was last checked */ \
+	udecl32(s_checkinterval)       /* The maximum time permissable between checks */ \
+	udecl32(s_creator_os)          /* Indicator of which OS created the filesystem */ \
+	udecl32(s_rev_level)           /* The revision level of the filesystem */ \
+	udecl16(s_def_resuid)          /* The default uid for reserved blocks */ \
+	udecl16(s_def_resgid)          /* The default gid for reserved blocks */
+
+#define groupdescriptor_decl \
+	udecl32(bg_block_bitmap)       /* Block number of the block bitmap */ \
+	udecl32(bg_inode_bitmap)       /* Block number of the inode bitmap */ \
+	udecl32(bg_inode_table)        /* Block number of the inode table */ \
+	udecl16(bg_free_blocks_count)  /* Free blocks in the group */ \
+	udecl16(bg_free_inodes_count)  /* Free inodes in the group */ \
+	udecl16(bg_used_dirs_count)    /* Number of directories in the group */ \
+	udecl16(bg_pad)
+
+#define inode_decl \
+	udecl16(i_mode)                /* Entry type and file mode */ \
+	udecl16(i_uid)                 /* User id */ \
+	udecl32(i_size)                /* File/dir size in bytes */ \
+	udecl32(i_atime)               /* Last access time */ \
+	udecl32(i_ctime)               /* Creation time */ \
+	udecl32(i_mtime)               /* Last modification time */ \
+	udecl32(i_dtime)               /* Deletion time */ \
+	udecl16(i_gid)                 /* Group id */ \
+	udecl16(i_links_count)         /* Number of (hard) links to this inode */ \
+	udecl32(i_blocks)              /* Number of blocks used (1 block = 512 bytes) */ \
+	udecl32(i_flags)               /* ??? */ \
+	udecl32(i_reserved1) \
+	utdecl32(i_block,15)           /* Blocks table */ \
+	udecl32(i_version)             /* ??? */ \
+	udecl32(i_file_acl)            /* File access control list */ \
+	udecl32(i_dir_acl)             /* Directory access control list */ \
+	udecl32(i_faddr)               /* Fragment address */ \
+	udecl8(i_frag)                 /* Fragments count*/ \
+	udecl8(i_fsize)                /* Fragment size */ \
+	udecl16(i_pad1)
+
+#define directory_decl \
+	udecl32(d_inode)               /* Inode entry */ \
+	udecl16(d_rec_len)             /* Total size on record */ \
+	udecl16(d_name_len)            /* Size of entry name */
+
+#define decl8(x) int8 x;
+#define udecl8(x) uint8 x;
+#define decl16(x) int16 x;
+#define udecl16(x) uint16 x;
+#define decl32(x) int32 x;
+#define udecl32(x) uint32 x;
+#define utdecl32(x,n) uint32 x[n];
+
+typedef struct
+{
+	superblock_decl
+	uint32 s_reserved[235];       // Reserved
+} superblock;
+
+typedef struct
+{
+	groupdescriptor_decl
+	uint32 bg_reserved[3];
+} groupdescriptor;
+
+typedef struct
+{
+	inode_decl
+	uint32 i_reserved2[2];
+} inode;
+
+typedef struct
+{
+	directory_decl
+	char d_name[0];
+} directory;
+
+typedef uint8 *block;
+
+/* blockwalker fields:
+   The blockwalker is used to access all the blocks of a file (including
+   the indirection blocks) through repeated calls to walk_bw.  
+   
+   bpdir -> index into the inode->i_block[]. Indicates level of indirection.
+   bnum -> total number of blocks so far accessed. including indirection
+           blocks.
+   bpind,bpdind,bptind -> index into indirection blocks.
+   
+   bpind, bpdind, bptind do *NOT* index into single, double and triple
+   indirect blocks resp. as you might expect from their names. Instead 
+   they are in order the 1st, 2nd & 3rd index to be used
+   
+   As an example..
+   To access data block number 70000:
+        bpdir: 15 (we are doing triple indirection)
+        bpind: 0 ( index into the triple indirection block)
+        bpdind: 16 ( index into the double indirection block)
+        bptind: 99 ( index into the single indirection block)
+	70000 = 12 + 256 + 256*256 + 16*256 + 100 (indexing starts from zero)
+
+   So,for double indirection bpind will index into the double indirection 
+   block and bpdind into the single indirection block. For single indirection
+   only bpind will be used.
+*/
+   
+typedef struct
+{
+	uint32 bnum;
+	uint32 bpdir;
+	uint32 bpind;
+	uint32 bpdind;
+	uint32 bptind;
+} blockwalker;
+
+
+/* Filesystem structure that support groups */
+typedef struct
+{
+	uint8 zero[1024];      // Room for bootloader stuff
+	superblock sb;         // The superblock, always at 1024
+	// group descriptors come next, see get_gd() below
+} filesystem;
+
+// now the endianness swap
+
+#undef decl8
+#undef udecl8
+#undef decl16
+#undef udecl16
+#undef decl32
+#undef udecl32
+#undef utdecl32
+
+#define decl8(x)
+#define udecl8(x)
+#define decl16(x) this->x = swab16(this->x);
+#define udecl16(x) this->x = swab16(this->x);
+#define decl32(x) this->x = swab32(this->x);
+#define udecl32(x) this->x = swab32(this->x);
+#define utdecl32(x,n) { int i; for(i=0; i<n; i++) this->x[i] = swab32(this->x[i]); }
+
+#define HDLINK_CNT   16
+static int32 hdlink_cnt = HDLINK_CNT;
+struct hdlink_s
+{
+	uint32	src_inode;
+	uint32	dst_nod;
+};
+
+struct hdlinks_s 
+{
+	int32 count;
+	struct hdlink_s *hdl;
+};
+
+static struct hdlinks_s hdlinks;
+
+static void
+swap_sb(superblock *sb)
+{
+#define this sb
+	superblock_decl
+#undef this
+}
+
+static void
+swap_gd(groupdescriptor *gd)
+{
+#define this gd
+	groupdescriptor_decl
+#undef this
+}
+
+static void
+swap_nod(inode *nod)
+{
+#define this nod
+	inode_decl
+#undef this
+}
+
+static void
+swap_dir(directory *dir)
+{
+#define this dir
+	directory_decl
+#undef this
+}
+
+static void
+swap_block(block b)
+{
+	int i;
+	uint32 *blk = (uint32*)b;
+	for(i = 0; i < BLOCKSIZE/4; i++)
+		blk[i] = swab32(blk[i]);
+}
+
+#undef decl8
+#undef udecl8
+#undef decl16
+#undef udecl16
+#undef decl32
+#undef udecl32
+#undef utdecl32
+
+static char * app_name;
+static const char *const memory_exhausted = "memory exhausted";
+
+// error (un)handling
+static void
+verror_msg(const char *s, va_list p)
+{
+	fflush(stdout);
+	fprintf(stderr, "%s: ", app_name);
+	vfprintf(stderr, s, p);
+}
+static void
+error_msg(const char *s, ...)
+{
+	va_list p;
+	va_start(p, s);
+	verror_msg(s, p);
+	va_end(p);
+	putc('\n', stderr);
+}
+
+static void
+error_msg_and_die(const char *s, ...)
+{
+	va_list p;
+	va_start(p, s);
+	verror_msg(s, p);
+	va_end(p);
+	putc('\n', stderr);
+	exit(EXIT_FAILURE);
+}
+
+static void
+vperror_msg(const char *s, va_list p)
+{
+	int err = errno;
+	if (s == 0)
+		s = "";
+	verror_msg(s, p);
+	if (*s)
+		s = ": ";
+	fprintf(stderr, "%s%s\n", s, strerror(err));
+}
+
+static void
+perror_msg_and_die(const char *s, ...)
+{
+	va_list p;
+	va_start(p, s);
+	vperror_msg(s, p);
+	va_end(p);
+	exit(EXIT_FAILURE);
+}
+
+static FILE *
+xfopen(const char *path, const char *mode)
+{
+	FILE *fp;
+	if ((fp = fopen(path, mode)) == NULL)
+		perror_msg_and_die("%s", path);
+	return fp;
+}
+
+static char *
+xstrdup(const char *s)
+{
+	char *t;
+
+	if (s == NULL)
+		return NULL;
+	t = strdup(s);
+	if (t == NULL)
+		error_msg_and_die(memory_exhausted);
+	return t;
+}
+
+static void *
+xrealloc(void *ptr, size_t size)
+{
+	ptr = realloc(ptr, size);
+	if (ptr == NULL && size != 0)
+		error_msg_and_die(memory_exhausted);
+	return ptr;
+}
+
+static char *
+xreadlink(const char *path)
+{
+	static const int GROWBY = 80; /* how large we will grow strings by */
+
+	char *buf = NULL;
+	int bufsize = 0, readsize = 0;
+
+	do {
+		buf = xrealloc(buf, bufsize += GROWBY);
+		readsize = readlink(path, buf, bufsize); /* 1st try */
+		if (readsize == -1) {
+			perror_msg_and_die("%s:%s", app_name, path);
+		}
+	}
+	while (bufsize < readsize + 1);
+
+	buf[readsize] = '\0';
+	return buf;
+}
+
+int
+is_hardlink(ino_t inode)
+{
+	int i;
+
+	for(i = 0; i < hdlinks.count; i++) {
+		if(hdlinks.hdl[i].src_inode == inode)
+			return i;
+	}
+	return -1;		
+}
+
+// printf helper macro
+#define plural(a) (a), ((a) > 1) ? "s" : ""
+
+// temporary working block
+static inline uint8 *
+get_workblk(void)
+{
+	unsigned char* b=calloc(1,BLOCKSIZE);
+	return b;
+}
+static inline void
+free_workblk(block b)
+{
+	free(b);
+}
+
+/* Rounds qty upto a multiple of siz. siz should be a power of 2 */
+static inline uint32
+rndup(uint32 qty, uint32 siz)
+{
+	return (qty + (siz - 1)) & ~(siz - 1);
+}
+
+// check if something is allocated in the bitmap
+static inline uint32
+allocated(block b, uint32 item)
+{
+	return b[(item-1) / 8] & (1 << ((item-1) % 8));
+}
+
+// return a given block from a filesystem
+static inline uint8 *
+get_blk(filesystem *fs, uint32 blk)
+{
+	return (uint8*)fs + blk*BLOCKSIZE;
+}
+
+// the group descriptors are aligned on the block size
+static inline groupdescriptor *
+get_gd(filesystem *fs, int no)
+{
+	int gdblk = (sizeof (filesystem) + BLOCKSIZE - 1) / BLOCKSIZE;
+	return ((groupdescriptor *) get_blk(fs, gdblk)) + no;
+}
+
+// return a given inode from a filesystem
+static inline inode *
+get_nod(filesystem *fs, uint32 nod)
+{
+	int grp,offset;
+	inode *itab;
+
+	offset = GRP_IBM_OFFSET(fs,nod);
+	grp = GRP_GROUP_OF_INODE(fs,nod);
+	itab = (inode *)get_blk(fs, get_gd(fs,grp)->bg_inode_table);
+	return itab+offset-1;
+}
+
+// allocate a given block/inode in the bitmap
+// allocate first free if item == 0
+static uint32
+allocate(block b, uint32 item)
+{
+	if(!item)
+	{
+		int i;
+		uint8 bits;
+		for(i = 0; i < BLOCKSIZE; i++)
+			if((bits = b[i]) != (uint8)-1)
+			{
+				int j;
+				for(j = 0; j < 8; j++)
+					if(!(bits & (1 << j)))
+						break;
+				item = i * 8 + j + 1;
+				break;
+			}
+		if(i == BLOCKSIZE)
+			return 0;
+	}
+	b[(item-1) / 8] |= (1 << ((item-1) % 8));
+	return item;
+}
+
+// deallocate a given block/inode
+static void
+deallocate(block b, uint32 item)
+{
+	b[(item-1) / 8] &= ~(1 << ((item-1) % 8));
+}
+
+// allocate a block
+static uint32
+alloc_blk(filesystem *fs, uint32 nod)
+{
+	uint32 bk=0;
+	uint32 grp,nbgroups;
+
+	grp = GRP_GROUP_OF_INODE(fs,nod);
+	nbgroups = GRP_NBGROUPS(fs);
+	if(!(bk = allocate(GRP_GET_GROUP_BBM(fs, grp), 0))) {
+		for(grp=0;grp<nbgroups && !bk;grp++)
+			bk = allocate(GRP_GET_GROUP_BBM(fs, grp), 0);
+		grp--;
+	}
+	if (!bk)
+		error_msg_and_die("couldn't allocate a block (no free space)");
+	if(!(get_gd(fs, grp)->bg_free_blocks_count--))
+		error_msg_and_die("group descr %d. free blocks count == 0 (corrupted fs?)",grp);
+	if(!(fs->sb.s_free_blocks_count--))
+		error_msg_and_die("superblock free blocks count == 0 (corrupted fs?)");
+	return fs->sb.s_first_data_block + fs->sb.s_blocks_per_group*grp + (bk-1);
+}
+
+// free a block
+static void
+free_blk(filesystem *fs, uint32 bk)
+{
+	uint32 grp;
+
+	grp = bk / fs->sb.s_blocks_per_group;
+	bk %= fs->sb.s_blocks_per_group;
+	deallocate(GRP_GET_GROUP_BBM(fs, grp), bk);
+	get_gd(fs, grp)->bg_free_blocks_count++;
+	fs->sb.s_free_blocks_count++;
+}
+
+// allocate an inode
+static uint32
+alloc_nod(filesystem *fs)
+{
+	uint32 nod,best_group=0;
+	uint32 grp,nbgroups,avefreei;
+
+	nbgroups = GRP_NBGROUPS(fs);
+
+	/* Distribute inodes amongst all the blocks                           */
+	/* For every block group with more than average number of free inodes */
+	/* find the one with the most free blocks and allocate node there     */
+	/* Idea from find_group_dir in fs/ext2/ialloc.c in 2.4.19 kernel      */
+	/* We do it for all inodes.                                           */
+	avefreei  =  fs->sb.s_free_inodes_count / nbgroups;
+	for(grp=0; grp<nbgroups; grp++) {
+		if (get_gd(fs, grp)->bg_free_inodes_count < avefreei ||
+		    get_gd(fs, grp)->bg_free_inodes_count == 0)
+			continue;
+		if (!best_group || 
+			get_gd(fs, grp)->bg_free_blocks_count > get_gd(fs, best_group)->bg_free_blocks_count)
+			best_group = grp;
+	}
+	if (!(nod = allocate(GRP_GET_GROUP_IBM(fs, best_group), 0)))
+		error_msg_and_die("couldn't allocate an inode (no free inode)");
+	if(!(get_gd(fs, best_group)->bg_free_inodes_count--))
+		error_msg_and_die("group descr. free blocks count == 0 (corrupted fs?)");
+	if(!(fs->sb.s_free_inodes_count--))
+		error_msg_and_die("superblock free blocks count == 0 (corrupted fs?)");
+	return fs->sb.s_inodes_per_group*best_group+nod;
+}
+
+// print a bitmap allocation
+static void
+print_bm(block b, uint32 max)
+{
+	uint32 i;
+	printf("----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0\n");
+	for(i=1; i <= max; i++)
+	{
+		putchar(allocated(b, i) ? '*' : '.');
+		if(!(i % 100))
+			printf("\n");
+	}
+	if((i-1) % 100)
+		printf("\n");
+}
+
+// initalize a blockwalker (iterator for blocks list)
+static inline void
+init_bw(blockwalker *bw)
+{
+	bw->bnum = 0;
+	bw->bpdir = EXT2_INIT_BLOCK;
+}
+
+// return next block of inode (WALK_END for end)
+// if *create>0, append a newly allocated block at the end
+// if *create<0, free the block - warning, the metadata blocks contents is
+//				  used after being freed, so once you start
+//				  freeing blocks don't stop until the end of
+//				  the file. moreover, i_blocks isn't updated.
+//				  in fact, don't do that, just use extend_blk
+// if hole!=0, create a hole in the file
+static uint32
+walk_bw(filesystem *fs, uint32 nod, blockwalker *bw, int32 *create, uint32 hole)
+{
+	uint32 *bkref = 0;
+	uint32 *b;
+	int extend = 0, reduce = 0;
+	if(create && (*create) < 0)
+		reduce = 1;
+	if(bw->bnum >= get_nod(fs, nod)->i_blocks / INOBLK)
+	{
+		if(create && (*create) > 0)
+		{
+			(*create)--;
+			extend = 1;
+		}
+		else	
+			return WALK_END;
+	}
+	// first direct block
+	if(bw->bpdir == EXT2_INIT_BLOCK)
+	{
+		bkref = &get_nod(fs, nod)->i_block[bw->bpdir = 0];
+		if(extend) // allocate first block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free first block
+			free_blk(fs, *bkref);
+	}
+	// direct block
+	else if(bw->bpdir < EXT2_NDIR_BLOCKS)
+	{
+		bkref = &get_nod(fs, nod)->i_block[++bw->bpdir];
+		if(extend) // allocate block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free block
+			free_blk(fs, *bkref);
+	}
+	// first block in indirect block
+	else if(bw->bpdir == EXT2_NDIR_BLOCKS)
+	{
+		bw->bnum++;
+		bw->bpdir = EXT2_IND_BLOCK;
+		bw->bpind = 0;
+		if(extend) // allocate indirect block
+			get_nod(fs, nod)->i_block[bw->bpdir] = alloc_blk(fs,nod);
+		if(reduce) // free indirect block
+			free_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		bkref = &b[bw->bpind];
+		if(extend) // allocate first block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free first block
+			free_blk(fs, *bkref);
+	}
+	// block in indirect block
+	else if((bw->bpdir == EXT2_IND_BLOCK) && (bw->bpind < BLOCKSIZE/4 - 1))
+	{
+		bw->bpind++;
+		b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		bkref = &b[bw->bpind];
+		if(extend) // allocate block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free block
+			free_blk(fs, *bkref);
+	}
+	// first block in first indirect block in first double indirect block
+	else if(bw->bpdir == EXT2_IND_BLOCK)
+	{
+		bw->bnum += 2;
+		bw->bpdir = EXT2_DIND_BLOCK;
+		bw->bpind = 0;
+		bw->bpdind = 0;
+		if(extend) // allocate double indirect block
+			get_nod(fs, nod)->i_block[bw->bpdir] = alloc_blk(fs,nod);
+		if(reduce) // free double indirect block
+			free_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		if(extend) // allocate first indirect block
+			b[bw->bpind] = alloc_blk(fs,nod);
+		if(reduce) // free  firstindirect block
+			free_blk(fs, b[bw->bpind]);
+		b = (uint32*)get_blk(fs, b[bw->bpind]);
+		bkref = &b[bw->bpdind];
+		if(extend) // allocate first block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free first block
+			free_blk(fs, *bkref);
+	}
+	// block in indirect block in double indirect block
+	else if((bw->bpdir == EXT2_DIND_BLOCK) && (bw->bpdind < BLOCKSIZE/4 - 1))
+	{
+		bw->bpdind++;
+		b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		b = (uint32*)get_blk(fs, b[bw->bpind]);
+		bkref = &b[bw->bpdind];
+		if(extend) // allocate block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free block
+			free_blk(fs, *bkref);
+	}
+	// first block in indirect block in double indirect block
+	else if((bw->bpdir == EXT2_DIND_BLOCK) && (bw->bpind < BLOCKSIZE/4 - 1))
+	{
+		bw->bnum++;
+		bw->bpdind = 0;
+		bw->bpind++;
+		b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		if(extend) // allocate indirect block
+			b[bw->bpind] = alloc_blk(fs,nod);
+		if(reduce) // free indirect block
+			free_blk(fs, b[bw->bpind]);
+		b = (uint32*)get_blk(fs, b[bw->bpind]);
+		bkref = &b[bw->bpdind];
+		if(extend) // allocate first block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free first block
+			free_blk(fs, *bkref);
+	}
+
+	/* Adding support for triple indirection */
+	/* Just starting triple indirection. Allocate the indirection
+	   blocks and the first data block
+	 */
+	else if (bw->bpdir == EXT2_DIND_BLOCK) 
+	{
+	  	bw->bnum += 3;
+		bw->bpdir = EXT2_TIND_BLOCK;
+		bw->bpind = 0;
+		bw->bpdind = 0;
+		bw->bptind = 0;
+		if(extend) // allocate triple indirect block
+			get_nod(fs, nod)->i_block[bw->bpdir] = alloc_blk(fs,nod);
+		if(reduce) // free triple indirect block
+			free_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		if(extend) // allocate first double indirect block
+			b[bw->bpind] = alloc_blk(fs,nod);
+		if(reduce) // free first double indirect block
+			free_blk(fs, b[bw->bpind]);
+		b = (uint32*)get_blk(fs, b[bw->bpind]);
+		if(extend) // allocate first indirect block
+			b[bw->bpdind] = alloc_blk(fs,nod);
+		if(reduce) // free first indirect block
+			free_blk(fs, b[bw->bpind]);
+		b = (uint32*)get_blk(fs, b[bw->bpdind]);
+		bkref = &b[bw->bptind];
+		if(extend) // allocate first data block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free first block
+			free_blk(fs, *bkref);
+	}
+	/* Still processing a single indirect block down the indirection
+	   chain.Allocate a data block for it
+	 */
+	else if ( (bw->bpdir == EXT2_TIND_BLOCK) && 
+		  (bw->bptind < BLOCKSIZE/4 -1) )
+	{
+		bw->bptind++;
+		b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		b = (uint32*)get_blk(fs, b[bw->bpind]);
+		b = (uint32*)get_blk(fs, b[bw->bpdind]);
+		bkref = &b[bw->bptind];
+		if(extend) // allocate data block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free block
+			free_blk(fs, *bkref);
+	}
+	/* Finished processing a single indirect block. But still in the 
+	   same double indirect block. Allocate new single indirect block
+	   for it and a data block
+	 */
+	else if ( (bw->bpdir == EXT2_TIND_BLOCK) &&
+		  (bw->bpdind < BLOCKSIZE/4 -1) )
+	{
+		bw->bnum++;
+		bw->bptind = 0;
+		bw->bpdind++;
+		b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		b = (uint32*)get_blk(fs, b[bw->bpind]);
+		if(extend) // allocate single indirect block
+			b[bw->bpdind] = alloc_blk(fs,nod);
+		if(reduce) // free indirect block
+			free_blk(fs, b[bw->bpind]);
+		b = (uint32*)get_blk(fs, b[bw->bpdind]);
+		bkref = &b[bw->bptind];
+		if(extend) // allocate first data block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free first block
+			free_blk(fs, *bkref);
+	}
+	/* Finished processing a double indirect block. Allocate the next
+	   double indirect block and the single,data blocks for it
+	 */
+	else if ( (bw->bpdir == EXT2_TIND_BLOCK) && 
+		  (bw->bpind < BLOCKSIZE/4 - 1) )
+	{
+		bw->bnum += 2;
+		bw->bpdind = 0;
+		bw->bptind = 0;
+		bw->bpind++;
+		b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]);
+		if(extend) // allocate double indirect block
+			b[bw->bpind] = alloc_blk(fs,nod);
+		if(reduce) // free double indirect block
+			free_blk(fs, b[bw->bpind]);
+		b = (uint32*)get_blk(fs, b[bw->bpind]);
+		if(extend) // allocate single indirect block
+			b[bw->bpdind] = alloc_blk(fs,nod);
+		if(reduce) // free indirect block
+			free_blk(fs, b[bw->bpind]);
+		b = (uint32*)get_blk(fs, b[bw->bpdind]);
+		bkref = &b[bw->bptind];
+		if(extend) // allocate first block
+			*bkref = hole ? 0 : alloc_blk(fs,nod);
+		if(reduce) // free first block
+			free_blk(fs, *bkref);
+	}
+	else
+		error_msg_and_die("file too big !"); 
+	/* End change for walking triple indirection */
+
+	if(*bkref)
+	{
+		bw->bnum++;
+		if(!reduce && !allocated(GRP_GET_BLOCK_BITMAP(fs,*bkref), GRP_BBM_OFFSET(fs,*bkref)))
+			error_msg_and_die("[block %d of inode %d is unallocated !]", *bkref, nod);
+	}
+	if(extend)
+		get_nod(fs, nod)->i_blocks = bw->bnum * INOBLK;
+	return *bkref;
+}
+
+// add blocks to an inode (file/dir/etc...)
+static void
+extend_blk(filesystem *fs, uint32 nod, block b, int amount)
+{
+	int create = amount;
+	blockwalker bw, lbw;
+	uint32 bk;
+	init_bw(&bw);
+	if(amount < 0)
+	{
+		uint32 i;
+		for(i = 0; i < get_nod(fs, nod)->i_blocks / INOBLK + amount; i++)
+			walk_bw(fs, nod, &bw, 0, 0);
+		while(walk_bw(fs, nod, &bw, &create, 0) != WALK_END)
+			/*nop*/;
+		get_nod(fs, nod)->i_blocks += amount * INOBLK;
+	}
+	else
+	{
+		lbw = bw;
+		while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END)
+			lbw = bw;
+		bw = lbw;
+		while(create)
+		{
+			int i, copyb = 0;
+			if(!(fs->sb.s_reserved[200] & OP_HOLES))
+				copyb = 1;
+			else
+				for(i = 0; i < BLOCKSIZE / 4; i++)
+					if(((int32*)(b + BLOCKSIZE * (amount - create)))[i])
+					{
+						copyb = 1;
+						break;
+					}
+			if((bk = walk_bw(fs, nod, &bw, &create, !copyb)) == WALK_END)
+				break;
+			if(copyb)
+				memcpy(get_blk(fs, bk), b + BLOCKSIZE * (amount - create - 1), BLOCKSIZE);
+		}
+	}
+}
+
+// link an entry (inode #) to a directory
+static void
+add2dir(filesystem *fs, uint32 dnod, uint32 nod, const char* name)
+{
+	blockwalker bw;
+	uint32 bk;
+	uint8 *b;
+	directory *d;
+	int reclen, nlen;
+	inode *node;
+	inode *pnode;
+
+	pnode = get_nod(fs, dnod);
+	if((pnode->i_mode & FM_IFMT) != FM_IFDIR)
+		error_msg_and_die("can't add '%s' to a non-directory", name);
+	if(!*name)
+		error_msg_and_die("can't create an inode with an empty name");
+	if(strchr(name, '/'))
+		error_msg_and_die("bad name '%s' (contains a slash)", name);
+	nlen = strlen(name);
+	reclen = sizeof(directory) + rndup(nlen, 4);
+	if(reclen > BLOCKSIZE)
+		error_msg_and_die("bad name '%s' (too long)", name);
+	init_bw(&bw);
+	while((bk = walk_bw(fs, dnod, &bw, 0, 0)) != WALK_END) // for all blocks in dir
+	{
+		b = get_blk(fs, bk);
+		// for all dir entries in block
+		for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + d->d_rec_len))
+		{
+			// if empty dir entry, large enough, use it
+			if((!d->d_inode) && (d->d_rec_len >= reclen))
+			{
+				d->d_inode = nod;
+				node = get_nod(fs, nod);
+				node->i_links_count++;
+				d->d_name_len = nlen;
+				strncpy(d->d_name, name, nlen);
+				return;
+			}
+			// if entry with enough room (last one?), shrink it & use it
+			if(d->d_rec_len >= (sizeof(directory) + rndup(d->d_name_len, 4) + reclen))
+			{
+				reclen = d->d_rec_len;
+				d->d_rec_len = sizeof(directory) + rndup(d->d_name_len, 4);
+				reclen -= d->d_rec_len;
+				d = (directory*) (((int8*)d) + d->d_rec_len);
+				d->d_rec_len = reclen;
+				d->d_inode = nod;
+				node = get_nod(fs, nod);
+				node->i_links_count++;
+				d->d_name_len = nlen;
+				strncpy(d->d_name, name, nlen);
+				return;
+			}
+		}
+	}
+	// we found no free entry in the directory, so we add a block
+	if(!(b = get_workblk()))
+		error_msg_and_die("get_workblk() failed.");
+	d = (directory*)b;
+	d->d_inode = nod;
+	node = get_nod(fs, nod);
+	node->i_links_count++;
+	d->d_rec_len = BLOCKSIZE;
+	d->d_name_len = nlen;
+	strncpy(d->d_name, name, nlen);
+	extend_blk(fs, dnod, b, 1);
+	get_nod(fs, dnod)->i_size += BLOCKSIZE;
+	free_workblk(b);
+}
+
+// find an entry in a directory
+static uint32
+find_dir(filesystem *fs, uint32 nod, const char * name)
+{
+	blockwalker bw;
+	uint32 bk;
+	int nlen = strlen(name);
+	init_bw(&bw);
+	while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END)
+	{
+		directory *d;
+		uint8 *b;
+		b = get_blk(fs, bk);
+		for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + d->d_rec_len))
+			if(d->d_inode && (nlen == d->d_name_len) && !strncmp(d->d_name, name, nlen))
+				return d->d_inode;
+	}
+	return 0;
+}
+
+// find the inode of a full path
+static uint32
+find_path(filesystem *fs, uint32 nod, const char * name)
+{
+	char *p, *n, *n2 = xstrdup(name);
+	n = n2;
+	while(*n == '/')
+	{
+		nod = EXT2_ROOT_INO;
+		n++;
+	}
+	while(*n)
+	{
+		if((p = strchr(n, '/')))
+			(*p) = 0;
+		if(!(nod = find_dir(fs, nod, n)))
+			break;
+		if(p)
+			n = p + 1;
+		else
+			break;
+	}
+	free(n2);
+	return nod;
+}
+
+// create a simple inode
+static uint32
+mknod_fs(filesystem *fs, uint32 parent_nod, const char *name, uint16 mode, uint16 uid, uint16 gid, uint8 major, uint8 minor, uint32 ctime, uint32 mtime)
+{
+	uint32 nod;
+	inode *node;
+	if((nod = find_dir(fs, parent_nod, name)))
+	{
+		node = get_nod(fs, nod);
+		if((node->i_mode & FM_IFMT) != (mode & FM_IFMT))
+			error_msg_and_die("node '%s' already exists and isn't of the same type", name);
+		node->i_mode = mode;
+	}
+	else
+	{
+		nod = alloc_nod(fs);
+		node = get_nod(fs, nod);
+		node->i_mode = mode;
+		add2dir(fs, parent_nod, nod, name);
+		switch(mode & FM_IFMT)
+		{
+			case FM_IFLNK:
+				mode = FM_IFLNK | FM_IRWXU | FM_IRWXG | FM_IRWXO;
+				break;
+			case FM_IFBLK:
+			case FM_IFCHR:
+				((uint8*)get_nod(fs, nod)->i_block)[0] = minor;
+				((uint8*)get_nod(fs, nod)->i_block)[1] = major;
+				break;
+			case FM_IFDIR:
+				add2dir(fs, nod, nod, ".");
+				add2dir(fs, nod, parent_nod, "..");
+				get_gd(fs, GRP_GROUP_OF_INODE(fs,nod))->bg_used_dirs_count++;
+				break;
+		}
+	}
+	node->i_uid = uid;
+	node->i_gid = gid;
+	node->i_atime = mtime;
+	node->i_ctime = ctime;
+	node->i_mtime = mtime;
+	return nod;
+}
+
+// make a full-fledged directory (i.e. with "." & "..")
+static inline uint32
+mkdir_fs(filesystem *fs, uint32 parent_nod, const char *name, uint32 mode,
+	uid_t uid, gid_t gid, uint32 ctime, uint32 mtime)
+{
+	return mknod_fs(fs, parent_nod, name, mode|FM_IFDIR, uid, gid, 0, 0, ctime, mtime);
+}
+
+// make a symlink
+static uint32
+mklink_fs(filesystem *fs, uint32 parent_nod, const char *name, size_t size, uint8 *b, uid_t uid, gid_t gid, uint32 ctime, uint32 mtime)
+{
+	uint32 nod = mknod_fs(fs, parent_nod, name, FM_IFLNK | FM_IRWXU | FM_IRWXG | FM_IRWXO, uid, gid, 0, 0, ctime, mtime);
+	extend_blk(fs, nod, 0, - (int)get_nod(fs, nod)->i_blocks / INOBLK);
+	get_nod(fs, nod)->i_size = size;
+	if(size <= 4 * (EXT2_TIND_BLOCK+1))
+	{
+		strncpy((char*)get_nod(fs, nod)->i_block, (char*)b, size);
+		return nod;
+	}
+	extend_blk(fs, nod, b, rndup(size, BLOCKSIZE) / BLOCKSIZE);
+	return nod;
+}
+
+// make a file from a FILE*
+static uint32
+mkfile_fs(filesystem *fs, uint32 parent_nod, const char *name, uint32 mode, size_t size, FILE *f, uid_t uid, gid_t gid, uint32 ctime, uint32 mtime)
+{
+	uint8 * b;
+	uint32 nod = mknod_fs(fs, parent_nod, name, mode|FM_IFREG, uid, gid, 0, 0, ctime, mtime);
+	extend_blk(fs, nod, 0, - (int)get_nod(fs, nod)->i_blocks / INOBLK);
+	get_nod(fs, nod)->i_size = size;
+	if (size) {
+		if(!(b = (uint8*)calloc(rndup(size, BLOCKSIZE), 1)))
+			error_msg_and_die("not enough mem to read file '%s'", name);
+		if(f)
+			fread(b, size, 1, f); // FIXME: ugly. use mmap() ...
+		extend_blk(fs, nod, b, rndup(size, BLOCKSIZE) / BLOCKSIZE);
+		free(b);
+	}
+	return nod;
+}
+
+// retrieves a mode info from a struct stat
+static uint32
+get_mode(struct stat *st)
+{
+	uint32 mode = 0;
+
+	if(st->st_mode & S_IRUSR)
+		mode |= FM_IRUSR;
+	if(st->st_mode & S_IWUSR)
+		mode |= FM_IWUSR;
+	if(st->st_mode & S_IXUSR)
+		mode |= FM_IXUSR;
+	if(st->st_mode & S_IRGRP)
+		mode |= FM_IRGRP;
+	if(st->st_mode & S_IWGRP)
+		mode |= FM_IWGRP;
+	if(st->st_mode & S_IXGRP)
+		mode |= FM_IXGRP;
+	if(st->st_mode & S_IROTH)
+		mode |= FM_IROTH;
+	if(st->st_mode & S_IWOTH)
+		mode |= FM_IWOTH;
+	if(st->st_mode & S_IXOTH)
+		mode |= FM_IXOTH;
+	if(st->st_mode & S_ISUID)
+		mode |= FM_ISUID;
+	if(st->st_mode & S_ISGID)
+		mode |= FM_ISGID;
+	if(st->st_mode & S_ISVTX)
+		mode |= FM_ISVTX;
+	return mode;
+}
+
+// add or fixup entries to the filesystem from a text file
+/*  device table entries take the form of:
+    <path>	<type> <mode>	<uid>	<gid>	<major>	<minor>	<start>	<inc>	<count>
+    /dev/mem     c    640       0       0         1       1       0     0         -
+
+    type can be one of: 
+	f	A regular file
+	d	Directory
+	c	Character special device file
+	b	Block special device file
+	p	Fifo (named pipe)
+
+    I don't bother with symlinks (permissions are irrelevant), hard
+    links (special cases of regular files), or sockets (why bother).
+
+    Regular files must exist in the target root directory.  If a char,
+    block, fifo, or directory does not exist, it will be created.
+*/
+
+static void
+add2fs_from_file(filesystem *fs, uint32 this_nod, FILE * fh, uint32 fs_timestamp, struct stats *stats)
+{
+	unsigned long mode, uid, gid, major, minor;
+	unsigned long start, increment, count;
+	uint32 nod, ctime, mtime;
+	char *c, type, *path = NULL, *path2 = NULL, *dir, *name, *line = NULL;
+	size_t len;
+	struct stat st;
+	int nbargs, lineno = 0;
+
+	fstat(fileno(fh), &st);
+	ctime = fs_timestamp;
+	mtime = st.st_mtime;
+	while(getline(&line, &len, fh) >= 0)
+	{
+		mode = uid = gid = major = minor = 0;
+		start = 0; increment = 1; count = 0;
+		lineno++;
+		if((c = strchr(line, '#')))
+			*c = 0;
+		if (path) {
+			free(path);
+			path = NULL;
+		}
+		if (path2) {
+			free(path2);
+			path2 = NULL;
+		}
+		nbargs = sscanf (line, "%" SCANF_PREFIX "s %c %lo %lu %lu %lu %lu %lu %lu %lu",
+					SCANF_STRING(path), &type, &mode, &uid, &gid, &major, &minor,
+					&start, &increment, &count);
+		if(nbargs < 3)
+		{
+			if(nbargs > 0)
+				error_msg("device table line %d skipped: bad format for entry '%s'", lineno, path);
+			continue;
+		}
+		mode &= FM_IMASK;
+		path2 = strdup(path);
+		name = basename(path);
+		dir = dirname(path2);
+		if((!strcmp(name, ".")) || (!strcmp(name, "..")))
+		{
+			error_msg("device table line %d skipped", lineno);
+			continue;
+		}
+		if(fs)
+		{
+			if(!(nod = find_path(fs, this_nod, dir)))
+			{
+				error_msg("device table line %d skipped: can't find directory '%s' to create '%s''", lineno, dir, name);
+				continue;
+			}
+		}
+		else
+			nod = 0;
+		switch (type)
+		{
+			case 'd':
+				mode |= FM_IFDIR;
+				break;
+			case 'f':
+				mode |= FM_IFREG;
+				break;
+			case 'p':
+				mode |= FM_IFIFO;
+				break;
+			case 's':
+				mode |= FM_IFSOCK;
+				break;
+			case 'c':
+				mode |= FM_IFCHR;
+				break;
+			case 'b':
+				mode |= FM_IFBLK;
+				break;
+			default:
+				error_msg("device table line %d skipped: bad type '%c' for entry '%s'", lineno, type, name);
+				continue;
+		}
+		if(stats) {
+			if(count > 0)
+				stats->ninodes += count - start;
+			else
+				stats->ninodes++;
+		} else {
+			if(count > 0)
+			{
+				char *dname;
+				unsigned long i;
+				unsigned len;
+				len = strlen(name) + 10;
+				dname = malloc(len + 1);
+				for(i = start; i < count; i++)
+				{
+					SNPRINTF(dname, len, "%s%lu", name, i);
+					mknod_fs(fs, nod, dname, mode, uid, gid, major, minor + (i * increment - start), ctime, mtime);
+				}
+				free(dname);
+			}
+			else
+				mknod_fs(fs, nod, name, mode, uid, gid, major, minor, ctime, mtime);
+		}
+	}
+	if (line)
+		free(line);
+	if (path) 
+		free(path);
+	if (path2)
+		free(path2);
+}
+
+// adds a tree of entries to the filesystem from current dir
+static void
+add2fs_from_dir(filesystem *fs, uint32 this_nod, int squash_uids, int squash_perms, uint32 fs_timestamp, struct stats *stats)
+{
+	uint32 nod;
+	uint32 uid, gid, mode, ctime, mtime;
+	const char *name;
+	FILE *fh;
+	DIR *dh;
+	struct dirent *dent;
+	struct stat st;
+	char *lnk;
+	uint32 save_nod;
+
+	if(!(dh = opendir(".")))
+		perror_msg_and_die(".");
+	while((dent = readdir(dh)))
+	{
+		if((!strcmp(dent->d_name, ".")) || (!strcmp(dent->d_name, "..")))
+			continue;
+		lstat(dent->d_name, &st);
+		uid = st.st_uid;
+		gid = st.st_gid;
+		ctime = fs_timestamp;
+		mtime = st.st_mtime;
+		name = dent->d_name;
+		mode = get_mode(&st);
+		if(squash_uids)
+			uid = gid = 0;
+		if(squash_perms)
+			mode &= ~(FM_IRWXG | FM_IRWXO);
+		if(stats)
+			switch(st.st_mode & S_IFMT)
+			{
+				case S_IFLNK:
+				case S_IFREG:
+					if((st.st_mode & S_IFMT) == S_IFREG || st.st_size > 4 * (EXT2_TIND_BLOCK+1))
+						stats->nblocks += (st.st_size + BLOCKSIZE - 1) / BLOCKSIZE;
+				case S_IFCHR:
+				case S_IFBLK:
+				case S_IFIFO:
+				case S_IFSOCK:
+					stats->ninodes++;
+					break;
+				case S_IFDIR:
+					stats->ninodes++;
+					if(chdir(dent->d_name) < 0)
+						perror_msg_and_die(dent->d_name);
+					add2fs_from_dir(fs, this_nod, squash_uids, squash_perms, fs_timestamp, stats);
+					chdir("..");
+					break;
+				default:
+					break;
+			}
+		else
+		{
+			save_nod = 0;
+			/* Check for hardlinks */
+			if (!S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode) && st.st_nlink > 1) {
+				int32 hdlink = is_hardlink(st.st_ino);
+				if (hdlink >= 0) {
+					add2dir(fs, this_nod, hdlinks.hdl[hdlink].dst_nod, name);
+					continue;
+				} else {
+					save_nod = 1;
+				}
+			}
+			switch(st.st_mode & S_IFMT)
+			{
+#if HAVE_STRUCT_STAT_ST_RDEV
+				case S_IFCHR:
+					nod = mknod_fs(fs, this_nod, name, mode|FM_IFCHR, uid, gid, major(st.st_rdev), minor(st.st_rdev), ctime, mtime);
+					break;
+				case S_IFBLK:
+					nod = mknod_fs(fs, this_nod, name, mode|FM_IFBLK, uid, gid, major(st.st_rdev), minor(st.st_rdev), ctime, mtime);
+					break;
+#endif
+				case S_IFIFO:
+					nod = mknod_fs(fs, this_nod, name, mode|FM_IFIFO, uid, gid, 0, 0, ctime, mtime);
+					break;
+				case S_IFSOCK:
+					nod = mknod_fs(fs, this_nod, name, mode|FM_IFSOCK, uid, gid, 0, 0, ctime, mtime);
+					break;
+				case S_IFLNK:
+					lnk = xreadlink(dent->d_name);
+					mklink_fs(fs, this_nod, name, st.st_size, (uint8*)lnk, uid, gid, ctime, mtime);
+					free(lnk);
+					break;
+				case S_IFREG:
+					fh = xfopen(dent->d_name, "rb");
+					nod = mkfile_fs(fs, this_nod, name, mode, st.st_size, fh, uid, gid, ctime, mtime);
+					fclose(fh);
+					break;
+				case S_IFDIR:
+					nod = mkdir_fs(fs, this_nod, name, mode, uid, gid, ctime, mtime);
+					if(chdir(dent->d_name) < 0)
+						perror_msg_and_die(name);
+					add2fs_from_dir(fs, nod, squash_uids, squash_perms, fs_timestamp, stats);
+					chdir("..");
+					break;
+				default:
+					error_msg("ignoring entry %s", name);
+			}
+			if (save_nod) {
+				if (hdlinks.count == hdlink_cnt) {
+					if ((hdlinks.hdl = 
+						 realloc (hdlinks.hdl, (hdlink_cnt + HDLINK_CNT) *
+								  sizeof (struct hdlink_s))) == NULL) {
+						error_msg_and_die("Not enough memory");
+					}
+					hdlink_cnt += HDLINK_CNT;
+				}
+				hdlinks.hdl[hdlinks.count].src_inode = st.st_ino;
+				hdlinks.hdl[hdlinks.count].dst_nod = nod;
+				hdlinks.count++;
+			}
+		}
+	}
+	closedir(dh);
+}
+
+// endianness swap of x-indirect blocks
+static void
+swap_goodblocks(filesystem *fs, inode *nod)
+{
+	uint32 i,j;
+	int done=0;
+	uint32 *b,*b2;
+
+	uint32 nblk = nod->i_blocks / INOBLK;
+	if((nod->i_size && !nblk) || ((nod->i_mode & FM_IFBLK) == FM_IFBLK) || ((nod->i_mode & FM_IFCHR) == FM_IFCHR))
+		for(i = 0; i <= EXT2_TIND_BLOCK; i++)
+			nod->i_block[i] = swab32(nod->i_block[i]);
+	if(nblk <= EXT2_IND_BLOCK)
+		return;
+	swap_block(get_blk(fs, nod->i_block[EXT2_IND_BLOCK]));
+	if(nblk <= EXT2_DIND_BLOCK + BLOCKSIZE/4)
+		return;
+	/* Currently this will fail b'cos the number of blocks as stored
+	   in i_blocks also includes the indirection blocks (see
+	   walk_bw). But this function assumes that i_blocks only
+	   stores the count of data blocks ( Actually according to
+	   "Understanding the Linux Kernel" (Table 17-3 p502 1st Ed)
+	   i_blocks IS supposed to store the count of data blocks). so
+	   with a file of size 268K nblk would be 269.The above check
+	   will be false even though double indirection hasn't been
+	   started.This is benign as 0 means block 0 which has been
+	   zeroed out and therefore points back to itself from any offset
+	 */
+	// FIXME: I have fixed that, but I have the feeling the rest of
+	// ths function needs to be fixed for the same reasons - Xav
+	assert(nod->i_block[EXT2_DIND_BLOCK] != 0);
+	for(i = 0; i < BLOCKSIZE/4; i++)
+		if(nblk > EXT2_IND_BLOCK + BLOCKSIZE/4 + (BLOCKSIZE/4)*i )
+			if (((uint32*)get_blk(fs, nod->i_block[EXT2_DIND_BLOCK]))[i])
+				swap_block(get_blk(fs, ((uint32*)get_blk(fs, nod->i_block[EXT2_DIND_BLOCK]))[i]));
+	swap_block(get_blk(fs, nod->i_block[EXT2_DIND_BLOCK]));
+	if(nblk <= EXT2_IND_BLOCK + BLOCKSIZE/4 + BLOCKSIZE/4 * BLOCKSIZE/4)
+		return;
+	/* Adding support for triple indirection */
+	b = (uint32*)get_blk(fs,nod->i_block[EXT2_TIND_BLOCK]);
+	for(i=0;i < BLOCKSIZE/4 && !done ; i++) {
+		b2 = (uint32*)get_blk(fs,b[i]); 
+		for(j=0; j<BLOCKSIZE/4;j++) {
+			if (nblk > ( EXT2_IND_BLOCK + BLOCKSIZE/4 + 
+				     (BLOCKSIZE/4)*(BLOCKSIZE/4) + 
+				     i*(BLOCKSIZE/4)*(BLOCKSIZE/4) + 
+				     j*(BLOCKSIZE/4)) ) 
+			  if (b2[j])
+			  	swap_block(get_blk(fs,b2[j]));
+			else {
+			  done = 1;
+			  break;
+			}
+		}
+		swap_block((uint8 *)b2);
+	}
+	swap_block((uint8 *)b);
+	return;
+}
+
+static void
+swap_badblocks(filesystem *fs, inode *nod)
+{
+	uint32 i,j;
+	int done=0;
+	uint32 *b,*b2;
+
+	uint32 nblk = nod->i_blocks / INOBLK;
+	if((nod->i_size && !nblk) || ((nod->i_mode & FM_IFBLK) == FM_IFBLK) || ((nod->i_mode & FM_IFCHR) == FM_IFCHR))
+		for(i = 0; i <= EXT2_TIND_BLOCK; i++)
+			nod->i_block[i] = swab32(nod->i_block[i]);
+	if(nblk <= EXT2_IND_BLOCK)
+		return;
+	swap_block(get_blk(fs, nod->i_block[EXT2_IND_BLOCK]));
+	if(nblk <= EXT2_DIND_BLOCK + BLOCKSIZE/4)
+		return;
+	/* See comment in swap_goodblocks */
+	assert(nod->i_block[EXT2_DIND_BLOCK] != 0);
+	swap_block(get_blk(fs, nod->i_block[EXT2_DIND_BLOCK]));
+	for(i = 0; i < BLOCKSIZE/4; i++)
+		if(nblk > EXT2_IND_BLOCK + BLOCKSIZE/4 + (BLOCKSIZE/4)*i )
+			if (((uint32*)get_blk(fs, nod->i_block[EXT2_DIND_BLOCK]))[i])
+				swap_block(get_blk(fs, ((uint32*)get_blk(fs, nod->i_block[EXT2_DIND_BLOCK]))[i]));
+	if(nblk <= EXT2_IND_BLOCK + BLOCKSIZE/4 + BLOCKSIZE/4 * BLOCKSIZE/4)
+		return;
+	/* Adding support for triple indirection */
+	b = (uint32*)get_blk(fs,nod->i_block[EXT2_TIND_BLOCK]);
+	swap_block((uint8 *)b);
+	for(i=0;i < BLOCKSIZE/4 && !done ; i++) {
+		b2 = (uint32*)get_blk(fs,b[i]); 
+		swap_block((uint8 *)b2);
+		for(j=0; j<BLOCKSIZE/4;j++) {
+			if (nblk > ( EXT2_IND_BLOCK + BLOCKSIZE/4 + 
+				     (BLOCKSIZE/4)*(BLOCKSIZE/4) + 
+				     i*(BLOCKSIZE/4)*(BLOCKSIZE/4) + 
+				     j*(BLOCKSIZE/4)) ) 
+			  if (b2[j])
+				swap_block(get_blk(fs,b2[j]));
+			else {
+			  done = 1;
+			  break;
+			}
+		}
+	}
+	return;
+}
+
+// endianness swap of the whole filesystem
+static void
+swap_goodfs(filesystem *fs)
+{
+	uint32 i;
+	for(i = 1; i < fs->sb.s_inodes_count; i++)
+	{
+		inode *nod = get_nod(fs, i);
+		if(nod->i_mode & FM_IFDIR)
+		{
+			blockwalker bw;
+			uint32 bk;
+			init_bw(&bw);
+			while((bk = walk_bw(fs, i, &bw, 0, 0)) != WALK_END)
+			{
+				directory *d;
+				uint8 *b;
+				b = get_blk(fs, bk);
+				for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + swab16(d->d_rec_len)))
+					swap_dir(d);
+			}
+		}
+		swap_goodblocks(fs, nod);
+		swap_nod(nod);
+	}
+	for(i=0;i<GRP_NBGROUPS(fs);i++)
+		swap_gd(get_gd(fs, i));
+	swap_sb(&fs->sb);
+}
+
+static void
+swap_badfs(filesystem *fs)
+{
+	uint32 i;
+	swap_sb(&fs->sb);
+	for(i=0;i<GRP_NBGROUPS(fs);i++)
+		swap_gd(get_gd(fs, i));
+	for(i = 1; i < fs->sb.s_inodes_count; i++)
+	{
+		inode *nod = get_nod(fs, i);
+		swap_nod(nod);
+		swap_badblocks(fs, nod);
+		if(nod->i_mode & FM_IFDIR)
+		{
+			blockwalker bw;
+			uint32 bk;
+			init_bw(&bw);
+			while((bk = walk_bw(fs, i, &bw, 0, 0)) != WALK_END)
+			{
+				directory *d;
+				uint8 *b;
+				b = get_blk(fs, bk);
+				for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + d->d_rec_len))
+					swap_dir(d);
+			}
+		}
+	}
+}
+
+// initialize an empty filesystem
+static filesystem *
+init_fs(int nbblocks, int nbinodes, int nbresrvd, int holes,
+		uint32 fs_timestamp, uint32 creator_os)
+{
+	uint32 i;
+	filesystem *fs;
+	directory *d;
+	uint8 * b;
+	uint32 nod, first_block;
+	uint32 nbgroups,nbinodes_per_group,overhead_per_group,free_blocks,
+		free_blocks_per_group,nbblocks_per_group,min_nbgroups;
+	uint32 gdsz,itblsz,bbmpos,ibmpos,itblpos;
+	uint32 j;
+	uint8 *bbm,*ibm;
+	inode *itab0;
+	
+	if(nbresrvd < 0)
+		error_msg_and_die("reserved blocks value is invalid. Note: options have changed, see --help or the man page.");
+	if(nbinodes < EXT2_FIRST_INO - 1 + (nbresrvd ? 1 : 0))
+		error_msg_and_die("too few inodes. Note: options have changed, see --help or the man page.");
+	if(nbblocks < 8)
+		error_msg_and_die("too few blocks. Note: options have changed, see --help or the man page.");
+
+	/* nbinodes is the total number of inodes in the system.
+	 * a block group can have no more than 8192 inodes.
+	 */
+	min_nbgroups = (nbinodes + INODES_PER_GROUP - 1) / INODES_PER_GROUP;
+
+	/* On filesystems with 1k block size, the bootloader area uses a full
+	 * block. For 2048 and up, the superblock can be fitted into block 0.
+	 */
+	first_block = (BLOCKSIZE == 1024);
+
+	/* nbblocks is the total number of blocks in the filesystem.
+	 * a block group can have no more than 8192 blocks.
+	 */
+	nbgroups = (nbblocks - first_block + BLOCKS_PER_GROUP - 1) / BLOCKS_PER_GROUP;
+	if(nbgroups < min_nbgroups) nbgroups = min_nbgroups;
+	nbblocks_per_group = rndup((nbblocks - first_block + nbgroups - 1)/nbgroups, 8);
+	nbinodes_per_group = rndup((nbinodes + nbgroups - 1)/nbgroups,
+						(BLOCKSIZE/sizeof(inode)));
+	if (nbinodes_per_group < 16)
+		nbinodes_per_group = 16; //minimum number b'cos the first 10 are reserved
+
+	gdsz = rndup(nbgroups*sizeof(groupdescriptor),BLOCKSIZE)/BLOCKSIZE;
+	itblsz = nbinodes_per_group * sizeof(inode)/BLOCKSIZE;
+	overhead_per_group = 3 /*sb,bbm,ibm*/ + gdsz + itblsz;
+	free_blocks = nbblocks - overhead_per_group*nbgroups - first_block;
+	free_blocks_per_group = nbblocks_per_group - overhead_per_group;
+	if(free_blocks < 0)
+		error_msg_and_die("too much overhead, try fewer inodes or more blocks. Note: options have changed, see --help or the man page.");
+
+	if(!(fs = (filesystem*)calloc(nbblocks, BLOCKSIZE)))
+		error_msg_and_die("not enough memory for filesystem");
+
+	// create the superblock for an empty filesystem
+	fs->sb.s_inodes_count = nbinodes_per_group * nbgroups;
+	fs->sb.s_blocks_count = nbblocks;
+	fs->sb.s_r_blocks_count = nbresrvd;
+	fs->sb.s_free_blocks_count = free_blocks;
+	fs->sb.s_free_inodes_count = fs->sb.s_inodes_count - EXT2_FIRST_INO + 1;
+	fs->sb.s_first_data_block = first_block;
+	fs->sb.s_log_block_size = BLOCKSIZE >> 11;
+	fs->sb.s_log_frag_size = BLOCKSIZE >> 11;
+	fs->sb.s_blocks_per_group = nbblocks_per_group;
+	fs->sb.s_frags_per_group = nbblocks_per_group;
+	fs->sb.s_inodes_per_group = nbinodes_per_group;
+	fs->sb.s_wtime = fs_timestamp;
+	fs->sb.s_magic = EXT2_MAGIC_NUMBER;
+	fs->sb.s_lastcheck = fs_timestamp;
+	fs->sb.s_creator_os = creator_os;
+
+	// set up groupdescriptors
+	for(i=0, bbmpos=first_block+1+gdsz, ibmpos=bbmpos+1, itblpos=ibmpos+1;
+		i<nbgroups;
+		i++, bbmpos+=nbblocks_per_group, ibmpos+=nbblocks_per_group, itblpos+=nbblocks_per_group)
+	{
+		groupdescriptor *gd = get_gd(fs, i);
+
+		if(free_blocks > free_blocks_per_group) {
+			gd->bg_free_blocks_count = free_blocks_per_group;
+			free_blocks -= free_blocks_per_group;
+		} else {
+			gd->bg_free_blocks_count = free_blocks;
+			free_blocks = 0; // this is the last block group
+		}
+		if(i)
+			gd->bg_free_inodes_count = nbinodes_per_group;
+		else
+			gd->bg_free_inodes_count = nbinodes_per_group -
+							EXT2_FIRST_INO + 2;
+		gd->bg_used_dirs_count = 0;
+		gd->bg_block_bitmap = bbmpos;
+		gd->bg_inode_bitmap = ibmpos;
+		gd->bg_inode_table = itblpos;
+	}
+
+	/* Mark non-filesystem blocks and inodes as allocated */
+	/* Mark system blocks and inodes as allocated         */
+	for(i = 0; i<nbgroups;i++) {
+
+		/* Block bitmap */
+		bbm = GRP_GET_GROUP_BBM(fs, i);
+		//non-filesystem blocks
+		for(j = get_gd(fs, i)->bg_free_blocks_count
+		        + overhead_per_group + 1; j <= BLOCKSIZE * 8; j++)
+			allocate(bbm, j); 
+		//system blocks
+		for(j = 1; j <= overhead_per_group; j++)
+			allocate(bbm, j); 
+		
+		/* Inode bitmap */
+		ibm = GRP_GET_GROUP_IBM(fs, i);
+		//non-filesystem inodes
+		for(j = fs->sb.s_inodes_per_group+1; j <= BLOCKSIZE * 8; j++)
+			allocate(ibm, j);
+
+		//system inodes
+		if(i == 0)
+			for(j = 1; j < EXT2_FIRST_INO; j++)
+				allocate(ibm, j);
+	}
+
+	// make root inode and directory
+	/* We have groups now. Add the root filesystem in group 0 */
+	/* Also increment the directory count for group 0 */
+	get_gd(fs, 0)->bg_free_inodes_count--;
+	get_gd(fs, 0)->bg_used_dirs_count = 1;
+	itab0 = (inode *)get_blk(fs, get_gd(fs,0)->bg_inode_table);
+	itab0[EXT2_ROOT_INO-1].i_mode = FM_IFDIR | FM_IRWXU | FM_IRGRP | FM_IROTH | FM_IXGRP | FM_IXOTH; 
+	itab0[EXT2_ROOT_INO-1].i_ctime = fs_timestamp;
+	itab0[EXT2_ROOT_INO-1].i_mtime = fs_timestamp;
+	itab0[EXT2_ROOT_INO-1].i_atime = fs_timestamp;
+	itab0[EXT2_ROOT_INO-1].i_size = BLOCKSIZE;
+	itab0[EXT2_ROOT_INO-1].i_links_count = 2;
+
+	if(!(b = get_workblk()))
+		error_msg_and_die("get_workblk() failed.");
+	d = (directory*)b;
+	d->d_inode = EXT2_ROOT_INO;
+	d->d_rec_len = sizeof(directory)+4;
+	d->d_name_len = 1;
+	strcpy(d->d_name, ".");
+	d = (directory*)(b + d->d_rec_len);
+	d->d_inode = EXT2_ROOT_INO;
+	d->d_rec_len = BLOCKSIZE - (sizeof(directory)+4);
+	d->d_name_len = 2;
+	strcpy(d->d_name, "..");
+	extend_blk(fs, EXT2_ROOT_INO, b, 1);
+
+	// make lost+found directory and reserve blocks
+	if(fs->sb.s_r_blocks_count)
+	{
+		nod = mkdir_fs(fs, EXT2_ROOT_INO, "lost+found", FM_IRWXU, 0, 0, fs_timestamp, fs_timestamp);
+		memset(b, 0, BLOCKSIZE);
+		((directory*)b)->d_rec_len = BLOCKSIZE;
+		/* We run into problems with e2fsck if directory lost+found grows
+		 * bigger than this. Need to find out why this happens - sundar
+		 */
+		if (fs->sb.s_r_blocks_count > fs->sb.s_blocks_count * MAX_RESERVED_BLOCKS ) 
+			fs->sb.s_r_blocks_count = fs->sb.s_blocks_count * MAX_RESERVED_BLOCKS;
+		for(i = 1; i < fs->sb.s_r_blocks_count; i++)
+			extend_blk(fs, nod, b, 1);
+		get_nod(fs, nod)->i_size = fs->sb.s_r_blocks_count * BLOCKSIZE;
+	}
+	free_workblk(b);
+
+	// administrative info
+	fs->sb.s_state = 1;
+	fs->sb.s_max_mnt_count = 20;
+
+	// options for me
+	if(holes)
+		fs->sb.s_reserved[200] |= OP_HOLES;
+	
+	return fs;
+}
+
+// loads a filesystem from disk
+static filesystem *
+load_fs(FILE * fh, int swapit)
+{
+	size_t fssize;
+	filesystem *fs;
+	if((fseek(fh, 0, SEEK_END) < 0) || ((ssize_t)(fssize = ftell(fh)) == -1))
+		perror_msg_and_die("input filesystem image");
+	rewind(fh);
+	fssize = (fssize + BLOCKSIZE - 1) / BLOCKSIZE;
+	if(fssize < 16) // totally arbitrary
+		error_msg_and_die("too small filesystem");
+	if(!(fs = (filesystem*)calloc(fssize, BLOCKSIZE)))
+		error_msg_and_die("not enough memory for filesystem");
+	if(fread(fs, BLOCKSIZE, fssize, fh) != fssize)
+		perror_msg_and_die("input filesystem image");
+	if(swapit)
+		swap_badfs(fs);
+	if(fs->sb.s_rev_level || (fs->sb.s_magic != EXT2_MAGIC_NUMBER))
+		error_msg_and_die("not a suitable ext2 filesystem");
+	return fs;
+}
+
+static void
+free_fs(filesystem *fs)
+{
+	free(fs);
+}
+
+// just walk through blocks list
+static void
+flist_blocks(filesystem *fs, uint32 nod, FILE *fh)
+{
+	blockwalker bw;
+	uint32 bk;
+	init_bw(&bw);
+	while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END)
+		fprintf(fh, " %d", bk);
+	fprintf(fh, "\n");
+}
+
+// walk through blocks list
+static void
+list_blocks(filesystem *fs, uint32 nod)
+{
+	int bn = 0;
+	blockwalker bw;
+	uint32 bk;
+	init_bw(&bw);
+	printf("blocks in inode %d:", nod);
+	while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END)
+		printf(" %d", bk), bn++;
+	printf("\n%d blocks (%d bytes)\n", bn, bn * BLOCKSIZE);
+}
+
+// saves blocks to FILE*
+static void
+write_blocks(filesystem *fs, uint32 nod, FILE* f)
+{
+	blockwalker bw;
+	uint32 bk;
+	int32 fsize = get_nod(fs, nod)->i_size;
+	init_bw(&bw);
+	while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END)
+	{
+		if(fsize <= 0)
+			error_msg_and_die("wrong size while saving inode %d", nod);
+		if(fwrite(get_blk(fs, bk), (fsize > BLOCKSIZE) ? BLOCKSIZE : fsize, 1, f) != 1)
+			error_msg_and_die("error while saving inode %d", nod);
+		fsize -= BLOCKSIZE;
+	}
+}
+
+
+// print block/char device minor and major
+static void
+print_dev(filesystem *fs, uint32 nod)
+{
+	int minor, major;
+	minor = ((uint8*)get_nod(fs, nod)->i_block)[0];
+	major = ((uint8*)get_nod(fs, nod)->i_block)[1];
+	printf("major: %d, minor: %d\n", major, minor);
+}
+
+// print an inode as a directory
+static void
+print_dir(filesystem *fs, uint32 nod)
+{
+	blockwalker bw;
+	uint32 bk;
+	init_bw(&bw);
+	printf("directory for inode %d:\n", nod);
+	while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END)
+	{
+		directory *d;
+		uint8 *b;
+		b = get_blk(fs, bk);
+		for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + d->d_rec_len))
+			if(d->d_inode)
+			{
+				int i;
+				printf("entry '");
+				for(i = 0; i < d->d_name_len; i++)
+					putchar(d->d_name[i]);
+				printf("' (inode %d): rec_len: %d (name_len: %d)\n", d->d_inode, d->d_rec_len, d->d_name_len);
+			}
+	}
+}
+
+// print a symbolic link
+static void
+print_link(filesystem *fs, uint32 nod)
+{
+	if(!get_nod(fs, nod)->i_blocks)
+		printf("links to '%s'\n", (char*)get_nod(fs, nod)->i_block);
+	else
+	{
+		printf("links to '");
+		write_blocks(fs, nod, stdout);
+		printf("'\n");
+	}
+}
+
+// make a ls-like printout of permissions
+static void
+make_perms(uint32 mode, char perms[11])
+{
+	strcpy(perms, "----------");
+	if(mode & FM_IRUSR)
+		perms[1] = 'r';
+	if(mode & FM_IWUSR)
+		perms[2] = 'w';
+	if(mode & FM_IXUSR)
+		perms[3] = 'x';
+	if(mode & FM_IRGRP)
+		perms[4] = 'r';
+	if(mode & FM_IWGRP)
+		perms[5] = 'w';
+	if(mode & FM_IXGRP)
+		perms[6] = 'x';
+	if(mode & FM_IROTH)
+		perms[7] = 'r';
+	if(mode & FM_IWOTH)
+		perms[8] = 'w';
+	if(mode & FM_IXOTH)
+		perms[9] = 'x';
+	if(mode & FM_ISUID)
+		perms[3] = 's';
+	if(mode & FM_ISGID)
+		perms[6] = 's';
+	if(mode & FM_ISVTX)
+		perms[9] = 't';
+	switch(mode & FM_IFMT)
+	{
+		case 0:
+			*perms = '0';
+			break;
+		case FM_IFSOCK:
+			*perms = 's';
+			break;
+		case FM_IFLNK:
+			*perms = 'l';
+			break;
+		case FM_IFREG:
+			*perms = '-';
+			break;
+		case FM_IFBLK:
+			*perms = 'b';
+			break;
+		case FM_IFDIR:
+			*perms = 'd';
+			break;
+		case FM_IFCHR:
+			*perms = 'c';
+			break;
+		case FM_IFIFO:
+			*perms = 'p';
+			break;
+		default:
+			*perms = '?';
+	}
+}
+
+// print an inode
+static void
+print_inode(filesystem *fs, uint32 nod)
+{
+	char *s;
+	char perms[11];
+	if(!get_nod(fs, nod)->i_mode)
+		return;
+	switch(nod)
+	{
+		case EXT2_BAD_INO:
+			s = "bad blocks";
+			break;
+		case EXT2_ROOT_INO:
+			s = "root";
+			break;
+		case EXT2_ACL_IDX_INO:
+		case EXT2_ACL_DATA_INO:
+			s = "ACL";
+			break;
+		case EXT2_BOOT_LOADER_INO:
+			s = "boot loader";
+			break;
+		case EXT2_UNDEL_DIR_INO:
+			s = "undelete directory";
+			break;
+		default:
+			s = (nod >= EXT2_FIRST_INO) ? "normal" : "unknown reserved"; 
+	}
+	printf("inode %d (%s, %d links): ", nod, s, get_nod(fs, nod)->i_links_count);
+	if(!allocated(GRP_GET_INODE_BITMAP(fs,nod), GRP_IBM_OFFSET(fs,nod)))
+	{
+		printf("unallocated\n");
+		return;
+	}
+	make_perms(get_nod(fs, nod)->i_mode, perms);
+	printf("%s,  size: %d byte%s (%d block%s)\n", perms, plural(get_nod(fs, nod)->i_size), plural(get_nod(fs, nod)->i_blocks / INOBLK));
+	switch(get_nod(fs, nod)->i_mode & FM_IFMT)
+	{
+		case FM_IFSOCK:
+			list_blocks(fs, nod);
+			break;
+		case FM_IFLNK:
+			print_link(fs, nod);
+			break;
+		case FM_IFREG:
+			list_blocks(fs, nod);
+			break;
+		case FM_IFBLK:
+			print_dev(fs, nod);
+			break;
+		case FM_IFDIR:
+			list_blocks(fs, nod);
+			print_dir(fs, nod);
+			break;
+		case FM_IFCHR:
+			print_dev(fs, nod);
+			break;
+		case FM_IFIFO:
+			list_blocks(fs, nod);
+			break;
+		default:
+			list_blocks(fs, nod);
+	}
+	printf("Done with inode %d\n",nod);
+}
+
+// describes various fields in a filesystem
+static void
+print_fs(filesystem *fs)
+{
+	uint32 i;
+	uint8 *ibm;
+
+	printf("%d blocks (%d free, %d reserved), first data block: %d\n",
+	       fs->sb.s_blocks_count, fs->sb.s_free_blocks_count,
+	       fs->sb.s_r_blocks_count, fs->sb.s_first_data_block);
+	printf("%d inodes (%d free)\n", fs->sb.s_inodes_count,
+	       fs->sb.s_free_inodes_count);
+	printf("block size = %d, frag size = %d\n",
+	       fs->sb.s_log_block_size ? (fs->sb.s_log_block_size << 11) : 1024,
+	       fs->sb.s_log_frag_size ? (fs->sb.s_log_frag_size << 11) : 1024);
+	printf("number of groups: %d\n",GRP_NBGROUPS(fs));
+	printf("%d blocks per group,%d frags per group,%d inodes per group\n",
+	     fs->sb.s_blocks_per_group, fs->sb.s_frags_per_group,
+	     fs->sb.s_inodes_per_group);
+	printf("Size of inode table: %d blocks\n",
+		(int)(fs->sb.s_inodes_per_group * sizeof(inode) / BLOCKSIZE));
+	for (i = 0; i < GRP_NBGROUPS(fs); i++) {
+		printf("Group No: %d\n", i+1);
+		printf("block bitmap: block %d,inode bitmap: block %d, inode table: block %d\n",
+		     get_gd(fs, i)->bg_block_bitmap,
+		     get_gd(fs, i)->bg_inode_bitmap,
+		     get_gd(fs, i)->bg_inode_table);
+		printf("block bitmap allocation:\n");
+		print_bm(GRP_GET_GROUP_BBM(fs, i),fs->sb.s_blocks_per_group);
+		printf("inode bitmap allocation:\n");
+		ibm = GRP_GET_GROUP_IBM(fs, i);
+		print_bm(ibm, fs->sb.s_inodes_per_group);
+		for (i = 1; i <= fs->sb.s_inodes_per_group; i++)
+			if (allocated(ibm, i))
+				print_inode(fs, i);
+	}
+}
+
+static void
+dump_fs(filesystem *fs, FILE * fh, int swapit)
+{
+	uint32 nbblocks = fs->sb.s_blocks_count;
+	fs->sb.s_reserved[200] = 0;
+	if(swapit)
+		swap_goodfs(fs);
+	if(fwrite(fs, BLOCKSIZE, nbblocks, fh) < nbblocks)
+		perror_msg_and_die("output filesystem image");
+	if(swapit)
+		swap_badfs(fs);
+}
+
+static void
+populate_fs(filesystem *fs, char **dopt, int didx, int squash_uids, int squash_perms, uint32 fs_timestamp, struct stats *stats)
+{
+	int i;
+	for(i = 0; i < didx; i++)
+	{
+		struct stat st;
+		FILE *fh;
+		int pdir;
+		char *pdest;
+		uint32 nod = EXT2_ROOT_INO;
+		if(fs)
+			if((pdest = strchr(dopt[i], ':')))
+			{
+				*(pdest++) = 0;
+				if(!(nod = find_path(fs, EXT2_ROOT_INO, pdest)))
+					error_msg_and_die("path %s not found in filesystem", pdest);
+			}
+		stat(dopt[i], &st);
+		switch(st.st_mode & S_IFMT)
+		{
+			case S_IFREG:
+				fh = xfopen(dopt[i], "rb");
+				add2fs_from_file(fs, nod, fh, fs_timestamp, stats);
+				fclose(fh);
+				break;
+			case S_IFDIR:
+				if((pdir = open(".", O_RDONLY)) < 0)
+					perror_msg_and_die(".");
+				if(chdir(dopt[i]) < 0)
+					perror_msg_and_die(dopt[i]);
+				add2fs_from_dir(fs, nod, squash_uids, squash_perms, fs_timestamp, stats);
+				if(fchdir(pdir) < 0)
+					perror_msg_and_die("fchdir");
+				if(close(pdir) < 0)
+					perror_msg_and_die("close");
+				break;
+			default:
+				error_msg_and_die("%s is neither a file nor a directory", dopt[i]);
+		}
+	}
+}
+
+static void
+showversion(void)
+{
+	printf("genext2fs " VERSION "\n");
+}
+
+static void
+showhelp(void)
+{
+	fprintf(stderr, "Usage: %s [options] image\n"
+	"Create an ext2 filesystem image from directories/files\n\n"
+	"  -x, --starting-image <image>\n"
+	"  -d, --root <directory>\n"
+	"  -D, --devtable <file>\n"
+	"  -B, --block-size <bytes>\n"
+	"  -b, --size-in-blocks <blocks>\n"
+	"  -i, --bytes-per-inode <bytes per inode>\n"
+	"  -N, --number-of-inodes <number of inodes>\n"
+	"  -m, --reserved-percentage <percentage of blocks to reserve>\n"
+	"  -o, --creator-os <os>      'linux', 'hurd', 'freebsd' or a numerical value.\n"
+	"  -g, --block-map <path>     Generate a block map file for this path.\n"
+	"  -e, --fill-value <value>   Fill unallocated blocks with value.\n"
+	"  -z, --allow-holes          Allow files with holes.\n"
+	"  -f, --faketime             Set filesystem timestamps to 0 (for testing).\n"
+	"  -q, --squash               Same as \"-U -P\".\n"
+	"  -U, --squash-uids          Squash owners making all files be owned by root.\n"
+	"  -P, --squash-perms         Squash permissions on all files.\n"
+	"  -h, --help\n"
+	"  -V, --version\n"
+	"  -v, --verbose\n\n"
+	"Report bugs to genext2fs-devel@lists.sourceforge.net\n", app_name);
+}
+
+#define MAX_DOPT 128
+#define MAX_GOPT 128
+
+#define MAX_FILENAME 255
+
+extern char* optarg;
+extern int optind, opterr, optopt;
+
+// parse the value for -o <os>
+int
+lookup_creator_os(const char *name)
+{
+	static const char *const creators[] =
+		{"linux", "hurd", "2", "freebsd", NULL};
+	char *endptr;
+	int i;
+
+	// numerical value ?
+	i = strtol(name, &endptr, 0);
+	if(name[0] && *endptr == '\0')
+		return i;
+
+	// symbolic name ?
+	for(i=0; creators[i]; i++)
+	       if(strcasecmp(creators[i], name) == 0)
+		       return i;
+
+	// whatever ?
+	return -1;
+}
+
+int
+main(int argc, char **argv)
+{
+	int nbblocks = -1;
+	int nbinodes = -1;
+	int nbresrvd = -1;
+	float bytes_per_inode = -1;
+	float reserved_frac = -1;
+	int fs_timestamp = -1;
+	int creator_os = CREATOR_OS;
+	char * fsout = "-";
+	char * fsin = 0;
+	char * dopt[MAX_DOPT];
+	int didx = 0;
+	char * gopt[MAX_GOPT];
+	int gidx = 0;
+	int verbose = 0;
+	int holes = 0;
+	int emptyval = 0;
+	int squash_uids = 0;
+	int squash_perms = 0;
+	uint16 endian = 1;
+	int bigendian = !*(char*)&endian;
+	filesystem *fs;
+	int i;
+	int c;
+	struct stats stats;
+
+#if HAVE_GETOPT_LONG
+	struct option longopts[] = {
+	  { "starting-image",	required_argument,	NULL, 'x' },
+	  { "root",		required_argument,	NULL, 'd' },
+	  { "devtable",		required_argument,	NULL, 'D' },
+	  { "block-size",	required_argument,	NULL, 'B' },
+	  { "size-in-blocks",	required_argument,	NULL, 'b' },
+	  { "bytes-per-inode",	required_argument,	NULL, 'i' },
+	  { "number-of-inodes",	required_argument,	NULL, 'N' },
+	  { "reserved-percentage", required_argument,	NULL, 'm' },
+	  { "creator-os",	required_argument,	NULL, 'o' },
+	  { "block-map",	required_argument,	NULL, 'g' },
+	  { "fill-value",	required_argument,	NULL, 'e' },
+	  { "allow-holes",	no_argument, 		NULL, 'z' },
+	  { "faketime",		no_argument,		NULL, 'f' },
+	  { "squash",		no_argument,		NULL, 'q' },
+	  { "squash-uids",	no_argument,		NULL, 'U' },
+	  { "squash-perms",	no_argument,		NULL, 'P' },
+	  { "help",		no_argument,		NULL, 'h' },
+	  { "version",		no_argument,		NULL, 'V' },
+	  { "verbose",		no_argument,		NULL, 'v' },
+	  { 0, 0, 0, 0}
+	} ;
+
+	app_name = argv[0];
+
+	while((c = getopt_long(argc, argv, "x:d:D:B:b:i:N:m:o:g:e:zfqUPhVv", longopts, NULL)) != EOF) {
+#else
+	app_name = argv[0];
+
+	while((c = getopt(argc, argv,      "x:d:D:B:b:i:N:m:o:g:e:zfqUPhVv")) != EOF) {
+#endif /* HAVE_GETOPT_LONG */
+		switch(c)
+		{
+			case 'x':
+				fsin = optarg;
+				break;
+			case 'd':
+			case 'D':
+				dopt[didx++] = optarg;
+				break;
+			case 'B':
+				blocksize = SI_atof(optarg);
+				break;
+			case 'b':
+				nbblocks = SI_atof(optarg);
+				break;
+			case 'i':
+				bytes_per_inode = SI_atof(optarg);
+				break;
+			case 'N':
+				nbinodes = SI_atof(optarg);
+				break;
+			case 'm':
+				reserved_frac = SI_atof(optarg) / 100;
+				break;
+			case 'o':
+				creator_os = lookup_creator_os(optarg);
+				break;
+			case 'g':
+				gopt[gidx++] = optarg;
+				break;
+			case 'e':
+				emptyval = atoi(optarg);
+				break;
+			case 'z':
+				holes = 1;
+				break;
+			case 'f':
+				fs_timestamp = 0;
+				break;
+			case 'q':
+				squash_uids = 1;
+				squash_perms = 1;
+				break;
+			case 'U':
+				squash_uids = 1;
+				break;
+			case 'P':
+				squash_perms = 1;
+				break;
+			case 'h':
+				showhelp();
+				exit(0);
+			case 'V':
+				showversion();
+				exit(0);
+			case 'v':
+				verbose = 1;
+				showversion();
+				break;
+			default:
+				error_msg_and_die("Note: options have changed, see --help or the man page.");
+		}
+	}
+
+	if(optind < (argc - 1))
+		error_msg_and_die("Too many arguments. Try --help or else see the man page.");
+	if(optind > (argc - 1))
+		error_msg_and_die("Not enough arguments. Try --help or else see the man page.");
+	fsout = argv[optind];
+
+	if(blocksize != 1024 && blocksize != 2048 && blocksize != 4096)
+		error_msg_and_die("Valid block sizes: 1024, 2048 or 4096.");
+	if(creator_os < 0)
+		error_msg_and_die("Creator OS unknown.");
+
+	hdlinks.hdl = (struct hdlink_s *)malloc(hdlink_cnt * sizeof(struct hdlink_s));
+	if (!hdlinks.hdl)
+		error_msg_and_die("Not enough memory");
+	hdlinks.count = 0 ;
+
+	if(fsin)
+	{
+		if(strcmp(fsin, "-"))
+		{
+			FILE * fh = xfopen(fsin, "rb");
+			fs = load_fs(fh, bigendian);
+			fclose(fh);
+		}
+		else
+			fs = load_fs(stdin, bigendian);
+	}
+	else
+	{
+		if(reserved_frac == -1)
+			nbresrvd = nbblocks * RESERVED_BLOCKS;
+		else 
+			nbresrvd = nbblocks * reserved_frac;
+
+		stats.ninodes = EXT2_FIRST_INO - 1 + (nbresrvd ? 1 : 0);
+		stats.nblocks = 0;
+
+		populate_fs(NULL, dopt, didx, squash_uids, squash_perms, fs_timestamp, &stats);
+
+		if(nbinodes == -1)
+			nbinodes = stats.ninodes;
+		else
+			if(stats.ninodes > (unsigned long)nbinodes)
+			{
+				fprintf(stderr, "number of inodes too low, increasing to %ld\n", stats.ninodes);
+				nbinodes = stats.ninodes;
+			}
+
+		if(bytes_per_inode != -1) {
+			int tmp_nbinodes = nbblocks * BLOCKSIZE / bytes_per_inode;
+			if(tmp_nbinodes > nbinodes)
+				nbinodes = tmp_nbinodes;
+		}
+		if(fs_timestamp == -1)
+			fs_timestamp = time(NULL);
+		fs = init_fs(nbblocks, nbinodes, nbresrvd, holes,
+				fs_timestamp, creator_os);
+	}
+	
+	populate_fs(fs, dopt, didx, squash_uids, squash_perms, fs_timestamp, NULL);
+
+	if(emptyval) {
+		uint32 b;
+		for(b = 1; b < fs->sb.s_blocks_count; b++)
+			if(!allocated(GRP_GET_BLOCK_BITMAP(fs,b),GRP_BBM_OFFSET(fs,b)))
+				memset(get_blk(fs, b), emptyval, BLOCKSIZE);
+	}
+	if(verbose)
+		print_fs(fs);
+	for(i = 0; i < gidx; i++)
+	{
+		uint32 nod;
+		char fname[MAX_FILENAME];
+		char *p;
+		FILE *fh;
+		if(!(nod = find_path(fs, EXT2_ROOT_INO, gopt[i])))
+			error_msg_and_die("path %s not found in filesystem", gopt[i]);
+		while((p = strchr(gopt[i], '/')))
+			*p = '_';
+		SNPRINTF(fname, MAX_FILENAME-1, "%s.blk", gopt[i]);
+		fh = xfopen(fname, "wb");
+		fprintf(fh, "%d:", get_nod(fs, nod)->i_size);
+		flist_blocks(fs, nod, fh);
+		fclose(fh);
+	}
+	if(strcmp(fsout, "-"))
+	{
+		FILE * fh = xfopen(fsout, "wb");
+		dump_fs(fs, fh, bigendian);
+		fclose(fh);
+	}
+	else
+		dump_fs(fs, stdout, bigendian);
+	free_fs(fs);
+	return 0;
+}
Index: genext2fs-1.4.1-new/test-gen.lib
===================================================================
--- genext2fs-1.4.1-new/test-gen.lib	(nonexistent)
+++ genext2fs-1.4.1-new/test-gen.lib	(revision 5)
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# These routines contain the filesystem generation code.
+# This code is sourced by the other scripts so that digest
+# generation is consistent.
+
+# dgen - Exercises the -d directory option of genext2fs
+# Creates an image with a file of given size
+# Usage: dgen file-size number-of-blocks 
+dgen () {
+	size=$1; blocks=$2; blocksz=$3;
+	rm -rf test
+	mkdir -p test
+	cd test
+	if [ x$size = x0 ]; then
+		> file.$1
+	else
+		dd if=/dev/zero of=file.$1 bs=$size count=1 2>/dev/null
+	fi
+        chmod 777 file.$1
+	TZ=UTC-11 touch -t 200502070321.43 file.$1 .
+	cd ..
+	./genext2fs -B $blocksz -N 17 -b $blocks -d test -f -o Linux -q ext2.img
+}
+
+# fgen - Exercises the -f spec-file option of genext2fs
+# Creates an image with the devices mentioned in the given spec file 
+# Usage: fgen spec-file number-of-blocks 
+fgen () {
+	fname=$1; blocks=$2; 
+	mkdir -p test
+	cp $fname test
+	TZ=UTC-11 touch -t 200502070321.43 test/$fname
+	./genext2fs -N 92 -b $blocks -D test/$fname -f -o Linux ext2.img
+}
+
+# gen_cleanup - Remove the files generated by the above functions
+# Usage: gen_cleanup
+gen_cleanup () {
+	rm -rf ext2.img test
+}
+
+# calc_digest - Return the MD5 digest of the test image
+# Usage: calc_digest
+calc_digest () {
+	digest=`md5sum ext2.img 2>/dev/null | cut -f 1 -d " "`
+	if [ x$digest != x ] ; then
+ 		echo $digest
+	else
+		digest=`md5 ext2.img 2>/dev/null | cut -f 4 -d " "`
+		echo $digest
+	fi
+}
+
+LC_ALL=C
+export LC_ALL
Index: genext2fs-1.4.1-new/test-mount.sh
===================================================================
--- genext2fs-1.4.1-new/test-mount.sh	(nonexistent)
+++ genext2fs-1.4.1-new/test-mount.sh	(revision 5)
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+# Use this script if you need to regenerate the digest values
+# in test.sh, or if you don't care about digests and you just
+# want to see some fsck results. Should be run as root.
+
+set -e
+
+. ./test-gen.lib
+
+test_cleanup () {
+	umount mnt 2>/dev/null || true
+	rm -rf mnt fout lsout
+}
+
+fail () {
+	echo FAILED
+	test_cleanup
+	gen_cleanup
+	exit 1
+}
+
+pass () {
+	md5=`calc_digest`
+	echo PASSED
+	echo $@ $md5
+	test_cleanup
+	gen_cleanup
+}
+
+# dtest-mount - Exercise the -d directory option of genext2fs
+# Creates an image with a file of given size, verifies it
+# and returns the command line with which to invoke dtest()
+# Usage: dtest-mount file-size number-of-blocks 
+dtest_mount () {
+	size=$1; blocks=$2; blocksz=$3;
+	echo Testing $blocks blocks of $blocksz bytes with file of size $size
+	dgen $size $blocks $blocksz
+	/sbin/e2fsck -fn ext2.img || fail
+	mkdir -p mnt
+	mount -t ext2 -o ro,loop ext2.img mnt || fail
+	if (! [ -f mnt/file.$size ]) || \
+	      [ $size != "`ls -al mnt | grep file.$size |
+	                                awk '{print $5}'`" ] ; then
+		fail
+	fi
+	pass dtest $size $blocks $blocksz
+}
+
+# ftest-mount - Exercise the -f spec-file option of genext2fs
+# Creates an image with the devices mentioned in the given spec 
+# file, verifies it, and returns the command line with which to
+# invoke ftest()
+# Usage: ftest-mount spec-file number-of-blocks 
+ftest_mount () {
+	fname=$1; blocks=$2 
+	echo Testing with devices file $fname
+	fgen $fname $blocks
+	/sbin/e2fsck -fn ext2.img || fail
+	mkdir -p mnt
+	mount -t ext2 -o ro,loop ext2.img mnt || fail
+	[ -d mnt/dev ] || fail
+	# Exclude those devices that have interpolated
+	# minor numbers, as being too hard to match.
+	egrep -v "(hda|hdb|tty|loop|ram|ubda)" $fname | \
+		grep '^[^	#]*	[bc]' | \
+		awk '{print $1,$4,$5,$6","$7}'| \
+		sort -d -k3.6 > fout
+	ls -aln mnt/dev | \
+		egrep -v "(hda|hdb|tty|loop|ram|ubda)" | \
+		grep ^[bc] | \
+		awk '{ print "/dev/"$10,$3,$4,$5$6}' | \
+		sort -d -k3.6 > lsout
+	diff fout lsout || fail
+	pass ftest $fname $blocks
+}
+
+dtest_mount 0 4096 1024
+dtest_mount 0 2048 2048
+dtest_mount 0 1024 4096
+dtest_mount 0 8193 1024
+dtest_mount 0 8194 1024
+dtest_mount 0 8193 4096
+dtest_mount 0 8194 2048
+dtest_mount 1 4096 1024
+dtest_mount 1 1024 4096
+dtest_mount 12288 4096 1024
+dtest_mount 274432 4096 1024
+dtest_mount 8388608 9000 1024
+dtest_mount 8388608 4500 2048
+dtest_mount 8388608 2250 4096
+dtest_mount 16777216 20000 1024
+dtest_mount 16777216 10000 2048
+
+ftest_mount device_table.txt 4096

Property changes on: genext2fs-1.4.1-new/test-mount.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: genext2fs-1.4.1-new/test.sh
===================================================================
--- genext2fs-1.4.1-new/test.sh	(nonexistent)
+++ genext2fs-1.4.1-new/test.sh	(revision 5)
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+# This script generates a variety of filesystems and checks that they
+# are identical to ones that are known to be mountable, and pass fsck
+# and various other sanity checks.
+
+# Passing these tests is preferable to passing test-mount.sh because
+# this script doesn't require root, and because passing these tests
+# guarantees byte-for-byte agreement with other builds, ports,
+# architectures, times of day etc.
+
+set -e
+
+. ./test-gen.lib
+
+# md5cmp - Calculate MD5 digest and compare it to an expected value.
+# Usage: md5cmp expected-digest
+md5cmp () {
+	checksum=$1
+	md5=`calc_digest`
+	if [ x$md5 = x$checksum ] ; then
+		echo PASSED
+	else
+		echo FAILED
+		exit 1
+	fi
+}
+
+# dtest - Exercises the -d directory option of genext2fs
+# Creates an image with a file of given size and verifies it
+# Usage: dtest file-size number-of-blocks correct-checksum
+dtest () {
+	size=$1; blocks=$2; blocksz=$3; checksum=$4
+	echo Testing with file of size $size
+	dgen $size $blocks $blocksz
+	md5cmp $checksum
+	gen_cleanup
+}
+
+# ftest - Exercises the -f spec-file option of genext2fs
+# Creates an image with the devices mentioned in the given spec 
+# file and verifies it
+# Usage: ftest spec-file number-of-blocks correct-checksum
+ftest () {
+	fname=$1; blocks=$2; checksum=$3
+	echo Testing with devices file $fname
+	fgen $fname $blocks
+	md5cmp $checksum
+	gen_cleanup
+}
+
+# NB: to regenerate these values, always use test-mount.sh, that is,
+# replace the following lines with the output of
+# sudo sh test-mount.sh|grep test
+
+dtest 0 4096 1024 3bc6424b8fcd51a0de34ee59d91d5f16
+dtest 0 2048 2048 230afa16496df019878cc2370c661cdc
+dtest 0 1024 4096 ebff5eeb38b70f3f1cd081e60eb44561
+dtest 0 8193 1024 f174804f6b433b552706cbbfc60c416d
+dtest 0 8194 1024 4855a55d0cbdc44584634df49ebd5711
+dtest 0 8193 4096 c493679698418ec7e6552005e2d2a6d8
+dtest 0 8194 2048 ec13f328fa7543563f35f494bddc059c
+dtest 1 4096 1024 09c569b6bfb45222c729c42d04d5451f
+dtest 1 1024 4096 d318a326fdc907810ae9e6b0a20e9b06
+dtest 12288 4096 1024 61febcbfbf32024ef99103fcdc282c39
+dtest 274432 4096 1024 0c517803552c55c1806e4220b0a0164f
+dtest 8388608 9000 1024 e0e5ea15bced10ab486d8135584b5d8e
+dtest 8388608 4500 2048 39f4d537a72f5053fd6891721c59680d
+dtest 8388608 2250 4096 1d697fa4bc2cfffe02ac91edfadc40bf
+dtest 16777216 20000 1024 fdf636eb905ab4dc1bf76dce5ac5d209
+dtest 16777216 10000 2048 f9824a81ea5e74fdf469c097927c292b
+ftest device_table.txt 4096 a0af06d944b11d2902dfd705484c64cc

Property changes on: genext2fs-1.4.1-new/test.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: genext2fs-1.4.1-new
===================================================================
--- genext2fs-1.4.1-new	(nonexistent)
+++ genext2fs-1.4.1-new	(revision 5)

Property changes on: genext2fs-1.4.1-new
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,51 ##
+
+# local config & object files
+build-config.mk
+sbin
+usr
+var
+
+# configure targets
+autom4te.cache
+.config
+config.log
+config.status
+configure
+
+# cpan configure targets
+.installed
+CPAN-Config.pm
+CPAN-install
+
+# Target build dirs
+.build-machine
+
+# Timestamps
+.makefile
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Object Files
+*.[ao]
+
+# backup copies
+*~
Index: .
===================================================================
--- .	(nonexistent)
+++ .	(revision 5)

Property changes on: .
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,51 ##
+
+# local config & object files
+build-config.mk
+sbin
+usr
+var
+
+# configure targets
+autom4te.cache
+.config
+config.log
+config.status
+configure
+
+# cpan configure targets
+.installed
+CPAN-Config.pm
+CPAN-install
+
+# Target build dirs
+.build-machine
+
+# Timestamps
+.makefile
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Object Files
+*.[ao]
+
+# backup copies
+*~