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

#define __NO_VERSION__

#include <xfs/xfs_locl.h>
#include <xfs/xfs_dev.h>
#include <xfs/xfs_syscalls.h>
#include <xfs/nxfs.h>

RCSID("$Id: xfs_vfsops.c,v 1.68 2000/06/26 00:40:15 assar Exp $");

struct xfs xfs[NXFS];

static void xfs_read_inode(struct inode *inode);
static void xfs_delete_inode(struct inode *inode);
static void xfs_put_super(struct super_block *sb);
static void xfs_put_inode(struct inode *inode);
static void xfs_write_super(struct super_block * sb);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,51)
static int xfs_statfs(struct super_block *sb, struct statfs *buf, int bufsiz);
#else
static int xfs_statfs(struct super_block *sb, struct statfs *buf);
#endif

#ifdef HAVE_STRUCT_SUPER_OPERATIONS_NOTIFY_CHANGE
static int xfs_notify_change(struct dentry *dentry, struct iattr *attr);
#endif

static struct super_operations xfs_sops = { 
    read_inode		: xfs_read_inode,
    put_inode		: xfs_put_inode,
    delete_inode	: xfs_delete_inode,
#ifdef HAVE_STRUCT_SUPER_OPERATIONS_NOTIFY_CHANGE
    notify_change	: xfs_notify_change,
#endif
    put_super		: xfs_put_super,
    write_super		: xfs_write_super,
    statfs		: xfs_statfs,
};

/*
 * try to replace the fake root of `sb' with a real one
 */

int
xfs_replace_root (struct super_block *sb)
{
    struct xfs *xfsp = VFS_TO_XFS(sb);
    int error = 0;

    XFSDEB(XDEBVFOPS, ("xfs_replace_root\n"));

    while (xfsp->root == NULL && error == 0) {
	struct xfs_message_getroot msg;

	msg.header.opcode = XFS_MSG_GETROOT;
	/*
	 * Mounting should done by root, so get the root node with
	 * root's priviliges (usually none, and none is needed).
	 */
	
	msg.cred.uid = 0;
	msg.cred.pag = 0;
	
	error = xfs_message_rpc(xfsp->fd, &msg.header, sizeof(msg));
	XFSDEB(XDEBVFOPS,
	       ("xfs_replace_root xfs_message_rpc error = %d\n", error));
	if (error == 0)
	    error = ((struct xfs_message_wakeup *) &msg)->error;
	XFSDEB(XDEBVFOPS,
	       ("xfs_replace_root xfs_message_wakeup error = %d\n", error));
    }
    if (error) {
	XFSDEB(XDEBVFOPS, ("xfs_replace_root failed: %d\n", error));
	return error;
    }

    /* swap xfsp->root & sb->s_root->d_inode */
    {
	struct inode *inode = XNODE_TO_VNODE(xfsp->root);
	struct inode *root  = sb->s_root->d_inode;
	struct xfs_node *xn = VNODE_TO_XNODE(root);

	XNODE_TO_VNODE(xfsp->root) = root;
	VNODE_TO_XNODE(root)       = xfsp->root;
	VNODE_TO_XNODE(inode)      = NULL;
	XNODE_TO_VNODE(xn)         = inode;
	free_xfs_node (xn);
	iput (inode);

	xfs_attr2inode (&xfsp->root->attr, root);
	root->i_op = &xfs_dir_inode_operations;
#ifndef HAVE_STRUCT_INODE_OPERATIONS_DEFAULT_FILE_OPS
	root->i_fop = &xfs_dir_operations;
#endif
	xfsp->status |= XFS_ROOTINSTALLED;
    }
    XFSDEB(XDEBVFOPS, ("xfs_replace_root: success\n"));
    return 0;
}

/*
 * create a `fake' root inode for `sb'
 */

static struct inode *
make_root_vnode(struct super_block *sb)
{
    struct xfs_node *xn;
    struct xfs_msg_node node;
    struct inode *inode;

    memset (&node, 0, sizeof(node));

    node.handle.a = node.handle.b = node.handle.c = node.handle.d = 0;
    node.anonrights = XFS_RIGHT_R;
    XA_SET_MODE(&node.attr, S_IFDIR | 0777);
    XA_SET_NLINK(&node.attr, 100);
    XA_SET_SIZE(&node.attr, 0);
    XA_SET_UID(&node.attr, 0);
    XA_SET_GID(&node.attr, 0);
    XA_SET_ATIME(&node.attr, 0);
    XA_SET_MTIME(&node.attr, 0);
    XA_SET_CTIME(&node.attr, 0);
    XA_SET_FILEID(&node.attr, 0);
    XA_SET_TYPE(&node.attr, XFS_FILE_DIR);

    xn = new_xfs_node (VFS_TO_XFS(sb), &node);
    inode = XNODE_TO_VNODE(xn);

    inode->i_op = &xfs_dead_inode_operations;
#ifndef HAVE_STRUCT_INODE_OPERATIONS_DEFAULT_FILE_OPS
    inode->i_fop = &xfs_dead_operations;
#endif
    XFS_TOKEN_CLEAR(xn, ~0,
		    XFS_OPEN_MASK | XFS_ATTR_MASK | XFS_DATA_MASK | XFS_LOCK_MASK);
    return inode;
}

/*
 * create a root dcache entry for `sb'
 */

static void
make_root (struct super_block *sb)
{
    struct dentry *dp;

#ifdef HAVE_D_ALLOC_ROOT_TWO_ARGS
    dp = d_alloc_root(make_root_vnode(sb), NULL);
#else
    dp = d_alloc_root(make_root_vnode(sb));
#endif
    xfs_d_init(dp);
    sb->s_root = dp;
    xfs_replace_root (sb);
}

struct super_block *
xfs_read_super (struct super_block * sb, void * data,
		int silent)
{
    int minordevice=0;
    struct dentry *ddev;
    
    XFSDEB(XDEBVFOPS, ("xfs_read_super starting\n"));
    XFSDEB(XDEBVFOPS, ("xfs_read_super: sb: %p data: %p silent: %d\n",
		       sb, data, silent));
    XFSDEB(XDEBVFOPS, ("xfs_read_super: %d:%d\n",
		       (int) MAJOR(sb->s_dev),
		       (int) MINOR(sb->s_dev)));
    
    XFSDEB(XDEBVFOPS, ("xfs_read_super: begin setting variables\n"));

#ifndef LINUX2_3
    MOD_INC_USE_COUNT;
#endif
    
    if (data != NULL) {
#ifndef LINUX2_3
	ddev = lookup_dentry (data, NULL, 1);
#else
	{
	    struct nameidata nd;
	    int error = 0;

	    if (path_init (data, LOOKUP_POSITIVE, &nd))
		error = path_walk (data, &nd);
	    if (error)
		ddev = ERR_PTR(PTR_ERR(error));
	    else
		ddev = nd.dentry;
	}
#endif
	if (!IS_ERR(ddev)) {
	    minordevice = MINOR(ddev->d_inode->i_rdev);
	    dput (ddev);
	}
    }

    xfs[minordevice].status |= XFS_MOUNTED;
    xfs[minordevice].sb = sb;
    xfs[minordevice].root = 0;
    xfs[minordevice].nnodes = 0;
    xfs[minordevice].nodes = 0;
    xfs[minordevice].fd = minordevice;
    VFS_TO_XFS(sb) = &xfs[minordevice];
    
    sb->s_op = &xfs_sops;
    make_root(sb);
    sb->s_blocksize = 1024;
    sb->s_blocksize_bits = 10;
    
    XFSDEB(XDEBVFOPS, ("xfs_read_super: returning\n"));

    return sb;
}

static void
xfs_write_super(struct super_block * sb)
{
    sb->s_dirt = 0;
}

static void
xfs_put_super(struct super_block *sb)
{
    struct xfs *xfsp = VFS_TO_XFS(sb);

    XFSDEB(XDEBVFOPS, ("xfs_put_super starting\n"));
    xfsp->status &= ~XFS_MOUNTED;
    xfsp->status &= ~XFS_ROOTINSTALLED;
    sb->s_dev = 0;
    xfsp->root = NULL;
    XFSDEB(XDEBVFOPS, ("xfs_put_super exiting\n"));
#ifndef LINUX2_3
    MOD_DEC_USE_COUNT;
#endif
}

static void
xfs_read_inode(struct inode *inode)
{
    XFSDEB(XDEBVFOPS,("xfs_read_inode starting inode: %p\n",inode));
    XFSDEB(XDEBVFOPS,("xfs_read_inode inode->i_ino: %ld\n",inode->i_ino));
    inode->i_blksize = 1024;
    inode->i_mode = 0;
    inode->i_op = NULL;
    XFSDEB(XDEBVFOPS,("xfs_read_inode exiting\n"));
}
 
static void
xfs_put_inode(struct inode *inode)
{
    XFSDEB(XDEBVFOPS,("xfs_put_inode: inode: %p count: %d aliases:",
		      inode, xfs_iread(inode)));
    print_aliases(inode);
    if (xfs_iread(inode) == 1)
	inode->i_nlink = 0;
}

/*
 * `inode' is about to die.  add it to the list of nodes to be inactivated.
 * this is not done immediately to avoid allocating memory in a function
 * that's called when memory is scarse.
 */

static void
xfs_delete_inode(struct inode *inode)
{
    struct xfs_node *xn = VNODE_TO_XNODE(inode);

    XFSDEB(XDEBNODE,
	   ("xfs_delete_inode inode = 0x%p i_ino:0x%lx i_count:%u\n",
	    inode, inode->i_ino, xfs_iread(inode)));

    if (xn == NULL) {
	clear_inode (inode);
	return;
    }

    clear_xfs_node (xn);
    xfs_queue_inactive (xn);
    clear_inode(inode);
}

void
vattr2xfs_attr(const struct iattr *iattr, struct xfs_attr *attr)
{
    XA_CLEAR(attr);
    if (iattr->ia_valid & ATTR_MODE)
	XA_SET_MODE(attr,iattr->ia_mode);
    if (iattr->ia_valid & ATTR_UID)
	XA_SET_UID(attr,iattr->ia_uid);
    if (iattr->ia_valid & ATTR_GID)
	XA_SET_GID(attr,iattr->ia_gid);
    if (iattr->ia_valid & ATTR_ATIME)
	XA_SET_ATIME(attr,iattr->ia_atime);
    if (iattr->ia_valid & ATTR_MTIME)
	XA_SET_MTIME(attr,iattr->ia_mtime);
    if (iattr->ia_valid & ATTR_CTIME)
	XA_SET_CTIME(attr,iattr->ia_ctime);
    if (iattr->ia_valid & ATTR_SIZE)
	XA_SET_SIZE(attr,iattr->ia_size);
}

#ifdef HAVE_STRUCT_SUPER_OPERATIONS_NOTIFY_CHANGE
static int
xfs_notify_change(struct dentry *dentry, struct iattr *attr)
{
    struct inode *inode = dentry->d_inode;
    struct xfs *xfsp = XFS_FROM_VNODE(inode);
    struct xfs_node *xn = VNODE_TO_XNODE(inode);
    int error = 0;
    
    XFSDEB(XDEBNODE, ("xfs_notify_change\n"));
    
    if (XFS_TOKEN_GOT(xn, XFS_ATTR_W)) {
        /* Update attributes and mark them dirty. */
        VNODE_TO_XNODE(inode)->flags |= XFS_ATTR_DIRTY;
	return -EINVAL;                /* XXX not yet implemented */
    } else {
        struct xfs_message_putattr msg;

        msg.header.opcode = XFS_MSG_PUTATTR;
	msg.cred.uid = current->uid;
	msg.cred.pag = xfs_get_pag();
        msg.handle = xn->handle;
        vattr2xfs_attr(attr, &msg.attr);
	inode_setattr(inode, attr);
	if (XFS_TOKEN_GOT(xn, XFS_DATA_R)) {
	    if (S_ISREG(inode->i_mode))
		XA_SET_SIZE(&msg.attr,  inode->i_size);
	    XA_SET_MTIME(&msg.attr, inode->i_mtime);
	}
        error = xfs_message_rpc(xfsp->fd, &msg.header, sizeof(msg));
        if (error == 0) {
            error = ((struct xfs_message_wakeup *) & msg)->error;
	    
	}
        XFS_TOKEN_CLEAR(xn, XFS_ATTR_VALID, XFS_ATTR_MASK);
    }
    
    return error;
}
#endif /* HAVE_STRUCT_SUPER_OPERATIONS_NOTIFY_CHANGE */

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,51)
static int xfs_statfs(struct super_block *sb, struct statfs *buf, int bufsiz)
#else
static int xfs_statfs(struct super_block *sb, struct statfs *buf)
#endif
{
    struct statfs tmp;
    
    tmp.f_type    = 0x47114711;
    tmp.f_bsize   = sb->s_blocksize;
    tmp.f_blocks  = 1024*1024;
    tmp.f_bfree   = 1024*1024;
    tmp.f_bavail  = 1024*1024;
    tmp.f_files   = 0;
    tmp.f_ffree   = 1024*1024;
    tmp.f_namelen = NAME_MAX;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,51)
    return copy_to_user(buf, &tmp, bufsiz) ? -EFAULT : 0;
#else
    *buf = tmp;
    return 0;
#endif
}

void
print_nodes(int i)
{
    printk("%d nodes\n", xfs[i].nnodes);
}
