/*
 *  inode.c
 *
 *  Copyright (C) 1995-1997 Martin von Löwis
 *  Copyright (C) 1996 Albert D. Cahalan
 *  Copyright (C) 1996-1997 Régis Duchesne
 */

#include "types.h"
#include "struct.h"
#include "inode.h"

#include <errno.h>
#include "macros.h"
#include "attr.h"
#include "super.h"
#include "dir.h"
#include "support.h"
#include "util.h"

typedef struct {
	int recno;
	unsigned char* record;
} ntfs_mft_record;

typedef struct {
	int size;
	int count;
	ntfs_mft_record* records;
} ntfs_disk_inode;

static void
fill_mft_header(ntfs_u8*mft,int record_size,int blocksize,
		int sequence_number)
{
	int fixup_count = record_size / blocksize + 1;
	int attr_offset = (0x2a + (2 * fixup_count) + 7) & ~7;
	int fixup_offset = 0x2a;

	NTFS_PUTU32(mft + 0x00, 0x454c4946);	     /* FILE */
	NTFS_PUTU16(mft + 0x04, 0x2a);		     /* offset to fixup */
	NTFS_PUTU16(mft + 0x06, fixup_count);	     /* Number of fixups */
	NTFS_PUTU16(mft + 0x10, sequence_number);
	NTFS_PUTU16(mft + 0x12, 1);                  /* hard link count */
	NTFS_PUTU16(mft + 0x14, attr_offset);	     /* Offset to attributes */
	NTFS_PUTU16(mft + 0x16, 1);                  /*FIXME: flags ?? */
	NTFS_PUTU32(mft + 0x18, attr_offset + 0x08);	/* In use */
	NTFS_PUTU32(mft + 0x1c, record_size);	     /* Total size */

	NTFS_PUTU16(mft + fixup_offset, 1);		/* Fixup word */
	NTFS_PUTU32(mft + attr_offset, 0xffffffff);	/* End marker */
}

/* Search in an inode an attribute by type and name */
ntfs_attribute* 
ntfs_find_attr(ntfs_inode *ino,int type,char *name)
{
	int i;
	if(!ino){
		ntfs_error("ntfs_find_attr: NO INODE!\n");
		return 0;
	}
	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,strlen(name))==0)
				return ino->attrs+i;
		}
		if(type<ino->attrs[i].type)
			return 0;
	}
	return 0;
}

/* FIXME: need better strategy to extend the MFT */
static int 
ntfs_extend_mft(ntfs_volume *vol)
{
	/* Try to allocate at least 0.1% of the remaining disk space
	   for inodes. If the disk is almost full, make sure at least one
	   inode is requested.
	 */
	int size,rcount,error,block;
	ntfs_attribute* mdata,*bmp;
	ntfs_u8 *buf;
	ntfs_io io;

	mdata=ntfs_find_attr(vol->mft_ino,vol->at_data,0);
	/* first check whether there is uninitialized space */
	if(mdata->allocated<mdata->size+vol->mft_recordsize){
		size=ntfs_get_free_cluster_count(vol->bitmap)*vol->clustersize;
		block=vol->mft_recordsize;
		size=max(size/1000,block);
		size=((size+block-1)/block)*block;
		/* require this to be a single chunk */
		error=ntfs_extend_attr(vol->mft_ino,mdata,&size,
				       ALLOC_REQUIRE_SIZE);
		/* Try again, now we have the largest available fragment */
		if(error==ENOSPC){
			/* round down to multiple of mft record size */
			size=(size/vol->mft_recordsize)*vol->mft_recordsize;
			if(!size)return ENOSPC;
			error=ntfs_extend_attr(vol->mft_ino,mdata,&size,
					       ALLOC_REQUIRE_SIZE);
		}
		if(error)
			return error;
	}
	/* even though we might have allocated more than needed,
	   we initialize only one record */
	mdata->size+=vol->mft_recordsize;
	
	/* now extend the bitmap if necessary*/
	rcount=mdata->size/vol->mft_recordsize;
	bmp=ntfs_find_attr(vol->mft_ino,vol->at_bitmap,0);
	if(bmp->size*8<rcount){ /* less bits than MFT records */
		ntfs_u8 buf[1];
		/* extend bitmap by one byte */
		error=ntfs_resize_attr(vol->mft_ino,bmp,bmp->size+1);
		if(error)return error;
		/* write the single byte */
		buf[0]=0;
		io.fn_put=ntfs_put;
		io.fn_get=ntfs_get;
		io.param=buf;
		io.size=1;
		error=ntfs_write_attr(vol->mft_ino,vol->at_bitmap,0,
				      bmp->size-1,&io);
		if(error)return error;
		if(io.size!=1)return EIO;
	}

	/* now fill in the MFT header for the new block */
	buf=ntfs_calloc(vol->mft_recordsize);
	if(!buf)return ENOMEM;
	fill_mft_header(buf,vol->mft_recordsize,vol->blocksize,0);
	ntfs_insert_fixups(buf,vol->blocksize);
	io.param=buf;
	io.size=vol->mft_recordsize;
	error=ntfs_write_attr(vol->mft_ino,vol->at_data,0,
			      (rcount-1)*vol->mft_recordsize,&io);
	if(error)return error;
	if(io.size!=vol->mft_recordsize)return EIO;
	return 0;
}

/* Insert all attributes from the record mftno of the MFT in the inode ino */
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;
	/* (re-)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 + NTFS_GETU16(mft + 0x14);
	do{
		type=NTFS_GETU32(it);
		len=NTFS_GETU32(it+4);
		if(type!=-1)
			ntfs_insert_attribute(ino,it);
		it+=len;
	}while(type!=-1); /* attribute list ends with type -1 */
}

/* Read and insert all the attributes of an 'attribute list' attribute
   Return the number of remaining bytes in *plen
*/
static int parse_attributes(ntfs_inode *ino, void *alist, int *plen)
{
	char *mft;
	int mftno,l,error;
	int last_mft=-1;
	int len=*plen;
	mft=ntfs_malloc(ino->vol->mft_recordsize);
	while(len>8)
	{
		l=NTFS_GETU16(alist+4);
		if(l>len)break;
	        /* process an attribute description */
		mftno=NTFS_GETU32(alist+0x10); /* BUG: this is u64 */
		if(mftno!=last_mft){
			last_mft=mftno;
			/* FIXME: avoid loading record if it's 
			   already processed */
			error=ntfs_read_mft_record(ino->vol,mftno,mft);
			if(error)return error;
			ntfs_insert_mft_attributes(ino,mft,mftno);
		}
		len-=l;
		alist+=l;
	}
	ntfs_free(mft);
	*plen=len;
	return 0;
}

static void ntfs_load_attributes(ntfs_inode* ino)
{
	ntfs_attribute *alist;
	int datasize;
	int offset,len,delta;
	char *buf;
	ntfs_volume *vol=ino->vol;
	ntfs_debug(DEBUG_OTHER, "load_attributes %x 1\n",ino->i_number);
	ntfs_insert_mft_attributes(ino,ino->attr,ino->i_number);
	ntfs_debug(DEBUG_OTHER, "load_attributes %x 2\n",ino->i_number);
	alist=ntfs_find_attr(ino,vol->at_attribute_list,0);
	ntfs_debug(DEBUG_OTHER, "load_attributes %x 3\n",ino->i_number);
	if(!alist)
		return;
	ntfs_debug(DEBUG_OTHER, "load_attributes %x 4\n",ino->i_number);
	datasize=alist->size;
	if(alist->resident)
	{
		parse_attributes(ino,alist->d.data,&datasize);
		return;
	}
	buf=ntfs_malloc(1024);
	delta=0;
	for(offset=0;datasize;datasize-=len)
	{
		ntfs_io io;
		io.fn_put=ntfs_put;
		io.fn_get=0;
		io.param=buf+delta;
		io.size=len=min(datasize,1024-delta);
		if(ntfs_read_attr(ino,vol->at_attribute_list,0,offset,&io)){
			ntfs_error("error in load_attributes\n");
		}
		delta=len;
		parse_attributes(ino,buf,&delta);
		if(delta)
			/* move remaining bytes to buffer start */
			ntfs_memmove(buf,buf+len-delta,delta);
	}
	ntfs_debug(DEBUG_OTHER, "load_attributes %x 5\n",ino->i_number);
	ntfs_free(buf);
}
	
int ntfs_init_inode(ntfs_inode *ino,ntfs_volume *vol,int inum)
{
	char *buf;
	int error;

	ntfs_debug(DEBUG_OTHER, "Initializing inode %x\n",inum);
	if(!vol)
		ntfs_error("NO VOLUME!\n");
	ino->i_number=inum;
	ino->vol=vol;
	ino->attr=buf=ntfs_malloc(vol->mft_recordsize);
	error=ntfs_read_mft_record(vol,inum,ino->attr);
	if(error){
		ntfs_debug(DEBUG_OTHER, "init inode: %x failed\n",inum);
		return error;
	}
	ntfs_debug(DEBUG_OTHER, "Init: got mft %x\n",inum);
	ino->sequence_number=NTFS_GETU16(buf+0x10);
	ino->attr_count=0;
	ino->record_count=0;
	ino->records=0;
	ino->attrs=0;
	ntfs_load_attributes(ino);
	ntfs_debug(DEBUG_OTHER, "Init: done %x\n",inum);
	return 0;
}

void ntfs_clear_inode(ntfs_inode *ino)
{
	int i;
	if(!ino->attr){
		ntfs_error("ntfs_clear_inode: double free\n");
		return;
	}
	ntfs_free(ino->attr);
	ino->attr=0;
	ntfs_free(ino->records);
	ino->records=0;
	for(i=0;i<ino->attr_count;i++)
	{
		if(ino->attrs[i].name)
			ntfs_free(ino->attrs[i].name);
		if(ino->attrs[i].resident)
		{
			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);
	ino->attrs=0;
}

/* Check and fixup a MFT record */
int ntfs_check_mft_record(ntfs_volume *vol,char *record)
{
	return ntfs_fixup_record(vol, record, "FILE", vol->mft_recordsize);
}

/* Return (in result) the value indicating the next available attribute 
   chunk number. Works for inodes w/o extension records only */
int ntfs_allocate_attr_number(ntfs_inode *ino, int *result)
{
	if(ino->record_count!=1)
		return EOPNOTSUPP;
	*result=NTFS_GETU16(ino->attr+0x28);
	NTFS_PUTU16(ino->attr+0x28, (*result)+1);
	return 0;
}

/* 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 + NTFS_GETU16(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=NTFS_GETU32(it);
		len=NTFS_GETU16(it+4);
		/* We found the attribute type. Is the name correct, too? */
		if(type==attr)
		{
			int namelen=NTFS_GETU8(it+9);
			char *name_it;
			/* match given name and attribute name if present,
			   make sure attribute name is Unicode */
			for(name_it=it+NTFS_GETU16(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;
}

int 
ntfs_get_attr_size(ntfs_inode*ino,int type,char*name)
{
	ntfs_attribute *attr=ntfs_find_attr(ino,type,name);
	if(!attr)return 0;
	return attr->size;
}
	
int 
ntfs_attr_is_resident(ntfs_inode*ino,int type,char*name)
{
	ntfs_attribute *attr=ntfs_find_attr(ino,type,name);
	if(!attr)return 0;
	return attr->resident;
}
	
/*
 * A run is coded as a type indicator, an unsigned length, and a signed cluster
 * offset.
 * . To save space, length and offset are fields of variable length. The low
 *   nibble of the type indicates the width of the length :), the high nibble
 *   the width of the offset.
 * . The first offset is relative to cluster 0, later offsets are relative to
 *   the previous cluster.
 *
 * This function decodes a run. Length is an output parameter, data and cluster
 * are in/out parameters.
 */
int ntfs_decompress_run(unsigned char **data, int *length, int *cluster,
	int *ctype)
{
	unsigned char type=*(*data)++;
	*ctype=0;
	switch(type & 0xF)
	{
	case 1: *length=NTFS_GETU8(*data);(*data)++;break;
	case 2: *length=NTFS_GETU16(*data);
		*data+=2;
		break;
	case 3: *length = NTFS_GETU24(*data);
		*data+=3;
		break;
		/* TODO: case 4-8 */
	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+=NTFS_GETS8(*data);(*data)++;break;
	case 0x20: *cluster+=NTFS_GETS16(*data);
		*data+=2;
		break;
	case 0x30: 	*cluster+=NTFS_GETS24(*data);
		*data+=3;
		break;
		/* TODO: case 0x40-0x80 */
	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 in the ntfs_io struct.
   Returns 0 on success, errno on failure */
int ntfs_readwrite_attr(ntfs_inode *ino, ntfs_attribute *attr, int offset,
	ntfs_io *dest)
{
	int datasize,rnum;
	int cluster,s_cluster,vcn,len,l,chunk,copied;
	int s_vcn;
	int clustersize;
	int error;

	clustersize=ino->vol->clustersize;
	datasize=attr->size;
	l=dest->size;
	if(dest->do_read)
	{
		if(offset>=datasize){
			dest->size=0;
			return 0;
		}
		if(offset+l>=datasize) 
			l=dest->size=datasize-offset;
	}else { /* fixed by CSA: if writing beyond end, extend attribute */
		if (offset+l>datasize) {
			error=ntfs_resize_attr(ino,attr,offset+l);
			if(error)
				return error;
		}
	}
	if(attr->resident)
	{
		if(dest->do_read)
			dest->fn_put(dest,attr->d.data+offset,l);
		else
		{
			dest->fn_get(attr->d.data+offset,dest,l);
			ntfs_update_inode(ino);
		}
		dest->size=l;
		return 0;
	}
	if(attr->compressed)
		if(dest->do_read)
			return ntfs_read_compressed(ino,attr,offset,dest);
		else
			return ntfs_write_compressed(ino,attr,offset,dest);
	vcn=0;
	s_vcn = offset/clustersize;
	for(rnum=0;rnum<attr->d.r.len && 
		    vcn+attr->d.r.runlist[rnum].len<=s_vcn;rnum++)
		vcn+=attr->d.r.runlist[rnum].len;
	if(rnum==attr->d.r.len)
		/*FIXME: should extend runlist */
		return EOPNOTSUPP;
	
	copied=0;
	while(l)
	{
		s_vcn = offset/clustersize;
		cluster=attr->d.r.runlist[rnum].cluster;
		len=attr->d.r.runlist[rnum].len;

		s_cluster = cluster+s_vcn-vcn;
			
		chunk=min((vcn+len)*clustersize-offset,l);
		dest->size=chunk;
		error=ntfs_getput_clusters(ino->vol,s_cluster,
					   offset-s_vcn*clustersize,dest);
		if(error)/* FIXME: maybe return failure */
		{
			ntfs_error("Read error\n");
			dest->size=copied;
			return 0;
		}
		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;
		}
	}
	dest->size=copied;
	return 0;
}

int ntfs_read_attr(ntfs_inode *ino, int type, char *name, int offset,
        ntfs_io *buf)
{
	ntfs_attribute *attr;
	buf->do_read=1;
	attr=ntfs_find_attr(ino,type,name);
	if(!attr)
		return EINVAL;
	return ntfs_readwrite_attr(ino,attr,offset,buf);
}

int ntfs_write_attr(ntfs_inode *ino, int type, char *name, int offset,
		ntfs_io *buf)
{
	ntfs_attribute *attr;
	buf->do_read=0;
	attr=ntfs_find_attr(ino,type,name);
	if(!attr)
		return EINVAL;
	return ntfs_readwrite_attr(ino,attr,offset,buf);
}

int ntfs_vcn_to_lcn(ntfs_inode *ino,int vcn)
{
	int rnum;
	ntfs_attribute *data=ntfs_find_attr(ino,ino->vol->at_data,0);
	/* It's hard to give an error code */
	if(!data)return -1;
	if(data->resident)return -1;
	if(data->compressed)return -1;
	if(data->size<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;
}

static int 
allocate_store(ntfs_volume *vol,ntfs_disk_inode *store,int count)
{
	int i;
	if(store->count>count)
		return 0;
	if(store->size<count){
		ntfs_mft_record* n=ntfs_malloc((count+4)*sizeof(ntfs_mft_record));
		if(!n)
			return ENOMEM;
		if(store->size){
			for(i=0;i<store->size;i++)
				n[i]=store->records[i];
			ntfs_free(store->records);
		}
		store->size=count+4;
		store->records=n;
	}
	for(i=store->count;i<count;i++){
		store->records[i].record=ntfs_malloc(vol->mft_recordsize);
		if(!store->records[i].record)
			return ENOMEM;
		store->count++;
	}
	return 0;
}

static void 
deallocate_store(ntfs_disk_inode* store)
{
	int i;
	for(i=0;i<store->count;i++)
		ntfs_free(store->records[i].record);
	ntfs_free(store->records);
	store->count=store->size=0;
	store->records=0;
}

int 
layout_runs(ntfs_attribute *attr,char* rec,int* offs,int size)
{
	int i,cluster,rclus,len,offset,coffs;
	ntfs_runlist *rl=attr->d.r.runlist;
	cluster=0;
	offset=*offs;
	for(i=0;i<attr->d.r.len;i++){
		rclus=rl[i].cluster-cluster;
		len=rl[i].len;
		rec[offset]=0;
		if(offset+8>size)
			return E2BIG; /* it might still fit, but this simplifies testing */
		if(len<0x100){
			*(rec+offset)|=1;
			NTFS_PUTU8(rec+offset+1,len);
			coffs=2;
		}else if(len<0x10000){
			*(rec+offset)|=2;
			NTFS_PUTU16(rec+offset+1,len);
			coffs=3;
		}else if(len<0x1000000){
			*(rec+offset)|=3;
			NTFS_PUTU24(rec+offset+1,len);
			coffs=4;
		}else{
			*(rec+offset)|=4;
			NTFS_PUTU32(rec+offset+1,len);
			coffs=5;
		}
    
		if(rl[i].cluster==0) /*compressed run*/
			/*nothing*/;
		else if(rclus>-0x80 && rclus<0x7F){
			*(rec+offset)|=0x10;
			NTFS_PUTS8(rec+offset+coffs,rclus);
			coffs+=1;
		}else if(rclus>-0x8000 && rclus<0x7FFF){
			*(rec+offset)|=0x20;
			NTFS_PUTS16(rec+offset+coffs,rclus);
			coffs+=2;
		}else if(rclus>-0x800000 && rclus<0x7FFFFF){
			*(rec+offset)|=0x30;
			NTFS_PUTS24(rec+offset+coffs,rclus);
			coffs+=3;
		}else{
			*(rec+offset)|=0x40;
			NTFS_PUTS32(rec+offset+coffs,rclus);
			coffs+=4;
		}
		offset+=coffs;
		if(rl[i].cluster)
			cluster=rl[i].cluster;
	}
	*offs=offset;
	return 0;
}

static void 
count_runs(ntfs_attribute *attr,char *buf)
{
	int first,count,last,i;
	first=0;
	for(i=0,count=0;i<attr->d.r.len;i++)
		count+=attr->d.r.runlist[i].len;
	last=first+count-1;

	NTFS_PUTU32(buf+0x10,first);
	NTFS_PUTU32(buf+0x18,last);
} 

static int
layout_attr(ntfs_attribute* attr,char*buf, int size,int *psize)
{
	int asize,error;
	if(size<10)return E2BIG;
	NTFS_PUTU32(buf,attr->type);
	/* fill in length later */
	NTFS_PUTU8(buf+8,attr->resident ? 0:1);
	NTFS_PUTU8(buf+9,attr->namelen);
	/* fill in offset to name later */
	NTFS_PUTU16(buf+0xA,0);
	NTFS_PUTU16(buf+0xC,attr->compressed);
	/* FIXME: assign attribute ID??? */
	NTFS_PUTU16(buf+0xE,attr->attrno);
	if(attr->resident){
		if(size<attr->size+0x18+attr->namelen)return E2BIG;
		asize=0x18;
		NTFS_PUTU32(buf+0x10,attr->size);
		NTFS_PUTU16(buf+0x16,attr->indexed);
		if(attr->name){
			ntfs_memcpy(buf+asize,attr->name,2*attr->namelen);
			NTFS_PUTU16(buf+0xA,asize);
			asize+=2*attr->namelen;
			asize=(asize+7) & ~7;
		}
		NTFS_PUTU16(buf+0x14,asize);
		ntfs_memcpy(buf+asize,attr->d.data,attr->size);
		asize+=attr->size;
	}else{
		/* FIXME: fragments */
		count_runs(attr,buf);
		/* offset to data is added later */
		NTFS_PUTU16(buf+0x22,attr->cengine);
		NTFS_PUTU32(buf+0x24,0);
		NTFS_PUTU64(buf+0x28,attr->allocated);
		NTFS_PUTU64(buf+0x30,attr->size);
		NTFS_PUTU64(buf+0x38,attr->initialized);
		if(attr->compressed){
			NTFS_PUTU64(buf+0x40,attr->compsize);
			asize=0x48;
		}else
			asize=0x40;
		if(attr->name){
			NTFS_PUTU16(buf+0xA,asize);
			ntfs_memcpy(buf+asize,attr->name,2*attr->namelen);
			asize+=2*attr->namelen;
			asize=(asize+7) & ~7;
		}
		/* asize points at the beginning of the data */
		NTFS_PUTU16(buf+0x20,asize);
		error=layout_runs(attr,buf,&asize,size);
		/* now asize pointes at the end of the data */
		if(error)
			return error;
	}
	asize=(asize+7) & ~7;
	NTFS_PUTU32(buf+4,asize);
	*psize=asize;
	return 0;
}
		
		

/* Try to layout ino into store. Return 0 on success,
   E2BIG if it does not fit, 
   ENOMEM if memory allocation problem,
   EOPNOTSUP if beyound our capabilities 
*/
int 
layout_inode(ntfs_inode *ino,ntfs_disk_inode *store)
{
	int offset,i;
	ntfs_attribute *attr;
	unsigned char *rec;
	int size,psize;
	int error;

	if(ino->record_count>1)
	{
		ntfs_error("layout_inode: attribute lists not supported\n");
		return EOPNOTSUPP;
	}
	error=allocate_store(ino->vol,store,1);
	if(error)
		return error;
	rec=store->records[0].record;
	size=ino->vol->mft_recordsize;
	store->records[0].recno=ino->records[0];
	/* copy header */
	offset=NTFS_GETU16(ino->attr+0x14);
	ntfs_memcpy(rec,ino->attr,offset);
	for(i=0;i<ino->attr_count;i++){
		attr=ino->attrs+i;
		error=layout_attr(attr,rec+offset,size-offset,&psize);
		if(error)return error;
		offset+=psize;
#if 0
		/* copy attribute header */
		ntfs_memcpy(rec+offset,attr->header,
			    min(sizeof(attr->header),size-offset)); /* consider overrun */
		if(attr->namelen)
			/* named attributes are added later */
			return EOPNOTSUPP;
		/* FIXME: assign attribute ID??? */
		if(attr->resident){
			asize=attr->size;
			aoffset=NTFS_GETU16(rec+offset+0x14);
			if(offset+aoffset+asize>size)
				return E2BIG;
			ntfs_memcpy(rec+offset+aoffset,attr->d.data,asize);
			next=offset+aoffset+asize;
		}else{
			count_runs(attr,rec+offset);
			aoffset=NTFS_GETU16(rec+offset+0x20);
			next=offset+aoffset;
			error=layout_runs(attr,rec,&next,size);
			if(error)
				return error;
		}
		next=(next+7) & ~7; /* align to DWORD */
		NTFS_PUTU16(rec+offset+4,next-offset);
		offset=next;
#endif
	}
	/* terminating attribute */
	if(offset+8<size){
		NTFS_PUTU32(rec+offset,0xFFFFFFFF);
		offset+=4;
		NTFS_PUTU32(rec+offset,0);
		offset+=4;
	}else
		return E2BIG;
	NTFS_PUTU32(rec+0x18,offset);
	return 0;
}
  
int ntfs_update_inode(ntfs_inode *ino)
{
	int error;
	ntfs_disk_inode store;
	ntfs_io io;
	int i;

	store.count=store.size=0;
	store.records=0;
	error=layout_inode(ino,&store);
	if(error==E2BIG){
		/* should try:
		   make attributes non-resident
		   introduce extension records
		   */
		ntfs_error("cannot handle saving inode %x\n",ino->i_number);
		deallocate_store(&store);
		return EOPNOTSUPP;
	}
	if(error){
		deallocate_store(&store);
		return error;
	}
	io.fn_get=ntfs_get;
	io.fn_put=0;
	for(i=0;i<store.count;i++){
		ntfs_insert_fixups(store.records[i].record,ino->vol->blocksize);
		io.param=store.records[i].record;
		io.size=ino->vol->mft_recordsize;
		/* FIXME: is this the right way? */
		error=ntfs_write_attr(
			ino->vol->mft_ino,ino->vol->at_data,0,
			store.records[i].recno*ino->vol->mft_recordsize,&io);
		if(error || io.size!=ino->vol->mft_recordsize){
			/* big trouble, partially written file */
			ntfs_error("Please unmount: write error in inode %x\n",ino->i_number);
			deallocate_store(&store);
			return error?error:EIO;
		}
	}
	return 0;
}

void ntfs_decompress(unsigned char *dest, unsigned char *src, ntfs_size_t l)
{
	int head,comp;
	int copied=0;
	unsigned char *stop;
	int bits;
	int tag=0;
	int clear_pos;
	while(1)
	{
		head = NTFS_GETU16(src) & 0xFFF;
		/* high bit indicates that compression was performed */
		comp = NTFS_GETU16(src) & 0x8000;
		src += 2;
		stop = src+head;
		bits = 0;
		clear_pos=0;
		if(head==0)
			/* block is not used */
			return;/* FIXME: copied */
		if(!comp) /* uncompressible */
		{
			ntfs_memcpy(dest,src,0x1000);
			dest+=0x1000;
			copied+=0x1000;
			src+=0x1000;
			if(l==copied)
				return;
			continue;
		}
		while(src<=stop)
		{
			if(clear_pos>4096)
			{
				ntfs_error("Error 1 in decompress\n");
				return;
			}
			if(!bits){
				tag=NTFS_GETU8(src);
				bits=8;
				src++;
				if(src>stop)
					break;
			}
			if(tag & 1){
				int i,len,delta,code,lmask,dshift;
				code = NTFS_GETU16(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++]=NTFS_GETU8(src);
				src++;
				copied++;
				if(copied==l)
					return;
			}
			tag>>=1;
			bits--;
		}
		dest+=clear_pos;
	}
}

/* Caveat: No range checking in either ntfs_set_bit or ntfs_clear_bit */
void 
ntfs_set_bit (unsigned char *byte, int bit)
{
	byte += (bit >> 3);
	bit &= 7;
	*byte |= (1 << bit);
}

void 
ntfs_clear_bit (unsigned char *byte, int bit)
{
	byte += (bit >> 3);
	bit &= 7;
	*byte &= ~(1 << bit);
}

/* We have to skip the 16 metafiles and the 8 reserved entries */
static int 
new_inode (ntfs_volume* vol,int* result)
{
	int byte,error;
	int bit;
	int size,length;
	unsigned char value;
	ntfs_u8 *buffer;
	ntfs_io io;
	ntfs_attribute *data;

	buffer=ntfs_malloc(2048);
	if(!buffer)return ENOMEM;
	io.fn_put=ntfs_put;
	io.fn_get=ntfs_get;
	io.param=buffer;
	/* FIXME: bitmaps larger than 2048 bytes */
	io.size=2048;
	error=ntfs_read_attr(vol->mft_ino,vol->at_bitmap,0,0,&io);
	if(error){
		ntfs_free(buffer);
		return error;
	}
	size=io.size;
	data=ntfs_find_attr(vol->mft_ino,vol->at_data,0);
	length=data->size/vol->mft_recordsize;

	for (byte = 3; 8*byte < length; byte++)
	{
		value = buffer[byte];
		if(value==0xFF)
			continue;
		for (bit = 0; (bit < 8) && (8*byte+bit<length); 
		     bit++, value >>= 1)
		{
			if (!(value & 1)){
				*result=byte*8+bit;
				return 0;
			}
		}
	}
	/* There is no free space.  We must first extend the MFT. */
	return ENOSPC;
}

static int 
add_mft_header (ntfs_inode *ino)
{
	unsigned char* mft;
	ntfs_volume *vol=ino->vol;
	mft=ino->attr;

	ntfs_bzero(mft, vol->mft_recordsize);
	fill_mft_header(mft,vol->mft_recordsize,vol->blocksize,
			ino->sequence_number);
	return 0;
}

/* We need 0x48 bytes in total */
static int 
add_standard_information (ntfs_inode *ino)
{
	ntfs_time64_t now;
	char data[0x30];
	char *position=data;
	int error;
	ntfs_attribute *si;

	now = ntfs_now();
	NTFS_PUTU64(position + 0x00, now);		/* File creation */
	NTFS_PUTU64(position + 0x08, now);		/* Last modification */
	NTFS_PUTU64(position + 0x10, now);		/* Last mod for MFT */
	NTFS_PUTU64(position + 0x18, now);		/* Last access */

	NTFS_PUTU64(position + 0x20, 0x00);		/* MSDOS file perms */
	NTFS_PUTU64(position + 0x28, 0);               /* unknown */
	error=ntfs_create_attr(ino,ino->vol->at_standard_information,0,
			       data,sizeof(data),&si);

	return error;
}

static int 
add_filename (ntfs_inode* ino, ntfs_inode* dir, 
			 const unsigned char *filename, int length)
{
	unsigned char   *position;
	unsigned int    size;
	ntfs_time64_t   now;
	int		count;
	int error;
	unsigned char* data;
	ntfs_attribute *fn;

	/* work out the size */
	size = 0x42 + 2 * length;
	data = ntfs_malloc(size);
	ntfs_bzero(data,size);

	/* search for a position */
	position = data;

	NTFS_PUTINUM(position, dir);	/* Inode num of dir */

	now = ntfs_now();
	NTFS_PUTU64(position + 0x08, now);		/* File creation */
	NTFS_PUTU64(position + 0x10, now);		/* Last modification */
	NTFS_PUTU64(position + 0x18, now);		/* Last mod for MFT */
	NTFS_PUTU64(position + 0x20, now);		/* Last access */

	/* Don't know */
	NTFS_PUTU8(position+0x38, 0x0);  /*should match standard attributes*/

	NTFS_PUTU8(position + 0x40, length);	      /* Filename length */
	NTFS_PUTU8(position + 0x41, 0x0);	      /* only long name */

	position += 0x42;
	for (count = 0; count < length; count++)
	{
		NTFS_PUTU16(position + 2 * count, filename[count]);
	}

	error=ntfs_create_attr(ino,ino->vol->at_file_name,0,data,size,&fn);
	if(!error)
		error=ntfs_dir_add(dir,ino,fn);
	ntfs_free(data);
	return error;
}

int 
add_security (ntfs_inode* ino, ntfs_inode* dir)
{
	int error;
	char *buf;
	int size;
	ntfs_attribute* attr;
	ntfs_io io;
	ntfs_attribute *se;

	attr=ntfs_find_attr(dir,ino->vol->at_security_descriptor,0);
	if(!attr)
		return EOPNOTSUPP; /* need security in directory */
	size = attr->size;
	if(size>512)
		return EOPNOTSUPP;
	buf=ntfs_malloc(size);
	if(!buf)
		return ENOMEM;
	io.fn_get=ntfs_get;
	io.fn_put=ntfs_put;
	io.param=buf;
	io.size=size;
	error=ntfs_read_attr(dir,ino->vol->at_security_descriptor,0,0,&io);
	if(!error && io.size!=size)ntfs_error("wrong size in add_security");
	if(error){
		ntfs_free(buf);
		return error;
	}
	/* FIXME: consider ACL inheritance */
	error=ntfs_create_attr(ino,ino->vol->at_security_descriptor,
			       0,buf,size,&se);
	ntfs_free(buf);
	return error;
}

static int 
add_data (ntfs_inode* ino, unsigned char *data, int length)
{
	int error;
	ntfs_attribute *da;
	error=ntfs_create_attr(ino,ino->vol->at_data,0,data,length,&da);
	return error;
}


/* We _could_ use 'dir' to help optimise inode allocation */
int ntfs_alloc_inode (ntfs_inode *dir, ntfs_inode *result, char *filename,
		int namelen)
{
	ntfs_io io;
	int error;
	ntfs_u8 buffer[1];
	ntfs_volume* vol=dir->vol;
	int byte,bit;

	error=new_inode (vol,&(result->i_number));
	if(error==ENOSPC){
		error=ntfs_extend_mft(vol);
		if(error)return error;
		error=new_inode(vol,&(result->i_number));
	}
	if(error){
		ntfs_error ("ntfs_get_empty_inode: no free inodes\n");
		return error;
	}
	byte=result->i_number/8;
	bit=result->i_number & 7;

	io.fn_put = ntfs_put;
	io.fn_get = ntfs_get;
	io.param = buffer;
	io.size=1;
	/* set a single bit */
	error=ntfs_read_attr(vol->mft_ino,vol->at_bitmap,0,byte,&io);
	if(error)return error;
	if(io.size!=1)
		return EIO;
	ntfs_set_bit (buffer, bit);
	io.param = buffer;
	io.size = 1;
 	error = ntfs_write_attr (vol->mft_ino, vol->at_bitmap, 0, byte, &io);
	if(error)return error;
	if (io.size != 1)
		return EIO;
	/*FIXME: Should change MFT on disk
	  error=ntfs_update_inode(vol->mft_ino);
	  if(error)return error;
	  */
	/* get the sequence number */
	io.param = buffer;
	io.size = 0x10;
	error = ntfs_read_attr(vol->mft_ino, vol->at_data, 0, 
			       result->i_number*vol->mft_recordsize+0x10,&io);
	if(error)
		return error;
	result->sequence_number=NTFS_GETU16(buffer)+1;
	result->vol=vol;
	result->attr=ntfs_malloc(vol->mft_recordsize);
	result->attr_count=0;
	result->attrs=0;
	result->record_count=1;
	result->records=ntfs_malloc(8*sizeof(int));
	result->records[0]=result->i_number;
	error=add_mft_header(result);
	if(error)
		return error;
	error=add_standard_information(result);
	if(error)
		return error;
	error=add_filename(result,dir,filename,namelen);
	if(error)
		return error;
	error=add_security(result,dir);
	/*FIXME: check error */
	error=add_data(result,0,0);
	if(error)
		return error;
	return 0;
}

/*
 * Local variables:
 * c-file-style: "linux"
 * End:
 */
