/* adosfs_util.c 930819 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 "param.h"
#include "types.h"
#include "time.h"
#include "errno.h"
#include "stat.h"
#include "systm.h"
#include "time.h"
#include "vnode.h"
#include "buf.h"		/* For bread() and friends */
#include "file.h"		/* For FREAD */
#include "mount.h"
#include "malloc.h"

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

#define MINS	60
#define DAYS	(24 * 60 * MINS)
#define DAYLIGHT 1		/* Dynamic ??? */

/*
 * Convert an AmigaDOS DateStamp into a Unix timeval
 */
struct timeval *date2unix (DateStamp *ds)
{
  struct timeval timex;

  /* days * DAYS + mins * MINS + 8years + 5hrs - (60 * MINS) (dst!?) */

  timex.tv_usec = 0;
  timex.tv_sec = ((ds->ds_Days + 8 * 365 + 2) * DAYS +
		  (ds->ds_Minute + (DAYLIGHT ? 240 : 300)) * MINS);
  return &timex;
}

/*
 * Convert a Unix timestruc_t into an AmigaDOS DateStamp
 */
DateStamp *time2ados (time_t timex)
{
  static DateStamp ds;
  struct tm *tm;

#if 0
  tm = gmtime (&timex);
  tm->tm_year -= 8;		/* AmigaDOS is from 1978, not 1970 */

  ds.ds_Days = tm->tm_yday + (tm->tm_year * 365) + (tm->tm_year / 4);
  ds.ds_Minute = tm->tm_hour * 60 * 60 + tm->tm_min * 60;
  ds.ds_Tick = tm->tm_sec * 50 / 60;
#endif
  return &ds;
}

/*
 * Convert AmigaDOS protection bits to Unix protection bits. AmigaDOS
 * protection bits are "SPARWED", bit6 to bit0. Delete permission within
 * AmigaDOS is handled with a bit which applies to a particular file.  Under
 * Unix, this is done by removing write permission to the parent directory.
 * We simulate the delete bit by turning on the "sticky" bit. Otherwise, the
 * Read, Write, and Execute bits are mapped to the same permissions under
 * Unix.
 */

mode_t modes2unix (ados_long type, ados_long prot)
{
  mode_t m;

  /* The bit is on (1) if the permission is DENIED!  */
  m = ((~prot >> 1) & 0x7) * 0111;
  if (prot & 1)
    m |= S_ISVTX;

  switch (type)
    {
    case ST_LINKDIR:
    case ST_USERDIR:
    case ST_ROOT:
      m |= S_IFDIR;
      break;

    case ST_LINKFILE:
    case ST_FILE:
      m |= S_IFREG;
      break;

    case ST_SOFTLINK:
      m |= S_IFLNK;
      break;
    }
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_UTIL)
    printf ("from %x to %x", prot, (int) m, 0);
#endif
  return m;
}

/*
 *  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
adosfs_cache_dir (struct vnode *dvp, struct anode* ap, struct buf *bp)
{
  register struct llchain *new;
  register int i, count = 0, others = 0;
  struct adosfsmount *amp = VFSTOADOSFS (ATOV (ap)->v_mount);
  int error, bsize = 512;
  ados_u_long blk;
  struct buf *ebp;

  blk = ap->a_keyblk;		/* Key block of directory */
  MALLOC (new, struct llchain *, sizeof *new, M_LLCHAIN, M_WAITOK);
  bzero (new, sizeof *new);
  new->ll_next = NUMHASH;
  ap->a_ll = new;

  if (error = adosfs_walkchn (amp->am_dos, dvp, &blk, bsize, new, bp))
    {
#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_UTIL)
	printf ("error %d from adosfs_walkchn; block %d (%d)\n", error,
		ap->a_keyblk, blk);
#endif
      return error;
    }

  /* I'm not sure what is best to do with this slot.  AmigaDOS *does* have
     hardlinks so there is a natural meaning we could use.  On the other
     hand this differs from standard Unix filesystems, where "." and ".."
     counts as links to directories.  Both users and programs use this
     fact to determine the number of subdirectories a directory has.  It
     might be confusing to give this slot a value which don't correspond
     to the Unix view.  Until I decide this slot will be zero!
     Well, I've decided to simulate Unix approximately (subdircount + 2).  */
#if 0
  ap->a_nlink = ap->a_blocks + 2;
#else
  ap->a_nlink = 2;
#endif
  i = NUMHASH;
  while (i-- > 0)
    {
      if (blk = ap->a_ll->ll_addr[i])
	{
	  count++;
	  new = adosfs_addblk (new, blk);
	  error = bread (amp->am_devvp, blk, bsize, NOCRED, &ebp);
	  if (error)
	    return error;
	  if (AFILE (ebp->b_un.b_addr)->uf_type2 == ST_USERDIR)
	    ap->a_nlink++;
	  while (((error
		   = adosfs_walkchn (amp->am_dos, dvp, &blk, bsize, NULL, ebp))
		  == 0)
		 && blk)
	    {
	      brelse (ebp);
#ifdef ADOSFSDEBUG
	      if (adosfs_debug & AD_UTIL)
		printf ("idx %d, block %d\n", i, blk);
#endif
	      others++;
	      new = adosfs_addblk (new, blk);
	      error = bread (amp->am_devvp, blk, bsize, NOCRED, &ebp);
	      if (error)
		return error;
	      if (AFILE (ebp->b_un.b_addr)->uf_type2 == ST_USERDIR)
		ap->a_nlink++;
	    }
	  brelse (ebp);
	  if (error)
	    {
#ifdef ADOSFSDEBUG
	      if (adosfs_debug & AD_UTIL)
		printf ("abandoning block %d (chain %d)\n", blk, i);
#endif
	    }
	}
    }
  ap->a_blocks = count + others;
  ap->a_size = ap->a_blocks * bsize;
  
  if (ap->a_ll->ll_exten != 0)	/* Directories don't have extensions */
    panic ("ados_cache_dir: ll_exten != 0");
  ap->a_ll->ll_keyblk = ap->a_keyblk;
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_UTIL)
    printf ("key %d, size %d, blocks %d\n", ap->a_keyblk, ap->a_size,
	    ap->a_blocks);
#endif
  return 0;
}

/*
 * Read the anode header block from disk and initialize all of the pertinent
 * entries in (struct anode).
 */
int adosfs_read_anode (struct anode *ap, ados_u_long key,
		     struct adosfsmount *amp)
{
  register struct vnode *vp = ATOV (ap);
  struct buf *bp;
  register struct a_udir *ud;
  int len;
  int error;

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_UTIL)
    printf ("ados_read_anode: looking for block %d\n", key);
#endif /* ADOSFSDEBUG */
  error = bread (amp->am_devvp, key, 512, NOCRED, &bp);
  if (error)
    return error;
  ud = AUDIR (bp->b_un.b_addr);

  len = ud->ud_name[0];
  bcopy (ud->ud_name + 1, ap->a_name, len);
  ap->a_name[len] = '\0';
  if (ud->ud_type2 == ST_ROOT)
    {
      vp->v_flag = VROOT;
      ap->a_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
      ap->a_parent = key;
      strcpy (amp->am_fstr, ap->a_name);
    }
  else
    {
      ap->a_mode = modes2unix (ud->ud_type2, ud->ud_prot);
      ap->a_parent = ud->ud_parent;
    }

  ap->a_mtime = *date2unix (&ud->ud_dirstmp);
  ap->a_uid = 0;
  ap->a_gid = 0;
  ap->a_keyblk = key;

  ap->a_idx = -1;	   /* adosfs_getblk/adosfs_getattr checks this
			      condition.  */
  if (ud->ud_type2 == ST_ROOT || ud->ud_type2 == ST_USERDIR)
    {
      vp->v_type = VDIR;
#if 1
      error = adosfs_cache_dir (amp->am_devvp, ap, bp);
      ap->a_idx = 0;
#endif
   }
  else if (ud->ud_type2 == ST_SOFTLINK)
    {
      vp->v_type = VLNK;
      ap->a_nlink = 1;
      ap->a_size = strlen ((char *)AFILE (ud)->uf_data);
    }
  else
    {
      vp->v_type = VREG;
      ap->a_nlink = 1;		/* This isn't really OK, but most of the time
				   it should reflect reality.  */
      ap->a_size = AFILE (ud)->uf_size;
#if 0
      ap->a_blocks = (ap->a_size + vfsp->vfs_bsize - 1) / vfsp->vfs_bsize;
#else
      ap->a_blocks = (ap->a_size + 511) / 512;
#endif
    }
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_UTIL)
    printf ("ap @ %x, error %d\n", ap, error, 0);
#endif

  brelse (bp);
  return error;
}

#if 0
/*
 * Search a directory for an entry named nm. Return anode pointer if found,
 * NULL otherwise.
 */
anode_t *ados_searchdir(vnode_t * dvp, char *nm, struct cred * cr)
{
    struct ados *afsp = ADOS_VFS(dvp->v_vfsp);
    anode_t *np;
    int error;

IN_FUNC(ados_searchdir);
    error = ados_dotsearch(afsp, VTOA(dvp), nm, cr, &np, (off_t *) 0);
IN_FUNC(ados_searchdir);
    if (error)
	return 0;
    return np;
}

/*
 * Search a directory for an entry. If found, return dir offset of the entry
 * (in offp) and pointer to the anode (in npp).  If not found, return dir
 * offset of first empty slot (in offp) or 0.
 */

/* ARGSUSED */
int ados_dotsearch(struct ados * afsp, anode_t * dnp, char *nm,
		       struct cred * cr, anode_t ** npp, off_t * offp)
{
    struct uio iod;
    struct iovec iov;
    struct a_udir udir;
    register int error = 0, block, bsize = 1 << afsp->vfs_bufbits;

IN_FUNC(ados_dotsearch);
    iod.uio_segflg = UIO_SYSSPACE;
    iod.uio_fmode = FREAD;
    iod.uio_limit = -1;
    iod.uio_iov = &iov;
    iod.uio_iovcnt = 1;
    for (block = 0; !error && block < dnp->a_size; block += bsize) {
	iod.uio_resid = sizeof(udir);
	iod.uio_offset = block;
	iov.iov_base = (caddr_t) &udir;
	iov.iov_len = sizeof(udir);

	if ((error = reada(dnp, &iod, 0)) || iod.uio_resid == bsize)
	    break;
	if (iod.uio_resid == 0) {
	    udir.ud_name[ (int)udir.ud_name[0]+1 ] = '\0';
	    DBG("checking %s", (char *)udir.ud_name+1, 0, 0);
	    if (strcmp((char *) udir.ud_name+1, nm) == 0) {
		error = aget(afsp->vfs_vfsp, udir.ud_hkey, npp);
		if (offp)
		    *offp = block;
		return error;
	    }
	} else if (iod.uio_resid != sizeof(udir))
	    error = EIO;
    }
IN_FUNC(ados_dotsearch);
    return error ? error : ENOENT;
}
#endif
