#define MODULE_ID	5020

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

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

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

#include "sys/proc.h"		/* For <disp.h> */
#include "sys/disp.h"		/* For "extern curproc" */
#include "sys/kmem.h"		/* For KM_SLEEP */
#include "sys/uio.h"		/* For prototypes (ugh) */
#include "sys/buf.h"		/* For B_INVAL, et al */
#include "sys/cmn_err.h"	/* For CE_WARN, et al */
#include "sys/mode.h"		/* For IFTOVT() macro */
#include "sys/swap.h"		/* For IS_SWAPVP() macro */
#include "fs/fs_subr.h"		/* For fs_vcode() */

#include "ados.h"
#include "ados_udir.h"
#include "missing.h"

/*
 * Add "count" anodes to the linked list. On return, the original value of
 * "chn" points to the first newly added link.
 */
int extend_chain(register struct ados * afsp, register int count)
{
    register anode_t *chn = (void *) &afsp->vfs_forw;
    register anode_t *new;

IN_FUNC(extend_chain);
    while (count-- > 0) {
	new = (anode_t *) kmem_zalloc(sizeof(*new), KM_SLEEP);
	DBG("inserting 0x%x into anode list", new, 0, 0);
	new->a_forw = chn->a_forw;
	chn->a_forw = new;
	new->a_back = new->a_forw->a_back;
	new->a_forw->a_back = new;
	afsp->vfs_nels++;
    }
IN_FUNC(extend_chain);
    return 0;
}

void dump_chain(char *type, register anode_t *start)
{
    register anode_t *ptr = start;

    /*
     *  "%s" allowed 'cuz ados_DBG2() calls ados_debug_fmtv() to
     *  perform the formatting and _then_ calls strlog().
     */
    DBG("Dump of %s anodes start at %x", type, start);
    do {
	DBG("from %x --> %x", ptr, ptr->a_forw);
	ptr = ptr->a_forw;
    } while (ptr && ptr != start);
}

/*
 * Search the anode list to locate the file header named by the given key
 * block number.  If not found, allocate one from the list of free anodes.
 */
int aget(struct vfs * vfsp, ulong key, anode_t ** app)
{
    register struct ados *afsp = ADOS_VFS(vfsp);
    register anode_t *aused = &afsp->vfs_root;
    register anode_t *afree = (void *) &afsp->vfs_forw;
    register anode_t *ap;
    register vnode_t *vp;
    int error = afsp->vfs_nels, flag;

loop:
IN_FUNC(aget:loop);
    DBG("total checks %d", error);
#ifdef TYPICAL
    dump_chain("Used", aused);
    dump_chain("Free", afree);
#endif
    for (flag=0, ap = afree->a_forw; ap != afree && error--; ap = ap->a_forw) {
/*	DBG("check # %d is 0x%x", error, ap);	*/
	if (ap->a_keyblk == key && ATOV(ap)->v_vfsp == vfsp)
	    goto found;
    }
    for (flag=1, ap = aused->a_forw; ap != aused && error--; ap = ap->a_forw) {
	if (ap->a_keyblk == key && ATOV(ap)->v_vfsp == vfsp)
	    goto found;
    }
again:
IN_FUNC(aget:again);
    while (afree == afree->a_forw && dnlc_purge1() == 1) /* apurge(vfsp) */
	;
    if (afree == afree->a_forw) {
	if (afsp->vfs_nels < afsp->vfs_nelmax - 4) {
	    extend_chain(afsp, 4);			/* Low on anodes! */
	    goto again;
	} else {
	    DBG("Anode cache overflow!  %d of %d",
		afsp->vfs_nels, afsp->vfs_nelmax, 0);
	    return ENFILE;
	}
    }
    ap = afree->a_forw;
    DBG("found unused anode @ 0x%x", ap, 0, 0);
    DBG("a_forw 0x%x, a_back 0x%x", ap->a_forw, ap->a_back, 0);

    vp = ATOV(ap);
    ASSERT(vp->v_count == 0);
    ASSERT(ap == ap->a_back->a_forw);	/* Verify validity of chain links */
    ASSERT(ap == ap->a_forw->a_back);

    ap->a_back->a_forw = ap->a_forw;	/* Remove "ap" from list */
    ap->a_forw->a_back = ap->a_back;
    ap->a_forw = NULL;
    ap->a_back = NULL;
IN_MACRO(ILOCKED, ap);
    ap->a_flag = ILOCKED;
    ap->a_owner = curproc->p_slot;
    ap->a_nilocks = 1;

    if ((vp->v_vfsp && syncap(ap, B_INVAL)) || (ap->a_flag & IWANT)) {
	DBG("Oops, somebody wants that one!  0x%x", ap, 0, 0);
	apfree(ap);		/* If a_forw == NULL, no list removal */
	IUNLOCK(ap);
	goto loop;
    }
    if (vp->v_pages) {
	cmn_err(CE_WARN, "Don't reboot:  report this to filesystem group.");
	cmn_err(CE_CONT, "vp: 0x%x  ap: 0x%x", vp, ap);
    }
    if (ap->a_ll.ll_forw)
	freechn(ap);		/* NULLs out ll_forw & ll_back */

    /* Add "ap" to the used list of anodes */
    DBG("a_forw 0x%x, a_back 0x%x", ap->a_forw, ap->a_back, 0);
    ap->a_forw = aused->a_forw;
    ap->a_forw->a_back = ap;
    ap->a_back = aused;
    aused->a_forw = ap;
#ifdef TYPICAL
    dump_chain("Used", aused);
    dump_chain("Free", afree);
#endif

    if ((error = ados_read_anode(ap, key, vfsp)) ||
	    ((vp->v_type = IFTOVT(ap->a_mode)) == VREG &&
		 (error = fs_vcode(vp, &ap->a_vcode)))) {
	DBG("couldn't read anode/set type/assign vcode, 0x%x", ap, 0, 0);
	apfree(ap);	/* Since a_forw != NULL, remove from "in use" list */
	vp->v_data = NULL;
	vp->v_count = 0;
	IUNLOCK(ap);
    } else {
	vp->v_rdev = 0;
	/*
	 *  This shouldn't be done 'cuz the v_count member is already
	 *  set to 1 by ados_read_anode() when it initializes the
	 *  anode structure.  This would bump it too far.
	 */
/*	VN_HOLD(vp);		/* Increments vp->v_count */
	*app = ap;
	DBG("allocated anode @ 0x%x (error %d)", ap, error, 0);
	error = 0;
    }
    return error;

found:
    if ((ap->a_flag & (IRWLOCKED|ILOCKED)) && ap->a_owner != curproc->p_slot) {
	DBG("Oops, not free; set IWANT flag", 0, 0, 0);
IN_MACRO(IWANT, ap);
	ap->a_flag |= IWANT;
	sleep((caddr_t) ap, PINOD);
	goto loop;
    }
    vp = ATOV(ap);
    if (vp->v_count == 0) {
	DBG("a_forw 0x%x, a_back 0x%x", ap->a_forw, ap->a_back, 0);
	ASSERT(ap == ap->a_back->a_forw);
	ASSERT(ap == ap->a_forw->a_back);
	ap->a_back->a_forw = ap->a_forw;	/* Take off free list */
	ap->a_forw->a_back = ap->a_back;

	ap->a_back = aused;			/* And put on used list */
	aused->a_forw->a_back = ap;
	ap->a_forw = aused->a_forw;
	aused->a_forw = ap;
    }
IN_FUNC(aget);
    VN_HOLD(vp);			/* Increments v_count */
    ap->a_owner = curproc->p_slot;	/* XXX */
    ap->a_nilocks++;			/* XXX */
IN_MACRO(ILOCKED, ap);
    ap->a_flag |= ILOCKED;
    *app = ap;
    return 0;
}

/*
 * Decrement reference count of an anode structure. On the last reference,
 * write the anode out and if necessary, truncate and deallocate the file.
 */
void ados_aput(register anode_t *ap)
{
    vnode_t *vp = ATOV(ap);

IN_FUNC(ados_aput);
    ASSERT(ap->a_flag & ILOCKED);
    ASSERT(vp->v_count > 0);
    IUNLOCK(ap);
    VN_RELE(vp);
}

/*
 * Purge any cached anodes on the given VFS.  If "force" is 0, -1 is returned
 * if an active anode (other than the filesystem root) is found, otherwise 0.
 * If "force" is non-zero, the search doesn't stop if an active anode is
 * encountered.
 */

int aflush(vfsp, force)
register struct vfs *vfsp;
int force;
{
    register anode_t *ap, *next, *rvp = &ADOS_VFS(vfsp)->vfs_root;
    register vnode_t *vp;
    register int i;
    dev_t dev = vfsp->vfs_dev;

IN_FUNC(aflush);
    ASSERT(rvp != NULL);
    for (i = 0, ap = rvp->a_forw; ap != rvp; i++, ap = next) {
	next = ap->a_forw;
	vp = ATOV(ap);
	if (vp->v_vfsp == 0)
	    continue;
	if (vp->v_vfsp->vfs_dev == dev) {
	    DBG("aflush: ap @ %x, vp @ %x", ap, vp, 0);
	    if (vp->v_count == 0) {
IN_MACRO(if_LOCKED, ap);
		if (ap->a_flag & (IRWLOCKED | ILOCKED)) {
		    DBG("aflush:  locked anode @ 0x%x", ap, 0, 0);
		    if (force)
			continue;
		    return -1;
		}
		/*
		 * Dispose of this inode.  Flush any associated pages.
		 */
		ILOCK(ap);	/* Won't sleep */
		DBG("aflush:  free'ing ap @ %x", ap, 0, 0);
		(void) syncap(ap, B_INVAL);
IN_MACRO(if_WANTED, ap);
		if (ap->a_flag & IWANT) {
		    DBG("aflush:  anode is wanted", 0, 0, 0);
		    IUNLOCK(ap);
		    if (force)
			continue;
		    return -1;
		}
		IUNLOCK(ap);
		apfree(ap);	/* If a_forw != 0, remove from "in use" list */
	    } else if (force == 0)
		return -1;
	}
    }
IN_FUNC(aflush);
    vp = ATOV(rvp);
    if (vp->v_count > 1 && force == 0)
	return -1;
    ILOCK(rvp);
    (void) syncap(rvp, B_INVAL);
    IUNLOCK(rvp);
IN_FUNC(aflush);
    return 0;
}

/*
 * Flush all the pages associated with an anode using the given flags, then
 * force anode information to be written back.
 */
int syncap(anode_t *ap, int flags)
{
IN_FUNC(syncap);
    return 0;
}

/*
 * Put an in-core inode on the free list.
 */
void apfree(register anode_t * ap)
{
    register anode_t *afree =
	(anode_t *) & (ADOS_VFS(ATOV(ap)->v_vfsp)->vfs_forw);

IN_FUNC(apfree);
    ASSERT(ap != &ADOS_VFS(ATOV(ap)->v_vfsp)->vfs_root);

    DBG("a_forw 0x%x, a_back 0x%x", ap->a_forw, ap->a_back, 0);
    if (ap->a_forw) {
	ASSERT(ap->a_back != NULL);
	ap->a_back->a_forw = ap->a_forw;	/* Remove "ap" from old list */
	ap->a_forw->a_back = ap->a_back;
    }
    if ((!ap->a_mode || !ATOV(ap)->v_pages) && afree->a_forw != afree) {
	DBG("method 1 (front)", 0, 0, 0);
	ap->a_forw = afree->a_forw;
	ap->a_forw->a_back = ap;
	ap->a_back = afree;
	afree->a_forw = ap;
    } else {
	DBG("method 2 (rear)", 0, 0, 0);
	afree->a_back->a_forw = ap;
	ap->a_forw = afree;
	ap->a_back = afree->a_back;
	afree->a_back = ap;
    }
    DBG("a_forw 0x%x, a_back 0x%x", ap->a_forw, ap->a_back, 0);
}

static int apurge(struct vfs * vfsp)
{
IN_FUNC(apurge);
    return (0);
}

static void aupdat(anode_t *ap)
{
IN_FUNC(aupdat);
    return;
}

/*
 * ados_update() goes through the disk queues to initiate sandbagged I/O,
 * through the inodes to write modified nodes, and through the VFS list
 * table to initiate modified superblocks.
 */
void ados_update(void)
{
    register struct vfs *vfsp;
    extern struct vfsops ados_vfsops;

IN_FUNC(ados_update);
    for (vfsp = rootvfs; vfsp; vfsp = vfsp->vfs_next)
	if (vfsp->vfs_op == &ados_vfsops) {
#if 0
	    ados_flushsb(vfsp);
#endif
	    ados_flusha(vfsp, 0);
	}
    bflush(NODEV);	/* XXX */
}

int ados_flusha(struct vfs *vfsp, short flag)
{
    register vnode_t *vp;
    register anode_t *ap;
    register int cheap = flag & SYNC_ATTR;
    register anode_t *aused, *next;

IN_FUNC(ados_flusha);
    if (!vfsp)
	return 0;
    aused = & ADOS_VFS(vfsp)->vfs_root;
#ifdef TYPICAL
    dump_chain("Used", aused);
#endif
    for (ap = aused->a_forw; ap && ap != aused; ap = next) {
	vp = ATOV(ap);
	next = ap->a_forw;
	DBG("ap 0x%x, flag 0x%x, count %d", ap, ap->a_flag, vp->v_count);
IN_MACRO(if_LOCKED, ap);
	if ((ap->a_flag & (ILOCKED|IRWLOCKED)) == 0
		  && vp->v_count != 0
		  && vp->v_vfsp != 0) {
	    ILOCK(ap);
	    VN_HOLD(vp);
	    /*
	     * If this is an inode sync for file system hardening
	     * or this is a full sync but file is a swap file,
	     * don't sync pages but make sure the inode is up
	     * to date.  In other cases, push everything out.
	     */
	    if (cheap || IS_SWAPVP(vp))
		aupdat(ap);
	    else
		(void) syncap(ap, B_ASYNC);
	    ados_aput(ap);
	}
    }
    return 0;
}
