/*
 *  dump.c
 *  Output functions for the ntfs tools
 *
 *  Copyright (C) 1995 Martin von Löwis
 */

#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
#include "config.h"
#include "ntfs.h"

void dump_runs(unsigned char*,int,int,int);
void list_attr_mem(char*);
void print_name(char*);
void uniprint(char*,int);
void dump_attribute_list(char *start,char *stop);

/* Dump a block of memory starting at buf. Display length bytes. The displayed
   index of the first byte is start */
void dump_mem(unsigned char *buf,int start,int length)
{
	int offs,i;
	for(offs=0;offs<length;offs+=16)
	{
		printf("%8.8X ",start+offs);
		for(i=0;i<16;i++)printf("%2X ",buf[offs+i]);
		for(i=0;i<16;i++)
			if(buf[offs+i]>31 && buf[offs+i]<128)putchar(buf[offs+i]);
			else putchar('.');
		putchar('\n');
	}
}

	

/* dump from the raw volume, starting at position */
void dump(int position,int start,int length)
{
	int offset;
	lseek(NTFS_FD(the_vol),position,SEEK_SET);
	for(offset=0;length==-1 || offset<length;offset+=16)
	{
		int i;
		unsigned char buf[16];
		if(read(NTFS_FD(the_vol),buf,16)!=16)
			{perror("read");return;}
		printf("%8.8X ",start+offset);
		for(i=0;i<16;i++)
			printf("%2X ",buf[i]);
		for(i=0;i<16;i++)
			if(buf[i]>31 && buf[i]<128)putchar(buf[i]);
			else putchar('.');
		putchar('\n');
	}
}

/* Find a string on the volume, starting at position. If searching for Unicode
   strings, the string argument should already be Unicode */
#define BSIZE	32768
int grep(int position,int length,
	unsigned char *string,int stringlen,int ignore_case)
{
	int offset;
	int b_offs;
	int i;
	unsigned char buf[2*BSIZE];

	if(ignore_case)
		for(i=0;string[i];i++)string[i]=tolower(string[i]);
	lseek(NTFS_FD(the_vol),position,SEEK_SET);
	read(NTFS_FD(the_vol),buf,BSIZE);
	if(ignore_case)
		for(i=0;i<BSIZE;i++)buf[i]=tolower(buf[i]);
	for(offset=b_offs=0;length==-1 || offset<length;offset+=BSIZE)
	{
		if(read(NTFS_FD(the_vol),buf+BSIZE,BSIZE)!=BSIZE)
			{perror("read");return -1;}
		if(ignore_case)
			for(i=BSIZE;i<2*BSIZE;i++)buf[i]=tolower(buf[i]);
		for(;b_offs<BSIZE;b_offs++)
			if(buf[b_offs]==string[0])
			{
				for(i=0;i<stringlen;i++)
					if(buf[b_offs+i]!=string[i])
							break;
				if(i==stringlen)return position+offset+b_offs;
			}
		memcpy(buf,buf+BSIZE,BSIZE);
		b_offs-=BSIZE;
	}
	return -1;
}
			
/* print the attribute list for the MTF record at offset on the volume */
void list_attributes(int offset)
{
	char rec[4096];
	lseek(NTFS_FD(the_vol),offset,SEEK_SET);
	if(read(NTFS_FD(the_vol),rec,sizeof(rec))!=sizeof(rec))
	{	perror("read");
		return;
	}
	if(!ntfs_check_mft_record(the_vol,rec))
	{	fprintf(stderr,"Not a mft record\n");
		return;
	}
	list_attr_mem(rec);
}

/* print the attribute list at rec */
void list_attr_mem(char *rec)
{
	int offs;
	int type,length,resident,namelen,compressed;
	char *start;
	/* first attribute should start at *0x14 */
	offs= (* (unsigned short*) (rec + 0x14));
	while(offs<the_vol->mft_recordsize)
	{
		printf("%4.4X:",offs);
		printf("Type %X ",type=*(int*)(rec+offs));
		if(type==-1)break;
		/* offset to the next attribute */
		printf("Length %X ",length=*(short*)(rec+offs+4));
		resident=*(rec+offs+8)=='\0';
		compressed=*(rec+offs+0xC);
		if(resident)
			printf("resident ");
		if(compressed)
			printf("compressed ");
		printf("Flags %X ",*(short*)(rec+offs+14));
		/* position of attribute data if resident */
		start=rec+offs+*(unsigned char*)(rec+offs+10);
		if(*(unsigned char*)(rec+offs+10)==0)start+=*(short*)(rec+offs+0x20);
		/* length attribute name, name starts at start */
		namelen=*(unsigned char*)(rec+offs+9);
		if(namelen!=0)
		{
			printf("named(");
			uniprint(start,namelen);
			start+=namelen*2;
			printf(") ");
		}
		if(*(char*)(rec+offs+11))
			fprintf(stderr,"Found [B] at offset %X\n",offs);
		if(*(char*)(rec+offs+13))
			fprintf(stderr,"Found [D] at offset %X\n",offs);
		switch(type)
		{
		case 0x10: printf("Standard attributes\n");
			printf("\tCreation time ");print_time((long long*)(rec+offs+0x18));
			printf("\n\tModification time ");print_time((long long*)(rec+offs+0x20));
			printf("\n\tMFT Modification time ");print_time((long long*)(rec+offs+0x28));
			printf("\n\tAccess time ");print_time((long long*)(rec+offs+0x30));
			puts("");
			break;
		case 0x20: printf("Attribute list\n");
			dump_attribute_list(rec+offs+0x18,rec+offs+length);
			break;
		case 0x30: printf("File name\n");
			printf("\t");print_name(rec+offs+0x58);
			printf("\tIndexed in 0x%X\n",*(int*)(rec+offs+0x18));
			break;
		case 0x40: printf("Volume version, please report\n");break;
		case 0x50: printf("Security descriptor\n");break;
		case 0x60: printf("Volume name\n");break;
		case 0x70: printf("Volume information\n");break;
		case 0x80: printf("Data\n");break;
		case 0x90: printf("Index root\n");break;
		case 0xa0: printf("Index allocation\n");break;
		case 0xb0: printf("Bitmap\n");break;
		case 0xc0: printf("Symlink, please report\n");break;
		/* HPFS extended attributes */
		case 0xd0: printf("EA info\n");break;
		case 0xf0: printf("EA\n");break;
		default: printf("Unknown, please report\n");break;
		}
		{
			ntfs_inode ino;
			int length;
			ino.attr=rec;
			if(resident)
				printf("\tSize %x \n",*(int*)(rec+offs+0x10));
			else{
				printf("\tSize %x, allocated %x, initialized %x",
					length=*(int*)(rec+offs+0x28),*(int*)(rec+offs+0x30),
					*(int*)(rec+offs+0x38));
				if(compressed)
					printf(", compressed %x\n",*(int*)(rec+offs+0x40));
				else
					putchar('\n');
				if(*(short*)(rec+offs+0x22))
					fprintf(stderr,"Found [22]=%x at offset %X\n",*(short*)(rec+offs+0x22),offs);
				if(*(int*)(rec+offs+0x24))
					fprintf(stderr,"Found [24] at offset %X\n",offs);
				dump_runs(start,*(int*)(rec+offs+0x10),
					*(int*)(rec+offs+0x18),compressed);
			}
		}
		offs+=length;
	}
	puts("");
}	

/* unit of 100ns since 1.1.1601 */
void print_time(long long* t)
{
	long long sec;
	time_t unix_utc;
	char *str;
	sec=*t/10000000;
	unix_utc=sec-((long long)369*365+89)*24*3600;
	str=ctime(&unix_utc);
	/* remove \n */
	str[strlen(str)-1]='\0';
	printf("%s",str);
}

void print_name(char *first)
{
	int length=*(unsigned char*)first++;
	switch(*first++)
	{
		case 1: printf("Unicode-Name:");break;
		case 2: printf("DOS-Name:");break;
		case 3: printf("Unicode+DOS:");break;
		default:
			printf("Don't know how to read the name\n");
			return;
	}
	uniprint(first,length);
	puts("");
}

/* print a unicode string */
void uniprint(char *first,int length)
{
	while(length--){
		putchar(*first++);
		/* character above 255 or not a valid unicode string */
		if(*first++){
			printf("!!!!Error printing file name\n");
			return;
		}
	}
}	

void uniprintz(char *first)
{
	while(*first){
		putchar(*first++);
		if(*first++){
			printf("!!!!Error printing string\n");
			return;
		}
	}
}

/* display all subentries, then display this entry */
dumpdir_entry(ntfs_inode* ino,char *entry)
{
	int length=*(short*)(entry+8);
	int used=(*(entry+12)&2)==0;
	if(used)printf("\tinode %x\t",*(int*)entry);
	if(*(char*)(entry+13))
		fprintf(stderr,"Found [D] at %X\n",ino);
	if(*(short*)(entry+14))
		fprintf(stderr,"Found [E] at %X\n", ino);
	if((int)*(entry+12)&1){
		int nextblock=*(int*)(entry+length-8);
		printf("Going down to block %x\n",nextblock);
		dumpdir_record(ino,nextblock);
		printf("back to\tinode %x\t",*(int*)entry);
	}
	if(used)print_name(entry+0x50);
}

/* display a directory record */
dumpdir_record(ntfs_inode* ino,int nextblock)
{
	int length;
	char record[8192];
	char *offset;
	if(ntfs_read_attr(ino,AT_INDEX_ALLOCATION,"$I30",
		nextblock*the_vol->clustersize,record,
		the_vol->index_recordsize,memcpy)!=
		the_vol->index_recordsize){
		printf("read failed\n");
		return;
	}
	if(!ntfs_check_index_record(the_vol,record)){
		printf("Not a index record\n");
		return;
	}
	offset=record+*(short*)(record+0x18)+0x18;
	do{
		dumpdir_entry(ino,offset);
		if(*(offset+0xC)&2)break;
		length=*(short*)(offset+8);
		if(!length)break;
		offset+=length;
	}while(1);
}

/* display an inode as directory */
void dumpdir(ntfs_inode* ino)
{
	int length=ino->vol->mft_recordsize;
	char *buf=(char*)malloc(length);
	char *data;
	if(ntfs_read_attr(ino,AT_INDEX_ROOT,"$I30",
		0,buf,length,memcpy)<=0)
	{
		printf("Not a directory\n");
		free(buf);
		return;
	}
	data=buf+0x20;
	while(1)
	{
		length=*(short*)(data+8);
		dumpdir_entry(ino,data);
		if(*(data+12)&2)break;
		data+=length;
		if(!length){
			printf("length==0!!\n");
			break;
		}
	}
	free(buf);
}

/* display the list of runs of an attribute */
void dump_runs(unsigned char *start,int vcnstart,int vcnend,int compressed)
{
	int length=0;
	int cluster=0;
	int l=vcnend-vcnstart;
	int vcn=vcnstart;
	int ctype;
	while(l>0 && decompress_run(&start,&length,&cluster,&ctype)!=-1)
	{
		l-=length;
		if(!ctype)
			printf("\tRun from %x to %x (VCN=%x)\n",cluster,cluster+length,vcn);
		else
			printf("\tCompression unit size %x\n",length);
		vcn+=length;
	}
}

/* dump the attribute list attribute */
void dump_attribute_list(char *start,char *stop)
{
	while(start!=stop){
		printf("\tType %X,MFT# %X,Start VCN %X ",*(int*)start,
			*(int*)(start+0x10),*(int*)(start+0x8));
		uniprint(start+0x1A,*(unsigned char*)(start+0x6));
		start+=*(short*)(start+4);
		putchar('\n');
	}
}

static void putchar1(unsigned char c)
{
	if(c>=32 && c<=127)
		putchar(c);
	else switch(c)
	{
		case 10: printf("\\n");break;
		case 13: printf("\\r");break;
		default: printf("\\%o",c);
	}
}

void dump_decompress(ntfs_inode *ino,int run,int verbose)
{
	int block,len,clear_pos;
	unsigned char *compressed;
	char clear[16384];
	int tag,bits,charmode;
	int ctype;
	unsigned char *data;
	int offset=0;
	unsigned char *attr=ntfs_get_attr(ino,AT_DATA,0);
	if(!attr)
	{
		fprintf(stderr,"No data attribute in this inode\n");
		return;
	}
	if(RESIDENT(attr))
	{
		fprintf(stderr,"Attribute is resident\n");
		return;
	}
	if(!COMPRESSED(attr))
	{
		fprintf(stderr,"Data attribute is not compressed\n");
		return;
	}
	/*Skip name and valueoffset*/
	attr+=*(short*)(attr+0x20)+*(attr+9);
	block=0;
	do{
		decompress_run(&attr,&len,&block,&ctype);
	}while(run--);
	compressed=(char*)malloc(len*the_vol->clustersize);
	ntfs_getput_clusters(the_vol,block,0,len*the_vol->clustersize,
		compressed,memcpy,1);
	data=compressed;
while(*(data+1) & 0xF0)
{
	int block_size;
	unsigned char *stop;
	block_size = *(unsigned short*)data;
	if(verbose)printf("Head %x",block_size);
	block_size &= 0xFFF;
	data+=2;
	offset+=2;
	stop = data + block_size;
	bits=0;
	charmode=0;
	clear_pos=0;
	while(data<=stop)
	{
		if(!bits){
			if(verbose)printf("\nOffset %x",offset);
			charmode=0;
			tag=*data;
			bits=8;
			data++;
			offset++;
			if(data>stop)
				break;
		}
		if(tag&1){
			int i,len,delta,delta1;
			delta = *(unsigned short*)(data);
			len=*data;
			len&=0x1f;
			if(clear_pos<=0x10)
			{
				delta1=delta>>12;
				len = delta & 0xFFF;
			}
			else if(clear_pos<=0x20)
			{
				delta1=delta>>11;
				len = delta & 0x7FF;
			}
			else if(clear_pos<=0x40)
			{
				delta1=delta>>10;
				len = delta & 0x3FF;
			}
			else if(clear_pos<=0x80)
			{
				delta1=delta>>9;
				len = delta & 0x1FF;
			}
			else if(clear_pos<=0x100)
			{
				delta1=delta>>8;
				len = delta & 0xFF;
			}
			else if(clear_pos<=0x200)
			{
				delta1=delta>>7;
				len = delta & 0x7F;
			}
			else if(clear_pos<=0x400)
			{
				delta1=delta>>6;
				len = delta & 0x3F;
			}
			else if(clear_pos<=0x800)
			{
				delta1=delta>>5;
				len = delta & 0x1F;
			}
			else if(clear_pos<=0x1000)
			{
				delta1=delta>>4;
				len = delta & 0xF;
			}else
				fprintf(stderr,"NOW WHAT?\n");
			len+=3;
			if(verbose)printf("\n%8.8X:len %x(%x) delta %x(%x) ",
				clear_pos,len,*data,delta,delta1);
			charmode=0;
			for(i=0;i<len;i++)
			{
				if(verbose)
					putchar1(clear[clear_pos-delta1-1]);
				else
					putchar(clear[clear_pos-delta1-1]);
				clear[clear_pos]=clear[clear_pos-delta1-1];
				clear_pos++;
			}
			data+=2;
			offset+=2;
		}else{
			if(verbose)if(!charmode)
				printf("\n%8.8X:",clear_pos);
			if(verbose)
				putchar1(*data);
			else
				putchar(*data);
			clear[clear_pos++]=*data;
			data++;
			offset++;
			charmode=1;
		}
		tag>>=1;
		bits--;
	}
	if(verbose)putchar('\n');
}/*while*/
}

void dump_inode(ntfs_inode *ino)
{
	int i,j,vcn;
	printf("Inode %d, %d attributes, %d mft clusters\n",
		ino->i_number, ino->attr_count, ino->record_count);
	for(i=0;i<ino->attr_count;i++)
	{
		printf("attribute %X",ino->attrs[i].type);
		if(ino->attrs[i].name)
		{
			printf(" named(");
			uniprint((char*)ino->attrs[i].name,ino->attrs[i].namelen);
			printf(")\n");
		}else
			printf("\n");
		if(RESIDENT(&ino->attrs[i].header))
			printf("  resident\n");
		else
			for(j=0,vcn=0;j<ino->attrs[i].d.r.len;j++)
			{
				printf("  Run %d from %x len %x (VCN=%x)\n", j,
					ino->attrs[i].d.r.runlist[j].cluster,
					ino->attrs[i].d.r.runlist[j].len,vcn);
				vcn+=ino->attrs[i].d.r.runlist[j].len;
			}
	}
}
