#define MODULE_ID	5010

/***
Copyright (c) 1991, 1992 by Frank J. Edwards
See the file COPYRIGHT in this distribution for copyright details.
***/

#ident "$Id: blocks.c,v 1.14 1992/11/26 02:31:03 root Exp root $";

#include "sys/types.h"
#include "sys/param.h"
#include "sys/fcntl.h"
#include "sys/sysmacros.h"
#include "sys/vfs.h"
#include "sys/vnode.h"
#include "sys/systm.h"
#include "sys/errno.h"
#include "sys/cred.h"

#include "sys/uio.h"		/* Only needed to keep the prototypes happy */
#include "sys/fbuf.h"		/* Added for ADOS_fs */
#include "sys/kmem.h"		/* Added for ADOS_fs */
#include "vm/seg_map.h"		/* Added for ADOS_fs */
#include "vm/seg.h"		/* Added for ADOS_fs */
#include "missing.h"

#include "ados.h"
#include "ados_udir.h"
#include "ados_file.h"

#define SIZEOF(x)	(sizeof(x) / sizeof(*(x)))

/*
 * Check to make sure that contiguous LONG addresses starting at "addr" are
 * all zero.  If so, return TRUE.
 */
static INLINE int zero(register ulong * addr, register int len)
{
    while (len-- > 0)
	if (*addr++)
	    return 0;
    return 1;
}

static INLINE int chksum(void *loc, int num)
{
    long a = 0, *p = (long *) loc;

    while (num--)
	a += *p++;
    return (a == 0);
}

/*
 * Perform all of the AmigaDOS sanity checks on the given block.
 */

#define FILE	ptr		/* Undefined after sanitychk() */
#define UDIR	AUDIR(ptr)	/* "   */
#define ROOT	AROOT(ptr)	/* "   */

static int sanitychk(struct a_file * ptr, int *err, int dos)
{
    register int temp;

IN_FUNC(sanitychk);
    if (!chksum(ptr, sizeof(*ptr) / sizeof(LONG))) {
	DBG("bad checksum, PT %d, ST %d, blk %d",
	    FILE->uf_type, FILE->uf_type2, FILE->uf_hkey);
	*err = EINVAL /* CheckSum */;
	return EIO;
    }
    switch (FILE->uf_type) {
    case PT_DIR:
#if PT_FILE != PT_DIR
    case PT_FILE:
#endif
	/*
	 * [FJE] Need to handle ST_LINKDIR (4) here...
	 * 
	 * See README for details on which blocks are non-zero, et al.
	 */
	if (FILE->uf_type2 == ST_LINKFILE || FILE->uf_type2 == ST_SOFTLINK) {
	    DBG("unimpl header type %d", FILE->uf_type2, 0, 0);
	    *err = EINVAL /* UnImplemented */;
	    return EIO;
	}
	if (FILE->uf_type2 == ST_FILE || FILE->uf_type2 == ST_USERDIR) {
	    if (!UDIR->ud_hkey || !UDIR->ud_parent) {
		DBG("bad hkey (0) or parent (0)", 0, 0, 0);
		*err = EINVAL /* BadKeyOrParent */;
		return EIO;
	    }
	    if (FILE->uf_type2 == ST_USERDIR) {
		if (!zero(UDIR->ud_rsvp1, SIZEOF(UDIR->ud_rsvp1)) ||
		    !zero(UDIR->ud_rsvp2, SIZEOF(UDIR->ud_rsvp2)) ||
		    !zero(UDIR->ud_rsvp3, SIZEOF(UDIR->ud_rsvp3)) ||
		    !zero(UDIR->ud_rsvp4, SIZEOF(UDIR->ud_rsvp4))) {
		    DBG("non-zero rsvp (USERDIR)", 0, 0, 0);
		    *err = EINVAL /* NonZeroRsvp */;
		    return EIO;
		}
	    } else if (FILE->uf_type2 == ST_FILE) {
		if (!zero(FILE->uf_rsvp1, SIZEOF(FILE->uf_rsvp1))) {
		    DBG("non-zero rsvp (FILE)", 0, 0, 0);
		    *err = EINVAL;
		    return EIO;
		}
		if (FILE->uf_hiseq > 72 && !FILE->uf_exten) {
		    DBG("hiseq %d, exten %d (FILE)", FILE->uf_hiseq, FILE->uf_exten, 0);
		    *err = EINVAL /* FileInconsistent */;
		    return EIO;
		}
	    }
	} else if (FILE->uf_type2 == ST_ROOT) {
	    if (!zero(ROOT->rd_rsvp1, SIZEOF(ROOT->rd_rsvp1))) {
		DBG("non-zero root rsvp1", 0, 0, 0);
		*err = EINVAL /* NonZeroRsvp */;
		return EIO;
	    } else if (!zero(ROOT->rd_rsvp2, SIZEOF(ROOT->rd_rsvp2))) {
		DBG("non-zero root rsvp2", 0, 0, 0);
		*err = EINVAL /* NonZeroRsvp */;
		return EIO;
	    } else if (ROOT->rd_hashsz != NUMHASH) {
		DBG("bad hashsz field %d (ROOT)", ROOT->rd_hashsz, 0, 0);
		*err = EINVAL /* RootHasWrongHashSize */;
		return EIO;
	    } else if (!zero(ROOT->rd_rsvp3, SIZEOF(ROOT->rd_rsvp3))) {
		DBG("non-zero root rsvp3 [%d] [%d]",
		    ROOT->rd_rsvp3[0], ROOT->rd_rsvp3[1], 0);
		*err = EINVAL /* NonZeroRsvp */;
		return EIO;
	    } else if (ROOT->rd_dircache && !(dos & DIR_CACHING)) {
		DBG("non-zero dircache %d; %x", ROOT->rd_dircache, dos, 0);
		*err = EINVAL;
		return EIO;
	    }
	}
	return 0;
    case PT_LIST:
	if (!FILE->uf_hkey || !FILE->uf_parent) {
	    DBG("bad hkey or parent (LIST)", 0, 0, 0);
	    *err = EINVAL /* BadKeyOrParent */;
	    return EIO;
	}
	if (!zero(FILE->uf_rsvp1, SIZEOF(FILE->uf_rsvp1))) {
	    DBG("non-zero rsvp (LIST)", 0, 0, 0);
	    *err = EINVAL /* NonZeroRsvp */;
	    return EIO;
	}
	return 0;
    }
    DBG("Shouldn't get here!", 0, 0, 0);
    DBG("block @ 0x%x, PT %d, ST %d", ptr, FILE->uf_type, FILE->uf_type2);

    *err = EINVAL /* UnImplemented */;
    return EIO;
}

#undef FILE
#undef UDIR
#undef ROOT

int walkchn(int dos, vnode_t *dvp, LONG *block, int bsize, struct llchain *fill)
{
    struct fbuf *mmap;
    struct a_file *file;
    int errtyp;

IN_FUNC(walkchn);
    DBG("block %d", *block, 0, 0);
    errtyp = fbread(dvp, *block * bsize, bsize, S_WRITE, &mmap);
    if (errtyp) {
	DBG("error %d from fbread()", errtyp, 0, 0);
	return errtyp;
    }
    file = AFILE(mmap->fb_addr);
    if (sanitychk(file, &errtyp, dos)) {
	DBG("error %d from sanitychk()", errtyp, 0, 0);
	goto exit;
    }
    *block = file->uf_hashchn;
    if (fill != NULL) {
	fill->ll_keyblk = file->uf_hkey;
	fill->ll_exten = file->uf_exten;
	bcopy((caddr_t) file->uf_data, (caddr_t) fill->ll_addr, HASHSIZE);
    }
    errtyp = 0;
exit:
    fbrelse(mmap, S_OTHER);
    return (errtyp);
}

#define CHKRANGE(x)	((long)(x) < 0x40000000 || (long)(x) > 0x40ffffff)

void freechn(anode_t * ap)
{
    struct llchain *list, *nlist;

IN_FUNC(freechn);
    for (list = ap->a_ll.ll_forw; list; list = nlist) {
	if (CHKRANGE(list)) {
	    DBG("ll_forw links error; anode 0x%x", ap, 0, 0);
	    break;
	}
	nlist = list->ll_forw;
	if (nlist != NULL && (nlist->ll_back != list || CHKRANGE(nlist))) {
	    DBG("ll_back links error; anode 0x%x", ap, 0, 0);
	    break;
	}
	DBG("free'ing 0x%x (%d bytes)", list, sizeof(*list), 0);
	kmem_free((caddr_t) list, sizeof(*list));
    }
    ap->a_ll.ll_forw = NULL;	/* Used later for caching the hash data */
    ap->a_ll.ll_back = NULL;	/*   slots in files and/or directories. */
    ap->a_idx = -1;		/* Force ados_cache_dir() to be called. */
}
