#define MODULE_ID	5030

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

#ident "$Id: subr2.c,v 1.17 1992/11/26 03:51:42 root Exp root $";

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

#include "sys/fbuf.h"		/* Added for ADOS_fs */
#include "sys/file.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"

/*
 * If the return value is not the same as what was passed in as parameter #1,
 * then a new block had to be allocated and the caller should initialize it
 * if necessary, but without touching the ll_forw/ll_back or ll_next
 * fields.
 *
 * This routine _could_ implement some type of insertion sort on the data, but
 * only within the given (struct llchain).  It's not obvious whether there'd
 * be any advantage to this.
 */

static struct llchain *allocchn(struct llchain * old)
{
    old->ll_forw = kmem_zalloc(sizeof(struct llchain), KM_SLEEP);
    old->ll_forw->ll_back = old;
    return (old->ll_forw);
}

static struct llchain *addblk(struct llchain * old, LONG block)
{
    struct llchain *next = old->ll_forw;

IN_FUNC(addblk);
    if (next && next->ll_next <= 0) {
	DBG("addblk:  forw link from 0x%x", next, 0, 0);
	old = next, next = old->ll_forw;
	ASSERT(next == 0);
	next = 0;
    }
    if (!next) {
	next = allocchn(old);
	next->ll_next = NUMHASH;
	DBG("addblk: 0x%x chains to 0x%x", old, next, 0);
    }
    next->ll_addr[--next->ll_next] = block;
    return (old);
}

/*
 *  Only called from ados_read_anode() where the directory size needs
 *  to be obtained so that the stat() family of system calls has
 *  immediate access to the information in the cache.
 */
int ados_cache_dir(vnode_t * dvp, anode_t * ap)
{
    register struct llchain *new;
    register int i, count = 0, others = 0;
    struct ados *afsp = ADOS_VFS(ATOV(ap)->v_vfsp);
    int error, bsize = 1 << afsp->vfs_bufbits;
    LONG blk;

IN_FUNC(ados_cache_dir);
    blk = ap->a_keyblk;		/* Key block of directory */
    new = &ap->a_ll;
    if (new->ll_forw)		/* If already exists, clear the list */
	freechn(ap);		/*   also zeroes out ll_next */

    if (error = walkchn(afsp->vfs_dos, dvp, &blk, bsize, new)) {
	DBG("error %d from walkchn; block %d (%d)", error, ap->a_keyblk, blk);
	return error;
    }
#ifdef TYPICAL
    dump_chain("Used", &ADOS_VFS(ATOV(ap)->v_vfsp)->vfs_root);
#endif
    i = NUMHASH;
    while (i-- > 0) {
	if (blk = ap->a_ll.ll_addr[i]) {
	    count++;
	    new = addblk(new, blk);
	    while ((error = walkchn(afsp->vfs_dos, dvp, &blk, bsize, NULL)) == 0 && blk) {
		DBG("idx %d, block %d", i, blk, 0);
		others++;
		new = addblk(new, blk);
	    }
	    if (error) {
		DBG("abandoning block %d (chain %d)", blk, i, 0);
	    }
	}
    }
#ifdef TYPICAL
    dump_chain("Used", &ADOS_VFS(ATOV(ap)->v_vfsp)->vfs_root);
#endif
    ap->a_blocks = count + others;
    ap->a_size = ap->a_blocks * bsize;
    ap->a_nlink = ap->a_blocks + 2;

    ASSERT(ap->a_ll.ll_exten == 0);
    ap->a_ll.ll_keyblk = ap->a_keyblk;
    ap->a_ll.ll_exten = 0;		/* Directories don't have extensions */
    DBG("key %d, size %d, blocks %d",
	ap->a_keyblk, ap->a_size, ap->a_blocks);
    return 0;
}

static int getblk(anode_t *ap, int idx, struct llchain **llc, LONG *block)
{
    register struct llchain *start;
    register int error, i;
    struct ados *afsp = ADOS_VFS(ATOV(ap)->v_vfsp);
    vnode_t *dvp = afsp->vfs_devvp;
    LONG blk;
    int bsize = 1 << afsp->vfs_bufbits;

IN_FUNC(getblk);
    *llc = NULL;
    *block = 0;
    if (!(ap->a_mode & S_IFDIR) && (afsp->vfs_dos & SMALL_BLOCK))
	idx /= (bsize - sizeof(struct a_data));
    else
	idx /= bsize;

    DBG("block %d of ap @ %x", idx, ap, 0);
    if (ap->a_idx < 0) {
	if (ap->a_mode & S_IFDIR) {
	    printf("Calling ados_cache_dir on '%s'\n", ap->a_name);
	    error = ados_cache_dir(dvp, ap);
	} else {
	    blk = ap->a_keyblk;
	    DBG("FILE key from block %d", blk, 0, 0);
	    error = walkchn(afsp->vfs_dos, dvp, &blk, bsize, &ap->a_ll);
	}
	if (error)
	    return error;
    }
    if (ap->a_mode & S_IFDIR)
	start = ap->a_ll.ll_forw;	/* ados_cache_dir() skips a_ll */
    else
	start = &ap->a_ll;
    while (idx >= NUMHASH) {
	if (start->ll_forw) {
	    DBG("in-core, idx %d, going to 0x%x", idx, start->ll_forw, 0);
	    start = start->ll_forw;
	} else if (ap->a_mode & S_IFDIR) {
	    DBG("read past end of dir (chn @ 0x%x)", start, 0, 0);
	    return 0;
	} else {
	    DBG("block %d --> %d", start->ll_keyblk, start->ll_exten, 0);
	    if (!(blk = start->ll_exten))
		return 0;	/* Attempt to move past EOF */
	    start = allocchn(start);
	    error = walkchn(afsp->vfs_dos, dvp, &blk, bsize, start);
	    if (error)
		return error;
	}
	idx -= NUMHASH;
    }
    ASSERT(start != NULL);
    ASSERT(idx >= 0 && idx < NUMHASH);

    ap->a_idx = NUMHASH - idx;
    *llc = start;
    *block = start->ll_addr[--ap->a_idx];
    DBG("Leaving function getblk (a_idx %d)", ap->a_idx, 0, 0);
    return 0;
}

/*
 * Read the file corresponding to the supplied anode.
 */

/* ARGSUSED */
int reada(anode_t * ap, struct uio * uiop, int ioflag)
{
    const int a_data_sz = sizeof(struct a_data);
    unsigned int on, n;
    int error = 0, bsize;
    struct fbuf *mapp;
    struct ados *afsp = ADOS_VFS(ATOV(ap)->v_vfsp);
    vnode_t *dvp = afsp->vfs_devvp;
    struct llchain *current = NULL;
    LONG block, maxblock = afsp->vfs_root.a_keyblk << 1;

IN_FUNC(reada);
    DBG("... from 0x%x", ((long *) &ap)[-1], 0, 0);
    if (MANDLOCK(ATOV(ap), ap->a_mode)
	&& (error = chklock(ATOV(ap), FREAD,
		       uiop->uio_offset, uiop->uio_resid, uiop->uio_fmode)))
	return error;
    if (uiop->uio_offset < 0)
	return EINVAL;
    if (uiop->uio_resid == 0 || uiop->uio_offset >= ap->a_size)
	return 0;

    bsize = (1 << afsp->vfs_bufbits);
    do {
	/*
	 * Prepare to map in a 512-byte chunk of the file for I/O. Compute n,
	 * the number of bytes which can be read from this mapping.
	 */
	if (!(ap->a_mode & S_IFDIR) && (afsp->vfs_dos & SMALL_BLOCK))
	    on = (uiop->uio_offset % (bsize - a_data_sz)) + a_data_sz;
	else
	    on = uiop->uio_offset & (bsize - 1);

	n = MIN(bsize - on, uiop->uio_resid);
	n = (ap->a_size < uiop->uio_offset) ?
	    0 : MIN(n, ap->a_size - uiop->uio_offset);
	if (n == 0) {
	    DBG("no more data requested/available", 0, 0, 0);
	    break;
	}
	if (error = getblk(ap, uiop->uio_offset, &current, &block)) {
	    DBG("error %d from getblk(.,%d,..)", error, uiop->uio_offset, 0);
	    break;
	}
	if (current == NULL || block == 0) {
	    DBG("EOF; offset %d, blk %d, llchn %x",
		uiop->uio_offset, block, current);
	    break;
	}
	DBG("key %d, idx %d, block %d", current->ll_keyblk, ap->a_idx, block);
	if (block < 0 || block > maxblock) {
	    error = EIO;
	    break;
	}
/*	error = fbread(dvp, block * bsize + on, n, S_WRITE, &mapp);	*/
	error = fbread(dvp, block * bsize, bsize, S_WRITE, &mapp);

	if (!error) {
	    error = uiomove(mapp->fb_addr + on, n, UIO_READ, uiop);
	    fbrelse(mapp, S_OTHER);
	} else {
	    DBG("error %d from fbread", error, 0, 0);
	}
    } while (error == 0 && uiop->uio_resid > 0);
    if (error) {
	DBG("error %d from getblk/fbread/uiomove", error, 0, 0);
	DBG("dvp = 0x%x, block = %d, bsize = %d", dvp, block, bsize);
	DBG("mapp->fb_addr = %x, on = %d, n = %d", mapp->fb_addr, on, n);
    }
    return error;
}
