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

#include <stdio.h>
#include <string.h>
#include "ntfs.h"
#include "config.h"
#define BY_NAME		0
#define BY_POSITION	1

int ntfs_getdir_record(ntfs_inode*,int,int,long long*,const char*,char*);

/*	Tree walking is done using position numbers. The following numbers have
    a special meaning:
        0    start (.)
        -1   no more entries
        -2   ..
    All other numbers encode sequences of indices. The sequence a,b,c is 
    encoded as <stop><c><b><a>, where <foo> is the encoding of foo. The
    first few integers are encoded as follows:
        0:    0000    1:    0010    2:    0100    3:    0110
        4:    1000    5:    1010    6:    1100 stop:    1110
        7:  000001    8:  000101	9:  001001   10:  001101
	The least significant bits give the width of this encoding, the
	other bits encode the value, starting from the first value of the 
	interval.
		tag		width	first value		last value
		0		3		0				6
		01		4		7				22
		011		5		23				54
		0111	6		55				119
	More values are hopefully not needed, as the file position has currently
	64 bits in total.
*/

int	is_top(long long stack)
{
	return stack==14;
}

unsigned int top()
{
	return 14;
}

long long push(long long stack,int i)
{
	if(i<7)return (stack<<4)|(i<<1);
	if(i<23)return (stack<<6)|((i-7)<<2)|1;
	if(i<55)return (stack<<8)|((i-23)<<3)|3;
	if(i<120)return (stack<<10)|((i-55)<<4)|7;
	ntfs_error("Too many entries\n");
	return -1;
}

long long pop(long long *stack)
{
	static width[16]={1,2,1,3,1,2,1,4,1,2,1,3,1,2,1,-1};
	int res;
	switch(width[*stack & 15])
	{
	case 1:res=(*stack&15)>>1;
			*stack>>=4;
			break;
	case 2:res=((*stack&63)>>2)+7;
	 		*stack>>=6;
			break;
	case 3:res=((*stack & 255)>>3)+23;
			*stack>>=8;
			break;
	case 4:res=((*stack & 1023)>>4)+55;
			*stack>>=10;
			break;
	default:ntfs_error("Unknown encoding\n");
	}
	return res;
}

#if 0
void display_stack(long long stack)
{
	while(!is_top(stack))
	{
		printf("%d ",pop(&stack));
	}
	printf("\n");
}
#endif

/* True if it is not the 'end of dir' entry */
static inline int entry_is_used(char* entry)
{
	return (int)(*(entry+12)&2)==0;
}

/* True if the entry points to another block of entries */
static inline int entry_has_subnodes(char* entry)
{
	return (int)*(entry+12)&1;
}

/* use $UpCase some day */
static inline unsigned short my_toupper(ntfs_volume *vol,unsigned short x)
{
	/* we should read any pending rest of $UpCase here */
	if(x >= vol->upcase_length)
		return x;
	return vol->upcase[x];
}

static int my_strcmp(ntfs_volume *vol,
	const unsigned char *uni,const unsigned char *ascii,int la)
{
	int lu=*(uni+0x50);
	int i;
	uni+=0x52;
	for(i=0;i<lu && i<la;i++)
		if(my_toupper(vol,uni[2*i])!=my_toupper(vol,ascii[i]) 
		|| uni[2*i+1]!='\0')
			break;
	if(i==lu && i==la)return 0;
	if(i==lu)return -1;
	if(i==la)return 1;
	if(uni[2*i+1])return 1;
	if(my_toupper(vol,uni[2*i])<my_toupper(vol,ascii[i]))return -1;
	return 1;
}


/* an index record should start with INDX, and the last word in each
   block should contain the check value. If it passes, the original
   values need to be restored */
int ntfs_check_index_record(ntfs_volume *vol,char *record)
{
	short *fixup=(short*)(record+0x28);
	char *dest=record+vol->blocksize-2;
	int i;
	if(!IS_INDEX_RECORD(record))return 0;
	for(i=0;i<vol->index_clusters_per_record*vol->clusterfactor;i++)
	{
		if(*(short*)dest!=*fixup)return 0;
		*(short*)dest=fixup[i+1];
		dest+=vol->blocksize;
	}
	return 1;
}

/* go down to the next block of entries. These collate before
   the current entry */
int ntfs_getdir_descend(ntfs_inode *ino,char *entry,
	int type,long long *pos,const char* name,char *result)
{
	int length=*(short*)(entry+8);
	int nextblock=*(int*)(entry+length-8);
	if(!entry_has_subnodes(entry))
	{	ntfs_error("illegal descend call\n");
		return 0;
	}
	return ntfs_getdir_record(ino,nextblock,type,pos,name,result);
}

/* The entry has been found. Copy the result in the caller's buffer */
int ntfs_getdir_copyresult(char *dest,char *source)
{
	int length=*(short*)(source+8);
	ntfs_memcpy(dest,source,length);
	return 1;
}

/* type == BY_POSITION: pos points to index, which is decremented to zero
   type == BY_NAME:     look for name, pos points to strlen(name) */

/* Parse a block of entries. Load the block, fix it up, and iterate
   over the entries. The block is given as virtual cluster number */
int ntfs_getdir_record(ntfs_inode* ino,int block,
	int type,long long *pos,const char *name,char *result)
{
	int length=ino->vol->index_recordsize;
	char *record=(char*)ntfs_malloc(length);
	char *offset;
	int curpos=0;
	int destpos;
	int retval;
	/* Read the block from the index allocation attribute */
	if(ntfs_read_attr(ino,AT_INDEX_ALLOCATION,"$I30",
		block*ino->vol->clustersize,record,length,ntfs_memcpy)!=length){
		ntfs_error("read failed\n");
		ntfs_free(record);
		return 0;
	}
	if(!ntfs_check_index_record(ino->vol,record)){
		ntfs_error("%x is not an index record\n",block);
		ntfs_free(record);
		return 0;
	}
	offset=record+*(short*)(record+0x18)+0x18;
	retval=ntfs_getdir_iterate(ino,offset,type,pos,name,result);
	ntfs_free(record);
	return retval;
}
	
/* Iterate over a list of entries, either from an index block, or from
   the index root. 
   If searching BY_POSITION, pop the top index from the position. If the
   position stack is empty then, return the item at the index and set the
   position to the next entry. If the position stack is not empty, 
   recursively proceed for subnodes. If the entry at the position is the
   'end of dir' entry, return 'not found' and the empty stack.
   If searching BY_NAME, walk through the items until found or until
   one item is collated after the requested item. In the former case, return
   the result. In the latter case, recursively proceed to the subnodes.
   If 'end of dir' is reached, the name is not in the directory */
int ntfs_getdir_iterate(ntfs_inode *ino,char* entry,
	int type,long long *pos,const char *name,char *result)
{
	int length;
	int curpos=0;
	int destpos;
	int retval;
	if(type==BY_POSITION && *pos!=0)
	{	if(is_top(*pos))return 0;
		destpos=pop(pos);
	}
	do{
		if(type==BY_POSITION)
		{
			if(*pos==0)
			{
				if(entry_has_subnodes(entry))
					ntfs_getdir_descend(ino,entry,type,pos,name,result);
				else
					*pos=top();
				if(is_top(*pos) && !entry_is_used(entry))
				{
					return 1;
				}
				*pos=push(*pos,curpos);
				return 1;
			}
			if(curpos==destpos)
			{
				if(!is_top(*pos) && entry_has_subnodes(entry))
				{
					retval=ntfs_getdir_descend(ino,entry,type,pos,name,result);
					if(retval){
						*pos=push(*pos,curpos);
						return retval;
					}else{
						if(!entry_is_used(entry))
							return 0;
						*pos=0;
					}
				}
				if(entry_is_used(entry))
				{
					retval=ntfs_getdir_copyresult(result,entry);
					*pos=0;
				}else{
					*pos=top();
					return 0;
				}
			}
			curpos++;
		}else/*BY_NAME*/
		{
			if(entry_is_used(entry))
			switch(my_strcmp(ino->vol,entry,name,*pos))
			{
				case -1:	break;
				case  0:	return ntfs_getdir_copyresult(result,entry);
				case  1:	return entry_has_subnodes(entry)?
					ntfs_getdir_descend(ino,entry,type,pos,name,result):0;
			}else
				return entry_has_subnodes(entry)?
					ntfs_getdir_descend(ino,entry,type,pos,name,result):0;
		}
		if(!entry_is_used(entry))break;
		length=*(short*)(entry+8);
		if(!length){
			ntfs_error("infinite loop\n");
			break;
		}
		entry+=length;
	}while(1);
	return retval;
}

/* Find an entry in the directory. Return 0 if not found, otherwise copy
   the entry to the result buffer. */
int ntfs_getdir(ntfs_inode *ino,int type,long long *pos,const char* name,char *result)
{
	int length=ino->vol->mft_recordsize;
	int retval;
	/* start at the index root.*/
	char *root=ntfs_malloc(length);
	/* The first 0x20 bytes of the attribute are skipped. I don't know
	   what they are used for. */
	if(ntfs_read_attr(ino,AT_INDEX_ROOT,"$I30",
		0x20,root,length,ntfs_memcpy)<=0)
	{
		ntfs_error("Not a directory\n");
		return 0;
	}
	retval = ntfs_getdir_iterate(ino,root,type,pos,name,result);
	ntfs_free(root);
	return retval;
}

/* Find an entry in the directory by its position stack. Iteration starts
   if the stack is 0, in which case the position is set to the first item
   in the directory. If the position is nonzero, return the item at the
   position and change the position to the next item. The position is -1
   if there are no more items */
int ntfs_getdir_byposition(ntfs_inode* ino,long long *position,char *result)
{
	return ntfs_getdir(ino,BY_POSITION,position,NULL,result);
}

/* Find an entry in the directory by its name. Return 0 if not found */
int ntfs_getdir_byname(ntfs_inode *ino,const char *name,int namelen,char *result)
{
	long long len=namelen;
	return ntfs_getdir(ino,BY_NAME,&len,name,result);
}
