/* adosfs_blocks.c 930821 Niklas Hallqvist <niklas@appli.se> */

/* This file contains code based on work by Frank J. Edwards.  Be sure to
   read his copyright conditions if you intend to use this work.  */

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

#include "types.h"
#include "param.h"
#include "mount.h"
#include "vnode.h"
#include "systm.h"
#include "errno.h"
#include "malloc.h"
#include "buf.h"
#include "time.h"
#include "file.h"
#include "stat.h"

#include "adosfs_defs.h"
#include "adosfs_date.h"
#include "adosfs_udir.h"
#include "adosfs_root.h"
#include "adosfs_file.h"
#include "adosfs_anode.h"
#include "adosfsmount.h"

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

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

static inline int
chksum (void *loc, int num)
{
  ados_long a = 0, *p = (ados_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;
  
  if (!chksum (ptr, sizeof (*ptr) / sizeof (ados_long)))
    {
#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_BLOCKS)
	printf ("bad checksum, PT %d, ST %d, blk %d\n", FILE->uf_type,
		FILE->uf_type2, FILE->uf_hkey);
#endif
      *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)
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("unimpl header type %d\n", FILE->uf_type2);
#endif
	  *err = EINVAL /* UnImplemented */;
	  return EIO;
	}
      if (FILE->uf_type2 == ST_FILE || FILE->uf_type2 == ST_USERDIR)
	{
	  if (!UDIR->ud_hkey || !UDIR->ud_parent)
	    {
#ifdef ADOSFSDEBUG
	      if (adosfs_debug & AD_BLOCKS)
		printf ("bad hkey (0) or parent (0)\n");
#endif
	      *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)))
		{
#ifdef ADOSFSDEBUG
		  if (adosfs_debug & AD_BLOCKS)
		    printf ("non-zero rsvp (USERDIR)\n");
#endif
		  *err = EINVAL /* NonZeroRsvp */;
		  return EIO;
		}
	    }
	  else if (FILE->uf_type2 == ST_FILE)
	    {
	      if (!zero (FILE->uf_rsvp1, SIZEOF (FILE->uf_rsvp1)))
		{
#ifdef ADOSFSDEBUG
		  if (adosfs_debug & AD_BLOCKS)
		    printf ("non-zero rsvp (FILE)\n");
#endif
		  *err = EINVAL;
		  return EIO;
		}
	      if (FILE->uf_hiseq > 72 && !FILE->uf_exten)
		{
#ifdef ADOSFSDEBUG
		  if (adosfs_debug & AD_BLOCKS)
		    printf ("hiseq %d, exten %d (FILE)\n", FILE->uf_hiseq,
			    FILE->uf_exten);
#endif
		  *err = EINVAL /* FileInconsistent */;
		  return EIO;
		}
	    }
	}
      else if (FILE->uf_type2 == ST_ROOT)
	{
	  if (!zero (ROOT->rd_rsvp1, SIZEOF (ROOT->rd_rsvp1)))
	    {
#ifdef ADOSFSDEBUG
	      if (adosfs_debug & AD_BLOCKS)
		printf ("non-zero root rsvp1\n");
#endif
	      *err = EINVAL /* NonZeroRsvp */;
	      return EIO;
	    }
	  else if (!zero (ROOT->rd_rsvp2, SIZEOF (ROOT->rd_rsvp2)))
	    {
#ifdef ADOSFSDEBUG
	      if (adosfs_debug & AD_BLOCKS)
		printf ("non-zero root rsvp2\n");
#endif
	      *err = EINVAL /* NonZeroRsvp */;
	      return EIO;
	    }
	  else if (ROOT->rd_hashsz != NUMHASH)
	    {
#ifdef ADOSFSDEBUG
	      if (adosfs_debug & AD_BLOCKS)
		printf ("bad hashsz field %d (ROOT)\n", ROOT->rd_hashsz);
#endif
	      *err = EINVAL /* RootHasWrongHashSize */;
	      return EIO;
	    }
	  else if (!zero (ROOT->rd_rsvp3, SIZEOF (ROOT->rd_rsvp3)))
	    {
#ifdef ADOSFSDEBUG
	      if (adosfs_debug & AD_BLOCKS)
		printf ("non-zero root rsvp3 [%d] [%d]\n", ROOT->rd_rsvp3[0],
			ROOT->rd_rsvp3[1]);
#endif
	      *err = EINVAL /* NonZeroRsvp */;
	      return EIO;
	    }
	}
      return 0;
    case PT_LIST:
      if (!FILE->uf_hkey || !FILE->uf_parent)
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("bad hkey or parent (LIST)\n");
#endif
	  *err = EINVAL /* BadKeyOrParent */;
	  return EIO;
	}
      if (!zero (FILE->uf_rsvp1, SIZEOF (FILE->uf_rsvp1)))
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("non-zero rsvp (LIST)\n");
#endif
	  *err = EINVAL /* NonZeroRsvp */;
	  return EIO;
	}
      return 0;
    }
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_BLOCKS)
    {
      printf ("Shouldn't get here!\n");
      printf ("block @ 0x%x, PT %d, ST %d\n", ptr, FILE->uf_type,
	      FILE->uf_type2);
    }
#endif
  
  *err = EINVAL /* UnImplemented */;
  return EIO;
}

#undef FILE
#undef UDIR
#undef ROOT

int
adosfs_walkchn(int dos, struct vnode *dvp, ados_u_long *block, int bsize,
	       struct llchain *fill, struct buf *rbp)
{
  struct buf *bp;
  struct a_file *file;
  int error;

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_BLOCKS)
    printf ("adosfs_walkchn: block %d\n", *block);
#endif
  if (rbp)
    bp = rbp;
  else
    {
      error = bread (dvp, *block, bsize, NOCRED, &bp);
      if (error)
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("adosfs_walkchn: error %d from bread()\n", error);
#endif
	  return error;
	}
    }
  file = AFILE (bp->b_un.b_addr);
  if (sanitychk (file, &error, dos))
    {
#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_BLOCKS)
	printf ("error %d from sanitychk()\n", error);
#endif
      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);
    }
  error = 0;

 exit:
  if (!rbp)
    brelse (bp);
  return error;
}

void
adosfs_freechn (struct anode *ap)
{
  struct llchain *list, *nlist;
  
  for (list = ap->a_ll; list; list = nlist)
    {
      nlist = list->ll_forw;
      if (nlist != NULL && nlist->ll_back != list)
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("ll_back links error; anode 0x%x\n", ap);
#endif
	  break;
	}
#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_BLOCKS)
	printf ("free'ing 0x%x (%d bytes)\n", list, sizeof (*list));
#endif
      FREE (list, M_LLCHAIN);
    }
  ap->a_ll = NULL;
  ap->a_idx = -1;		/* Force ados_cache_dir() to be called. */
}

/*
 * 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.
 */

struct llchain *
adosfs_allocchn (struct llchain *old)
{
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_BLOCKS)
    printf ("adosfs_allocchn: about to chain on to 0x%x\n", old);
#endif
  MALLOC (old->ll_forw, struct llchain *, sizeof *old, M_LLCHAIN, M_WAITOK);
  bzero (old->ll_forw, sizeof *old);
  old->ll_forw->ll_back = old;
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_BLOCKS)
    printf ("adosfs_allocchn: chained 0x%x\n", old->ll_forw);
#endif
  return old->ll_forw;
}

struct llchain *
adosfs_addblk (struct llchain *old, ados_long block)
{
  struct llchain *next = old->ll_forw;

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_BLOCKS)
    printf ("adosfs_addblk: next: 0x%x, next->ll_next: %d\n", next,
	    next->ll_next);
#endif
  if (next && next->ll_next <= 0)
    {
#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_BLOCKS)
	printf ("adosfs_addblk: forw link from 0x%x\n", next);
#endif
      old = next;
      next = old->ll_forw;
      if (next != 0)
	panic ("adosfs_addblk: next != 0");
    }
  if (!next)
    {
      next = adosfs_allocchn (old);
      next->ll_next = NUMHASH;
#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_BLOCKS)
	printf ("adosfs_addblk: 0x%x chains to 0x%x\n", old, next);
#endif
    }
  next->ll_addr[--next->ll_next] = block;
  return old;
}

int
adosfs_getblk (struct anode *ap, int idx, ados_u_long *block)
{
  register struct llchain *start;
  register int error, i;
  struct adosfsmount *amp = VFSTOADOSFS (ATOV (ap)->v_mount);
  struct vnode *dvp = amp->am_devvp;
  ados_u_long blk;
  int bsize = 512;

  *block = 0;
  if (!(ap->a_mode & S_IFDIR) && (amp->am_dos == 0))
    idx /= bsize - sizeof (struct a_data);
  else
    idx /= bsize;

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_BLOCKS)
    printf ("block %d of ap @ %x\n", idx, ap);
#endif
  if (ap->a_idx < 0)
    {
      if (ap->a_mode & S_IFDIR)
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("Calling adosfs_cache_dir on '%s'\n", ap->a_name);
#endif
	  error = adosfs_cache_dir (dvp, ap, NULL);
	}
      else
	{
	  blk = ap->a_keyblk;
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("FILE key from block %d\n", blk);
#endif
	  MALLOC (ap->a_ll, struct llchain *, sizeof *ap->a_ll, M_LLCHAIN,
		  M_WAITOK);
	  bzero (ap->a_ll, sizeof *ap->a_ll);
	  error = adosfs_walkchn (amp->am_dos, dvp, &blk, bsize, ap->a_ll,
				  NULL);
	}
      if (error)
	return error;
    }

  /* Is the code below really OK?  Why?  */
  if (ap->a_mode & S_IFDIR)
    start = ap->a_ll->ll_forw;	/* adosfs_cache_dir() skips a_ll */
  else
    start = ap->a_ll;

  /* Check for an empty directory.  */
  if (start == NULL)
    return 0;

  while (idx >= NUMHASH)
    {
      if (start->ll_forw)
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("in-core, idx %d, going to 0x%x\n", idx, start->ll_forw);
#endif
	  start = start->ll_forw;
	}
      else if (ap->a_mode & S_IFDIR)
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("read past end of dir (chn @ 0x%x)\n", start);
#endif
	  return 0;
	}
      else
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_BLOCKS)
	    printf ("block %d --> %d\n", start->ll_keyblk, start->ll_exten);
#endif
	  if (!(blk = start->ll_exten))
	    return 0;		/* Attempt to move past EOF */
	  start = adosfs_allocchn (start);
	  error = adosfs_walkchn (amp->am_dos, dvp, &blk, bsize, start, NULL);
	  if (error)
	    return error;
	}
      idx -= NUMHASH;
    }
  if (start == NULL)
    panic ("adosfs_getblk: start == 0");
  if (idx < 0 || idx >= NUMHASH)
    panic ("adosfs_getblk: idx < 0 || idx >= NUMHASH");

  ap->a_idx = NUMHASH - idx;
  *block = start->ll_addr[--ap->a_idx];
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_BLOCKS)
    printf ("Leaving function adosfs_getblk (a_idx %d)\n", ap->a_idx);
#endif
  return 0;
}
