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

RCSID("$Id: xfs_vfsops.c,v 1.3 1998/12/22 13:16:46 lha 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>

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

struct xfs xfs[NXFS];

static int
xfs_mount(struct mount * mp,
#if defined(__APPLE__)
	  char *user_path,
#else
	  const char *user_path,
#endif
	  caddr_t user_data,
	  struct nameidata * ndp,
	  struct proc * p)
{
    struct vnode *devvp;
    dev_t dev;
    int error;
    struct vattr vat;
    char path[MAXPATHLEN];
    char data[MAXPATHLEN];
    size_t len;

    error = copyinstr(user_path, path, MAXPATHLEN, &len);
    if (error)
	return error;

    error = copyinstr(user_data, data, MAXPATHLEN, &len);
    if (error)
	return error;

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

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

    devvp = ndp->ni_vp;

    if (devvp->v_type != VCHR) {
	vput(devvp);
	return ENXIO;
    }
    error = VOP_GETATTR(devvp, &vat, p->p_ucred, p);
    if (error) {
	vput(devvp);
	return error;
    }
    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__) || defined(__APPLE__)
    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)];
    vfs_getnewfsid(mp);

    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));

    strncpy(mp->mnt_stat.f_fstypename,
	    "xfs",
	    sizeof(mp->mnt_stat.f_fstypename));

    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);
#if !defined(__APPLE__)
    extern int doforce;
#endif
    int flags = 0;
    int error;

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

    if (mntflags & MNT_FORCE) {
#if !defined(__APPLE__)
	if (!doforce)
	    return EINVAL;
#endif
	flags |= FORCECLOSE;
    }

    error = free_all_xfs_nodes(xfsp, flags);
    if (error)
	return error;

    xfsp->status = 0;
    XFS_TO_VFS(xfsp) = NULL;
    return 0;
}

static int
xfs_root(struct mount * mp, struct vnode ** vpp)
{
    struct xfs *xfsp = VFS_TO_XFS(mp);
    struct xfs_message_getroot msg;
    int error;
#if defined(__APPLE__)
    struct proc *curproc = current_proc();
#endif	

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

    do {
	if (xfsp->root != NULL) {
	    *vpp = XNODE_TO_VNODE(xfsp->root);
	    VREF(*vpp);
	    XFSDEB(XDEBVFOPS, ("xfs_root returning succesfully\n"));
	    return 0;
	}
	msg.header.opcode = XFS_MSG_GETROOT;
	msg.cred.uid = curproc->p_ucred->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);
    /*
     * Failed to get message through, need to pretend that all went well
     * and return a fake dead vnode to be able to unmount.
     */
    XFSDEB(XDEBVFOPS, ("xfs_root returning dead vnode\n"));
    *vpp = make_dead_vnode(mp);
    (*vpp)->v_flag |= VROOT;
#if 1
    VREF(*vpp);
#endif
    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;
}


/* sysctl()able variables :-) */
static int
xfs_sysctl(int *name, u_int namelen, void *oldp, size_t * oldlenp,
	   void *newp, size_t newlen, struct proc * p)
{
    /* Supposed to be terminal... */
    if (namelen != 1)
	return (ENOTDIR);

    return (EOPNOTSUPP);
}

static int
xfs_init(struct vfsconf * vfsp)
{
    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,
    xfs_sysctl
};

/*
 * 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 0
    {
	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 0
    {
	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)
{

    struct vfsconf *vfsp;
    struct vfsconf **vfspp;


    /* Check if filesystem already known */
    for (vfspp = &vfsconf, vfsp = vfsconf;
	 vfsp;
	 vfspp = &vfsp->vfc_next, vfsp = vfsp->vfc_next)
	if (strncmp(vfsp->vfc_name,
		    "xfs", MFSNAMELEN) == 0)
	    return (EEXIST);

    /* Allocate and initialize */
#if defined(__APPLE__)
    MALLOC(vfsp, struct vfsconf *, sizeof(struct vfsconf),
	   M_MOUNT, M_WAITOK);
#else
    MALLOC(vfsp, struct vfsconf *, sizeof(struct vfsconf),
	   M_VFS, M_WAITOK);
#endif

    vfsp->vfc_vfsops = &xfs_vfsops;
    strncpy(vfsp->vfc_name, "xfs", MFSNAMELEN);
    vfsp->vfc_typenum = 0;
    vfsp->vfc_refcount = 0;
    vfsp->vfc_flags = 0;
    vfsp->vfc_mountroot = 0;
    vfsp->vfc_next = NULL;

    maxvfsconf++;

    /* Add to the end of the list */
    *vfspp = vfsp;

    /* Call vfs_init() */
    printf("Calling vfs_init()\n");
    (*(vfsp->vfc_vfsops->vfs_init)) (vfsp);

    /* done! */

    return 0;
}

int
xfs_uninstall_filesys(void)
{

    struct vfsconf *vfsp;
    struct vfsconf **vfspp;


    /* Find our vfsconf struct */
    for (vfspp = &vfsconf, vfsp = vfsconf;
	 vfsp;
	 vfspp = &vfsp->vfc_next, vfsp = vfsp->vfc_next)
	if (strncmp(vfsp->vfc_name,
		    "xfs",
		    MFSNAMELEN) == 0)
	    break;

    if (!vfsp)			       /* Not found */
	return (EEXIST);

    if (vfsp->vfc_refcount)	       /* In use */
	return (EBUSY);

    /* Remove from list and free  */
    *vfspp = vfsp->vfc_next;
#if defined(__APPLE__)
    FREE(vfsp, M_MOUNT);
#else
    FREE(vfsp, M_VFS);
#endif

    maxvfsconf--;

    return 0;
}



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

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;
}
