/*
 * Copyright (c) 1995, 1996, 1997, 1998 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_locl.h"

RCSID("$Id: xfs_vfsops.c,v 1.8 1998/02/10 02:01:42 assar Exp $");

/*
 * XFS vfs operations.
 */

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

static struct vnode *make_dead_vnode(struct mount * mp);

struct xfs xfs[NXFS];

static int
xfs_mount(struct mount * mp,
	  char *path,
	  caddr_t data,
	  struct nameidata * ndp,
	  struct proc * p)
{
    struct vnode *devvp;
    dev_t dev;
    int errno;
    struct vattr vat;

    XFSDEB(XDEBVFOPS, ("xfs_mount: "
		       "struct mount mp = %p path = %s data = '%s'\n",
		       mp, path, data));

    NDINIT(ndp, LOOKUP, FOLLOW | LOCKLEAF,
	   UIO_USERSPACE, data, p);
    errno = namei(ndp);
    if (errno)
	return errno;

    devvp = ndp->ni_vp;

    if (devvp->v_type != VCHR) {
	vput(devvp);
	return ENXIO;
    }
    errno = VOP_GETATTR(devvp, &vat, p->p_ucred, p);
    if (errno) {
	vput(devvp);
	return errno;
    }
    dev = vat.va_rdev;
    vput(devvp);

    /* Check that this device really is an xfs_dev */
    if (major(dev) < 0 || nchrdev < major(dev))
	return ENXIO;
    if (minor(dev) < 0 || NXFS < minor(dev))
	return ENXIO;
#if defined(__NetBSD__) || defined(__OpenBSD__)
    if (cdevsw[major(dev)].d_open != xfs_devopen)
	return ENXIO;
#elif defined(__FreeBSD__)
    if (cdevsw[major(dev)] == NULL
	|| cdevsw[major(dev)]->d_open != xfs_devopen)
	return ENXIO;
#endif

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

    xfs[minor(dev)].status = XFS_MOUNTED;
    xfs[minor(dev)].mp = mp;
    xfs[minor(dev)].root = 0;
    xfs[minor(dev)].nnodes = 0;
    xfs[minor(dev)].fd = minor(dev);

    VFS_TO_XFS(mp) = &xfs[minor(dev)];
#if defined(__NetBSD__) || defined(__OpenBSD__)
    getnewfsid(mp, makefstype(MOUNT_AFS));
#elif defined(__FreeBSD__)
    getnewfsid(mp, MOUNT_AFS);
    mp->mnt_stat.f_type = MOUNT_AFS;
#endif

    /*
     * XXX - is this really correct? xfs could talk to a normal fs and should
     * return the correct stats for it.
     */
    mp->mnt_stat.f_bsize = DEV_BSIZE;
    mp->mnt_stat.f_iosize = DEV_BSIZE;
    mp->mnt_stat.f_blocks = 4711 * 4711;
    mp->mnt_stat.f_bfree = 4711 * 4711;
    mp->mnt_stat.f_bavail = 4711 * 4711;
    mp->mnt_stat.f_files = 0;
    mp->mnt_stat.f_ffree = 4711;
    mp->mnt_stat.f_owner = 0;
    mp->mnt_stat.f_flags = mp->mnt_flag;
    strncpy(mp->mnt_stat.f_mntonname,
	    path,
	    sizeof(mp->mnt_stat.f_mntonname));

    strncpy(mp->mnt_stat.f_mntfromname,
	    "arla",
	    sizeof(mp->mnt_stat.f_mntfromname));

#if defined(__NetBSD__) || defined(__OpenBSD__)
    strncpy(mp->mnt_stat.f_fstypename,
	    "xfs",
	    sizeof(mp->mnt_stat.f_fstypename));
#endif

    return 0;
}

static int
xfs_start(struct mount * mp, int flags, struct proc * p)
{
    XFSDEB(XDEBVFOPS, ("xfs_start mp = 0x%x\n", (u_int) mp));
    return 0;
}

static int
xfs_unmount(struct mount * mp, int mntflags, struct proc * p)
{
    struct xfs *xfsp = VFS_TO_XFS(mp);
    extern int doforce;
    int flags = 0;
    int error;

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

    if (mntflags & MNT_FORCE) {
	if (!doforce)
	    return EINVAL;
	flags |= FORCECLOSE;
    }
    error = free_all_xfs_nodes(xfsp, flags);
    xfsp->status = 0;
    return 0;			       /* Always allow umount to succed */
}

static int
xfs_root(struct mount * mp, struct vnode ** vpp)
{
    struct xfs *xfsp = VFS_TO_XFS(mp);
    struct xfs_message_getroot msg;
    int errno;

    XFSDEB(XDEBVFOPS, ("xfs_root mp = 0x%x\n", (u_int) mp));

    do {
	if (xfsp->root != NULL) {
	    *vpp = XNODE_TO_VNODE(xfsp->root);
	    VREF(*vpp);
	    return 0;
	}
	msg.header.opcode = XFS_MSG_GETROOT;
	msg.cred.uid = curproc->p_ucred->cr_uid;
	msg.cred.pag = 0;	       /* XXX */
	errno = xfs_message_rpc(xfsp->fd, &msg.header, sizeof(msg));
	if (errno == 0)
	    errno = ((struct xfs_message_wakeup *) & msg)->errno;
    } while (errno == 0);
    /*
     * 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(mp);
    (*vpp)->v_flag |= VROOT;
    VREF(*vpp);
    return 0;
}

static int
xfs_quotactl(struct mount * mp,
	     int cmd,
	     uid_t uid,
	     caddr_t arg,
	     struct proc * p)
{
    XFSDEB(XDEBVFOPS, ("xfs_quotactl\n"));
    return (EOPNOTSUPP);
}

static int
xfs_statfs(struct mount * mp,
	   struct statfs * sbp,
	   struct proc * p)
{
    XFSDEB(XDEBVFOPS, ("xfs_statfs\n"));

    bcopy(&mp->mnt_stat, sbp, sizeof(*sbp));
    return 0;
}

static int
xfs_sync(struct mount * mp,
	 int waitfor,
	 struct ucred * cred,
	 struct proc * p)
{
    XFSDEB(XDEBVFOPS, ("xfs_sync\n"));
    return 0;
}

static int
xfs_vget(struct mount * mp,
	 ino_t ino,
	 struct vnode ** vpp)
{
    XFSDEB(XDEBVFOPS, ("xfs_vget\n"));
    return EOPNOTSUPP;
}

static int
xfs_fhtovp(struct mount * mp,
	   struct fid * fhp,
	   struct mbuf * nam,
	   struct vnode ** vpp,
	   int *exflagsp,
	   struct ucred ** credanonp)
{
    XFSDEB(XDEBVFOPS, ("xfs_fhtovp\n"));
    return EOPNOTSUPP;
}

static int
xfs_vptofh(struct vnode * vp,
	   struct fid * fhp)
{
    XFSDEB(XDEBVFOPS, ("xfs_vptofh\n"));
    return EOPNOTSUPP;
}



#if defined(__NetBSD__) || defined(__OpenBSD__)
static void
xfs_init(void)
{
    XFSDEB(XDEBVFOPS, ("xfs_init\n"));
    return;
}

static struct vfsops xfs_vfsops = {
    "xfs",
    xfs_mount,
    xfs_start,
    xfs_unmount,
    xfs_root,
    xfs_quotactl,
    xfs_statfs,
    xfs_sync,
    xfs_vget,
    xfs_fhtovp,
    xfs_vptofh,
    xfs_init
};

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

extern int (**dead_vnodeop_p) (void *);

/*
 *
 */
static int
xfs_uprintf_filsys(void)
{
#if 1
    {
	int i;

	uprintf("Currently loaded filesystems are:\n");
	for (i = 0; i < nvfssw; i++)
	    if (vfssw[i] != NULL)
		uprintf("vfssw[%d] = { \"%s\", 0x%x}\n",
			i, vfssw[i]->vfs_name, vfssw[i]);
    }
#endif
#if 1
    {
	int i;
	struct vnode *t;

	for (i = 0; i < NXFS; i++) {
	    uprintf("Current xfs_nodes on xfs[%d] are:\n", i);

	    if (XFS_TO_VFS(&xfs[i]) == NULL)
		continue;

	    t = XFS_TO_VFS(&xfs[i])->mnt_vnodelist.lh_first;
	    while (t != NULL) {
		struct xfs_node *x = VNODE_TO_XNODE(t);

		uprintf("%d.%d.%d.%d(%d) ",
			x->handle.a,
			x->handle.b,
			x->handle.c,
			x->handle.d,
			x->vn->v_usecount);
		uprintf(" !\n");
		t = t->v_mntvnodes.le_next;
	    }
	}
    }
#endif
    return 0;
}

/*
 * Install and uninstall filesystem.
 */

extern struct vnodeopv_desc xfs_vnodeop_opv_desc;

static int vfs_offset;

int
xfs_install_filesys(void)
{
    int i;

    XFSDEB(XDEBLKM, ("Got to begining of loading xfs_filesys\n"));
    XFSDEB(XDEBLKM, ("Checking for double loading of filesystem\n"));

    /* make sure there's no VFS in the table with this name */
    for (i = 0; i < nvfssw; i++)
	if (vfssw[i] != (struct vfsops *) 0 &&
	    strncmp(vfssw[i]->vfs_name,
		    xfs_vfsops.vfs_name,
		    MFSNAMELEN) == 0)
	    return (EEXIST);

    XFSDEB(XDEBLKM, ("Searching the last available slot\n"));

    /* pick the last available empty slot */
    for (i = nvfssw - 1; i >= 0; i--)
	if (vfssw[i] == (struct vfsops *) 0)
	    break;
    if (i < 0) {		       /* or if none, punt */
	return (EINVAL);
    }
    /*
     * Set up file system
     */
    XFSDEB(XDEBLKM, ("Setting up filesystem\n"));

    vfssw[i] = &xfs_vfsops;
    vfssw[i]->vfs_refcount = 0;

    /*
     * Call init function for this VFS...
     */
    XFSDEB(XDEBLKM, ("Doing vfs_init()\n"));
    (*(vfssw[i]->vfs_init)) ();

    /* done! */
    vfs_offset = i;

    XFSDEB(XDEBLKM, ("Using VFS slot %d\n", i));

    return 0;
}

int
xfs_uninstall_filesys(void)
{
    /* maybe brutal unloading would be better? */
#if 0
    if (vfssw[vfs_offset]->vfs_refcount != 0)
	return (EBUSY);
#endif

    /* replace current slot contents with old contents */
    vfssw[vfs_offset] = (struct vfsops *) 0;

    return 0;
}

int
xfs_stat_filesys(void)
{
    return xfs_uprintf_filsys();
}

#elif defined(__FreeBSD__)
static int
xfs_init(void)
{
    XFSDEB(XDEBVFOPS, ("xfs_init\n"));
    return 0;
}

static struct vfsops xfs_vfsops = {
    xfs_mount,
    xfs_start,
    xfs_unmount,
    xfs_root,
    xfs_quotactl,
    xfs_statfs,
    xfs_sync,
    xfs_vget,
    xfs_fhtovp,
    xfs_vptofh,
    xfs_init
};

static struct vfsconf xfs_vfc = {
    &xfs_vfsops,
    "xfs",
    0,
    0,
    0
};

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

extern vop_t **dead_vnodeop_p;

/*
 *
 */
static int
xfs_uprintf_filsys(void)
{
#if 1
    {
	int i;

	uprintf("Currently loaded filesystems are:\n");
	for (i = 0; i < MOUNT_MAXTYPE; i++)
	    if (vfsconf[i]->vfc_name)
		uprintf("vfsconf[%d] = { \"%s\", 0x%x}\n",
			i, vfsconf[i]->vfc_name, vfssw[i]);
    }
#endif
#if 1
    {
	int i;

	struct vnode *t;

	for (i = 0; i < NXFS; i++) {
	    uprintf("Current xfs_nodes on xfs[%d] are:\n", i);

	    if (XFS_TO_VFS(&xfs[i]) == NULL)
		continue;

	    t = XFS_TO_VFS(&xfs[i])->mnt_vnodelist.lh_first;
	    while (t != NULL) {
		struct xfs_node *x = VNODE_TO_XNODE(t);

		uprintf("%d.%d.%d.%d(%d) ",
			x->handle.a,
			x->handle.b,
			x->handle.c,
			x->handle.d,
			x->vn->v_usecount);
		uprintf(" !\n");
		t = t->v_mntvnodes.le_next;
	    }
	}
    }
#endif
    return 0;
}

/*
 * Install and uninstall filesystem.
 */

extern struct vnodeopv_desc xfs_vnodeop_opv_desc;

int
xfs_install_filesys(void)
{
    int i;
    struct vnodeopv_desc *foo[] = {&xfs_vnodeop_opv_desc, NULL};

    for (i = 0; i < MOUNT_MAXTYPE; i++)
	if (strcmp(vfsconf[i]->vfc_name, xfs_vfc.vfc_name) == 0)
	    return EEXIST;

    for (i = MOUNT_MAXTYPE - 1; i >= 0; --i)
	if (vfsconf[i] == &void_vfsconf)
	    break;
    if (i < 0) {
	uprintf("failed to find free VFS slot\n");
	return EINVAL;
    }
    printf("Using VFS slot %d\n", i);

    xfs_vfc.vfc_index = i;
    vfsconf[i] = &xfs_vfc;
    vfssw[i] = xfs_vfc.vfc_vfsops;

    *(foo[0]->opv_desc_vector_p) = NULL;
    vfs_opv_init(foo);
    (*(vfssw[xfs_vfc.vfc_index]->vfs_init)) ();
    return 0;
}

int
xfs_uninstall_filesys(void)
{
    int i;

    /* Check for open, mounted and active vnodes */
    for (i = 0; i < NXFS; i++)
	if (xfs[i].nnodes)
	    printf("Warning (error really): There are active vnodes!\n");
    i = xfs_vfc.vfc_index;

    if (vfsconf[i]->vfc_refcount)
	printf("Warning: refcount != 0\n");

    vfsconf[i] = &void_vfsconf;
    vfssw[i] = NULL;
    return 0;
}

int
xfs_stat_filesys(void)
{
    return xfs_uprintf_filsys();
}

#endif

static struct vnode *
make_dead_vnode(struct mount * mp)
{
    struct vnode *dead;
    int error;

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

    error = getnewvnode(VT_NON, mp, dead_vnodeop_p, &dead);
    if (error) {
	panic("make_dead_vnode: getnewvnode failed: error = %d\n", error);
    }
    return dead;
}
