/*
 * Copyright (c) 1995 - 2000 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * 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 Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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.
 */

#include <xfs/xfs_locl.h>

RCSID("$Id: xfs_vfsops.c,v 1.14 2000/03/26 18:59:42 assar Exp $");

/*
 * XFS vfs operations.
 */

#include <xfs/xfs_common.h>
#include <xfs/xfs_message.h>
#include <xfs/xfs_dev.h>
#include <xfs/xfs_fs.h>
#include <xfs/xfs_deb.h>
#include <xfs/nxfs.h>
#include <xfs/xfs_vfsops.h>

extern int nchrdev;

static struct vnode *make_dead_vnode (struct vfs *vfsp);

struct xfs xfs[NXFS];

static int xfsfstype;

static int
xfs_mount(struct vfs *vfsp,
	  struct vnode *mvp,
	  struct mounta *uap,
	  struct cred *cred)
{
  struct vnode *devvp;
  dev_t dev;
  int error;
  
#ifdef DEBUG
  char dir[MAXPATHLEN], spec[MAXPATHLEN];

  if (copyinstr(uap->dir, dir, sizeof(dir), NULL) ||
      copyinstr(uap->spec, spec, sizeof(spec), NULL))
      return EFAULT;
  XFSDEB(XDEBVFOPS, ("xfs_mount vfsp = 0x%x path = '%s' args = '%s'\n",
		     (u_int) vfsp, dir, spec));
#endif

  /*
   * This is something that should be done before calling this
   * function, but it's not, so we do it here.
   */

  if (mvp->v_type != VDIR)
      return ENOTDIR;
  mutex_enter(&mvp->v_lock);
  if (mvp->v_count != 1 || (mvp->v_flag & VROOT)) {
      mutex_exit(&mvp->v_lock);
      return EBUSY;
  }
  mutex_exit(&mvp->v_lock);

  error = lookupname(uap->spec, UIO_USERSPACE, FOLLOW, 0, &devvp);
  if (error != 0)
    return error;

  if (devvp->v_type != VCHR) {
      VN_RELE(devvp);
      return ENXIO;
  }
  dev = devvp->v_rdev;
  VN_RELE(devvp);

  /* Check that this device really is an xfs_dev */

  /* I'm not sure how to test this under solaris */
#if 0
  if (getmajor(dev) < 0 || nchrdev < getmajor(dev))
    return ENXIO;
#endif

  XFSDEB(XDEBVFOPS, ("xfs_mount dev = %x, minor = %x, major = %x,"
		     "ops = %x, "
		     "cb_ops = %x, "
		     "open = %x, "
		     "(xfs_devopen = %x)\n",
		     (unsigned)dev,
		     (unsigned)getminor(dev),
		     (unsigned)getmajor(dev),
		     (unsigned)devopsp[getmajor(dev)],
		     (unsigned) (devopsp[getmajor(dev)] ? devopsp[getmajor(dev)]->devo_cb_ops : 0),
		     (unsigned) ((devopsp[getmajor(dev)] && devopsp[getmajor(dev)]->devo_cb_ops) ? devopsp[getmajor(dev)]->devo_cb_ops->cb_open : 0),
		     (unsigned) xfs_devopen));

  if (getminor(dev) < 0 || NXFS < getminor(dev))
    return ENXIO;
  if (devopsp[getmajor(dev)] == NULL ||
      devopsp[getmajor(dev)]->devo_cb_ops == NULL ||
      devopsp[getmajor(dev)]->devo_cb_ops->cb_open != xfs_devopen)
      return ENXIO;

  if (xfs[getminor(dev)].status & XFS_MOUNTED)
    return EBUSY;

  xfs[getminor(dev)].status = XFS_MOUNTED;
  xfs[getminor(dev)].vfsp = vfsp;
  xfs[getminor(dev)].root = 0;
  xfs[getminor(dev)].nnodes = 0;
  xfs[getminor(dev)].nodes = 0;
  xfs[getminor(dev)].fd = getminor(dev);

  VFS_TO_XFS(vfsp) = &xfs[getminor(dev)];
  vfsp->vfs_fstype = xfsfstype;
  vfsp->vfs_dev    = getminor(dev);
  vfsp->vfs_bsize  = PAGESIZE;
  vfsp->vfs_flag  |= VFS_NOTRUNC;
  vfsp->vfs_fsid.val[0] = getminor(dev);
  vfsp->vfs_fsid.val[1] = getmajor(dev); /* What is this good for */

  return 0;
}

static int
xfs_unmount(struct vfs *vfsp, struct cred *cred)
{
  struct xfs *xfsp = VFS_TO_XFS(vfsp);

  XFSDEB(XDEBVFOPS, ("xfs_umount vfsp = 0x%x\n", (u_int) vfsp));

  free_all_xfs_nodes(xfsp);
  xfsp->status = 0;
  return 0;			/* Always allow umount to succed */
}

static int
xfs_root(struct vfs *vfsp,
	 struct vnode **vpp)     
{
  struct xfs *xfsp = VFS_TO_XFS(vfsp);
  struct xfs_message_getroot msg;
  int error;
  
  XFSDEB(XDEBVFOPS, ("xfs_root vfsp = 0x%x\n", (u_int) vfsp));

  do {
    if (xfsp->root != 0) {
	*vpp = XNODE_TO_VNODE(xfsp->root);
	VN_HOLD(*vpp);
	XFSDEB(XDEBVFOPS, ("xfs_root: returning real vnode\n"));
	return 0;
    }

    msg.header.opcode = XFS_MSG_GETROOT;
    msg.cred.uid = CRED()->cr_uid;
    msg.cred.pag = 0;		/* XXX */
    error = xfs_message_rpc(xfsp->fd, &msg.header, sizeof(msg));
    if (error == 0)
      error = ((struct xfs_message_wakeup *) &msg)->error;
  } while (error == 0);

  XFSDEB(XDEBVFOPS, ("xfs_root: returning dead vnode\n"));

  /*
   * Failed to get message through, need to pretend that all went well
   * and return a fake dead vnode to be able to unmount.
   */
  *vpp = make_dead_vnode(vfsp);
  VN_HOLD(*vpp);
  return 0;
}

static int
xfs_statvfs(struct vfs *vfsp,
#ifdef _LARGEFILE64_SOURCE
	    struct statvfs64 *sbp)
#else
	    struct statvfs *sbp)
#endif
{
    XFSDEB(XDEBVFOPS, ("xfs_statvfs\n"));

    sbp->f_bsize = 8192;
    sbp->f_frsize = 1024;
    sbp->f_blocks = 4711*4711;
    sbp->f_bfree = 4711*4711;
    sbp->f_bavail = 4711*4711;
    sbp->f_files = 4711;
    sbp->f_ffree = 4711;
    sbp->f_favail = 4711;
    sbp->f_fsid = 0x47114711;
    strcpy(sbp->f_basetype, "xfs");
    sbp->f_flag = ST_NOTRUNC;
    sbp->f_namemax = 256;
    sbp->f_fstr[0] = 0;

    return 0;
}

static int
xfs_sync(struct vfs *vfsp, short flag, struct cred *cred)
{
  XFSDEB(XDEBVFOPS, ("xfs_sync\n"));
  return 0;
}

static int
xfs_vget(struct vfs *vfsp,
	 struct vnode **vpp,
	 struct fid *fidp)     
{
  XFSDEB(XDEBVFOPS, ("xfs_vget\n"));
  return ENOSYS;
}

static int
xfs_mountroot(struct vfs *vfsp,
	      enum whymountroot reason)
{
  XFSDEB(XDEBVFOPS, ("xfs_mountroot\n"));
  return ENOSYS;
}

static int
xfs_swapvp(struct vfs *vfsp,
	   struct vnode **vpp,
	   char *path)     
{
  XFSDEB(XDEBVFOPS, ("xfs_swapvp\n"));
  return ENOSYS;
}

/*
 * To be able to unmount when the XFS daemon is not
 * responding we need a root vnode, use a dead vnode!
 */

static void
dead_vnode_inactive(struct vnode *vp, struct cred *cred)
{
  XFSDEB(XDEBVFOPS, ("dead_vnode_inactive\n"));
  xfs_free(vp, sizeof(*vp));
}

struct vnodeops dead_vnodeops = {
        nodev,			/* xfs_open */
        nodev,			/* xfs_close */
        nodev,			/* xfs_read */
        nodev,			/* xfs_write */
        nodev,			/* xfs_ioctl */
	nodev,			/* xfs_setfl */
        nodev,			/* xfs_getattr */
        nodev,			/* xfs_setattr */
        nodev,			/* xfs_access */
        nodev,			/* xfs_lookup */
        nodev,			/* xfs_create */
        nodev,			/* xfs_remove */
        nodev,			/* xfs_link */
        nodev,			/* xfs_rename */
        nodev,			/* xfs_mkdir */
        nodev,			/* xfs_rmdir */
        nodev,			/* xfs_readdir */
        nodev,			/* xfs_symlink */
        nodev,			/* xfs_readlink */
        nodev,			/* xfs_fsync */
        dead_vnode_inactive,	/* xfs_inactive */
        nodev,			/* xfs_fid */
        (void*) nodev,		/* xfs_rwlock */
        (void*) nodev,		/* xfs_rwunlock */
        nodev,			/* xfs_seek */
        nodev,			/* xfs_cmp */
        nodev,			/* xfs_frlock */
        nodev,			/* xfs_space */
        nodev,			/* xfs_realvp */
        nodev,			/* xfs_getpage */
        nodev,			/* xfs_putpage */
        (void*) nodev,		/* xfs_map */
        (void*) nodev,		/* xfs_addmap */
        nodev,			/* xfs_delmap */
        (void*) nodev,		/* xfs_poll */
        nodev,			/* xfs_dump */
        nodev,			/* xfs_pathconf */
        nodev,			/* xfs_pageio */
        nodev,			/* xfs_dumpctl */
        (void*) nodev,		/* xfs_dispose */
        nodev,			/* xfs_setsecattr */
        nodev			/* xfs_getsecattr */
};

static struct vnode *
make_dead_vnode(struct vfs *vfsp)
{
  struct vnode *dead;

  XFSDEB(XDEBNODE, ("make_dead_vnode vfsp = 0x%x\n", (u_int) vfsp));

  dead = xfs_alloc(sizeof(*dead));
  bzero((caddr_t)dead, sizeof(*dead));
  VN_INIT(dead, vfsp, VDIR, 0);
  dead->v_flag = VROOT;
  dead->v_op = &dead_vnodeops;
  dead->v_count = 0;
  return dead;
}

int
xfs_uprintf_filsys(void)
{
#if 0
  {
    struct vfssw *fs;
    uprintf("Currently loaded filesystems are:\n");
    for (fs = vfssw; fs < vfsNVFS; fs++)
      uprintf("vfssw[%d] == { \"%s\", 0x%x}\n",
	      fs - vfssw, fs->vsw_name, fs->vsw_ops);
  }
#endif
#if 1
  {
    int i;
    struct xfs_node *t;
    for (i = 0; i < NXFS; i++)
      if (xfs[i].nodes)
	{
	  uprintf("Current xfs_nodes on xfs[%d] are:\n", i);
	  for (t = xfs[i].nodes; t; t = t->next)
	    uprintf("%d.%d.%d.%d(%d) ",
		    t->handle.a,
		    t->handle.b,
		    t->handle.c,
		    t->handle.d,
		    t->vn.v_count);
	  uprintf(" !\n");
	}
  }
#endif
  return 0;
}

/*
 *
 */

int
xfs_fhlookup (fsid_t fsid,
	      fid_t fid,
	      struct vnode **vpp)
{
    int error;
    struct vfs *vfs;

    if (!suser(CRED()))
	return EPERM;

    vfs = getvfs (&fsid);
    if (vfs == NULL)
	return ENXIO;

    error = VFS_VGET(vfs, vpp, &fid);
    return error;
}

/*
 *
 */

int
xfs_fhopen (fsid_t fsid,
	    fid_t fid,
	    int flags)
{
    int error;
    struct vnode *vp;
    int fmode = flags - FOPEN;
    struct file *fp;
    int fd;

    error = xfs_fhlookup (fsid, fid, &vp);
    if (error)
	return set_errno(error);

    error = VOP_OPEN(&vp, fmode, CRED());
    if (error)
	goto rele;

    XFSDEB(XDEBSYS, ("xfs_fhopen: falloc fmode = %d\n", fmode & FMASK));

    error = falloc(vp, fmode & FMASK, &fp, &fd);
    if (error)
	goto rele;

    mutex_exit(&fp->f_tlock);

    setf(fd, fp);

    XFSDEB(XDEBSYS, ("xfs_fhopen: returning fd = %d\n", fd));

    return fd;

rele:
    VN_RELE(vp);
    return set_errno(error);
}

/*
 * file system
 */

static int
xfs_vfs_init (struct vfssw *vfssw, int offset)
{
    printf ("xfs_vfs_init: offset = %d\n", offset);
    xfsfstype = offset;
    return 0;
}

#ifdef HAVE_STRUCT_VFSOPS_VFS_FREEVFS
static void
xfs_freevfs(struct vfs *vfsp)
{
    XFSDEB(XDEBVFOPS, ("xfs_freevfs\n"));
}
#endif

static struct vfsops xfs_vfsops = {
    xfs_mount,			/* mount */
    xfs_unmount,		/* unmount */
    xfs_root,			/* root */
    xfs_statvfs,		/* statvfs */
    xfs_sync,			/* sync */
    xfs_vget,			/* vget */
    xfs_mountroot,		/* mountroot */
    xfs_swapvp			/* swapvp */
#if HAVE_STRUCT_VFSOPS_VFS_FREEVFS
    ,
    xfs_freevfs			/* freevfs */
#endif
};

static struct vfssw xfs_vfssw = {
    "xfs",			/* name */
    xfs_vfs_init,		/* init */
    &xfs_vfsops,		/* vfsops */
    0				/* flags */
};

struct modlfs xfs_modlfs = {
    &mod_fsops,
    "xfs filesystem",
    &xfs_vfssw
};
