/* adosfs_vnops.c 930819 Niklas Hallqvist <niklas@appli.se> */

/* Bits & pieces of the code herein comes from the ufs directory of the
   original NetBSD distribution and also from Paul Popelkas pcfs code as
   well as Frank J. Edwards' Amiga Unix AmigaDOS filesystem, hence the
   Copyright notices.  */

/*
 * Copyright (c) 1989 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written ion.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

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

/*
 *  Written by Paul Popelka (paulp@uts.amdahl.com)
 *
 *  You can do anything you want with this software,
 *    just don't say you wrote it,
 *    and don't remove this notice.
 *
 *  This software is provided "as is".
 *
 *  The author supplies this software to be publicly
 *  redistributed on the understanding that the author
 *  is not responsible for the correct functioning of
 *  this software in any circumstances and is not liable
 *  for any damages caused by this software.
 *
 *  October 1992
 */

#include "param.h"
#include "types.h"
#include "systm.h"
#include "errno.h"
#include "vnode.h"
#include "namei.h"
#include "proc.h"
#include "stat.h"
#include "mount.h"
#include "buf.h"
#include "file.h"
#include "uio.h"
#include "malloc.h"

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

int adosfs_debug = 0;
int adosfs_prtactive = 0;

inline u_char
toupper (u_char c)
{
  return (c >= 'a' && c <= 'z') ? c - 'a' + 'A' : c;
}

inline u_int
adosfs_hash (u_long len, u_char *s)
{
  u_int val;

  val = len;
  while (len--)
    val = (val * 13 + toupper (*s++)) & 0x7ff;
  return val % 72;
}

int
adosfs_dummy ()
{
#ifdef ADOSFSDEBUG
  printf ("adosfs_dummy\n");
#endif
  return EOPNOTSUPP;
}

int adosfs_lookup (struct vnode *vdp, struct nameidata *ndp, struct proc *p)
{
  register struct anode *dp;	/* the directory we are searching */
  struct buf *bp = 0;		/* a buffer of directory entries */
  struct anode *pdp;		/* saved dp during symlink work */
  struct anode *tdp;		/* returned by iget */
  int flag;			/* LOOKUP, CREATE, RENAME, or DELETE */
  int lockparent;		/* 1 => lockparent flag is set */
  int wantparent;		/* 1 => wantparent or lockparent flag */
  ados_u_long key;
  int error;
  struct adosfsmount *amp = VFSTOADOSFS (vdp->v_mount);
  ados_u_long maxblock = amp->am_rootblk << 1;
  struct vnode *devvp = amp->am_devvp;
  int i, len;
  u_int hash;
  ados_u_char nm[64];

  ndp->ni_dvp = vdp;
  ndp->ni_vp = NULL;
  dp = VTOA (vdp);
  lockparent = ndp->ni_nameiop & LOCKPARENT;
  flag = ndp->ni_nameiop & OPMASK;
  wantparent = ndp->ni_nameiop & (LOCKPARENT | WANTPARENT);

  /*
   * Check accessiblity of directory.
   */
  if ((dp->a_mode & S_IFMT) != S_IFDIR)
    return ENOTDIR;
  if (error = adosfs_access (vdp, VEXEC, ndp->ni_cred, p))
    return (error);

#ifdef ADOSFSDEBUG
  bcopy (ndp->ni_ptr, nm, ndp->ni_namelen);
  nm[ndp->ni_namelen] = '\0';
  if (adosfs_debug & AD_VNOPS)
    printf ("adosfs_lookup: looking for \"%s\" in vp 0x%x\n", nm, vdp);
#endif
/*
   * We now have a segment name to search for, and a directory to search.
   *
   * Before performing a hash search of the directory,
   * check the name cache to see if the directory/name pair
   * we are looking for is known already.
   */
  if (error = cache_lookup (ndp))
    {
      int vpid;			/* capability number of vnode */

#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_VNOPS)
	printf ("cache_lookup returned non-zero status %d\n", error);
#endif
      if (error == ENOENT)
	return error;
#ifdef PARANOID
      if (vdp == ndp->ni_rootdir && ndp->ni_isdotdot)
	panic ("adosfs_lookup: .. through root");
#endif
      /*
       * Get the next vnode in the path.
       * See comment below starting `Step through' for
       * an explaination of the locking protocol.
       */
      pdp = dp;
      dp = VTOA (ndp->ni_vp);
      vdp = ndp->ni_vp;
      vpid = vdp->v_id;
      if (pdp == dp)
	{
	  VREF (vdp);
	  error = 0;
	}
      else if (ndp->ni_isdotdot)
	{
	  AUNLOCK(pdp);
	  error = vget (vdp);
	  if (!error && lockparent && *ndp->ni_next == '\0')
	    ALOCK (pdp);
	}
      else
	{
	  error = vget (vdp);
	  if (!lockparent || error || *ndp->ni_next != '\0')
	    AUNLOCK(pdp);
	}
      /*
       * Check that the capability number did not change
       * while we were waiting for the lock.
       */
      if (!error)
	{
	  if (vpid == vdp->v_id)
	    {
#ifdef ADOSFSDEBUG
	      if (adosfs_debug & AD_VNOPS)
		printf("cache hit, vnode %08x, file %s, blk %d\n", vdp,
		       dp->a_name, dp->a_keyblk);
#endif
	      return 0;
	    }
	  aput(dp);
	  if (lockparent && pdp != dp && *ndp->ni_next == '\0')
	    AUNLOCK (pdp);
	}
      ALOCK (pdp);
      dp = pdp;
      vdp = ATOV (dp);
      ndp->ni_vp = NULL;
    }

  if (ndp->ni_namelen == 1 && ndp->ni_ptr[0] == '.')
    VREF (ndp->ni_vp = vdp);
  else if (ndp->ni_namelen == 2 && ndp->ni_ptr[0] == '.'
	   && ndp->ni_ptr[1] == '.')
    {
      error = aget (amp, dp->a_parent, &pdp);
      AUNLOCK (dp);
      ndp->ni_vp = ATOV (pdp);
      if (!error && lockparent && *ndp->ni_next == '\0')
	ALOCK (dp);
    }
  else
    {
      dp = VTOA (vdp);
      hash = adosfs_hash (ndp->ni_namelen, ndp->ni_ptr);
      if (!dp->a_ll)
	{
	  error = bread (devvp, dp->a_keyblk, 512, NOCRED, &bp);
	  if (error)
	    return error;
	  key = AFILE (bp->b_un.b_addr)->uf_data[hash];
	  brelse (bp);
	}
      else
	key = dp->a_ll->ll_addr[hash];
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_VNOPS)
	    printf ("hash %d start looking at %d\n", hash, key);
#endif
      while (key)
	{
	  u_long next_key;

#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_VNOPS)
	    printf ("# %d", key);
#endif
	  error = bread (devvp, key, 512, NOCRED, &bp);
	  if (error)
	    return error;
	  next_key = AFILE (bp->b_un.b_addr)->uf_hashchn;
	  bcopy (AFILE (bp->b_un.b_addr)->uf_name + 1, nm,
		 len = AFILE (bp->b_un.b_addr)->uf_name[0]);
	  brelse (bp);
#ifdef ADOSFSDEBUG
	  nm[len] = '\0';
	  if (adosfs_debug & AD_VNOPS)
	    printf (" == \"%s\"? ", nm);
#endif
	  if (ndp->ni_namelen == len && bcmp (nm, ndp->ni_ptr, len) == 0)
	    goto found;
	  key = next_key;
#ifdef ADOSFSDEBUG
          if (adosfs_debug & AD_VNOPS)
	    printf ("FAIL\n");
#endif
	}

      /*
       * Insert name into cache (as non-existent) if appropriate.
       */
      if (ndp->ni_makeentry && flag != CREATE)
	cache_enter (ndp);
      return ENOENT;

    found:
#ifdef ADOSFSDEBUG
          if (adosfs_debug & AD_VNOPS)
	    printf ("FOUND\n");
#endif
      if (error = aget (amp, key, &tdp))
	return error;
      if (!lockparent || *ndp->ni_next != '\0')
	AUNLOCK (dp);
      ndp->ni_vp = ATOV (tdp);
    }

  /*
   * Insert name into cache if appropriate.
   */
  if (ndp->ni_makeentry)
    cache_enter (ndp);
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VNOPS)
    printf ("lookup found vp 0x%x\n", ndp->ni_vp);
#endif
  return 0;
}

int
adosfs_open (struct vnode *vp, int mode, struct ucred *cred, struct proc *p)
{
  return 0;
}

int
adosfs_close (struct vnode *vp, int fflag, struct ucred *cred, struct proc *p)
{
  return 0;
}

int
adosfs_access(struct vnode *vp, int mode, struct ucred *cred, struct proc *p)
{
  register struct anode *ap = VTOA (vp);
  register gid_t *gp;
  int i, error;

#ifdef DIAGNOSTIC
  if (!VOP_ISLOCKED (vp))
    {
      vprint ("adosfs_access: not locked", vp);
      panic ("adosfs_access: not locked");
    }
#endif
  /* If you're the super-user, you always get access.  */
  if (cred->cr_uid == 0)
    return 0;
  /*
   * Access check is based on only one of owner, group, public.
   * If not owner, then check group. If not a member of the
   * group, then check public access.
   */
  if (cred->cr_uid != ap->a_uid)
    {
      mode >>= 3;
      gp = cred->cr_groups;
      for (i = 0; i < cred->cr_ngroups; i++, gp++)
	if (ap->a_gid == *gp)
	  goto found;
      mode >>= 3;
    found:
      ;
    }
  if ((ap->a_mode & mode) == mode)
    return 0;
  return EACCES;
}

int
adosfs_getattr (struct vnode *vp, struct vattr *vap, struct ucred *cred,
		struct proc *p)
{
  struct anode *ap = VTOA (vp);

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VNOPS)
    printf ("adosfs_getattr: ap = 0x%x\n", ap);
#endif
#if 0
  if (vp->v_type == VDIR && ap->a_idx < 0))
    adosfs_cache_dir (VFSTOADOSFS (vp->v_mount)->am_devvp, ap, NULL);
#endif
  vap->va_type = vp->v_type;
  vap->va_fsid = ap->a_dev;
  vap->va_fileid = ap->a_keyblk;
  vap->va_mode = ap->a_mode;
  vap->va_nlink = ap->a_nlink;
  vap->va_size = ap->a_size;
  vap->va_uid = ap->a_uid;
  vap->va_gid = ap->a_gid;
  vap->va_mtime = ap->a_mtime;
  vap->va_blocksize = 512;
  vap->va_bytes = ap->a_blocks * 512;
  vap->va_bytes_rsv = 0;
  return 0;
}

int
adosfs_read (struct vnode *vp, struct uio *uio, int ioflag, struct ucred *cred)
{
  struct anode *ap = VTOA (vp);
  struct adosfsmount *amp = VFSTOADOSFS (vp->v_mount);
  struct vnode *dvp = amp->am_devvp;
  struct buf *bp;
  ados_u_long block;
  ados_u_long maxblock = amp->am_rootblk << 1;
  int bsize = 512;
  int error;
  int on, n;
  const int a_data_sz = sizeof (struct a_data);

  if (uio->uio_offset < 0)
    return EINVAL;
  if (uio->uio_resid == 0 || uio->uio_offset >= ap->a_size)
    return 0;

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VNOPS)
    printf ("adosfs_read: vp 0x%x, offset %d len %d\n", vp, uio->uio_offset,
	uio->uio_resid);
#endif
  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) && (amp->am_dos == 0))
	on = (uio->uio_offset % (bsize - a_data_sz)) + a_data_sz;
      else
	on = uio->uio_offset & (bsize - 1);

      n = MIN (bsize - on, uio->uio_resid);
      n = ((ap->a_size < uio->uio_offset) ?
	   0 : MIN (n, ap->a_size - uio->uio_offset));
      if (n == 0)
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_VNOPS)
	    printf ("no more data requested/available\n");
#endif
	  break;
	}
      if (error = adosfs_getblk (ap, uio->uio_offset, &block))
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_VNOPS)
	    printf ("error %d from adosfs_getblk(.,%d,..)\n", error,
		    uio->uio_offset);
#endif
	  break;
	}
      if (block == 0)
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_VNOPS)
	    printf ("EOF; offset %d, blk %d\n", uio->uio_offset, block);
#endif
	  break;
	}
#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_VNOPS)
	printf ("idx %d, block %d\n", ap->a_idx, block);
#endif
      if (block < 0 || block > maxblock)
	{
	  error = EIO;
	  break;
	}
      error = bread (dvp, block, bsize, NOCRED, &bp);
      if (!error)
	{
	  error = uiomove (bp->b_un.b_addr + on, n, uio);
	  brelse (bp);
	}
      else
	{
#ifdef ADOSFSDEBUG
	  if (adosfs_debug & AD_VNOPS)
	    printf ("error %d from bread\n", error);
#endif
	}
    }
  while (error == 0 && uio->uio_resid > 0);
#ifdef ADOSFSDEBUG
  if ((adosfs_debug & AD_VNOPS) && error)
    {
      printf ("error %d from adosfs_getblk/bread/uiomove\n", error);
      printf ("dvp = 0x%x, block = %d, bsize = %d\n", dvp, block, bsize);
      printf ("bp->b_un.b_addr = %x, on = %d, n = %d\n", bp->b_un.b_addr, on,
	      n);
    }
#endif
  return error;
}

/*
 *  Dummy dirents to simulate the "." and ".." entries
 *  in an AmigaDOS filesystem.  Dos doesn't provide these.
 */
struct adosfs_dirent {
  u_long d_fileno;
  u_short d_reclen;
  u_short d_namlen;
  u_char d_name[31];
};

#define adosfs_dirent_size ((sizeof (struct adosfs_dirent) + 3) & ~3)

static struct adosfs_dirent dots[2] = {
  {
    0,				/* d_fileno			*/
    adosfs_dirent_size,		/* d_reclen			*/
    1,				/* d_namlen			*/
    "."				/* d_name			*/
  }, {
    0,				/* d_fileno			*/
    adosfs_dirent_size,		/* d_reclen			*/
    2,				/* d_namlen			*/
    ".."			/* d_name			*/
  }
};

int
adosfs_readdir (struct vnode *vp, struct uio *uio, struct ucred *cred,
		int *eofflagp)
{
  struct anode *ap = VTOA (vp);
  struct mount *mp = vp->v_mount;
  struct buf *bp;
  unsigned int count, lost;
  int error = 0;
  int blkno, tot = 0;
  ados_u_long blk;
  char entbuf[adosfs_dirent_size];
  struct adosfs_dirent *ent = (struct adosfs_dirent *)entbuf;

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VNOPS)
    printf("adosfs_readdir(): vp %08x, uio %08x, cred %08x, eofflagp %08x\n",
	   vp, uio, cred, eofflagp);
#endif /* ADOSFSDEBUG */
  dots[0].d_fileno = ap->a_keyblk;
  dots[1].d_fileno = ap->a_parent;

  /*
   *  If the user buffer is smaller than the size of one dos
   *  directory entry or the file offset is not a multiple of
   *  the size of a directory entry, then we fail the read.
   */
  count = uio->uio_resid / adosfs_dirent_size;
  lost  = uio->uio_resid % adosfs_dirent_size;
  if (count < 1 || uio->uio_offset % adosfs_dirent_size != 0)
    return EINVAL;
  uio->uio_resid -= lost;
  uio->uio_iov->iov_len = uio->uio_resid;
  blkno = uio->uio_offset / adosfs_dirent_size - 2;
  while (blkno < 0)
    {
      bcopy (&dots[blkno + 2], ent, sizeof *ent);
      error = uiomove((caddr_t)ent, adosfs_dirent_size, uio);
      if (error)
	goto out;
#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_VNOPS)
	printf ("read entry \"%s\"\n", dots[blkno + 2].d_name);
#endif
      count--;
      tot++;
      blkno++;
    }

  *eofflagp = 0;
  while (count--)
    {
      error = adosfs_getblk (ap, blkno++ * 512, &blk);
      if (error)
	break;
      if (blk == 0)
	{
	  *eofflagp = 1;
	  break;
	}
      error = bread (VFSTOADOSFS (mp)->am_devvp, blk, 512, NOCRED, &bp);
      if (error)
	break;
      ent->d_fileno = blk;
      ent->d_reclen = adosfs_dirent_size;
      ent->d_namlen = AFILE (bp->b_un.b_addr)->uf_name[0];
      bcopy (AFILE (bp->b_un.b_addr)->uf_name + 1, ent->d_name, ent->d_namlen);
      ent->d_name[ent->d_namlen] = '\0';
      brelse (bp);
#ifdef ADOSFSDEBUG
      if (adosfs_debug & AD_VNOPS)
	printf ("read entry \"%s\"\n", ent->d_name);
#endif
      error = uiomove ((caddr_t)ent, adosfs_dirent_size, uio);
      if (error)
	break;
      tot++;
    }

 out:
  uio->uio_resid += lost;
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VNOPS)
    printf ("adosfs_readdir returned %d entries (error %d)\n", tot, error);
#endif
  return error;
}

int
adosfs_reclaim (struct vnode *vp)
{
  struct anode *ap = VTOA (vp);
  struct adosfsmount *amp = VFSTOADOSFS (vp->v_mount);
  int i;

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VNOPS)
    printf("adosfs_reclaim(): ap %08x, file %s\n", ap, ap->a_name);
#endif /* ADOSFSDEBUG */

  if (adosfs_prtactive && vp->v_usecount != 0)
    vprint ("adosfs_reclaim(): pushing active", vp);

  /*
   *  Remove the anode from the anode hash chain we are in.
   */
  remque (ap);
  ap->a_forw = ap;
  ap->a_back = ap;
  adosfs_freechn (ap);

  cache_purge (vp);
  /*
   *  Indicate that one less file on the filesystem is open.
   */
  vrele (amp->am_devvp);

  return 0;
}

int
adosfs_abortop (ndp)
     struct nameidata *ndp;
{
  if ((ndp->ni_nameiop & (HASBUF | SAVESTART)) == HASBUF)
    FREE (ndp->ni_pnbuf, M_NAMEI);
  return 0;
}

int
adosfs_inactive (struct vnode *vp, struct proc *p)
{
  struct anode *ap = VTOA (vp);
  int error = 0;

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VNOPS)
    printf("adosfs_inactive(): ap %08x, a_name %s\n", ap, ap->a_name);
#endif /* ADOSFSDEBUG */

  if (adosfs_prtactive && vp->v_usecount != 0)
    vprint ("adosfs_inactive(): pushing active", vp);

#if 0
  /* As this FS eats memory for breakfast, lunch *and* dinner, let's
     throw some up right here.  Rule Bulimia...  */
  adosfs_freechn (ap);
#endif
  return error;
}

int
adosfs_lock (struct vnode *vp)
{
  ALOCK (VTOA (vp));
  return 0;
}

int
adosfs_unlock (struct vnode *vp)
{
  AUNLOCK (VTOA (vp));
  return 0;
}

int
adosfs_islocked (struct vnode *vp)
{
  return (VTOA (vp)->a_flag & ALOCKED) != 0;
}

struct vnodeops adosfs_vnodeops = {
  adosfs_lookup,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_open,
  adosfs_close,
  adosfs_access,
  adosfs_getattr,
  adosfs_dummy,
  adosfs_read,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_readdir,
  adosfs_dummy,
  adosfs_abortop,
  adosfs_inactive,
  adosfs_reclaim,
  adosfs_lock,
  adosfs_unlock,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_dummy,
  adosfs_islocked,
  adosfs_dummy
};
