/* Minixfs version 0.60 , Copyright S.N. Henson 1991,1992,1993
 * see the file 'copying' for more info.
 */

/* Define all global variables */
#define EXTERN /**/

#include "minixfs.h"
#include "proto.h"
#include "global.h"
#include "xhdi.h"
#include "pun.h"
#include <basepage.h>
#include <mintbind.h>
#include <signal.h>

/* This rubbish ensures that str(XXX) gets expanded then stringifyed */
#define str(x) _stringify(x)
#define _stringify(x) #x

/* Initialisation routine called first by the kernel */

extern FILESYS minix_filesys;

typedef long (*lfptr)();

lfptr old_rwabs,old_getbpb,old_mediach;

unsigned long shadmap;

extern long kludge_rwabs(),kludge_getbpb(),kludge_mediach(),setstack();

long _stksize=1024l;	/* Update stacksize */

FILESYS *
minix_init(k)
	struct kerinfo *k;
{
	extern int init_icd();
	unsigned long p2,vdrvs;
	int i;

	kernel = k;
	CCONWS("Minix file system driver for MiNT. Version " 
	str(MFS_MAJOR) "." str(MFS_MINOR) "\r\n"
	"Copyright 1991,1992,1993,1994,1995 S.N.Henson\r\n");
#ifdef MFS_PLEV
	CCONWS("Patch Level " str(MFS_PLEV) "\r\n");
#endif
#ifdef PRE_RELEASE
	CCONWS("Pre-Release Test Version\r\n");
#endif
	CCONWS("Please read the file COPYING for conditions of use.\r\n");

	if( (k->maj_version > 0) || (k->min_version >= 94) ) lockok=1;
	else CCONWS("File Locking not Installed , Use MiNT 0.94 or newer\r\n");

	if( (k->maj_version==0) && (k->min_version < 97) ) no_length=1;

	/* Allocate Cache */
	if(init_cache()) return NULL; 

	/* See if there are any shadow drives */

	vdrvs=*((unsigned long *)0x4c2);

	for(i=0,p2=1;i<NUM_DRIVES;i++,p2<<=1)
	{
		if(ppart[i].start !=-1 )
		{
			if(vdrvs & p2)
			ALERT("Minixfs: shadow drive %c: not installed: drive"
				" already exists!",i+'A');
			else
			{
				ALERT("Shadow Drive %c Installed",i+'A');
				ALERT("Start %ld Finish %ld Device %d",
				ppart[i].start,ppart[i].finish,ppart[i].shadow);
				shadmap |= p2;
			}
		}
	}

	*((unsigned long *)0x4c2) = vdrvs | shadmap;

	/* Initialise icd kludge */
	init_icd();

	if(shadmap) /* Any shadows installed ? */
	{
		/* Set new system vectors */

		old_mediach = *((lfptr *) 0x47e);
		old_rwabs = *((lfptr *) 0x476);
		old_getbpb = *((lfptr *) 0x472);

		*((lfptr *) 0x47e) = kludge_mediach;
		*((lfptr *) 0x476) = kludge_rwabs;
		*((lfptr *) 0x472) = kludge_getbpb;
	}

	switch(cache_mode)
	{
		case ROBUST:
		CCONWS("Robust mode\r\n");
		break;

		case NORMAL:
		CCONWS("Normal mode\r\n");
		break;

		case TURBO:
		CCONWS("TURBO mode\r\n");
		break;
	}

/* If we are in Turbo mode then run a backround updater.
 * This is a variant of the code used in 'thread.c' in mintlib
 * If we have Addroottimeout, use that.
 */

	if(cache_mode==TURBO)
	{
		BASEPAGE *b;

#ifdef SYSUPDATE
		if ((kernel->maj_version) && (kernel->min_version >= 12) &&
		    (kernel->dos_tab[0x150] != NULL) )
		{
			/* Do not start the update daemon, or use the root timeout
			 * method, as the system already has a Sync() system call,
			 * and hopefully a running update daemon.
			 */
		}
		else
#endif
		if(Addroottimeout) Addroottimeout(sync_time*1000l,t_sync,0);
		else
		{
		  b = (BASEPAGE *)p_exec(5, 0L, "", 0L);	/* create a basepage */

		  (void)m_shrink(0,b, 256 + _stksize);	/* Basepage + stack */

		  b->p_tbase = (char *)update;		/* text start = func to start */
		  b->p_hitpa = ((char *)b) + 256 + _stksize;

		  update_pid = (short) p_exec(104, "update", b, 0L);

		  /* run in backround */
		}
	}

	return &minix_filesys;
}

/* Note: update is a user level process and must act accordingly i.e. use
 * the syscalls not the dos/bios tables.
 */

int update(bp)
long bp;
{
#if 1
#define _base ((BASEPAGE *)(&init_addr)-1)
	extern long init_addr;
	((BASEPAGE *)bp)->p_tbase = _base->p_tbase;
	((BASEPAGE *)bp)->p_tlen = _base->p_tlen;
	((BASEPAGE *)bp)->p_dbase = _base->p_dbase;
	((BASEPAGE *)bp)->p_dlen = _base->p_dlen;
	((BASEPAGE *)bp)->p_bbase = _base->p_bbase;
	((BASEPAGE *)bp)->p_blen = _base->p_blen;
#endif

	setstack(bp+256+_stksize);

	/* Trap most signals */

	Psignal(SIGALRM,do_sync);
	Psignal(SIGTERM,do_sync);
	Psignal(SIGQUIT,do_sync);
	Psignal(SIGHUP,do_sync);
	Psignal(SIGTSTP,do_sync);
	Psignal(SIGINT,do_sync);

	for(;;)
	{
		int tsync;
		tsync=sync_time;
		while(tsync > 32 )
		{
			(void) Fselect(32000,0l,0l,0l);
			tsync-=32;
		}
		if(tsync > 0) (void) Fselect(tsync*1000l,0l,0l,0l);
		do_sync(0);
	}
}

char mfspath[]="A:";

void do_sync(signum)
long signum;
{
	int i;
	if(update_suspend) return;
	for(i=0;i<NUM_DRIVES;i++)
	{
		if(super_ptr[i] && super_ptr[i]!=DFS)
		{
			mfspath[0]='A'+i;
			Dcntl(MFS_SYNC,mfspath,0);
			return;
		}
	}
}

/* Much cleaner method using addroottimeout(), this is now at kernel
 * level and so can call l_sync() safely, it also schedules the next
 * sync()
 */

void t_sync()
{
	TRACE("Minixfs: doing t_sync()");
	if(!update_suspend) l_sync();
	Addroottimeout(1000l*sync_time,t_sync,0);
}

/* Sanity checker, checks a filesystem is minix and then sets up all internal
 * structures accordingly, e.g. superblock and directory increment. Returns
 * '1' if the filesystem is minix, 0 otherwise. Uses get_hddinf to try all
 * possible means to access the drive.
 */

int
minix_sanity(drv)
int drv;
{
	int i;
	d_inode rip;
	struct hdinfo *dsk;
	char is_phys;
	long err;
	unsigned long drv_mask;
	super_info *psblk;

	drv_mask = 1l<<drv;

	if(!( *((unsigned long *)0x4c2) & drv_mask ))
	{
		DEBUG("Invalid drive %c:",drv+'A');
		return 0;
	}

	dsk=&disk[drv];
	psblk=Kmalloc(sizeof(super_info));
	if(!psblk)
	{
		ALERT("Minixfs: No memory for super_info structure");
		return 0;
	}

	psblk->dev=drv;

	if(ppart[drv].start!=-1) /* Is this a physical partition ? */
	{
		struct phys_part *pp;
		pp=&ppart[drv];
		dsk->start=pp->start;
		dsk->size=pp->finish-dsk->start+1;
		dsk->scsiz=pp->scsiz;
		dsk->major = pp->shadow;
		dsk->drive = drv;
		(void)GETBPB(drv); 	/* Satisfy disk changes */
		dsk->rwmode = RW_PHYS;
		if(dsk->start > 0xfff0)
		{
			if(no_plrecno(dsk->major))
			{
				ALERT("Cannot access physical parition %c:",drv+'A');
				Kfree(psblk);
				return 0;
			}
			dsk->rwmode |= RW_LRECNO;
		}
		is_phys=1;
	}
	else
	{
	    is_phys=0;
	    if( (err=get_hddinf(drv,dsk,0)) )
	    {
		DEBUG("Cannot access partition %c: %s.",drv+'A',hdd_err[err]);
		Kfree(psblk);
		return 0;
	    }
	}

	super_ptr[drv]=psblk;

	crwabs(2,&temp,1,SUPER_BLOCK,drv);

	psblk->sblk=*((super_block *)&temp);

	if(( (psblk->sblk.s_magic==SUPER_MAGIC) || 
	     (psblk->sblk.s_magic==SUPER_V2) ||
	     (psblk->sblk.s_magic==SUPER_V1_30)	)
		&& psblk->sblk.s_ninodes) {
		if(psblk->sblk.s_log_zsize) {
		DEBUG("Cannot read Drive %c Zone-size > Block-size",drv+'A');
			super_ptr[drv]=DFS;
			Kfree(psblk);
			return -1;
		}
		if(psblk->sblk.s_magic==SUPER_V2)
		{
			TRACE("Drive %c V2 filesystem",drv+'A');
			psblk->version=1;
			psblk->ipb=INODES_PER_BLOCK2;
			psblk->zpind=NR_INDIRECTS2;
			psblk->dzpi=NR_DZONE_NUM2;
			psblk->ndbl=NR_DBL2;

			/* Check if lrecno needed/supported */

			if(set_lrecno(dsk,psblk->sblk.s_zones))
			{
				ALERT("No way to access large partition %c:",drv+'A');
				Kfree(psblk);
				super_ptr[drv]=DFS;
				return -1;
			}
		}
		else
		{
			TRACE("Drive %c V1 filesyetem",drv+'A');
			psblk->version=0;
			psblk->ipb=INODES_PER_BLOCK;
			psblk->zpind=NR_INDIRECTS;
			psblk->sblk.s_zones=psblk->sblk.s_nzones;
			psblk->dzpi=NR_DZONE_NUM;
			psblk->ndbl=NR_DBL;
		}

		psblk->ioff = psblk->sblk.s_imap_blks + psblk->sblk.s_zmap_blks
									 + 2 ;
		read_inode(ROOT_INODE,&rip,drv);
		if(IS_DIR(rip)) {
		  void *p;
		  int dot=-1,dotdot=-1;
#if 1
		  p=Kmalloc((unsigned long) (BLOCK_SIZE*((unsigned long) psblk->sblk.s_imap_blks+
			psblk->sblk.s_zmap_blks)));
#else
		  p=Kmalloc((unsigned) (BLOCK_SIZE*(psblk->sblk.s_imap_blks+
			psblk->sblk.s_zmap_blks)));
#endif
		  if(!p) {
			DEBUG("No room for bitmaps");
			Kfree(psblk);
			super_ptr[drv]=DFS;
			return -1;
		  }
		  psblk->ibitmap=p;
		  psblk->zbitmap=p+BLOCK_SIZE*psblk->sblk.s_imap_blks;
		  crwabs(2,p,psblk->sblk.s_imap_blks+psblk->sblk.s_zmap_blks
									,2,drv);
		  psblk->idirty=0;
		  psblk->zdirty=0;
		  psblk->zlast=0;
		  psblk->ilast=0;

		  /* Clear all mount flags */

		  psblk->mnt_first=0;
		  psblk->mnt_next=0;
		  psblk->mnt_inode=0;
		  psblk->mnt_flags=0;
		  psblk->mnt_dev=0;

/* Final step , read in the root directory zone 1 and check the '.' and '..'
 * spacing , The spacing between the '.' and '..' will be used as an indicator
 * of the directory entry size. If in doubt assume a normal minix filesystem.
 */

		  read_zone(rip.i_zone[0],&temp,drv,&syscache);

		  for(i=0;i<min(NR_DIR_ENTRIES,rip.i_size/DIR_ENTRY_SIZE);i++)
		  {
			if(temp.bdir[i].d_inum)
			{
				if(!strcmp(temp.bdir[i].d_name,"."))
				{
					if(dot==-1) dot=i;
					else
					{
						ALERT ("Drive %c multiple \".\" in root dir!!", drv + 'A');
						dot=-1;
						i=NR_DIR_ENTRIES;
					}
				}

				if(!strcmp(temp.bdir[i].d_name,".."))
				{
					if(dotdot==-1) dotdot=i;
					else
					{
						ALERT ("Drive %c multiple \"..\" in root directory", drv + 'A');
						dotdot=-1;
						i=NR_DIR_ENTRIES;
					}
				}
			}
		  }

		  if( (dotdot==-1) || (dot==-1) )
		  {
			ALERT("Drive %c no . or .. in root directory",drv+'A');
			Kfree(psblk->ibitmap);
			Kfree(psblk);
			super_ptr[drv]=DFS;
			return -1;
		  }
		  else psblk->increment=dotdot-dot;

		  if( (psblk->increment < 1) || NPOW2(psblk->increment))
		  {
			ALERT("Drive %c weird . .. positions",drv+'A');
			Kfree(psblk->ibitmap);
			Kfree(psblk);
			super_ptr[drv]=DFS;
			return -1;
		  }

		   if(psblk->increment > MAX_INCREMENT)
		  {
			ALERT("Drive %c Increment %d",drv+'A',psblk->increment);
			ALERT("This minix.xfs binary can only handle %d",MAX_INCREMENT);
			ALERT("Recompile with a higher MAX_INCREMENT");
			Kfree(psblk->ibitmap);
			Kfree(psblk);
			super_ptr[drv]=DFS;
			return -1;
		  }

		  return 1;

		}
		else {
			ALERT("root inode on drive %c is not a directory??",
				drv+'A');
			Kfree(psblk->ibitmap);
			Kfree(psblk);
			super_ptr[drv]=DFS;
			return -1;
		}
	}

	Kfree(psblk);

	if(is_phys)
	{
		ALERT("Physical partition %c: not minix!!",drv+'A');
		super_ptr[drv]=DFS;
		return -1;
	}

	super_ptr[drv]=0;
	return 0;
}
