/*
 *	Copyright (C) 1988 Andrew Kemmis
 *
 *	All Rights Reserved
 *
 *	Date:	Jan 1988
 */
#include <ak/stdak.h>
#include <ak/debug.h>

char	Program_name[]	= "Val";
int	Version 	= 2;
int	MinorVer	= 3;
char	Date[]		= "Dec 1993";
char	CRDate[]	= "1988-93";

#include <ak/err.h>
#include <ak/pool.h>
#include <ak/dev.h>
#include <ak/dos.h>
#include <intuition/intuitionbase.h>
#include "val.h"

#include "val.qi"

#define ULONGS(l)       ((ulong)(l)*(ulong)sizeof(ulong))
#define ZAlloc(a)       PoolAlloc((a), sizeof(*(a)), &GenPoolKey)
#define LAlloc(a,n,z)   {       if (!Seek)\
					PoolAlloc((a), ULONGS(n), &GenPoolKey);\
				else\
					(char *)(a) = AllocMem(ULONGS(n), 0L);\
			}

#define ULONGMAX	4294967295

extern	char	*strchr();
extern	char	*strrchr();
extern	int	strlen();
extern	int	strcmp();
extern	int	sscanf();

int	cli;

struct	IBASE	*IBASE = (struct IBASE *)NULL;

Static	int	FileSystem;

#define IS_OFS	0
#define IS_FFS	1

Static	char	StrRoot[] = "Root";
Static	char	StrBitMap[] = "BitMap";
Static	char	StrEtc[] = "Dir,Fil,Lnk";
Static	char	StrDir[] = "Dir";
Static	char	StrFil[] = "Fil";
Static	char	StrExt[] = "Ext";
Static	char	StrDat[] = "Dat";
Static	char	StrSoftLnk[] = "SoftLnk";
Static	char	StrDirLnk[] = "DirLnk";
Static	char	StrFilLnk[] = "FilLnk";
Static	char	StrNextD[] = "NextDat";
Static	char	StrChain[] = "HashChain";
Static	char	StrNext[] = "Next";
Static	char	StrPrev[] = "Prev";
Static	char	StrHuh[] = "Unknown";
Static	struct	AkDevStuff	Stuff = DEVSTUFFINIT;
Static	uchar	*Buffer;
Static	long	Dev = -1;
Static	struct	Names	RootName =
	{	(struct Names *)NULL,   (struct Names *)NULL,   ULONGMAX, };
Static	struct	Links
{	ushort	type;
	ulong	block;
	ulong	prev;
	ulong	next;
	struct	Links	*another;
} *LinkHead, *LinkMaybe;
Static	uchar	*FName;
Static	int	Inhibited = 0;

Static	struct	PoolKey BufPoolKey;
Static	struct	PoolKey GenPoolKey;
Static	struct	PoolKey ZerPoolKey;

Static	ulong	ReadCount = 0;
Static	struct	Info	*LastInfo;
Static	ulong	LastBlk;
Static	uchar	*MyBitMap;

Static	ulong	ReadSmall = 0;
Static	ulong	ReadLarge = 0;
Static	ulong	ReadLNum = 0;
Static	ulong	HitSmall = 0;
Static	ulong	HitLarge = 0;
Static	ulong	MicSta;
Static	ulong	MicFin;
Static	ulong	MicCode;
Static	ulong	MicRead;

#define BMAP_TST(n)      (MyBitMap[(n)>>3]&(1<<((n)&7)))
#define BMAP_SET(n)      (MyBitMap[(n)>>3]|=(1<<((n)&7)))

Static Copy(d, s, l, siz)
REG	char	*d;
REG	char	*s;
REG	ulong	l;
REG	int	siz;
{
	REG	ulong  len = l * (ulong)siz;

DBGS(">C");

	while (len--)
		*(d++) = *(s++);

DBGS("<C");
}

Static Get(Rem, num)
REG	struct	Info	*Rem;
REG	ulong	num;
{
DBGS(">G");

	if ((Rem - InfoList) >= MaxDepth)
		MyError(ERR_TOODEEP, MaxDepth);

	getblok(num + Stuff.Info.Begin, Rem);

DBGS("<G");
}

Static LONG DevRead(num, buf, size)
REG	ulong	num;
REG	ulong	*buf;
REG	LONG	size;
{
	REG	LONG	ret;
	REG	int	retry = Retries + 1;

DBGS(">DR");

	while (retry-- && (ret = AkDevRead(num, buf, &Stuff, size)))
		;

	if (ret && FailReq)
	{	if (!akrequester("Disk Read Error", "Continue", "Abort"))
		{	puts("Aborted at users request on Read Error");
			Cleanup();
		}
	}

DBGS("<DR");

	return(ret);
}

Static	int	DoCache = !0;

#define CACHE_INVALID	-7

Static LONG CacheRead(num, buf)
REG	ulong	num;
REG	ulong	**buf;
{
	static	ulong	cache0 = CACHE_INVALID;
	static	ulong	cache1 = CACHE_INVALID;
	static	ulong	cache2 = CACHE_INVALID;
	REG	LONG	ret = 0;

DBGS(">CR");

	*buf = (ulong *)Buffer;

	if (Cache == 1)
		ret = DevRead(num, *buf, BLOCK_SIZE);
	else
	{	if (num == cache0 || num == (cache0 + 1) || (cache1 <= num && num <= cache2))
		{	if (StatCache)
				MicSta = IBASE->Micros;

			if (num == (cache0+1))
				*buf = (ulong *)&Buffer[BLOCK_SIZE];
			else
			if (num != cache0)
				*buf = (ulong *)&Buffer[BLOCK_SIZE*(2 + num - cache1)];

			if (StatCache)
				MicFin = IBASE->Micros;

			if (StatCache)
			{	if (num == cache0 || num == (cache0+1))
					HitSmall++;
				else
					HitLarge++;

				if (MicSta > MicFin)
					MicFin += 1000000;
				MicCode += (MicFin - MicSta);
			}
			if (DebugCache)
			{	if (num == cache0)
					printf("Hit at :0\n");
				else
				if (num == (cache0+1))
					printf("Hit at :1\n");
				else
					printf("Hit at %lu\n", 2 + num - cache1);
			}
		}
		else
		{	if (DebugCache)
			{	if (DoCache && num <= (Stuff.Info.End - 2))
					printf("Do  Cache (miss %ld %ld)\n", num-cache0, num-cache1);
				else
					printf("Non Cache (miss %ld %ld)\n", num-cache0, num-cache1);
			}

			if (StatCache)
				MicSta = IBASE->Micros;

			if (DoCache && num <= (Stuff.Info.End - 2))     /* Must read 3 Blocks! */
			{	cache0 = num;
				cache1 = num + 2;
				cache2 = num + Cache - 1;
				if (cache2 > Stuff.Info.End)
					cache2 = Stuff.Info.End;
				ret = DevRead(num, *buf, BLOCK_SIZE*(1 + cache2 - cache0));
				if (ret)
					cache0 = cache1 = cache2 = CACHE_INVALID;
			}
			else
			{	if (num != Stuff.Info.End)
					cache0 = num;
				else
				{	cache0 = num - 1;
					*buf = (ulong *)&Buffer[BLOCK_SIZE];
				}

				ret = DevRead(num, (ulong *)Buffer, BLOCK_SIZE*2);
				if (ret)
					cache0 = CACHE_INVALID;
			}

			if (StatCache)
				MicFin = IBASE->Micros;

			if (StatCache)
			{	if (DoCache && num <= (Stuff.Info.End - 2))
				{	ReadLarge++;
					ReadLNum += (1 + cache2 - cache0);
				}
				else
					ReadSmall++;

				if (MicSta > MicFin)
					MicFin += 1000000;
				MicRead += (MicFin - MicSta);
			}
		}
	}
DBGS("<CR");
	return(ret);
}

Static char *StrType(type)
REG	ushort	type;
{
	REG	char	*ans;

	switch(type)
	{
	case I_ROOT:
		ans = StrRoot;
		break;
	case I_DIR:
		ans = StrDir;
		break;
	case I_FIL:
		ans = StrFil;
		break;
	case I_EXT:
		ans = StrExt;
		break;
	case I_DAT:
		ans = StrDat;
		break;
	case I_S_LNK:
		ans = StrSoftLnk;
		break;
	case I_H_LNK_DIR:
		ans = StrDirLnk;
		break;
	case I_H_LNK_FIL:
		ans = StrFilLnk;
		break;
/*
	case I_UNK:
	case I_UNREADABLE:
*/
	default:
		ans = StrHuh;
		break;
	}
	return(ans);
}

Static	int	BaD = 0;
Static	int	WaR = 0;
Static	ulong	SeqNum;
Static	ulong	PrevD = 0;
Static	ulong	NextD = 0;

#define B_TYP		0
#define B_KEY		1
#define B_CHKSUM	2
#define B_BMAP		3
#define B_PAR		4
#define B_COUNT 	5
#define B_FIRST 	6
#define B_BYTESIZE	7
#define B_HED		8
#define B_RANGE 	9
#define B_SEQ		10
#define B_SHORT 	11
#define B_BMFLG 	12
#define B_HTSIZ 	13
#define B_BITMAP	14
#define B_NEXTD 	15
#define B_BADNAME	16
#define B_UNR		17
#define B_BLOCKSIZE	18
#define B_USED		19
#define B_USEDBY	20

#define B_PREVLINK	22
#define B_NEXTLINK	23
#define B_OFSLINK	24
#define B_NOPREVLINK	25
#define B_PREVINV	26
#define B_NOTPREV	27
#define B_PREVDIF	28
#define B_LINKTYPERR	29
#define B_XNOTLINK	30

Static bad(Rem, num, err, typ, ex1, ex2, ex3)
struct	Info	*Rem;
REG	ulong	num;
int	err;
REG	char	*typ;
REG	ulong	ex1;
ulong	ex2;
ulong	ex3;
{
	int	idx;
	ulong	blk;
	REG	struct	Info	*CurRem;
	REG	struct	Names	*CurName;
	ulong	*Buf = (ulong *)Buffer;
	int	seeked = 0;

DBGS(">b");

	switch(err)
	{
	case B_BMFLG:
	case B_BITMAP:
	case B_HTSIZ:
	case B_BMAP:
	case B_BLOCKSIZE:
	case B_BYTESIZE:
	case B_OFSLINK:
		WaR++;
		printf("War: B %lu %s ", num, typ);
		break;
	default:
		BaD++;
		printf("Err: B %lu %s ", num, typ);
		break;
	}

	if (Rem && DosNames && num != Stuff.Info.Root)
	{	CurRem = LastInfo;
		if (Seek)
			idx = CurRem - InfoList;
		else
			for (idx=0; CurRem != &InfoList[Stuff.Info.Root]; idx++)
				CurRem = &InfoList[CurRem->Obj.dir->parent];
					/* *** parent in same pos */

		CurName = &RootName;
		while (idx--)
		{	if (CurName->next == (struct Names *)NULL)
			{
DEBUGMEM_NAME("A CurName");
				ZAlloc(CurName->next);
				CurName->next->prev = CurName;
				CurName->next->next = (struct Names *)NULL;
				CurName->next->block = ULONGMAX;
			}
			CurName = CurName->next;
		}
		if (CurName->next)
			CurName->next->block = ULONGMAX;

		CurRem = LastInfo;
		blk = LastBlk;
		while (CurName)
		{	if (CurName->block != blk)
			{	CurName->block = blk;
				if (!Seek && !seeked)
				{	AkMotorOn(&Stuff);
					seeked = !0;
				}
				if (CacheRead(blk + Stuff.Info.Begin, &Buf))
					FName = (uchar *)"\011***Bad***";
				else
					FName = (uchar *)(&Buf[POS_NAME]);
				if (*FName >= sizeof(RootName.name))
					*FName = sizeof(RootName.name) - 1;
				Copy(CurName->name, FName, (long)((*FName) + 1), sizeof(char));
			}
			if (Seek)
				blk = (CurRem--)->Obj.dir->parent;
					/* *** parent in same pos, blk not used if Root */
			else
				blk = InfoList[blk].Obj.dir->parent;
					/* *** parent in same pos, blk not used if Root  */

			CurName = CurName->prev;
		}
		if (seeked)
			AkMotorOff(&Stuff);

		CurName = &RootName;
		while (CurName && CurName->block != ULONGMAX)
		{	if (CurName->prev)
			{	if (CurName->prev == &RootName)
					putchar(':');
				else
					putchar('/');
			}
			if (*CurName->name)
				printf("%.*s", (int)(*(CurName->name)), (CurName->name)+1);
			CurName = CurName->next;
		}
		putchar(' ');
	}

	switch(err)
	{
	case B_TYP:
		printf("Block type wrong (par = %lu)", ex1);
		break;
	case B_KEY:
		printf("Key wrong (par = %lu)", ex1);
		break;
	case B_CHKSUM:
		printf("Checksum wrong (par = %lu)", ex1);
		break;
	case B_BMAP:
		printf("No Bitmap blocks present!");
		break;
	case B_PAR:
		printf("Parent pointer (%lu) != parent block (%lu)", ex1, ex2);
		break;
	case B_COUNT:
		printf("Block Count incorrect (par = %lu)", ex1);
		break;
	case B_FIRST:
		printf("FirstData incorrect (should be = %lu)", ex1);
		break;
	case B_BYTESIZE:
		printf("Bytesize incorrect (calc = %lu actual = %lu)", ex1, ex2);
		break;
	case B_HED:
		printf("Header pointer (%lu) != header block (%lu)", ex1, ex2);
		break;
	case B_RANGE:
		printf("%s pointer (%lu) out of disk range!", ex1, ex2);
		break;
	case B_SEQ:
		printf("Sequence number wrong (should be %lu, is %lu)", ex1, ex2);
		break;
	case B_SHORT:
		printf("Data block not full (only allowed in last data block!)");
		break;
	case B_BMFLG:
		printf("Bitmap Flag says Bitmap is invalid!");
		break;
	case B_HTSIZ:
		printf("Hash table size != calculated size!");
		break;
	case B_BITMAP:
		printf("Block type NOT unknown type!");
		break;
	case B_NEXTD:
		printf("Next data block (%lu) != Previous data block's (%lu) Next data block (%lu)",
			ex1, ex2, ex3);
		break;
	case B_BADNAME:
		printf("Blocks name is invalid (BSTR size is 0 or > %d)", sizeof(RootName.name) - 1);
		break;
	case B_UNR:
		printf("Gave error (0x%lx) on read (par = %lu)", ex1, ex2);
		break;
	case B_BLOCKSIZE:
		printf("Total blocks incorrect (calc = %lu actual = %lu)", ex1, ex2);
		break;
	case B_USED:
		if (ex1)
			printf("Pointed to by %lu ", ex1);
		printf("Already used (FFS Error)");
		break;
	case B_USEDBY:
		printf("Pointed to by %lu Already used by %lu (FFS Error)", ex1, ex2);
		break;
	case B_PREVLINK:
		printf("Prev Link Pointer not 0");
		break;
	case B_NEXTLINK:
		printf("Next Link Pointer not 0");
		break;
	case B_OFSLINK:
		printf("Contains a link (but partition is OFS)");
		break;
	case B_NOPREVLINK:
		printf("Prev Link Pointer should not be 0");
		break;
	case B_PREVINV:
		printf("Prev (%lu) Must be Fil or Dir (not %s)", ex1, ex2);
		break;
	case B_NOTPREV:
		printf("Next's (%lu) Prev (%lu) should be This", ex1, ex2);
		break;
	case B_PREVDIF:
		printf("Next's (%lu) Prev (%lu) != This Prev (%lu)", ex3, ex2, ex1);
		break;
	case B_LINKTYPERR:
		printf("%s Pointer's type (%s) not valid for this type", ex1, ex2);
		break;
	case B_XNOTLINK:
		printf("%s (%lu) is not Linked", ex1, ex2);
		break;
	}
	putchar('\n');

	if (ErrLimit >= 0 && BaD > ErrLimit)
	{	printf("Validation aborted ... Error Limit (%ld) Exceeded\n", ErrLimit);
		Cleanup();
	}

	if (WarnLimit >= 0 && WaR > WarnLimit)
	{	printf("Validation aborted ... Warning Limit (%ld) Exceeded\n", WarnLimit);
		Cleanup();
	}

DBGS("<b");
}

Static CompareLinkType(prev_is_head, prev, next)
REG	int	prev_is_head;
REG	ushort	prev;
REG	ushort	next;
{
	int	ret = FALSE;

	if (!prev_is_head)
	{	if (prev == I_H_LNK_DIR && next == I_H_LNK_DIR)
			ret = TRUE;

		if (prev == I_H_LNK_FIL && next == I_H_LNK_FIL)
			ret = TRUE;
	}
	else
	{	if (prev == I_DIR && next == I_H_LNK_DIR)
			ret = TRUE;

		if (prev == I_FIL && next == I_H_LNK_FIL)
			ret = TRUE;
	}

	return(ret);
}

/* OK this is really quite slow - but I don't have many links! */
Static CheckLinks()
{
	REG	struct	Links	*LinkTmp;
	REG	struct	Links	*LinkTmp2;
	REG	ulong	next;
	REG	ulong	prev;
	REG	ulong	block;
	ushort	type;
	int	got_prev;
	int	got_next;

	LinkTmp = LinkHead;
	while (LinkTmp)
	{	next = LinkTmp->next;
		prev = LinkTmp->prev;
		block = LinkTmp->block;
		type = LinkTmp->type;
		got_prev = !prev;
		got_next = !next;

		LinkTmp2 = LinkHead;
		while (LinkTmp2)
		{	if (prev && prev == LinkTmp2->block)
			{	got_prev = !0;
				if (LinkTmp2->type != I_DIR && LinkTmp2->type != I_FIL)
					bad(NULL, block, B_PREVINV, StrType(type), prev, StrType(LinkTmp2->type));

				if (!CompareLinkType(!0, LinkTmp2->type, type))
					bad(NULL, block, B_LINKTYPERR, StrType(type), StrPrev, StrType(LinkTmp2->type));
			}
			if (next && next == LinkTmp2->block)
			{	got_next = !0;
				if (!prev && LinkTmp2->prev != block)
					bad(NULL, block, B_NOTPREV, StrType(type), next, LinkTmp2->prev);

				if (prev && LinkTmp2->prev != prev)
					bad(NULL, block, B_PREVDIF, StrType(type), prev, LinkTmp2->prev, next);

				if (!CompareLinkType(prev == 0, type, LinkTmp2->type))
					bad(NULL, block, B_LINKTYPERR, StrType(type), StrNext, StrType(LinkTmp2->type));
			}
			LinkTmp2 = LinkTmp2->another;
		}
		if (!got_prev)
			bad(NULL, block, B_XNOTLINK, StrType(type), StrPrev, prev);
		if (!got_next)
			bad(NULL, block, B_XNOTLINK, StrType(type), StrNext, next);
		LinkTmp = LinkTmp->another;
	}
}

Static MoveMaybe(block)
REG	ulong	block;
{
	REG	struct	Links	*LinkTmpPrev = NULL;
	REG	struct	Links	*LinkTmp = LinkMaybe;

	while (LinkTmp && LinkTmp->block != block)
	{	LinkTmpPrev = LinkTmp;
		LinkTmp = LinkTmp->another;
	}

	if (LinkTmp)
	{	if (LinkTmpPrev)
			LinkTmpPrev->another = LinkTmp->another;
		else
			LinkMaybe = LinkTmp->another;

		LinkTmp->another = LinkHead;
		LinkHead = LinkTmp;
	}
	else
	{	/* Huh? Shouldn't happen? */
		MyError(ERR_NOVAL, "Link Code Bug");
	}
}

Static AddLink(block, prev, next, type)
REG	ulong	block;
REG	ulong	prev;
REG	ulong	next;
REG	ushort	type;
{
	struct	Links	*LinkTmp;

DBGS(">AL");

DEBUGMEM_NAME("A LinkTmp");
	ZAlloc(LinkTmp);
	LinkTmp->type = type;
	LinkTmp->block = block - Stuff.Info.Begin;
	LinkTmp->prev = prev;
	LinkTmp->next = next;
	LinkTmp->another = LinkMaybe;
	LinkMaybe = LinkTmp;

DBGS("<AL");
}

Static getblok(num, Rem)
ulong	num;
REG	struct	Info	*Rem;
{
	REG	ulong  *Buf;
	REG	uchar  *obj;
	REG	ulong  i;
	REG	ulong  *l;
	REG	ulong  *l2;
	ulong	count;
	ushort	*flags;
	LONG	ret;
	ulong	*buf;

DBGS(">gb");

	if (ShowReads && (++ReadCount % Count) == 0)
	{	akdeco(ReadCount, 'l', NULL, NULL, NULL, ' ', ' ');
		akhexo(num, 'l', NULL, NULL, NULL, '\r', '0');
	}
	if (Tracks && (ReadCount++ % Stuff.Info.Cyl) == 0)
		akdeco(ReadCount / Stuff.Info.Cyl, 'l', NULL, NULL, NULL, '\r', ' ');

	ret = CacheRead(num, &buf);

	if (!Seek)
		Rem = &InfoList[num - Stuff.Info.Begin];

	flags = &(Rem->flags);
	*flags = 0;

	if (ret)
	{	Rem->type = I_UNREADABLE;
		Rem->Obj.ret = ret;
DBGS("<gb");
		return; 		/* N.B. */
	}

	Buf = buf;

	switch(Buf[POS_TYP])
	{
	case TYP_CONTROL:
		FName = (uchar *)(&Buf[POS_NAME]);
		if (*FName == 0 || *FName >= sizeof(RootName.name))
			*flags |= E_BADNAME;
		switch(Buf[POS_SEC])
		{
		case SEC_ROOT:
			Rem->type = I_ROOT;
			if (Buf[POS_KEY])
				*flags |= E_KEY;

/* Appears to be a date at POS_LINK_NEXT
			if (Buf[POS_LINK_PREV])
				*flags |= E_PREVLINK;
			if (Buf[POS_LINK_NEXT])
				*flags |= E_NEXTLINK;
*/
			if (!Buf[POS_BMFLG])
				*flags |= E_BMFLG;
			if (Buf[POS_HTSIZ] != (1L + POS_LST_BLK - POS_FST_BLK))
				*flags |= E_HTSIZ;
			if (Seek)
				(struct Root *)obj = Rem->Obj.root;
			else
			{
DEBUGMEM_NAME("Root");
				ZAlloc((struct Root *)obj);
				Rem->Obj.root = (struct Root *)obj;
			}
			for (count = 0, i = POS_FST_BLK, l = &Buf[i]; i <= POS_LST_BLK; l++, i++)
				if (*l)
					count++;
			if (count++)
			{
DEBUGMEM_NAME("Root Blk List");
				LAlloc(l, count, StrRoot);
				((struct Root *)obj)->blocks = l;
				for (i = POS_FST_BLK, l2 = &Buf[i]; i <= POS_LST_BLK; l2++, i++)
					if (*l2)
						*(l++) = *l2;
				*l = 0L;
			}
			else
				((struct Root *)obj)->blocks = (ulong *)NULL;

			for (i = POS_BBLK, l = &Buf[i]; *l && i < POS_BBLK_END; i++, l++)
				;
			count = i - POS_BBLK;
			if (*l)
				count++;
			if (count)
			{
DEBUGMEM_NAME("A BitMap Blk List");
				LAlloc(l, count+1, StrBitMap);
				Copy(l, &Buf[POS_BBLK], count, sizeof(ulong));
				((struct Root *)obj)->bblocks = l;
				l[count] = 0L;
			}
			else
				((struct Root *)obj)->bblocks = (ulong *)NULL;
			break;
		case SEC_DIR:
			Rem->type = I_DIR;
			if (Buf[POS_KEY] != (num - Stuff.Info.Begin))
				*flags |= E_KEY;
			if (Buf[POS_LINK_PREV])
				*flags |= E_PREVLINK;
			if (Buf[POS_LINK_NEXT])
			{	if (FileSystem == IS_OFS && !LinksAllowed)
					*flags |= E_OFSLINK;
				AddLink(num, 0L, Buf[POS_LINK_NEXT], I_DIR);
			}
			if (Seek)
				(struct Dir *)obj = Rem->Obj.dir;
			else
			{
DEBUGMEM_NAME("A Dir");
				ZAlloc((struct Dir *)obj);
				Rem->Obj.dir = (struct Dir *)obj;
			}
			for (count = 0, i = POS_FST_BLK, l = &Buf[i]; i <= POS_LST_BLK; l++, i++)
				if (*l)
					count++;
			if (count++)
			{
DEBUGMEM_NAME("A Dir Blk List");
				LAlloc(l, count, StrDir);
				((struct Dir *)obj)->blocks = l;
				for (i = POS_FST_BLK, l2 = &Buf[i]; i <= POS_LST_BLK; l2++, i++)
					if (*l2)
						*(l++) = *l2;
				*l = 0L;
			}
			else
				((struct Dir *)obj)->blocks = (ulong *)NULL;

			((struct Dir *)obj)->chain = Buf[POS_HASH_CH];
			((struct Dir *)obj)->parent = Buf[POS_PAR];
			((struct Dir *)obj)->next = Buf[POS_LINK_NEXT];
			break;
		case SEC_FIL:
			Rem->type = I_FIL;
			if (Buf[POS_KEY] != (num - Stuff.Info.Begin))
				*flags |= E_KEY;
			if (Buf[POS_LINK_PREV])
				*flags |= E_PREVLINK;
			if (Buf[POS_LINK_NEXT])
			{	if (FileSystem == IS_OFS && !LinksAllowed)
					*flags |= E_OFSLINK;
				AddLink(num, 0L, Buf[POS_LINK_NEXT], I_FIL);
			}
			if (Seek)
				(struct Fil *)obj = Rem->Obj.fil;
			else
			{
DEBUGMEM_NAME("A Fil");
				ZAlloc((struct Fil *)obj);
				Rem->Obj.fil = (struct Fil *)obj;
			}
			for (i=POS_LST_BLK, l = &Buf[i]; *l && i > POS_FST_BLK; i--, l--)
				;
			count = POS_LST_BLK - i;
			if (*l)
				count++;
			else
				l++;
			if (count != Buf[POS_NUM_BLK])
				*flags |= E_COUNT;
			((struct Fil *)obj)->num = count;
			if (count)
			{	if (Buf[POS_FST_DAT] != Buf[POS_LST_BLK])
					*flags |= E_FIRST;
DEBUGMEM_NAME("A Fil Blk List");
				LAlloc(l2, count, StrFil);
				Copy(l2, l, count, sizeof(ulong));
				((struct Fil *)obj)->blocks = l2;
			}
			else
			{	((struct Fil *)obj)->blocks = (ulong *)NULL;
				if (Buf[POS_FST_DAT])
					*flags |= E_FIRST;
			}
			((struct Fil *)obj)->bytesize = Buf[POS_F_BYTSIZ];
			((struct Fil *)obj)->chain = Buf[POS_HASH_CH];
			((struct Fil *)obj)->parent = Buf[POS_PAR];
			((struct Fil *)obj)->extension = Buf[POS_EXT];
			((struct Fil *)obj)->next = Buf[POS_LINK_NEXT];
			break;
		case SEC_S_LNK:
			Rem->type = I_S_LNK;
			if (Buf[POS_KEY] != (num - Stuff.Info.Begin))
				*flags |= E_KEY;
			if (Buf[POS_LINK_PREV])
				*flags |= E_PREVLINK;
			if (Buf[POS_LINK_NEXT])
				*flags |= E_NEXTLINK;
			if (FileSystem == IS_OFS && !LinksAllowed)
				*flags |= E_OFSLINK;
			if (Seek)
				(struct Lnk *)obj = Rem->Obj.lnk;
			else
			{
DEBUGMEM_NAME("A Lnk");
				ZAlloc((struct Lnk *)obj);
				Rem->Obj.lnk = (struct Lnk *)obj;
			}
			((struct Lnk *)obj)->chain = Buf[POS_HASH_CH];
			((struct Lnk *)obj)->parent = Buf[POS_PAR];
			break;
		case SEC_H_LNK_DIR:
			Rem->type = I_H_LNK_DIR;
			if (Buf[POS_KEY] != (num - Stuff.Info.Begin))
				*flags |= E_KEY;
			if (!Buf[POS_LINK_PREV])
				*flags |= E_PREVLINK;
			if (FileSystem == IS_OFS && !LinksAllowed)
				*flags |= E_OFSLINK;
			AddLink(num, Buf[POS_LINK_PREV], Buf[POS_LINK_NEXT], I_H_LNK_DIR);
			if (Seek)
				(struct DirLnk *)obj = Rem->Obj.dirlnk;
			else
			{
DEBUGMEM_NAME("A DirLnk");
				ZAlloc((struct DirLnk *)obj);
				Rem->Obj.dirlnk = (struct DirLnk *)obj;
			}
			((struct DirLnk *)obj)->prev = Buf[POS_LINK_PREV];
			((struct DirLnk *)obj)->next = Buf[POS_LINK_NEXT];
			((struct DirLnk *)obj)->chain = Buf[POS_HASH_CH];
			((struct DirLnk *)obj)->parent = Buf[POS_PAR];
			break;
		case SEC_H_LNK_FIL:
			Rem->type = I_H_LNK_FIL;
			if (Buf[POS_KEY] != (num - Stuff.Info.Begin))
				*flags |= E_KEY;
			if (!Buf[POS_LINK_PREV])
				*flags |= E_PREVLINK;
			if (FileSystem == IS_OFS && !LinksAllowed)
				*flags |= E_OFSLINK;
			AddLink(num, Buf[POS_LINK_PREV], Buf[POS_LINK_NEXT], I_H_LNK_FIL);
			if (Seek)
				(struct FilLnk *)obj = Rem->Obj.fillnk;
			else
			{
DEBUGMEM_NAME("A FilLnk");
				ZAlloc((struct FilLnk *)obj);
				Rem->Obj.fillnk = (struct FilLnk *)obj;
			}
			((struct FilLnk *)obj)->prev = Buf[POS_LINK_PREV];
			((struct FilLnk *)obj)->next = Buf[POS_LINK_NEXT];
			((struct FilLnk *)obj)->chain = Buf[POS_HASH_CH];
			((struct FilLnk *)obj)->parent = Buf[POS_PAR];
			break;
		default:
			Rem->type = I_UNK;
			break;
		}
		break;
	case TYP_EXT:
		Rem->type = I_EXT;
		if (Buf[POS_KEY] != (num - Stuff.Info.Begin))
			*flags |= E_KEY;
		if (Seek)
			(struct Ext *)obj = Rem->Obj.ext;
		else
		{
DEBUGMEM_NAME("An Ext");
			ZAlloc((struct Ext *)obj);
			Rem->Obj.ext = (struct Ext *)obj;
		}
		if (Buf[POS_FST_DAT])
			*flags |= E_FIRST;
		for (i=POS_LST_BLK, l = &Buf[i]; *l && i > POS_FST_BLK; i--, l--)
			;
		count = POS_LST_BLK - i;
		if (*l)
			count++;
		else
			l++;
		if (count != Buf[POS_NUM_BLK])
			*flags |= E_COUNT;
		((struct Ext *)obj)->num = count;
		if (count)
		{
DEBUGMEM_NAME("An Ext Blk List");
			LAlloc(l2, count, StrExt);
			Copy(l2, l, count, sizeof(ulong));
			((struct Ext *)obj)->blocks = l2;
		}
		else
			((struct Ext *)obj)->blocks = (ulong *)NULL;
		((struct Ext *)obj)->parent = Buf[POS_PAR];
		((struct Ext *)obj)->extension = Buf[POS_EXT];
		break;
	case TYP_DAT:
		Rem->type = I_DAT;
		if (Seek)
			(struct Dat *)obj = Rem->Obj.dat;
		else
		{
DEBUGMEM_NAME("A Dat");
			ZAlloc((struct Dat *)obj);
			Rem->Obj.dat = (struct Dat *)obj;
		}
		((struct Dat *)obj)->key = Buf[POS_KEY];
		((struct Dat *)obj)->seqnum = Buf[POS_D_SEQ_NUM];
		((struct Dat *)obj)->size = Buf[POS_D_DAT_SIZ];
		((struct Dat *)obj)->nextd = Buf[POS_D_NXT_DAT];
		if (((struct Dat *)obj)->nextd)
			if (((struct Dat *)obj)->size != (BLOCK_SIZE - (POS_FST_BLK << 2L)))
				*flags |= E_SHORT;
		break;
	default:
		Rem->type = I_UNK;
		break;
	}
	if (Rem->type != I_UNK && (Rem->type != I_DAT || FileSystem == IS_OFS))
		if (DosChecksum(Buf))
			*flags |= E_CHKSUM;
DBGS("<gb");
}

Static rchk(Rem, num, blk, typ, want)
REG	struct	Info	*Rem;
REG	ulong	num;
REG	ulong	blk;
REG	char	*typ;
REG	char	*want;
{
	if (blk < Stuff.Info.res || blk > (Stuff.Info.End - Stuff.Info.Begin))
	{	bad(Rem, num, B_RANGE, typ, want, blk);
		return(0);
	}
	return(!0);
}

Static int unreadable(Rem, num, Str, par)
REG	struct	Info	*Rem;
REG	ulong	num;
REG	char	*Str;
REG	ulong	par;
{
	if (Rem->type == I_UNREADABLE)
	{	bad(Rem, num, B_UNR, Str, Rem->Obj.ret, par);
		return(!0);
	}
	else
		return(0);
}

Static FreeSeek(Rem)
REG	struct	Info	*Rem;
{
	REG	ulong	*l;
	REG	ulong	count;

DBGS(">FS");

	if (Seek)
		switch(Rem->type)
		{
		case I_ROOT:
			l = Rem->Obj.root->bblocks;
			if (l)
			{	for (count = 1; *l; l++, count++)
					;
				FreeMem(Rem->Obj.root->bblocks, ULONGS(count));
			}
			l = Rem->Obj.root->blocks;
			if (l)
			{	for (count = 1; *l; l++, count++)
					;
				FreeMem(Rem->Obj.root->blocks, ULONGS(count));
			}
			break;
		case I_DIR:
			l = Rem->Obj.dir->blocks;
			if (l)
			{	for (count = 1; *l; l++, count++)
					;
				FreeMem(Rem->Obj.dir->blocks, ULONGS(count));
			}
			break;
		case I_FIL:
			if (Rem->Obj.fil->blocks)
				FreeMem(Rem->Obj.fil->blocks, ULONGS(Rem->Obj.fil->num));
			break;
		case I_EXT:
			if (Rem->Obj.ext->blocks)
				FreeMem(Rem->Obj.ext->blocks, ULONGS(Rem->Obj.ext->num));
			break;
/*
		case I_DAT:
		case I_S_LNK:
		case I_H_LNK_DIR:
		case I_H_LNK_FIL:
*/
		default:
			break;
		}
DBGS("<FS");
}

Static CheckUsed(num, Rem, str, par)
REG	ulong	num;
REG	struct	Info	*Rem;
REG	char	*str;
REG	ulong	par;
{
	if (FileSystem == IS_FFS)
		if (Seek)
		{	if (BMAP_TST(num))
				bad(Rem, num, B_USED, str, par);
			else
				BMAP_SET(num);
		}
		else
		{	if (Rem->flags & I_USED)
				switch(Rem->type)
				{
				case I_DAT:	/* well maybe? ;-) */
					bad(Rem, num, B_USEDBY, str, par, Rem->Obj.dat->key);
					break;
				case I_DIR:
				case I_FIL:
				case I_EXT:
				case I_S_LNK:
				case I_H_LNK_DIR:
				case I_H_LNK_FIL:
					bad(Rem, num, B_USEDBY, str, par, Rem->Obj.dir->parent);
					break;
/* Usually FFS Data Block
				case I_ROOT:
				case I_UNK:
				case I_UNREADABLE:
*/
				default:
					bad(Rem, num, B_USED, str, par);
					break;
				}
			else
				Rem->flags |= I_USED;
		}
}

Static long ChkDat(num, hed, Rem, key)
REG	ulong	num;
REG	ulong	hed;
REG	struct	Info	*Rem;
REG	ulong	key;
{
	REG	ulong  *l;

	DoCache = !0;

	if (Seek)
		Get(Rem, num);
	else
		Rem = &InfoList[num];

	CheckUsed(num, Rem, StrDat, key);

	if (unreadable(Rem, num, StrDat, hed))
		return(0);

	if (FileSystem == IS_FFS)
	{	FreeSeek(Rem);
		return(1);
	}

	PrevD = num;
	NextD = ULONGMAX;
	SeqNum++;

	if (Rem->type != I_DAT)
	{	bad(Rem, num, B_TYP, StrDat, hed);

		if (Rem->flags & E_CHKSUM)
			bad(Rem, num, B_CHKSUM, StrDat, hed);

		FreeSeek(Rem);
		return(0);
	}
	else
	{	if (Rem->Obj.dat->key != hed)
			bad(Rem, num, B_HED, StrDat, Rem->Obj.dat->key, hed);
		if (Rem->flags & E_CHKSUM)
			bad(Rem, num, B_CHKSUM, StrDat, hed);

		if (Rem->Obj.dat->seqnum != SeqNum)
			bad(Rem, num, B_SEQ, StrDat, SeqNum, Rem->Obj.dat->seqnum);

		if (Rem->flags & E_SHORT)
			bad(Rem, num, B_SHORT, StrDat);

		NextD = Rem->Obj.dat->nextd;
		if (NextD)
			rchk(Rem, num, NextD, StrDat, StrNextD);

		FreeSeek(Rem);
		return(Rem->Obj.dat->size);
	}
}

Static long ChkExt(num, par, Rem)
ulong	num;
ulong	par;
REG	struct	Info	*Rem;
{
	REG	ulong  *l;
	REG	ulong  *l2;
	REG	ulong  bytesize = 0;
	REG	int    oBaD = BaD;

	DoCache = 0;

	if (Seek)
		Get(Rem, num);
	else
		Rem = &InfoList[num];

	CheckUsed(num, Rem, StrExt, par);

	if (unreadable(Rem, num, StrExt, par))
		return(0);

	if (Rem->type != I_EXT)
		bad(Rem, num, B_TYP, StrExt, par);
	else
		if (Rem->Obj.ext->parent != par)
			bad(Rem, num, B_PAR, StrExt, Rem->Obj.ext->parent, par);

	if (Rem->flags & E_KEY)
		bad(Rem, num, B_KEY, StrExt, par);
	if (Rem->flags & E_CHKSUM)
		bad(Rem, num, B_CHKSUM, StrExt, par);

	if (Rem->flags & E_COUNT)
		bad(Rem, num, B_COUNT, StrExt, par);
	if (Rem->flags & E_FIRST)
		bad(Rem, num, B_FIRST, StrExt, 0L);

	if (IgnoreErr || BaD == oBaD)
	{	l = Rem->Obj.ext->blocks;
		if (l)
		{	for (l2 = &l[Rem->Obj.ext->num - 1]; l2 >= l; l2--)
				if (rchk(Rem, num, *l2, StrExt, StrDat))
				{	if (FileSystem == IS_OFS && NextD != *l2 && NextD != ULONGMAX)
						bad(Rem, num, B_NEXTD, StrExt, *l2, PrevD, NextD);
					bytesize += ChkDat(*l2, par, Rem+1, num);
				}
			FreeSeek(Rem);
			if (Rem->Obj.ext->extension && rchk(Rem, num, Rem->Obj.ext->extension, StrExt, StrExt))
				bytesize += ChkExt(Rem->Obj.ext->extension, par, Rem);
				/* Same Level (and don't need Rem anymore) */
		}
		else
			FreeSeek(Rem);
	}
	else
		FreeSeek(Rem);

	return(bytesize);
}

Static ChkFil(num, par, Rem)
ulong	num;
ulong	par;
REG	struct	Info	*Rem;
{
	REG	ulong  *l;
	REG	ulong  *l2;
	REG	ulong  bytesize = 0;
	REG	int    oBaD = BaD;
	int	flag = 0;
	struct	Info	*oLastInfo = LastInfo;
	ulong	oLastBlk = LastBlk;

	LastInfo = Rem;
	LastBlk = num;

	if (Rem->flags & E_KEY)
		bad(Rem, num, B_KEY, StrFil, par);
	if (Rem->flags & E_CHKSUM)
		bad(Rem, num, B_CHKSUM, StrFil, par);
	if (Rem->flags & E_COUNT)
		bad(Rem, num, B_COUNT, StrFil, par);
	if (Rem->flags & E_FIRST)
	{	if (Rem->Obj.fil->blocks)
			bad(Rem, num, B_FIRST, StrFil, Rem->Obj.fil->blocks[Rem->Obj.fil->num - 1]);
		else
			bad(Rem, num, B_FIRST, StrFil, 0L);
	}
	if (Rem->flags & E_BADNAME)
		bad(Rem, num, B_BADNAME, StrFil);
	if (Rem->flags & E_PREVLINK)
		bad(Rem, num, B_PREVLINK, StrFil);
	if (Rem->flags & E_OFSLINK)
		bad(Rem, num, B_OFSLINK, StrFil);

	if (Rem->Obj.fil->parent != par)
		bad(Rem, num, B_PAR, StrFil, Rem->Obj.fil->parent, par);

	if (Rem->Obj.fil->next)
	{	rchk(Rem, num, Rem->Obj.fil->next, StrFil, StrNext);
		MoveMaybe(num);
	}

	if (IgnoreErr || BaD == oBaD)
	{	SeqNum = 0L;
		l = Rem->Obj.fil->blocks;
		if (l)
		{	for(l2 = &l[Rem->Obj.fil->num - 1]; l2 >= l; l2--)
				if (rchk(Rem, num, *l2, StrFil, StrDat))
				{	if (flag)
					{	if (FileSystem == IS_OFS && NextD != *l2 && NextD != ULONGMAX)
							bad(Rem, num, B_NEXTD, StrFil, *l2, PrevD, NextD);
					}
					else
						flag = !0;
					bytesize += ChkDat(*l2, num, Rem+1, num);
				}
			if (Rem->Obj.fil->extension && rchk(Rem, num, Rem->Obj.fil->extension, StrFil, StrExt))
				bytesize += ChkExt(Rem->Obj.fil->extension, num, Rem+1);
			if (FileSystem == IS_OFS && NextD && NextD != ULONGMAX)
				bad(Rem, num, B_NEXTD, StrFil, 0L, PrevD, NextD);

			if (FileSystem == IS_OFS)
				if (bytesize != Rem->Obj.fil->bytesize)
					bad(Rem, num, B_BYTESIZE, StrFil, bytesize, Rem->Obj.fil->bytesize);

			if (FileSystem == IS_FFS)
			{	Rem->Obj.fil->bytesize += 511;
				Rem->Obj.fil->bytesize /= 512;	/* Calculate number of blocks - any other check available ???? */
				if (bytesize != Rem->Obj.fil->bytesize)
					bad(Rem, num, B_BLOCKSIZE, StrFil, bytesize, Rem->Obj.fil->bytesize);
			}
		}
		LastInfo = oLastInfo;
		LastBlk = oLastBlk;

		FreeSeek(Rem);

		if (Rem->Obj.fil->chain && rchk(Rem, num, Rem->Obj.fil->chain, StrFil, StrChain))
			ChkEtc(Rem->Obj.fil->chain, par, Rem);
			/* Same Level (and don't need Rem anymore) */
	}
	else
	{	LastInfo = oLastInfo;
		LastBlk = oLastBlk;

		FreeSeek(Rem);
	}
}

Static ChkDir(num, par, Rem)
REG	ulong	num;
REG	ulong	par;
REG	struct	Info	*Rem;
{
	REG	ulong  *l;
	REG	int    oBaD = BaD;
	struct	Info	*oLastInfo = LastInfo;
	ulong	oLastBlk = LastBlk;

	LastInfo = Rem;
	LastBlk = num;

	if (Rem->flags & E_KEY)
		bad(Rem, num, B_KEY, StrDir, par);
	if (Rem->flags & E_CHKSUM)
		bad(Rem, num, B_CHKSUM, StrDir, par);
	if (Rem->flags & E_BADNAME)
		bad(Rem, num, B_BADNAME, StrDir);
	if (Rem->flags & E_PREVLINK)
		bad(Rem, num, B_PREVLINK, StrDir);
	if (Rem->flags & E_OFSLINK)
		bad(Rem, num, B_OFSLINK, StrDir);

	if (Rem->Obj.dir->parent != par)
		bad(Rem, num, B_PAR, StrDir, Rem->Obj.dir->parent, par);

	if (Rem->Obj.dir->next)
	{	rchk(Rem, num, Rem->Obj.dir->next, StrDir, StrNext);
		MoveMaybe(num);
	}

	if (IgnoreErr || BaD == oBaD)
	{	l = Rem->Obj.dir->blocks;
		if (l)
			for (; *l; l++)
				if (rchk(Rem, num, *l, StrDir, StrEtc))
					ChkEtc(*l, num, Rem+1);

		LastInfo = oLastInfo;
		LastBlk = oLastBlk;

		FreeSeek(Rem);

		if (Rem->Obj.dir->chain && rchk(Rem, num, Rem->Obj.dir->chain, StrDir, StrChain))
			ChkEtc(Rem->Obj.dir->chain, par, Rem);
			/* Same Level (and don't need Rem anymore) */
	}
	else
	{	LastInfo = oLastInfo;
		LastBlk = oLastBlk;

		FreeSeek(Rem);
	}
}

Static ChkLnk(num, par, Rem)
REG	ulong	num;
REG	ulong	par;
REG	struct	Info	*Rem;
{
	REG	ulong  *l;
	REG	int    oBaD = BaD;
	struct	Info	*oLastInfo = LastInfo;
	ulong	oLastBlk = LastBlk;

	LastInfo = Rem;
	LastBlk = num;

	if (Rem->flags & E_KEY)
		bad(Rem, num, B_KEY, StrSoftLnk, par);
	if (Rem->flags & E_CHKSUM)
		bad(Rem, num, B_CHKSUM, StrSoftLnk, par);
	if (Rem->flags & E_BADNAME)
		bad(Rem, num, B_BADNAME, StrSoftLnk);
	if (Rem->flags & E_PREVLINK)
		bad(Rem, num, B_PREVLINK, StrRoot);
	if (Rem->flags & E_NEXTLINK)
		bad(Rem, num, B_NEXTLINK, StrRoot);
	if (Rem->flags & E_OFSLINK)
		bad(Rem, num, B_OFSLINK, StrSoftLnk);

	if (Rem->Obj.lnk->parent != par)
		bad(Rem, num, B_PAR, StrSoftLnk, Rem->Obj.lnk->parent, par);

	LastInfo = oLastInfo;
	LastBlk = oLastBlk;

	FreeSeek(Rem);

	if (IgnoreErr || BaD == oBaD)
	{	if (Rem->Obj.lnk->chain && rchk(Rem, num, Rem->Obj.lnk->chain, StrSoftLnk, StrChain))
			ChkEtc(Rem->Obj.lnk->chain, par, Rem);
			/* Same Level (and don't need Rem anymore) */
	}
}

Static ChkDirLnk(num, par, Rem)
REG	ulong	num;
REG	ulong	par;
REG	struct	Info	*Rem;
{
	REG	ulong  *l;
	REG	int    oBaD = BaD;
	struct	Info	*oLastInfo = LastInfo;
	ulong	oLastBlk = LastBlk;

	LastInfo = Rem;
	LastBlk = num;

	if (Rem->flags & E_KEY)
		bad(Rem, num, B_KEY, StrDirLnk, par);
	if (Rem->flags & E_CHKSUM)
		bad(Rem, num, B_CHKSUM, StrDirLnk, par);
	if (Rem->flags & E_BADNAME)
		bad(Rem, num, B_BADNAME, StrDirLnk);
	if (Rem->flags & E_PREVLINK)
		bad(Rem, num, B_NOPREVLINK, StrDirLnk);
	if (Rem->flags & E_OFSLINK)
		bad(Rem, num, B_OFSLINK, StrDirLnk);

	if (Rem->Obj.dirlnk->parent != par)
		bad(Rem, num, B_PAR, StrDirLnk, Rem->Obj.dirlnk->parent, par);

	if (Rem->Obj.dirlnk->prev)
		rchk(Rem, num, Rem->Obj.dirlnk->prev, StrDirLnk, StrPrev);
	if (Rem->Obj.dirlnk->next)
		rchk(Rem, num, Rem->Obj.dirlnk->next, StrDirLnk, StrNext);

	MoveMaybe(num);

	LastInfo = oLastInfo;
	LastBlk = oLastBlk;

	FreeSeek(Rem);

	if (IgnoreErr || BaD == oBaD)
	{	if (Rem->Obj.dirlnk->chain && rchk(Rem, num, Rem->Obj.dirlnk->chain, StrDirLnk, StrChain))
			ChkEtc(Rem->Obj.dirlnk->chain, par, Rem);
			/* Same Level (and don't need Rem anymore) */
	}
}

Static ChkFilLnk(num, par, Rem)
REG	ulong	num;
REG	ulong	par;
REG	struct	Info	*Rem;
{
	REG	ulong  *l;
	REG	int    oBaD = BaD;
	struct	Info	*oLastInfo = LastInfo;
	ulong	oLastBlk = LastBlk;

	LastInfo = Rem;
	LastBlk = num;

	if (Rem->flags & E_KEY)
		bad(Rem, num, B_KEY, StrFilLnk, par);
	if (Rem->flags & E_CHKSUM)
		bad(Rem, num, B_CHKSUM, StrFilLnk, par);
	if (Rem->flags & E_BADNAME)
		bad(Rem, num, B_BADNAME, StrFilLnk);
	if (Rem->flags & E_PREVLINK)
		bad(Rem, num, B_NOPREVLINK, StrFilLnk);
	if (Rem->flags & E_OFSLINK)
		bad(Rem, num, B_OFSLINK, StrFilLnk);

	if (Rem->Obj.fillnk->parent != par)
		bad(Rem, num, B_PAR, StrFilLnk, Rem->Obj.fillnk->parent, par);

	if (Rem->Obj.fillnk->prev)
		rchk(Rem, num, Rem->Obj.fillnk->prev, StrFilLnk, StrPrev);
	if (Rem->Obj.fillnk->next)
		rchk(Rem, num, Rem->Obj.fillnk->next, StrFilLnk, StrNext);

	MoveMaybe(num);

	LastInfo = oLastInfo;
	LastBlk = oLastBlk;

	FreeSeek(Rem);

	if (IgnoreErr || BaD == oBaD)
	{	if (Rem->Obj.fillnk->chain && rchk(Rem, num, Rem->Obj.fillnk->chain, StrFilLnk, StrChain))
			ChkEtc(Rem->Obj.fillnk->chain, par, Rem);
			/* Same Level (and don't need Rem anymore) */
	}
}

Static ChkEtc(num, par, Rem)
REG	ulong	num;
REG	ulong	par;
REG	struct	Info	*Rem;
{
	REG	ulong  *l;

	DoCache = 0;

	if (Seek)
		Get(Rem, num);
	else
		Rem = &InfoList[num];

	CheckUsed(num, Rem, StrEtc, par);

	if (unreadable(Rem, num, StrEtc, par))
		return;

	switch(Rem->type)
	{
	case I_DIR:
		ChkDir(num, par, Rem);
		break;
	case I_FIL:
		ChkFil(num, par, Rem);
		break;
	case I_S_LNK:
		ChkLnk(num, par, Rem);
		break;
	case I_H_LNK_DIR:
		ChkDirLnk(num, par, Rem);
		break;
	case I_H_LNK_FIL:
		ChkFilLnk(num, par, Rem);
		break;
	default:
		bad(Rem, num, B_TYP, StrEtc, par);
		FreeSeek(Rem);
		break;
	}
}

Static ChkBMap(num, par, Rem)
REG	ulong	num;
REG	ulong	par;
REG	struct	Info	*Rem;
{
	DoCache = !0;

	if (Seek)
		Get(Rem, num);
	else
		Rem = &InfoList[num];

	CheckUsed(num, Rem, StrBitMap, par);

	if (unreadable(Rem, num, StrBitMap, par))
		return;

	if (Rem->flags & E_CHKSUM)
		bad(Rem, num, B_CHKSUM, StrBitMap, par);

	if (Rem->type != I_UNK)
		bad(Rem, num, B_BITMAP, StrBitMap);
/* How do I test that this really is a bitmap block? Probably generate a warning */

	FreeSeek(Rem);
}

Static ChkRoot(num, Rem)
REG	ulong	num;
REG	struct	Info	*Rem;
{
	REG	ulong  *l;
	REG	int    oBaD = BaD;

DBGS(">ChkRoot");

	if (Seek)
		Get(Rem, num);
	else
		Rem = &InfoList[num];

	LastInfo = Rem;
	LastBlk = num;

	CheckUsed(num, Rem, StrRoot, 0L);

	if (unreadable(Rem, num, StrRoot, 0L))
		return;

	if (Rem->type != I_ROOT)
		bad(Rem, num, B_TYP, StrRoot, 0L);
	if (Rem->flags & E_BMFLG)
		bad(Rem, num, B_BMFLG, StrRoot);
	if (Rem->flags & E_HTSIZ)
		bad(Rem, num, B_HTSIZ, StrRoot);
	if (Rem->flags & E_KEY)
		bad(Rem, num, B_KEY, StrRoot, 0L);
	if (Rem->flags & E_CHKSUM)
		bad(Rem, num, B_CHKSUM, StrRoot, 0L);
	if (Rem->flags & E_BADNAME)
		bad(Rem, num, B_BADNAME, StrRoot);

/* appears to be a date in NEXTLINK position
	if (Rem->flags & E_PREVLINK)
		bad(Rem, num, B_PREVLINK, StrRoot);
	if (Rem->flags & E_NEXTLINK)
		bad(Rem, num, B_NEXTLINK, StrRoot);
*/

	if (IgnoreErr || BaD == oBaD)
	{	l = Rem->Obj.root->bblocks;
		if (!l)
			bad(Rem, num, B_BMAP, NULL);
		else
			for (; *l; l++)
				if (rchk(Rem, num, *l, StrRoot, StrBitMap))
					ChkBMap(*l, num, Rem+1);

		l = Rem->Obj.root->blocks;
		if (l)
			for (; *l; l++)
				if (rchk(Rem, num, *l, StrRoot, StrEtc))
					ChkEtc(*l, num, Rem+1);
	}
	FreeSeek(Rem);

DBGS("<ChkRoot");
}

Static	ValidateIt()
{
	REG	ulong	l;
	REG	ulong	size;
	REG	ulong	*bptr;

	if (Seek)
		size = MaxDepth;
	else
		size = 1L + Stuff.Info.End - Stuff.Info.Begin;

DEBUGMEM_NAME("InfoList");
	MPAlloc(InfoList, (sizeof(*InfoList) * size), &GenPoolKey);

	if (!Seek)
	{	AkMotorOn(&Stuff);
		DoCache = !0;
		for (l = Stuff.Info.Begin; l <= Stuff.Info.End; l++)
			getblok(l, NULL);
		AkMotorOff(&Stuff);
		ChkRoot(Stuff.Info.Root, NULL);
	}
	else
	{	while (size-- > 0)
		{
DEBUGMEM_NAME("An InfoList fil");
			MPAlloc(InfoList[size].Obj.fil, sizeof(struct Fil), &GenPoolKey);
		}
		if (FileSystem == IS_FFS)
		{	size = 1L + Stuff.Info.End - Stuff.Info.Begin;
DEBUGMEM_NAME("BitMap");
			MPAlloc(MyBitMap, (size+7)/8, &ZerPoolKey);
		}
		AkMotorOn(&Stuff);
		DoCache = !0;
		ChkRoot(Stuff.Info.Root, InfoList);
		AkMotorOff(&Stuff);
	}

	CheckLinks();

	if (BaD || WaR)
		printf("\nTotal Displayed errors for %s %d, Warnings: %d\n", Device, BaD, WaR);
	else
		printf("\nNo errors detected on %s\n", Device);

	if (DebugLink)
	{	puts("Links:");
		if (!LinkHead)
			puts("\tNone!");
		else
		{	while (LinkHead)
			{	printf("\t%-10s Blk:%10lu Prev:%10lu Next:%10lu\n",
					StrType(LinkHead->type),
					LinkHead->block,
					LinkHead->prev,
					LinkHead->next);
				LinkHead = LinkHead->another;
			}
		}
		puts("Maybes: (but not!)");
		if (!LinkMaybe)
			puts("\tNone!");
		else
		{	while (LinkMaybe)
			{	printf("\t%-10s Blk:%10lu Prev:%10lu Next:%10lu\n",
					StrType(LinkMaybe->type),
					LinkMaybe->block,
					LinkMaybe->prev,
					LinkMaybe->next);
				LinkMaybe = LinkMaybe->another;
			}
		}
	}
}

Static	Val()
{
	LONG	ret;
	ulong	*buf;

	if (Dev = AkDevOpen(Device, &Stuff))
		MyError(Dev, Device);

	if (Inhibit)
		Inhibited = AkDevInhibit(&Stuff, -1L);

DEBUGMEM_NAME("Buffer");

	MPAlloc(Buffer, (long)(BLOCK_SIZE*Cache), &BufPoolKey);

	AkDevMsg(&(Stuff.Info));

	if (Ffs || Ofs)         /* Force FS choice */
	{	if (Ofs)
			FileSystem = IS_OFS;
		else
			FileSystem = IS_FFS;
	}
	else
	{	if (ShowFileSystem)
			printf("FileSystemHandler is \"%s\"\n", Stuff.Info.fsys);

		printf("Boot Block File System ... ");
		fflush(stdout);

		FileSystem = IS_FFS;	/* Assume - change if wrong */

		AkMotorOn(&Stuff);
		DoCache = !Seek;
		ret = CacheRead(Stuff.Info.Begin, &buf);
		AkMotorOff(&Stuff);

		if (ret)
			printf("Error 0x%lx on Read Block Zero - Assumed FFS\n", ret);
		else
		{	if ((*buf & 0xffffff00) != 0x444f5300)
				printf("Doesn't appear to be DOS - Assumed FFS\n");
			else
			{	if ((*buf & 0xff) == 0)
				{	FileSystem = IS_OFS;
					printf("Appears to be OFS\n");
				}
				else
					printf("Appears to be FFS or similar\n");
			}
		}
	}
	ValidateIt();
}

main(argc, argv)
int	argc;
char	*argv[];
{
	REG	int	idx;

	cli = argc;

	DO_QUAL();

	if (Cache > 1)
		Cache += 2;

	if (!Quiet && cli)
		STARTUP_MSG;

	OPEN_LIB(IBASE, "intuition.library", INTUITION_REV);

	PoolInit(&BufPoolKey, 0L, MEMF_CHIP);
	PoolInit(&GenPoolKey, PoolSize, 0L);
	PoolInit(&ZerPoolKey, 0L, MEMF_CLEAR);

	Val();

	if (StatCache)
	{	ulong	BTot = ReadSmall*2 + ReadLNum;
		ulong	RTot = HitSmall + HitLarge + ReadSmall + ReadLarge;

#define PER(x)  (RTot ? (double)(x) / (double)(RTot) * (double)(100): (double)0)

		printf("Cache Statistics (Real Time) - Cache = 2:%d\n", Cache-2);
		if (Cache == 1)
			puts("Well ... first you need to HAVE a cache! (Try /cache=75)");
		else
		{	printf("Blocks Requested....... %10lu\n", RTot);
			printf("Blocks Read............ %10lu\n", BTot);
			printf("Average Reads/Request.. %10.2lf\n", (double)(BTot) / (double)(RTot));
			printf("Total µsecs Cache Code. %10lu\n", MicCode);
			if (MicCode)
				printf("Average µsec/CodeReq... %10.2lf\n",
					(double)(MicCode) / (double)(HitSmall + HitLarge));
			printf("Total µsecs Cache IOReq %10lu\n", MicRead);
			if (MicRead)
				printf("Average µsec/IOReq..... %10.2lf\n",
					(double)(MicRead) / (double)(ReadSmall + ReadLarge));
			printf("Total µsecs Cache Req.. %10lu\n", MicCode + MicRead);
			if (MicCode || MicRead)
				printf("Average µsec/Request... %10.2lf\n",
					((double)(MicCode) + (double)(MicRead)) / (double)(RTot));
			printf("Hits on Small Cache.... %10lu %6.2lf%%\n", HitSmall, PER(HitSmall));
			printf("Hits on Large Cache.... %10lu %6.2lf%%\n", HitLarge, PER(HitLarge));
			printf("Reads to Small Cache... %10lu %6.2lf%%\n", ReadSmall, PER(ReadSmall));
			printf("Reads to Large Cache... %10lu %6.2lf%%\n", ReadLarge, PER(ReadLarge));
		}
	}

	Cleanup();
}

MyError(num, why)
REG	long	num;
REG	char	*why;
{
	ERROR_INIT

	if (Inhibited)
		AkDevInhibit(&Stuff, 0L);

	if (!Dev)
		AkDevClose(&Stuff);

	ERROR_TEST
		CASE_ERR_TOODEEP
		CASE_ERR_AKDEVALL
		CASE_ERR_NOVAL
		CASE_ERR_NOMEM
		CASE_ERR_NOLIB
		CASE_ERR_BADFIL
	ERROR_END(FACILITY_VAL)

	PoolFree(&ZerPoolKey);
	PoolFree(&GenPoolKey);
	PoolFree(&BufPoolKey);

	if (IBASE)
		CloseLibrary(IBASE);

	CLEANUP_QUAL();

	ERROR_EXIT
}
