/* adosfs_vfsops.c 930817 Niklas Hallqvist <niklas@appli.se> */

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

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

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

#include "param.h"
#include "systm.h"
#include "namei.h"
#include "proc.h"
#include "vnode.h"
#include "miscfs/specfs/specdev.h"
#include "mount.h"
#include "file.h"
#include "malloc.h"
#include "time.h"
#include "stat.h"
#include "buf.h"
#include "conf.h"

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

#define ADOSFS_DOS0	0x444F5300
#define ADOSFS_DOS1	0x444F5301
#define ADOSFS_DOS3	0x444F5303
#define ADOSFS_DOS5	0x444F5305

extern struct vnodeops adosfs_vnodeops;

struct vfsops adosfs_vfsops = {
  adosfs_mount,
  adosfs_start,
  adosfs_unmount,
  adosfs_root,
  adosfs_quotactl,
  adosfs_statfs,
  adosfs_sync,
  adosfs_fhtovp,
  adosfs_vptofh,
  adosfs_init
};

int adosfsdoforce = 0;	/* 1 = force unmount */

int mountadosfs (struct vnode *, struct mount *, struct proc *);

/*
 *  mp -
 *  path - addr in user space of mount point (ie /usr or whatever)
 *  data - addr in user space of mount params including the
 *         name of the block special file to treat as a filesystem.
 *  ndp  - 
 *  p    -
 */
int
adosfs_mount (mp, path, data, ndp, p)
     register struct mount *mp;
     char *path;
     caddr_t data;
     struct nameidata *ndp;
     struct proc *p;
{
  struct vnode *devvp;		/* vnode for blk device to mount	*/
  struct adosfs_args args;	/* will hold data from mount request	*/
  struct adosfsmount *amp;	/* adosfs specific mount control block	*/
  int error;
  u_int size;

  /* Copy in the args for the mount request.  */
  if (error = copyin (data, (caddr_t)&args, sizeof (struct adosfs_args)))
    return error;

  /* Require the mount request to be readonly.  */
  if ((mp->mnt_flag & MNT_RDONLY) == 0)
    return EOPNOTSUPP;

  /* If they just want to update then be sure we can do what is asked.
     Can't change a filesystem from read/write to read only.  Why?  And
     if they've supplied a new device file name then we continue, other-
     wise return.  */
  if (mp->mnt_flag & MNT_UPDATE)
    {
      amp = VFSTOADOSFS (mp);
      if (amp->am_ronly  &&  (mp->mnt_flag & MNT_RDONLY) == 0)
	amp->am_ronly = 0;
      if (args.fspec == 0)
	return 0;
    }

  /* Now, lookup the name of the block device this mount or name update
     request is to apply to.  */
  ndp->ni_nameiop = LOOKUP | FOLLOW;
  ndp->ni_segflg = UIO_USERSPACE;
  ndp->ni_dirp = args.fspec;
  if (error = namei (ndp, p))
    return error;

  /* Be sure they've given us a block device to treat as a filesystem.
     And, that its major number is within the bdevsw table.  */
  devvp = ndp->ni_vp;
  if (devvp->v_type != VBLK)
    {
      vrele (devvp);
      return ENOTBLK;
    }
  if (major (devvp->v_rdev) >= nblkdev)
    {
      vrele (devvp);
      return ENXIO;
    }

  /* If this is an update, then make sure the vnode for the block special
     device is the same as for our filesystem.  */
  if (mp->mnt_flag & MNT_UPDATE)
    {
      if (devvp != amp->am_devvp)
	error = EINVAL;
      else
	vrele (devvp);
    }
  else
    {

      /* Well, it's not an update, it's a real mount request.  Time to get
	 dirty.  */
      error = mountadosfs (devvp, mp, p);
    }
  if (error)
    {
      vrele (devvp);
      return error;
    }

  /* Copy in the name of the directory the filesystem is to be mounted on.
     Then copy in the name of the block special file representing the file-
     system being mounted.  And we clear the remainder of the character
     strings to be tidy.  Then, we try to fill in the filesystem stats
     structure  as best we can with whatever applies from a dos file
     system.  */
  copyinstr (path, (caddr_t)mp->mnt_stat.f_mntonname,
	    sizeof (mp->mnt_stat.f_mntonname) - 1, &size);
  bzero (mp->mnt_stat.f_mntonname + size,
	 sizeof (mp->mnt_stat.f_mntonname) - size);
  copyinstr (args.fspec, mp->mnt_stat.f_mntfromname, MNAMELEN - 1, &size);
  bzero (mp->mnt_stat.f_mntfromname + size, MNAMELEN - size);
  (void)adosfs_statfs (mp, &mp->mnt_stat, p);
#if ADOSFSDEBUG
  if (adosfs_debug & AD_VFSOPS)
    printf ("adosfs_mount(): mp %x, amp %x\n", mp, VFSTOADOSFS (mp));
#endif /* ADOSFSDEBUG */
  return 0;
}

int
mountadosfs (devvp, mp, p)
     struct vnode *devvp;
     struct mount *mp;
     struct proc *p;
{
  int error, needclose;
  struct buf *bp0 = NULL;
  struct a_rdir *rdir;
  int ronly = (mp->mnt_flag & MNT_RDONLY) != 0;
  struct adosfsmount *amp = NULL;
  dev_t dev = devvp->v_rdev;
  struct anode *ap;
  struct vnode *vp;

  /* Multiple mounts of the same block special file aren't allowed.  Make
     sure no one else has the special file open.  And flush any old buffers
     from this filesystem.  Presumably this prevents us from running into
     buffers that are the wrong blocksize.  NOTE: mountedon() is a part of
     the ufs filesystem.  If the ufs filesystem is not gen'ed into the system
     we will get an unresolved reference.  */
  if (error = mountedon (devvp))
    return error;
  if (vcount (devvp) > 1)
    return EBUSY;
  vinvalbuf (devvp, 1);

  /* Now open the block special file.  */
  if (error = VOP_OPEN (devvp, ronly ? FREAD : FREAD | FWRITE, NOCRED, p))
    return error;
  needclose = 1;

  /* Read the boot sector of the filesystem, and then check the boot
     signature.  If not a dos boot sector then error out.  */
  if (error = bread (devvp, 0, 512, NOCRED, &bp0))
    goto error_exit;
  bp0->b_flags |= B_AGE;
  if (*(ados_long *)bp0->b_un.b_addr != ADOSFS_DOS0
      && *(ados_long *)bp0->b_un.b_addr != ADOSFS_DOS1
      && *(ados_long *)bp0->b_un.b_addr != ADOSFS_DOS3
      && *(ados_long *)bp0->b_un.b_addr != ADOSFS_DOS5)
    {
      error = EINVAL;
      goto error_exit;
    }

  MALLOC (amp, struct adosfsmount *, sizeof *amp, M_ADOSFSMNT, M_WAITOK);
  bzero (amp, sizeof *amp);
  amp->am_devvp = devvp;
  amp->am_dos = bp0->b_un.b_addr[3];
  amp->am_nelmax = 100;
  amp->am_mountp = mp;

  /* Release the bootsector buffer.  */
  brelse (bp0);
  bp0 = NULL;

  /* Read the root sector of the filesystem, and then check the root
     signature.  If not a dos root sector then error out.  */
  amp->am_rootblk = (*bdevsw[major (dev)].d_psize) (dev) / 2;
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VFSOPS)
    printf ("mountadosfs: root block found at %d\n", amp->am_rootblk);
#endif /* ADOSFSDEBUG */

  /* Finish up.  */
  VFSTOADOSFS (mp) = amp;
  mp->mnt_stat.f_fsid.val[0] = (long)dev;
  mp->mnt_stat.f_fsid.val[1] = MOUNT_ADOSFS;
  mp->mnt_flag |= MNT_LOCAL;
  devvp->v_specflags |= SI_MOUNTEDON;

  return 0;

 error_exit:
  if (bp0)
    brelse (bp0);
  if (needclose)
    (void)VOP_CLOSE (devvp, ronly ? FREAD : FREAD | FWRITE, NOCRED, p);
  if (amp)
    {
      FREE (amp, M_ADOSFSMNT);
      mp->mnt_data = (qaddr_t)0;
    }
  return error;
}

int
adosfs_start (mp, flags, p)
     struct mount *mp;
     int flags;
     struct proc *p;
{
  return 0;
}

/* Unmount the filesystem described by mp.  */
int
adosfs_unmount (mp, mntflags, p)
     struct mount *mp;
     int mntflags;
     struct proc *p;
{
  int flags = 0;
  int error;
  struct adosfsmount *amp = VFSTOADOSFS (mp);
  struct vnode *vp = amp->am_devvp;

#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VFSOPS)
    printf ("adosfs_unmount: mp 0x%x amp 0x%x vp 0x%\n", mp, amp, vp);
#endif
  if (mntflags & MNT_FORCE)
    {
      if (!adosfsdoforce)
	return EINVAL;
      flags |= FORCECLOSE;
    }
  mntflushbuf (mp, 0);
  if (mntinvalbuf (mp))
    return EBUSY;
  if (error = vflush (mp, NULLVP, flags))
    return error;
  vp->v_specflags &= ~SI_MOUNTEDON;
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VFSOPS)
    {
      printf ("adosfs_unmount(): just before calling VOP_CLOSE()\n");
      printf ("flag %08x, usecount %d, writecount %d, holdcnt %d\n",
	      vp->v_flag, vp->v_usecount, vp->v_writecount, vp->v_holdcnt);
      printf ("lastr %d, id %d, mount %08x, op %08x\n",
	      vp->v_lastr, vp->v_id, vp->v_mount, vp->v_op);
      printf ("freef %08x, freeb %08x, mountf %08x, mountb %08x\n",
	      vp->v_freef, vp->v_freeb, vp->v_mountf, vp->v_mountb);
      printf ("cleanblkhd %08x, dirtyblkhd %08x, numoutput %d, type %d\n",
	      vp->v_cleanblkhd, vp->v_dirtyblkhd, vp->v_numoutput, vp->v_type);
      printf ("union %08x, tag %d, data[0] %08x, data[1] %08x\n",
	      vp->v_socket, vp->v_tag, vp->v_data[0], vp->v_data[1]);
    }
#endif /* ADOSFSDEBUG */
  error = VOP_CLOSE (vp, amp->am_ronly ? FREAD : FREAD | FWRITE, NOCRED, p);
  vrele (vp);
  FREE (amp, M_ADOSFSMNT);
  mp->mnt_data = (qaddr_t)0;
  mp->mnt_flag &= ~MNT_LOCAL;
  return error;
}

int
adosfs_root (mp, vpp)
     struct mount *mp;
     struct vnode **vpp;
{
  struct adosfsmount *amp = VFSTOADOSFS (mp);
  struct anode *ap;
  int error;

  if (error = aget (amp, amp->am_rootblk, &ap))
    return error;
#ifdef ADOSFSDEBUG
  if (adosfs_debug & AD_VFSOPS)
    printf ("adosfs_root: mp %08x, amp %08x, ap %08x\n", mp, amp, ap);
#endif /* ADOSFSDEBUG */
  *vpp = ATOV (ap);
  return 0;
}

int
adosfs_quotactl (mp, cmds, uid, arg, p)
     struct mount *mp;
     int cmds;
     uid_t uid;
     caddr_t arg;
     struct proc *p;
{
  return EOPNOTSUPP;
}

int
adosfs_statfs (mp, sbp, p)
     struct mount *mp;
     struct statfs *sbp;
     struct proc *p;
{
  struct adosfsmount *amp = VFSTOADOSFS (mp);

  /* Fill in the stat block.  */
  sbp->f_type = MOUNT_ADOSFS;
  sbp->f_fsize = 512;
  sbp->f_bsize = 512;
  sbp->f_blocks= amp->am_rootblk << 1;
  sbp->f_bfree = 0;
  sbp->f_bavail = 0;
  sbp->f_files = 0;
  sbp->f_ffree = 0;

  /* Copy the mounted on and mounted from names into the passed in stat
     block, if it is not the one in the mount structure.  */
  if (sbp != &mp->mnt_stat)
    {
      bcopy ((caddr_t)mp->mnt_stat.f_mntonname, (caddr_t)&sbp->f_mntonname[0],
	     MNAMELEN);
      bcopy ((caddr_t)mp->mnt_stat.f_mntfromname,
	     (caddr_t)&sbp->f_mntfromname[0], MNAMELEN);
    }
  return 0;
}

int
adosfs_sync(mp, waitfor)
     struct mount *mp;
     int waitfor;
{
  return 0;

}

int
adosfs_fhtovp (mp, fhp, vpp)
     struct mount *mp;
     struct fid *fhp;
     struct vnode **vpp;
{
  struct adosfsmount *amp = (struct adosfsmount *)mp->mnt_data;
  struct adosfs_fid *afhp = (struct adosfs_fid *)fhp;
  struct anode *ap;
  int error;

  error = aget (amp, afhp->af_key, &ap);
  if (error)
    return (error);
  *vpp = ATOV (ap);
  return 0;
}


int
adosfs_vptofh (vp, fhp)
     struct vnode *vp;
     struct fid *fhp;
{
  struct anode *ap = VTOA (vp);
  struct adosfs_fid *afhp = (struct adosfs_fid *)fhp;

  afhp->af_len = sizeof (struct adosfs_fid);
  afhp->af_key = ap->a_keyblk;
  return 0;
}
