/* This file is part of 'minixfs' Copyright 1991,1992,1993 S.N.Henson */

#include "minixfs.h"
#include "proto.h"
#include "global.h"

/* Inode routines */

/* Read an inode from the cache , if the filesystem is V1 convert to V2
 * first , this greatly simplifies matters as other routines can then deal
 * exclusively with V2 inodes.
 */

int read_inode(num,rip,drv)
unsigned num;
d_inode *rip;
int drv;
{
	bufr *tmp;
	super_info *psblk=super_ptr[drv];
	num-=1;
	tmp=cget_block(num/psblk->ipb+psblk->ioff,drv,&icache);
	if(psblk->version) *rip=tmp->binode[num%psblk->ipb];
	else
	{
		d_inode1 *oldrip;
		int i;
		oldrip=&tmp->binode1[num%psblk->ipb];
		/* Convert V1 inode to V2 */
		rip->i_mode=oldrip->i_mode;
		rip->i_nlinks=oldrip->i_nlinks;
		rip->i_uid=oldrip->i_uid;
		rip->i_gid=oldrip->i_gid;
		rip->i_size=oldrip->i_size;
		rip->i_atime=oldrip->i_mtime;
		rip->i_mtime=oldrip->i_mtime;
		rip->i_ctime=oldrip->i_mtime;
		for(i=0;i< NR_ZONE_NUMS;i++) rip->i_zone[i]=oldrip->i_zone[i];
		rip->i_zone[NR_ZONE_NUMS]=0;
	}
	return(0);
}

/* Write out an inode , assuming it is V2. If the filesystem is V1 it converts
 * the supplied inode first.
 */

int write_inode(num,rip,drv)
unsigned num;
d_inode *rip;
int drv;
{
	cache *tmp;
	super_info *psblk=super_ptr[drv];
	num-=1;
	tmp=cache_get(num/psblk->ipb+psblk->ioff,drv,&icache,NOGUESS);
	if(psblk->version) tmp->buffer->binode[num%psblk->ipb]=*rip;
	else
	{
		d_inode1 *oldrip;
		int i;
		oldrip=&tmp->buffer->binode1[num%psblk->ipb];
		/* Convert V2 inode to V1 */
		oldrip->i_mode=rip->i_mode;
		oldrip->i_nlinks=rip->i_nlinks;
		oldrip->i_uid=rip->i_uid;
		oldrip->i_gid=rip->i_gid;
		oldrip->i_size=rip->i_size;
		oldrip->i_mtime=rip->i_mtime;
		for(i=0;i<NR_ZONE_NUMS;i++) oldrip->i_zone[i]=rip->i_zone[i];
	}
	tmp->status=2;
	return(0);
}

/* 'get' an inode from the cache, return a pointer to the inode and
 * if the pointer 'flag' is non-zero, return a pointer to the 'status' 
 * flag as well. Need two versions for this as well.
 */

d_inode1 *get_inode1(inum,drive,flag,guess)
unsigned inum;
int drive;
int **flag;
cache **guess;
{
	cache *tmp;
	super_info *psblk=super_ptr[drive];
	inum-=1;
	tmp=cache_get((inum>>L_IPB)+psblk->ioff,drive,&icache,guess);
	if(flag) *flag=&tmp->status;

	return &tmp->buffer->binode1[inum & (INODES_PER_BLOCK-1)] ;
}

d_inode *get_inode2(inum,drive,flag,guess)
unsigned inum;
int drive;
int **flag;
cache **guess;
{
	cache *tmp;
	super_info *psblk=super_ptr[drive];
	inum-=1;
	tmp=cache_get((inum>>L_IPB2)+psblk->ioff,drive,&icache,guess);

	if(flag) *flag=&tmp->status;

	return &tmp->buffer->binode[inum & (INODES_PER_BLOCK2-1)] ;
}

/* Truncate an inode to 'count' zones, this is used by unlink() as well as
 * (f)truncate() . Bit tricky this , we have to note which blocks to free,
 * and free indirection/double indirection blocks too but iff all the blocks
 * inside them are free too. We also need to keep count of how much to leave 
 * alone , sparse files complicate this a bit .... so do 2 fs versions ....
 */

void trunc_inode(rip,drive,count,zap)
d_inode *rip;	
int drive;
long count;			/* number of blocks to leave */
int zap;			/* flag to alter inode */
{
	int i,j;
	bufr *tmp;
	char some,dirty;
	super_info *psblk=super_ptr[drive];
	char vers;
	cache_control *control = (IS_DIR((*rip))||IS_SYM((*rip))) ? &syscache : &usrcache;
	cache *p, *q, *guess = control->start;
	vers=psblk->version;
	/* Handle zones in inode first */
	if(count<psblk->dzpi)
	{
		for(i=count;i<psblk->dzpi;i++) {
			if(rip->i_zone[i]) {
			/* remove zones from cache too so they can't end up
			   in both ones when later reallocated... also may
			   save a few needless writes.	-nox */
				if( (p=in_cache(rip->i_zone[i],drive,control,&guess)) )
					p->status=0;
				free_zone(rip->i_zone[i],drive);
			}
			if(zap)rip->i_zone[i]=0;
		}
		count=0;
	}
	else count-=psblk->dzpi;
/* Handle indirect zone */
	if(count< psblk->zpind) {
		some=0;
		dirty=0;
		if(rip->i_zone[7]) {
			tmp=(q=cache_get(rip->i_zone[7],drive,&syscache,NOGUESS))->buffer;
			for(i=0;i<psblk->zpind;i++) {
				if(PIND(vers,tmp,i)) {
					if(count)some=1;
					else {
						if( (p=in_cache(PIND(vers,tmp,i),drive,control,&guess)) )
							p->status=0;
						free_zone(PIND(vers,tmp,i),drive);
						if(zap)PIND(vers,tmp,i)=0;
						dirty=1;
					}
				}	
				if(count)count--;
			}
			if(!some) {
				q->status=0;
				free_zone(rip->i_zone[7],drive);
				if(zap)rip->i_zone[7]=0;
			}
			else if(dirty) 
#if 1
			/* a bit faster :) */
				q->status=2;
#else
				write_zone(rip->i_zone[7],tmp,drive,&syscache);
#endif
		}
	}
	else count-=psblk->zpind;
	/* Handle double indirect ... */
	if (count < (long) psblk->zpind * psblk->zpind) {
	    if(rip->i_zone[8]) {
		some=0;
		dirty=0;
		read_zone(rip->i_zone[8],&temp,drive,&syscache);
		for(i=0;i<psblk->zpind;i++) {
			if(IND(vers,temp,i)) {
			    char lsome,ldirty; /* local some,dirty for inds */
			    lsome=0;
			    ldirty=0;
			    tmp=(q=cache_get(IND(vers,temp,i),drive,&syscache,NOGUESS))->buffer;
			    for(j=0;j<psblk->zpind;j++) {
				if(PIND(vers,tmp,j)) {
					if(count) { 
						some=1;
						lsome=1;
					}
					else {
						if( (p=in_cache(PIND(vers,tmp,j),drive,control,&guess)) )
							p->status=0;
						free_zone(PIND(vers,tmp,j),drive);
						if(zap)PIND(vers,tmp,j)=0;
						ldirty=1;
					}
				}
				if(count)count--;
			    }
			    if(!lsome) {
				q->status=0;
				free_zone(IND(vers,temp,i),drive);
				if(zap)IND(vers,temp,i)=0;
				dirty=1;
			    }
			    else if(ldirty)
#if 1
				q->status=2;
#else
				write_zone(IND(vers,temp,i),tmp,drive,&syscache);
#endif
			}
			else 
			{
				if(count>=psblk->zpind)count-=psblk->zpind;
				else count=0;
			}
		}
		if(!some) {
			if( (p=in_cache(rip->i_zone[8],drive,&syscache,NOGUESS)) )
				p->status=0;
			free_zone(rip->i_zone[8],drive);
			if(zap)rip->i_zone[8]=0;
		}
		else if(dirty)write_zone(rip->i_zone[8],&temp,drive,&syscache);
	    }
	}
	else
	    count -= (long) psblk->zpind * psblk->zpind;
	/* Handle triple indirect ... */
	if (rip->i_zone[9])
	  {
	    some = 0;
	    dirty = 0;
	    read_zone (rip->i_zone[9], &temp, drive, &syscache);
	    for (i = 0; i < psblk->zpind; i++)
	      {
		if (IND (vers, temp, i))
		  {
		    char lsome, ldirty; /* local some, dirty for inds */
		    lsome = 0;
		    ldirty = 0;
		    q = cache_get (IND (vers, temp, i), drive, &syscache,
				   NOGUESS);
		    tmp = q->buffer;
		    for (j = 0; j < psblk->zpind; j++)
		      {
			if (PIND (vers, tmp, j))
			  {
			    char lsome1, ldirty1;
			    int k;
			    bufr *tmp2;
			    cache *r;
			    lsome1 = 0;
			    ldirty1 = 0;
			    r = cache_get (PIND (vers, tmp, j), drive,
					   &syscache, NOGUESS);
			    tmp2 = q->buffer;
			    for (k = 0; k < psblk->zpind; k++)
			      {
				if (PIND (vers, tmp2, k))
				  {
				    if (count)
				      {
					some = 1;
					lsome = 1;
					lsome1 = 1;
				      }
				    else
				      {
					p = in_cache (PIND (vers, tmp2, k),
						      drive, control, &guess);
					if (p)
					  p->status = 0;
					free_zone (PIND (vers, tmp2, k),
						   drive);
					if (zap)
					  PIND (vers, tmp2, k) = 0;
					ldirty1 = 1;
				      }
				  }
				if (count)
				  count--;
			      }
			    if (!lsome1)
			      {
				r->status = 0;
				free_zone (PIND (vers, tmp, j), drive);
				if (zap)
				  PIND (vers, tmp, j) = 0;
				ldirty = 1;
			      }
			    else if (ldirty1)
			      r->status = 2;
			  }
		      }
		    if (!lsome)
		      {
			q->status = 0;
			free_zone (IND (vers, temp, i), drive);
			if (zap)
			  IND (vers, temp, i) = 0;
			dirty = 1;
		      }
		    else if (ldirty)
		      q->status = 2;
		  }
		else 
		  {
		    if (count >= psblk->zpind)
		      count -= psblk->zpind;
		    else
		      count = 0;
		  }
	      }
	    if (!some)
	      {
		p = in_cache (rip->i_zone[9], drive, &syscache, NOGUESS);
		if (p)
		  p->status = 0;
		free_zone (rip->i_zone[9], drive);
		if (zap)
		  rip->i_zone[9] = 0;
	      }
	    else if (dirty)
	      write_zone (rip->i_zone[9], &temp, drive, &syscache);
	  }
}

/* Inode version of (f)truncate , truncates a file to size 'length'
 */

long itruncate(inum,drive,length)
unsigned inum;
int drive;
long length;
{
	long count;
	d_inode rip;
	FILEPTR *p;

	read_inode(inum,&rip,drive);
	/* Regulars only , clever directory compaction stuff later ... */
	if(!IS_REG(rip))return EACCDN;
	/* If file smaller than 'length' nothing to do */
	if(rip.i_size <= length)
	{
#if 0
		rip.i_size=length;
#endif
		return 0;
	}
	count=(length+1023)/1024;
	/* invalidate f_cache zones */
	for(p=firstptr;p;p=p->next)
	    if((drive==p->fc.dev) && (inum==p->fc.index) &&
	       ((f_cache *) p->devinfo)->lzone > count)
		      ((f_cache *) p->devinfo)->lzone = 0;
	trunc_inode(&rip,drive,count,1);
	rip.i_size=length;	
	write_inode(inum,&rip,drive);
	if(cache_mode) l_sync();
	return 0;
}


