#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

#define getint(buffer,a) (buffer[a]|(buffer[a+1]<<8)|(buffer[a+2]<<16)|(buffer[a+3]<<24))
#define getshort(buffer,a) (buffer[a]|(buffer[a+1]<<8))
#define readbuf(readaddr,offs,readsize) \
if (readaddr>=bufferaddr && (readaddr+readsize)<=bufferaddr+bufsize) \
{ \
	offs=readaddr-bufferaddr; \
} else { \
	offs=0; \
		fseeko(inputimage,readaddr,SEEK_SET); \
		fread(buffer,sizeof(char),bufsize,inputimage); \
		bufferaddr=readaddr; \
}	

FILE *inputimage;
unsigned int bufsize=(1<<16);
unsigned char buffer[1<<16];
off_t bufferaddr=0;


typedef struct {
	unsigned int	s_inodes_count;
	unsigned int	s_blocks_count;
	unsigned int	s_r_blocks_count;
	unsigned int	s_free_blocks_count;
	unsigned int	s_free_inodes_count;
	unsigned int	s_first_data_block;
	unsigned int	s_log_block_size;
	signed int	s_log_frag_size;
	unsigned int	s_blocks_per_group;
	unsigned int	s_frags_per_group;
	unsigned int	s_inodes_per_group;
	unsigned int	s_mtime;
	unsigned int	s_wtime;
	unsigned short	s_mnt_count;
	signed short	s_max_mnt_count;
	unsigned short	s_magic;
	unsigned short	s_state;	
	unsigned short	s_errors;
	unsigned short	s_pad;
	unsigned long	s_lastcheck;
	unsigned long	s_creator_os;
	unsigned long	s_rev_level;
	//	unsigned long	s_reserved[235];
	unsigned int	s_block_size;
} superblock_;

typedef struct {
	unsigned long	bg_block_bitmap;
	unsigned long	bg_inode_bitmap;
	unsigned long	bg_inode_table;
	unsigned short	bg_free_block_count;
	unsigned short	bg_free_inodes_count;
	unsigned short	bg_used_dirs_count;
	unsigned short	bg_pad;
	unsigned long	bg_reserved[3];
} group_descriptor_;

typedef struct {
	unsigned short	i_mode;
	unsigned short	i_uid;
	unsigned int	i_size;
	unsigned int	i_atime;
	unsigned int	i_ctime;
	unsigned int	i_mtime;
	unsigned int	i_dtime;
	unsigned short	i_gid;
	unsigned short	i_links_count;
	unsigned int	i_blocks;
	unsigned int	i_flags;
	unsigned int	i_reserved1;
	unsigned int	i_block[15];
	unsigned int	i_version;
	unsigned int	i_file_acl;
	unsigned int	i_dir_acl;
	unsigned int	i_faddr;
	unsigned char	i_frag;
	unsigned char	i_fsize;
	unsigned short	i_pad1;
	unsigned long	i_reserved2[2];
} inode_;

typedef struct {
	unsigned int	inode_number;
	unsigned short	rec_len;
	unsigned char	name_len;
	unsigned char	file_type;
	unsigned char	name[256];
} direntry_;

superblock_ getsuperblock()
{
	superblock_ superblock;
	fseeko(inputimage,1024,SEEK_SET);
	fread(buffer,sizeof(char),bufsize,inputimage);
	bufferaddr=1024;

	superblock.s_inodes_count=getint(buffer,0);
	superblock.s_blocks_count=getint(buffer,4);
	superblock.s_r_blocks_count=getint(buffer,8);
	superblock.s_free_blocks_count=getint(buffer,12);
	superblock.s_free_inodes_count=getint(buffer,16);
	superblock.s_first_data_block=getint(buffer,20);
	superblock.s_log_block_size=getint(buffer,24);
	superblock.s_block_size=1024<<superblock.s_log_block_size;
	superblock.s_log_frag_size=getint(buffer,28);
	superblock.s_blocks_per_group=getint(buffer,32);
	superblock.s_frags_per_group=getint(buffer,36);	
	superblock.s_inodes_per_group=getint(buffer,40);
	superblock.s_mtime=getint(buffer,44);
	superblock.s_wtime=getint(buffer,48);
	superblock.s_mnt_count=getshort(buffer,52);
	superblock.s_max_mnt_count=getshort(buffer,54);
	superblock.s_magic=getshort(buffer,56);
	superblock.s_state=getshort(buffer,58);
	superblock.s_errors=getshort(buffer,60);
	superblock.s_pad=getshort(buffer,62);
	superblock.s_lastcheck=getint(buffer,64);
	superblock.s_creator_os=getint(buffer,68);
	superblock.s_rev_level=getint(buffer,72);

	if (superblock.s_magic!=0xef53) fprintf(stderr,"warning> Superblock magic number is 0x%04x instead of 0xef53.\nwarning> Maybe this is not an ext2 image?\n",superblock.s_magic);
	return superblock;
}
void printsuperblock(superblock_ superblock)
{
	printf("superblock> s_inodes_count      %12i\n",superblock.s_inodes_count);
	printf("superblock> s_blocks_count      %12i\n",superblock.s_blocks_count);
	printf("superblock> r_blocks_count      %12i\n",superblock.s_r_blocks_count);
	printf("superblock> s_free_blocks_count %12i\n",superblock.s_free_blocks_count);
	printf("superblock> s_free_inodes_count %12i\n",superblock.s_free_inodes_count);
	printf("superblock> s_first_data_block  %12i\n",superblock.s_first_data_block);
	printf("superblock> s_log_block_size          %1ikbyte\n",(1<<superblock.s_log_block_size));
	printf("superblock> s_log_frag_size     %12i\n",superblock.s_log_frag_size);
	printf("superblock> s_blocks_per_group  %12i\n",superblock.s_blocks_per_group);
	printf("superblock> s_frags_per_group   %12i\n",superblock.s_frags_per_group);
	printf("superblock> s_inodes_per_group  %12i\n",superblock.s_inodes_per_group);
	printf("superblock> s_mtime             %12i\n",superblock.s_mtime);
	printf("superblock> s_wtime             %12i\n",superblock.s_wtime);
	printf("superblock> s_mnt_count         %12i\n",superblock.s_mnt_count);
	printf("superblock> s_max_mnt_count     %12i\n",superblock.s_max_mnt_count);
	printf("superblock> s_magic             %12x\n",superblock.s_magic);
	printf("superblock> s_state             %12x\n",superblock.s_state);
	printf("superblock> s_errors            %12x\n",superblock.s_errors);
	printf("superblock> s_lastcheck         %12i\n",superblock.s_lastcheck);
	printf("superblock> s_creator_os        %12i\n",superblock.s_creator_os);
	printf("superblock> s_rev_level         %12i\n",superblock.s_rev_level);
}
group_descriptor_ getgroupdescriptor(off_t addr)
{
	unsigned int offs;
	group_descriptor_ group_descriptor;

	readbuf(addr,offs,32);
	printf("offs> %lli %lli %i\n",bufferaddr,addr,offs);
	group_descriptor.bg_block_bitmap=getint(buffer,offs+0);
	group_descriptor.bg_inode_bitmap=getint(buffer,offs+4);
	group_descriptor.bg_inode_table=getint(buffer,offs+8);
	group_descriptor.bg_free_block_count=getshort(buffer,offs+12);
	group_descriptor.bg_free_inodes_count=getshort(buffer,offs+14);
	group_descriptor.bg_used_dirs_count=getshort(buffer,offs+16);
	group_descriptor.bg_pad=getshort(buffer,offs+18);
	group_descriptor.bg_reserved[0]=getint(buffer,offs+20);
	group_descriptor.bg_reserved[1]=getint(buffer,offs+24);
	group_descriptor.bg_reserved[2]=getint(buffer,offs+28);

	return group_descriptor;
}
void printgroupdescriptor(group_descriptor_ group_descriptor)
{
	printf("group descriptor> bg_block_bitmap        @0x%08x\n",group_descriptor.bg_block_bitmap);
	printf("group descriptor> bg_inode_bitmap        @0x%08x\n",group_descriptor.bg_inode_bitmap);
	printf("group descriptor> bg_inode_table         @0x%08x\n",group_descriptor.bg_inode_table);
	printf("group descriptor> bg_free_block_count   %12i\n",group_descriptor.bg_free_block_count);
	printf("group descriptor> bg_free_inodes_count  %12i\n",group_descriptor.bg_free_inodes_count);
	printf("group descriptor> bg_used_dirs_count    %12i\n",group_descriptor.bg_used_dirs_count);
}

inode_ getinode(off_t addr)
{
	unsigned int offs;
	inode_ inode;
	int i;

	readbuf(addr,offs,128);

	inode.i_mode=getshort(buffer,offs+0);
	inode.i_uid=getshort(buffer,offs+2);
	inode.i_size=getint(buffer,offs+4);
	inode.i_atime=getint(buffer,offs+8);
	inode.i_ctime=getint(buffer,offs+12);
	inode.i_mtime=getint(buffer,offs+16);
	inode.i_dtime=getint(buffer,offs+20);
	inode.i_gid=getshort(buffer,offs+24);
	inode.i_links_count=getshort(buffer,offs+26);
	inode.i_blocks=getint(buffer,offs+28);
	inode.i_flags=getint(buffer,offs+32);
	inode.i_reserved1=getint(buffer,offs+36);
	for (i=0;i<15;i++) inode.i_block[i]=getint(buffer,offs+40+i*4);
	inode.i_version=getint(buffer,offs+100);
	inode.i_file_acl=getint(buffer,offs+104);
	inode.i_dir_acl=getint(buffer,offs+108);
	inode.i_faddr=getint(buffer,offs+112);
	inode.i_frag=buffer[offs+116];
	inode.i_fsize=buffer[offs+117];
	inode.i_pad1=buffer[offs+118];
	inode.i_reserved2[0]=getint(buffer,offs+120);
	inode.i_reserved2[1]=getint(buffer,offs+124);

	return inode;
}
void printinode(inode_ inode)
{
	int i;
	unsigned short x;
	printf("inode> mode          0x%08x ",inode.i_mode);
	switch(inode.i_mode&0xf000)
	{
		case 0xa000: printf("socket       ");break;
		case 0xc000: printf("symlink      ");break;
		case 0x8000: printf("file         ");break;
		case 0x6000: printf("block device ");break;
		case 0x4000: printf("directory    ");break;
		case 0x2000: printf("char device  ");break;
		case 0x1000: printf("fifo         ");break;
	}
	if (inode.i_mode&0x800) printf("U");else printf("-"); // suid
	if (inode.i_mode&0x400) printf("g");else printf("-"); // sgid
	if (inode.i_mode&0x200) printf("t");else printf("-"); // sticky

	x=inode.i_mode&0x1ff;
	for (i=0;i<3;i++)
	{
		if (x&0x100) printf("r");else printf("-");
		if (x&0x080) printf("w");else printf("-");
		if (x&0x040) printf("x");else printf("-");
		x<<=3;
	}
	printf("\n");	
	printf("inode> uid           0x%08x\n",inode.i_uid);
	printf("inode> size        %12i\n",inode.i_size);
	printf("inode> atime       %12i\n",inode.i_atime);
	printf("inode> ctime       %12i\n",inode.i_ctime);
	printf("inode> mtime       %12i\n",inode.i_mtime);
	printf("inode> dtime       %12i\n",inode.i_dtime);
	printf("inode> gid           0x%08x\n",inode.i_gid);
	printf("inode> links_count %12i\n",inode.i_links_count);
	printf("inode> blocks      %12i @",inode.i_blocks);for (i=0;i<15;i++) printf("0x%08x ",inode.i_block[i]);printf("\n");
	printf("inode> flags         0x%08x\n",inode.i_flags);
	printf("inode> version     %12i\n",inode.i_version);
	printf("inode> file_acl      0x%08x\n",inode.i_file_acl);
	printf("inode> dir_acl       0x%08x\n",inode.i_dir_acl);
	printf("inode> faddr         0x%08x\n",inode.i_faddr);
	printf("inode> frag                0x%02x\n",inode.i_frag);
	printf("inode> fsize               0x%02x\n",inode.i_fsize);
}
direntry_ getdirentry(off_t addr)
{
	unsigned int offs;
	direntry_ direntry;
	int i;

	readbuf(addr,offs,8);
	direntry.inode_number=getint(buffer,offs+0);
	direntry.rec_len=getshort(buffer,offs+4);
	direntry.name_len=buffer[offs+6];
	direntry.file_type=buffer[offs+7];

	readbuf(addr+8,offs,256);
	for (i=0;(i<256 && i<direntry.name_len);i++)
	{
		direntry.name[i]=buffer[offs+i];	
	}	
	return direntry;
}

void printdirentry(direntry_ direntry)
{
	int i;
	printf("%12i %4i %3i %3i",direntry.inode_number,direntry.rec_len,direntry.name_len,direntry.file_type);
	printf("[");
	for (i=0;i<direntry.name_len;i++) printf("%c",direntry.name[i]);
	printf("]\n");
}
off_t calcinodeaddr(superblock_ superblock,group_descriptor_ group_descriptor,unsigned int inodenum)
{
	unsigned int group;
	unsigned int grouprem;
	off_t groupoffset;
	off_t inodeaddr;
	group=inodenum/superblock.s_inodes_per_group;
	grouprem=inodenum%superblock.s_inodes_per_group;
	groupoffset=group;
	groupoffset*=superblock.s_blocks_per_group;
	groupoffset*=superblock.s_block_size;
	inodeaddr=groupoffset;
	inodeaddr+=group_descriptor.bg_inode_table*superblock.s_block_size;
	inodeaddr+=(grouprem-1)*128;
	return inodeaddr;

}
void printdir(superblock_ superblock,group_descriptor_ group_descriptor,unsigned int inodenum)
{
	inode_ inode;
	inode_ inode2;
	int block_num=0;
	int i,j,k,l,m,n;
	unsigned int blocknum=0;
	off_t addr;
	unsigned int offs;
	int block_len;
	direntry_ direntry;
	unsigned int blockpointer0[65536];
	unsigned int blockpointer1[65536];
	unsigned int blockpointer2[65536];

	inode=getinode(calcinodeaddr(superblock,group_descriptor,inodenum));
	printinode(inode);
	i=0;
	block_len=0;
	blocknum=inode.i_block[i++];
	for (k=0;k<65536;k++) 
	{
		blockpointer0[k]=0;
		blockpointer1[k]=0;
		blockpointer2[k]=0;
	}
	while (blocknum!=0)
	{
		addr=blocknum;
		addr*=superblock.s_block_size;
		if (addr!=0)
		{
			block_len=0;
			direntry.rec_len=1;
			while (block_len<superblock.s_block_size)
			{	
				direntry=getdirentry(addr);
				inode2=getinode(calcinodeaddr(superblock,group_descriptor,direntry.inode_number));
				switch(inode2.i_mode&0xf000)
				{
					case 0xa000: printf("socket       ");break;
					case 0xc000: printf("symlink      ");break;
					case 0x8000: printf("file         ");break;
					case 0x6000: printf("block device ");break;
					case 0x4000: printf("directory    ");break;
					case 0x2000: printf("char device  ");break;
					case 0x1000: printf("fifo         ");break;
				}

				printdirentry(direntry);
				block_len+=direntry.rec_len;
				addr+=direntry.rec_len;
			}
		}
		if (i<12)
		{
			blocknum=inode.i_block[i++];
			j=0;
		} 
		else if (i==12) 	// indirect blocks
		{
			if (j==0) 
			{
				addr=inode.i_block[12];
				addr*=superblock.s_block_size;
				readbuf(addr,offs,superblock.s_block_size);
				for (k=0;k<superblock.s_block_size;k+=4)
				{
					blockpointer0[k/4]=getint(buffer,offs+k);
				}
			}
			blocknum=blockpointer0[j++];
			if (j==superblock.s_block_size/4) 
			{
				j=0;
				i=13;
				l=0;
			}
		} 
		else if (i==13)		// double indirect blocks
		{
			if (j==0) 
			{
				addr=inode.i_block[13];
				addr*=superblock.s_block_size;
				readbuf(addr,offs,superblock.s_block_size);
				for (k=0;k<superblock.s_block_size;k+=4)
				{
					blockpointer0[k/4]=getint(buffer,offs+k);
				}
				l=0;
			}
			if (j==superblock.s_block_size/4) 
			{
				j=0;
				i=14;
				l=0;
			} else {	
				if (l==0)
				{
					blocknum=blockpointer0[j++];
					addr=blocknum;
					addr*=superblock.s_block_size;
					readbuf(addr,offs,superblock.s_block_size);
					for (k=0;k<superblock.s_block_size;k+=4)
					{
						blockpointer1[k/4]=getint(buffer,offs+k);
					}
				}	
				blocknum=blockpointer1[l++];
				if (l==superblock.s_block_size/4) l=0;
			}
		}
		else if (i==14)		// triple indirect blocks
		{
			if (j==0) 
			{
				addr=inode.i_block[14];
				addr*=superblock.s_block_size;
				readbuf(addr,offs,superblock.s_block_size);
				for (k=0;k<superblock.s_block_size;k+=4)
				{
					blockpointer0[k/4]=getint(buffer,offs+k);
				}
				l=0;
			}
			if (j==superblock.s_block_size/4) 
			{
				j=0;
				i=14;
				l=0;
				blocknum=0;
			} else {	
				if (l==0)
				{
					blocknum=blockpointer0[j++];
					addr=blocknum;
					addr*=superblock.s_block_size;
					readbuf(addr,offs,superblock.s_block_size);
					for (k=0;k<superblock.s_block_size;k+=4)
					{
						blockpointer1[k/4]=getint(buffer,offs+k);
					}
					m=0;
				}
				if (m==0)
				{
					blocknum=blockpointer1[l++];
					if (l==superblock.s_block_size/4) l=0;
					addr=blocknum;
					addr*=superblock.s_block_size;
					readbuf(addr,offs,superblock.s_block_size);
					for (k=0;k<superblock.s_block_size;k+=4)
					{
						blockpointer2[k/4]=getint(buffer,offs+k);
					}
				}	
				blocknum=blockpointer2[m++];
				if (m==superblock.s_block_size/4) m=0;
			}
		}	
	}	
}
void readfile(superblock_ superblock,group_descriptor_ group_descriptor,const char* filename,unsigned int inodenum)
{
	inode_ inode;
	int written=0;
	int i;
	int j,l,m,n;
	int k;
	off_t size;
	off_t addr;
	unsigned int offs;
	unsigned int blocknum;
	unsigned int blockpointer0[65536];
	unsigned int blockpointer1[65536];
	unsigned int blockpointer2[65536];

	FILE *f=fopen(filename,"wb");
	inode=getinode(calcinodeaddr(superblock,group_descriptor,inodenum));
	printinode(inode);
	size=inode.i_dir_acl;
	size<<=32;
	size|=inode.i_size;

	blocknum=inode.i_block[0];	
	i=1;
	for (k=0;k<65536;k++) 
	{
		blockpointer0[k]=0;
		blockpointer1[k]=0;
		blockpointer2[k]=0;
	}
	while (blocknum!=0)
	{
		addr=blocknum;
		addr*=superblock.s_block_size;
		readbuf(addr,offs,superblock.s_block_size);
		if (size>superblock.s_block_size) 
			fwrite(&buffer[offs],sizeof(char),superblock.s_block_size,f); 
		else 
			fwrite(&buffer[offs],sizeof(char),(unsigned int)size,f); 	
		written+=superblock.s_block_size;
		size-=superblock.s_block_size;
		fprintf(stderr,"file> %20lli bytes to go\r",size);
		if (i<12)
		{
			blocknum=inode.i_block[i++];
			j=0;
		} 
		else if (i==12) 	// indirect blocks
		{
			if (j==0) 
			{
				addr=inode.i_block[12];
				addr*=superblock.s_block_size;
				readbuf(addr,offs,superblock.s_block_size);
				for (k=0;k<superblock.s_block_size;k+=4)
				{
					blockpointer0[k/4]=getint(buffer,offs+k);
				}
			}
			blocknum=blockpointer0[j++];
			if (j==superblock.s_block_size/4) 
			{
				j=0;
				i=13;
				l=0;
			}
		} 
		else if (i==13)		// double indirect blocks
		{
			if (j==0) 
			{
				addr=inode.i_block[13];
				addr*=superblock.s_block_size;
				readbuf(addr,offs,superblock.s_block_size);
				for (k=0;k<superblock.s_block_size;k+=4)
				{
					blockpointer0[k/4]=getint(buffer,offs+k);
				}
				l=0;
			}
			if (j==superblock.s_block_size/4) 
			{
				j=0;
				i=14;
				l=0;
			} else {	
				if (l==0)
				{
					blocknum=blockpointer0[j++];
					addr=blocknum;
					addr*=superblock.s_block_size;
					readbuf(addr,offs,superblock.s_block_size);
					for (k=0;k<superblock.s_block_size;k+=4)
					{
						blockpointer1[k/4]=getint(buffer,offs+k);
					}
				}	
				blocknum=blockpointer1[l++];
				if (l==superblock.s_block_size/4) l=0;
			}
		}
		else if (i==14)		// triple indirect blocks
		{
			if (j==0) 
			{
				addr=inode.i_block[14];
				addr*=superblock.s_block_size;
				readbuf(addr,offs,superblock.s_block_size);
				for (k=0;k<superblock.s_block_size;k+=4)
				{
					blockpointer0[k/4]=getint(buffer,offs+k);
				}
				l=0;
			}
			if (j==superblock.s_block_size/4) 
			{
				j=0;
				i=14;
				l=0;
				blocknum=0;
			} else {	
				if (l==0)
				{
					blocknum=blockpointer0[j++];
					addr=blocknum;
					addr*=superblock.s_block_size;
					readbuf(addr,offs,superblock.s_block_size);
					for (k=0;k<superblock.s_block_size;k+=4)
					{
						blockpointer1[k/4]=getint(buffer,offs+k);
					}
					m=0;
				}
				if (m==0)
				{
					blocknum=blockpointer1[l++];
					if (l==superblock.s_block_size/4) l=0;
					addr=blocknum;
					addr*=superblock.s_block_size;
					readbuf(addr,offs,superblock.s_block_size);
					for (k=0;k<superblock.s_block_size;k+=4)
					{
						blockpointer2[k/4]=getint(buffer,offs+k);
					}
				}	
				blocknum=blockpointer2[m++];
				if (m==superblock.s_block_size/4) m=0;
			}
		}	
	}
	fclose(f);
}
int main(int argc,char** argv)
{
	superblock_ superblock;
	group_descriptor_ group_descriptor;
	int i;
	inode_ inode;
	const unsigned int tmp[]={32769,2,65537,163841,245761,262145,344065,77049};

	inputimage=fopen(argv[1],"rb");
	superblock=getsuperblock();
	printsuperblock(superblock);	
	group_descriptor=getgroupdescriptor((1+superblock.s_first_data_block)*(1024<<superblock.s_log_block_size));
	printgroupdescriptor(group_descriptor);
	if (strncmp(argv[2],"dir",3)==0) 
	{
		printdir(superblock,group_descriptor,atoi(argv[3]));
	} 
	if (strncmp(argv[2],"file",4)==0)
	{
		readfile(superblock,group_descriptor,argv[4],atoi(argv[3]));
	}

	fclose(inputimage);
}
