/*
 *  inode.c
 *
 *  Copyright (C) 1995 Martin von Löwis
 */

#include <stdio.h>
#include <fcntl.h>
#include "ntfs.h"
#include "config.h"

int ntfs_init_inode(ntfs_inode *ino,ntfs_volume* vol,int inum)
{
	ntfs_debug("Initializing inode %x\n",inum);
	ino->i_number=inum;
	ino->vol=vol;
	ino->attr=ntfs_malloc(vol->mft_recordsize);
	if(!ino->attr)
		return -1;
	if(inum!=0)
	{
		if(ntfs_read_mft_record(vol,inum,ino->attr)==-1)
		{
			ntfs_debug("init inode: %x failed\n",inum);
			return -1;
		}
	}else
		ntfs_memcpy(ino->attr,vol->mft,vol->mft_recordsize);
	ntfs_debug("Init: got mft %x\n",inum);
	ino->attr_count=0;
	ino->record_count=0;
	ino->records=0;
	ino->attrs=0;
	ntfs_load_attributes(ino);
	ntfs_debug("Init: done %x\n",inum);
	return 0;
}

void ntfs_clear_inode(ntfs_inode *ino)
{
	int i;
	ntfs_free(ino->attr);
	ntfs_free(ino->records);
	for(i=0;i<ino->attr_count;i++)
	{
		if(ino->attrs[i].name)
			ntfs_free(ino->attrs[i].name);
		if(RESIDENT(&ino->attrs[i].header))
		{
			if(ino->attrs[i].d.data)
				ntfs_free(ino->attrs[i].d.data);
		}else{
			if(ino->attrs[i].d.r.runlist)
				ntfs_free(ino->attrs[i].d.r.runlist);
		}
	}
	ntfs_free(ino->attrs);
}

void ntfs_insert_run(ntfs_attribute *attr,int cnum,int cluster,int len)
{
	if(attr->d.r.len % 8 == 0)
	{
		ntfs_runlist* old;
		old=attr->d.r.runlist;
		attr->d.r.runlist=ntfs_malloc((attr->d.r.len+8)*sizeof(ntfs_runlist));
		if(old) {
			ntfs_memcpy(attr->d.r.runlist,old,attr->d.r.len
				*sizeof(ntfs_runlist));
			ntfs_free(old);
		}
	}
	if(attr->d.r.len>cnum)
		ntfs_memcpy(attr->d.r.runlist+cnum+1,attr->d.r.runlist+cnum,
			(attr->d.r.len-cnum)*sizeof(ntfs_runlist));
	attr->d.r.runlist[cnum].cluster=cluster;
	attr->d.r.runlist[cnum].len=len;
	attr->d.r.len++;
}

static int process_runs(ntfs_inode *ino,ntfs_attribute* attr,
	unsigned char *data)
{
	int startvcn,endvcn;
	int vcn,cnum;
	int cluster,len,ctype;
	startvcn = *(int*)(data+0x10);
	endvcn = *(int*)(data+0x18);

	/* check whether this chunk really belongs to the end */
	for(cnum=0,vcn=0;cnum<attr->d.r.len;cnum++)
		vcn+=attr->d.r.runlist[cnum].len;
	if(vcn!=startvcn)
	{
		ntfs_error("Problem with runlist in extended record\n");
		return;
	}
	if(!endvcn)
	{
		endvcn = *(int*)(data+0x28)-1; /* allocated length */
		endvcn /= ino->vol->clustersize;
	}
	data=data+*(short*)(data+0x20);
	cnum=attr->d.r.len;
	cluster=0;
	for(vcn=startvcn; vcn<=endvcn; vcn+=len)
	{
		if(decompress_run(&data,&len,&cluster,&ctype))
			return -1;
		if(ctype)
			ntfs_insert_run(attr,cnum,-1,len);
		else
			ntfs_insert_run(attr,cnum,cluster,len);
		cnum++;
	}
}

int ntfs_insert_attribute(ntfs_inode* ino,unsigned char* attr)
{
	int i,do_insert=0;
	int type;
	int *name;
	int namelen;
	void *data;
	int datasize;

	type = *(int*)attr;
	namelen = *(attr+9);
	if(!namelen)
		name=0;
	else
	{
		name=ntfs_malloc(2*namelen);
		ntfs_memcpy(name,attr+*(unsigned short*)(attr+10),2*namelen);
	}
	/* search for attribute, consider ordering */
	for(i=0;i<ino->attr_count;i++)
	{
		int n=min(namelen,ino->attrs[i].namelen);
		int s=ntfs_uni_strncmp(ino->attrs[i].name,name,n);
		if(ino->attrs[i].type==type && ino->attrs[i].namelen==namelen && !s)
		/*FIXME*/if(type!=AT_FILE_NAME)
		{
			process_runs(ino,ino->attrs+i,attr);
			return;
		}
		if(ino->attrs[i].type>type || (ino->attrs[i].type==type && s==1))
		{
			do_insert=1;
			break;
		}
	}
	/* allocate space */
	if(ino->attr_count % 8 ==0)
	{
		ntfs_attribute* old=ino->attrs;
		ino->attrs = (ntfs_attribute*)ntfs_malloc((ino->attr_count+8)*
				sizeof(ntfs_attribute));
		if(old){
			ntfs_memcpy(ino->attrs,old,ino->attr_count
					*sizeof(ntfs_attribute));
			ntfs_free(old);
		}
	}
	if(do_insert)
		ntfs_memcpy(ino->attrs+i+1,ino->attrs+i,(ino->attr_count-i)*
			sizeof(ntfs_attribute));
	ino->attr_count++;
	ino->attrs[i].type=type;
	ino->attrs[i].namelen=namelen;
	ino->attrs[i].name=name;
	ntfs_memcpy(&ino->attrs[i].header,attr,sizeof(ino->attrs[i].header));
	datasize=DATASIZE(attr);
	if(RESIDENT(attr)) {
		data=attr+*(short*)(attr+0x14);
		ino->attrs[i].d.data = (void*)ntfs_malloc(datasize);
		ntfs_memcpy(ino->attrs[i].d.data,data,datasize);
	}else{
		int cluster,len,ctype,cnum,vcn;
		ino->attrs[i].d.r.runlist=0;
		ino->attrs[i].d.r.len=0;
		process_runs(ino,ino->attrs+i,attr);
	}
	return 0;
}

void ntfs_insert_mft_attributes(ntfs_inode* ino,char *mft,int mftno)
{
	int i;
	char *it;
	int type,len;
	/* check for duplicate */
	for(i=0;i<ino->record_count;i++)
		if(ino->records[i]==mftno)
			return;
	/* allocate space if necessary */
	if(ino->record_count % 8==0)
	{
		int *old=ino->records;
		ino->records=ntfs_malloc((ino->record_count+8)*sizeof(int));
		if(old) {
			for(i=0;i<ino->record_count;i++)
				ino->records[i]=old[i];
			ntfs_free(old);
		}
	}
	ino->records[ino->record_count]=mftno;
	ino->record_count++;
	it = mft + (* (unsigned short*) (mft + 0x14));
	do{
		type=*(int*)it;
		len=*(short*)(it+4);
		if(type!=-1)
			ntfs_insert_attribute(ino,it);
		it+=len;
	}while(type!=-1); /* attribute list end with type -1 */
}

static int parse_attributes(ntfs_inode* ino,void *alist,int len)
{
	char *mft;
	int mftno,l;
	int last_mft=-1;
	mft=ntfs_malloc(ino->vol->mft_recordsize);
	while(len>0)
	{
		mftno=*(int*)(alist+0x10);
		if(mftno!=last_mft){
			last_mft=mftno;
			if(ntfs_read_mft_record(ino->vol,mftno,mft)==-1)
				return -1;
			ntfs_insert_mft_attributes(ino,mft,mftno);
		}
		l=*(short*)(alist+4);
		len-=l;
		alist+=l;
	}
	ntfs_free(mft);
	return 0;
}

void ntfs_load_attributes(ntfs_inode* ino)
{
	ntfs_attribute *alist;
	int datasize;
	int offset,len;
	char *buf;
	ntfs_debug("load_attributes %x 1\n",ino->i_number);
	ntfs_insert_mft_attributes(ino,ino->attr,ino->i_number);
	ntfs_debug("load_attributes %x 2\n",ino->i_number);
	alist=ntfs_find_attr(ino,AT_ATTRIBUTE_LIST,0);
	ntfs_debug("load_attributes %x 3\n",ino->i_number);
	if(!alist)
		return;
	ntfs_debug("load_attributes %x 4\n",ino->i_number);
	datasize=DATASIZE(&alist->header);
	if(RESIDENT(&alist->header))
	{
		parse_attributes(ino,alist->d.data,datasize);
		return;
	}
	buf=ntfs_malloc(1024);
	for(offset=0;datasize;datasize-=len)
	{
		len=min(datasize,1024);
		ntfs_read_attr(ino,AT_ATTRIBUTE_LIST,0,offset,buf,len,ntfs_memcpy);
		parse_attributes(ino,buf,len);
	}
	ntfs_debug("load_attributes %x 5\n",ino->i_number);
	ntfs_free(buf);
}
	
/* mft records start with the string 'FILE'. Each cluster should end in the
   word at offset 0x2A, and needs to be replaced with the words following
   0x2A */
int ntfs_check_mft_record(ntfs_volume *vol,char *record)
{
	short *fixup=(short*)(record+0x2A);
	char *dest=record+vol->blocksize-2;
	int i;
	if(!IS_MFT_RECORD(record))return 0;
	for(i=0;i<vol->mft_clusters_per_record*vol->clusterfactor;i++)
	{
		if(*(short*)dest!=*fixup)return 0;
		*(short*)dest=fixup[i+1];
		dest+=vol->blocksize;
	}
	return 1;
}

/* find the location of an attribute in the inode. A name of NULL indicates
   unnamed attributes. Return pointer to attribute or NULL if not found */
char *ntfs_get_attr(ntfs_inode *ino,int attr,char *name)
{
	/* location of first attribute */
	char *it= ino->attr + (* (unsigned short*) (ino->attr + 0x14));
	int type;
	int len;
	/* Only check for magic DWORD here, fixup should have happened before */
	if(!IS_MFT_RECORD(ino->attr))return 0;
	do{
		type=*(int*)it;
		len=*(short*)(it+4);
		/* We found the attribute type. Is the name correct, too? */
		if(type==attr)
		{
			int namelen=*(it+9);
			char *name_it;
			/* match given name and attribute name if present,
				make sure attribute name is Unicode */
			for(name_it=it+*(unsigned short*)(it+10);namelen;
				name++,name_it+=2,namelen--)
				if(*name_it!=*name || name_it[1])break;
			if(!namelen)break;
		}
		it+=len;
	}while(type!=-1); /* attribute list end with type -1 */
	if(type==-1)return 0;
	return it;
}

/* Returns number of segments with correct type and name - 1,
   or -1 if none */
static int simple_alist_find(ntfs_attrlist_item *attr,char *alist,int length,int segment)
{
	int found=-1;
	char *it=alist;
	while(it<(alist+length))
	{
		char *next=it+*(short*)(it+4);
		int namelen=*(it+0x6);
		if(*(int*)it==attr->type)
			if(!attr->name)
				if(namelen==0)
						found++;
				else ;
			else{
				int i;
				/* compare both names if both are present */
				for(i=0;attr->name[i] && i<namelen;i++)
					if(it[2*i+0x1A]!=attr->name[i])
						break;
				if(i==namelen || attr->name[i]==0)
					found++;
			}
		if(found==segment)
		{
			attr->start_vcn=*(int*)(it+0x8);
			attr->mftno=*(int*)(it+0x10);
			return segment;
		}
		it=next;
	}
	return found;
}

/* returns 1 if found and 0 if not */
int ntfs_find_in_attr_list(ntfs_attrlist_item *attr,int segment)
{
	char *alist;
	int len,offset=0;
	int ret;
	char *buf;
	alist=ntfs_get_attr(attr->ino,AT_ATTRIBUTE_LIST,0);
	if(!alist)
		return 0;
	buf = ntfs_malloc(1024);
	while(1)
	{
		len = ntfs_readwrite_attr(attr->ino,
			AT_ATTRIBUTE_LIST,0,offset,buf,1024,ntfs_memcpy,1);
		if(len==-1)
		{
			ret=0;
			break;
		}
		ret=simple_alist_find(attr,buf,len,segment);
		if(ret==segment)
		{
			ret=1;
			break;
		}
		offset+=len;
		segment-=ret;
	}
	ntfs_free(buf);
	return ret;
}

ntfs_attribute* ntfs_find_attr(ntfs_inode *ino,int type,char *name)
{
	int i;
	for(i=0;i<ino->attr_count;i++)
	{
		if(type==ino->attrs[i].type)
		{
			if(!name && !ino->attrs[i].name)
				return ino->attrs+i;
			if(name && !ino->attrs[i].name)
				return 0;
			if(!name && ino->attrs[i].name)
				return 0;
			if(ntfs_ua_strncmp(ino->attrs[i].name,name,ntfs_strlen(name))==0)
				return ino->attrs+i;
		}
		if(type<ino->attrs[i].type)
			return 0;
	}
	return 0;
}

char *ntfs_get_ext_ino(ntfs_inode*ino,int type,char *name,int offset)
{
	ntfs_attrlist_item attr;
	int section;
	int found;
	char *result;
	int mftno=-1;
	attr.vol=ino->vol;
	attr.ino=ino;
	attr.type=type;
	attr.name=name;
	for(section=0;ntfs_find_in_attr_list(&attr,section) &&
		(attr.start_vcn*ino->vol->clustersize <= offset); section++)
		mftno=attr.mftno;
	if(mftno==-1)
		return (char*)-1;
	result=ntfs_malloc(ino->vol->mft_recordsize);
	if(!result)
		return  (char*)-1;
	if(ntfs_read_mft_record(ino->vol,mftno,result)==-1)
		return (char*)-1;
	return result;
}

int ntfs_get_attr_size(ntfs_inode*ino,int type,char*name)
{
	ntfs_attribute *attr=ntfs_find_attr(ino,type,name);
	return DATASIZE(&attr->header);
}
	
int ntfs_attr_is_resident(ntfs_inode*ino,int type,char*name)
{
	ntfs_attribute *attr=ntfs_find_attr(ino,type,name);
	return RESIDENT(&attr->header);
}
	

/* A run is coded as a type indicator, a length, and a cluster offset.
   The first run is relative to cluster 0, later runs are relative to
   the previous cluster.
   To save space, length and offset are integers of variable length.
   The low byte of the type indicates the width of the length :), the
   high byte the width of the offset. The offsets are interpreted signed,
   the length unsigned.
   length is an output parameter, data and cluster are inout parameters.
   As some type fields are probably reserved for sparse and compressed
   files, only those values known to be used are decoded */
int decompress_run(unsigned char **data,int *length,int *cluster,int *ctype)
{
	unsigned char type=*(*data)++;
	int offset;
	*ctype=0;
	switch(type & 0xF)
	{
		case 1: *length=*(*data)++;break;
		case 2: *length=*(short int*)*data;
				*data+=2;
				break;
		/*case 3: *length=(unsigned char)**data|
						((unsigned char)*(*data+1)<<8)+*(*data+2)<<16;*/
		case 3: *length = *(unsigned int*)data;
		        *length &= 0xFFFFFF;
				*data+=3;
				break;
		default:
				ntfs_error("Can't decode run type field %x\n",type);
				return -1;
	}
	switch(type & 0xF0)
	{
		case 0:    *ctype=2;break;
		case 0x10: *cluster+=*((char*)*data)++;break;
		case 0x20: *cluster+=*(short int*)*data;
					*data+=2;
					break;
		case 0x30: 	offset=(*(int*)*data)&(0xffffff);
					if(offset>0x7fffff)offset-=0x1000000;
					*cluster+=offset;
					*data+=3;
					break;
		default:
				ntfs_error("Can't decode run type field %x\n",type);
				return -1;
	}
	return 0;
}

/* Reads l bytes of the attribute (attr,name) of ino starting at offset
   on vol into buf. Returns the number of bytes read. 0 is end of attribute,
   -1 is error */
int ntfs_readwrite_attr(ntfs_inode *ino,int type,
	char *name,int offset,char *buf,int l,copyfunction pmemcpy,int do_read)
{
	ntfs_attribute *attr;
	int datasize,resident,rnum,compressed;
	int cluster,s_cluster,vcn,len,chunk,copied;
	int s_vcn,s_vcn_comp;
	int clustersize;

	attr=ntfs_find_attr(ino,type,name);
	if(!attr)
		return -1;
	clustersize=ino->vol->clustersize;
	datasize=DATASIZE(&attr->header);
	if(do_read)
	{
		if(offset>datasize)
			return 0;
		if(offset+l>=datasize)
			l=datasize-offset;
	}else{
		/* if writing beyond end, extend attribute */
	}
	resident=RESIDENT(&attr->header);
	if(resident)
	{
		if(do_read)
			pmemcpy(buf,attr->d.data+offset,l);
		else
		{
			pmemcpy(attr->d.data+offset,buf,l);
			update_inode(ino);
		}
		return l;
	}
	compressed=COMPRESSED(&attr->header);
	vcn=0;
	s_vcn = offset/clustersize;
	s_vcn_comp = compressed ? (s_vcn/16)*16 : s_vcn;
	for(rnum=0;vcn+attr->d.r.runlist[rnum].len<=s_vcn_comp;rnum++)
		vcn+=attr->d.r.runlist[rnum].len;
	
	copied=0;
	while(l)
	{
		s_vcn = offset/clustersize;
		s_vcn_comp = compressed ? (s_vcn/16)*16 : s_vcn;
		cluster=attr->d.r.runlist[rnum].cluster;
		len=attr->d.r.runlist[rnum].len;

		if(compressed & cluster==-1)
		{
			/* We are in the middle of a sparse data block */
			char *sparse = ntfs_malloc(512);
			if(!sparse)return -1;
			ntfs_memset(sparse,0,512);
			chunk = min((vcn+len)*clustersize-offset,l);
			while(chunk)
			{
				int i=min(chunk,512);
				pmemcpy(buf,sparse,i);
				chunk-= i;
			}
			/* restore original value */
			chunk = min((vcn+len)*clustersize-offset,l);
			ntfs_free(sparse);
		}else if(compressed)
		{
			char *comp,*comp1;
			char *decomp;
			int got,vcn_comp1;
			int offs1;
			/* FIXME: writing compressed files */
			if(!do_read)
				return -1;
			if(!(comp= ntfs_malloc(16*clustersize)))
				return -1;
			if(!(decomp = ntfs_malloc(16*clustersize)))
			{
				ntfs_free(comp);
				return -1;
			}
			comp1=comp;
			got=0;
			vcn_comp1=s_vcn_comp;
			do{
				int l1=min(len*clustersize,8192-got);
				if(!ntfs_getput_clusters(ino->vol,cluster+vcn_comp1-vcn,0,
					l1,comp1,ntfs_memcpy,do_read))
				{
					ntfs_free(comp);ntfs_free(decomp);return -1;
				}
				comp1+=len*ino->vol->clustersize;
				if(l1==len*clustersize){
					rnum++;
					vcn+=len;
					cluster=attr->d.r.runlist[rnum].cluster;
					len=attr->d.r.runlist[rnum].len;
					vcn_comp1=vcn;
				}
				got+=l1;
			}while(cluster!=-1 && got<8192); /*until 'emtpy' run*/

			offs1 = offset-s_vcn_comp*clustersize;
			if(cluster!=-1){
				/* uncompressible */
				comp1 = comp;
			}else{
				ntfs_decompress(decomp,comp,min(offs1+l,8192));
				comp1 = decomp;
			}		
			chunk = min(8192-offs1,l);
			pmemcpy(buf,comp1+offs1,chunk);
			ntfs_free(comp);
			ntfs_free(decomp);
		}else{
			s_cluster = cluster+s_vcn-vcn;

			chunk=min((vcn+len)*clustersize-offset,l);
			if(!ntfs_getput_clusters(ino->vol,s_cluster,
				offset-s_vcn*clustersize,chunk,buf,pmemcpy,do_read))
			{
				ntfs_error("Read error\n");
				return copied;
			}
		}
		buf+=chunk;
		l-=chunk;
		copied+=chunk;
		offset+=chunk;
		if(l && offset>=((vcn+len)*clustersize))
		{
			rnum++;
			vcn+=len;
			cluster = attr->d.r.runlist[rnum].cluster;
			len = attr->d.r.runlist[rnum].len;
		}
	}
	return copied;
}

int ntfs_read_attr(ntfs_inode *ino,int attr,
	char *name,int offset,char *buf,int l,copyfunction pmemcpy)
{
	return ntfs_readwrite_attr(ino,attr,name,offset,buf,l,pmemcpy,1);
}

int ntfs_write_attr(ntfs_inode *ino,int attr,
	char *name,int offset,char *buf,int l,copyfunction pmemcpy)
{
	return ntfs_readwrite_attr(ino,attr,name,offset,buf,l,pmemcpy,0);
}

int ntfs_vcn_to_lcn(ntfs_inode *ino,int vcn)
{
	int rnum;
	ntfs_attribute *data=ntfs_find_attr(ino,AT_DATA,NULL);
	/* It's hard to give an error code */
	if(!data)return -1;
	if(RESIDENT(&data->header))return -1;
	if(COMPRESSED(&data->header))return -1;
	if(DATASIZE(&data->header)<vcn*ino->vol->clustersize)return -1;

	for(rnum=0;rnum<data->d.r.len && vcn>data->d.r.runlist[rnum].len;rnum++)
		vcn-=data->d.r.runlist[rnum].len;
	
	return data->d.r.runlist[rnum].cluster+vcn;
}

/* copy len unicode characters from from to to :) */
void ntfs_uni2ascii(char *to,char *from,int len)
{
	int i;
	for(i=0;i<len;i++)
		to[i]=from[2*i];
	to[i]='\0';
}

int ntfs_uni_strncmp(short int* a,short int *b,int n)
{
	int i;
	for(i=0;i<n;i++)
	{
		if(a[i]<b[i])
			return -1;
		if(b[i]<a[i])
			return 1;
	}
	return 0;
}

int ntfs_ua_strncmp(short int* a,char* b,int n)
{
	int i;
	for(i=0;i<n;i++)
	{
		if(a[i]<b[i])
			return -1;
		if(b[i]<a[i])
			return 1;
	}
	return 0;
}

/* Convert the NT UTC (based 1.1.1601, in hundred nanosecond units)
   into Unix UTC (based 1.1.1970, in seconds)
   Need to use floating point, because we cannot use libgcc for long
   long operations in the kernel */
time_t ntfs_ntutc2unixutc(long long ntutc)
{
	double sec=ntutc;
	sec=sec/10000000;
	return sec-((long long)369*365+89)*24*3600;
}

int update_inode(ntfs_inode *ino)
{
	ntfs_error("update_inode: not implemented\n");
	return -1;
}

void ntfs_decompress(unsigned char* dest,unsigned char*src,size_t l)
{
	int head;
	int copied=0;
	unsigned char *stop;
	int bits;
	int tag;
	int clear_pos;
	while(1)
	{
		head = *(unsigned short*)src & 0xFFF;
		src += 2;
		stop = src+head;
		bits = 0;
		clear_pos=0;
		if(head==0xFFF) /* uncompressible */
		{
			ntfs_memcpy(dest,src,0x1000);
			dest+=0x1000;
			copied+=0x1000;
			if(l==copied)
				return;
			continue;
		}
		while(src<=stop)
		{
			if(clear_pos>4096)
			{
				ntfs_error("Error 1 in decompress\n");
				return;
			}
			if(!bits){
				tag=*src;
				bits=8;
				src++;
				if(src>stop)
					break;
			}
			if(tag & 1){
				int i,len,delta,code,lmask,dshift;
				code = *(unsigned short*)src;
				src+=2;
				if(!clear_pos)
				{
					ntfs_error("Error 2 in decompress\n");
					return;
				}
				for(i=clear_pos-1,lmask=0xFFF,dshift=12;i>=0x10;i>>=1)
				{
					lmask >>= 1;
					dshift--;
				}
				delta = code >> dshift;
				len = (code & lmask) + 3;
				for(i=0; i<len; i++)
				{
					dest[clear_pos]=dest[clear_pos-delta-1];
					clear_pos++;
					copied++;
					if(copied==l)
						return;
				}
			}else{
				dest[clear_pos++]=*src++;
				copied++;
				if(copied==l)
					return;
			}
			tag>>=1;
			bits--;
		}
		dest+=clear_pos;
	}
}
