/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
 * vim:expandtab:shiftwidth=8:tabstop=8:
 *
 *  Copyright (C) 2000 Stelias Computing, Inc.
 *  Copyright (C) 2000 Red Hat, Inc.
 *  Copyright (C) 2000 Mountain View Data, Inc.
 *
 *  Extended Attribute Support
 *  Copyright (C) 2001 Shirish H. Phatak, Tacit Networks, Inc.
 *
 *   This file is part of InterMezzo, http://www.inter-mezzo.org.
 *
 *   InterMezzo is free software; you can redistribute it and/or
 *   modify it under the terms of version 2 of the GNU General Public
 *   License as published by the Free Software Foundation.
 *
 *   InterMezzo is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with InterMezzo; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <stdarg.h>

#include <asm/bitops.h>
#include <asm/uaccess.h>
#include <asm/system.h>

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/ext2_fs.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/sched.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/locks.h>
#include <linux/blkdev.h>
#include <linux/init.h>
#define __NO_VERSION__
#include <linux/module.h>

#include <linux/fsfilter.h>
#include <linux/intermezzo_fs.h>


int filter_print_entry = 0;
int filter_debug = 0xfffffff;
/*
 * The function in this file are responsible for setting up the 
 * correct methods layered file systems like InterMezzo and snapfs
 */


static struct filter_fs filter_oppar[FILTER_FS_TYPES];

/* get to the upper methods (intermezzo, snapfs) */
inline struct super_operations *filter_c2usops(struct filter_fs *cache)
{
        return &cache->o_fops.filter_sops;
}

inline struct inode_operations *filter_c2udiops(struct filter_fs *cache)
{
        return &cache->o_fops.filter_dir_iops;
}


inline struct inode_operations *filter_c2ufiops(struct filter_fs *cache)
{
        return &cache->o_fops.filter_file_iops;
}

inline struct inode_operations *filter_c2usiops(struct filter_fs *cache)
{
        return &cache->o_fops.filter_sym_iops;
}


inline struct file_operations *filter_c2udfops(struct filter_fs *cache)
{
        return &cache->o_fops.filter_dir_fops;
}

inline struct file_operations *filter_c2uffops(struct filter_fs *cache)
{
        return &cache->o_fops.filter_file_fops;
}

inline struct file_operations *filter_c2usfops(struct filter_fs *cache)
{
        return &cache->o_fops.filter_sym_fops;
}

inline struct dentry_operations *filter_c2udops(struct filter_fs *cache)
{
        return &cache->o_fops.filter_dentry_ops;
}

/* get to the cache (lower) methods */
inline struct super_operations *filter_c2csops(struct filter_fs *cache)
{
        return cache->o_caops.cache_sops;
}

inline struct inode_operations *filter_c2cdiops(struct filter_fs *cache)
{
        return cache->o_caops.cache_dir_iops;
}

inline struct inode_operations *filter_c2cfiops(struct filter_fs *cache)
{
        return cache->o_caops.cache_file_iops;
}

inline struct inode_operations *filter_c2csiops(struct filter_fs *cache)
{
        return cache->o_caops.cache_sym_iops;
}

inline struct file_operations *filter_c2cdfops(struct filter_fs *cache)
{
        return cache->o_caops.cache_dir_fops;
}

inline struct file_operations *filter_c2cffops(struct filter_fs *cache)
{
        return cache->o_caops.cache_file_fops;
}

inline struct file_operations *filter_c2csfops(struct filter_fs *cache)
{
        return cache->o_caops.cache_sym_fops;
}

inline struct dentry_operations *filter_c2cdops(struct filter_fs *cache)
{
        return cache->o_caops.cache_dentry_ops;
}


void filter_setup_journal_ops(struct filter_fs *ops, char *cache_type)
{
        if ( strlen(cache_type) == strlen("ext2") &&
             memcmp(cache_type, "ext2", strlen("ext2")) == 0 ) {
#if CONFIG_EXT2_FS
                ops->o_trops = &presto_ext2_journal_ops;
#else
                ops->o_trops = NULL;
#endif
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if ( strlen(cache_type) == strlen("ext3") &&
             memcmp(cache_type, "ext3", strlen("ext3")) == 0 ) {
#if defined(CONFIG_EXT3_FS) || defined (CONFIG_EXT3_FS_MODULE)
                ops->o_trops = &presto_ext3_journal_ops;
#else
                ops->o_trops = NULL;
#endif
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if ( strlen(cache_type) == strlen("tmpfs") &&
             memcmp(cache_type, "tmpfs", strlen("tmpfs")) == 0 ) {
#if defined(CONFIG_TMPFS)
                ops->o_trops = &presto_tmpfs_journal_ops;
#else
                ops->o_trops = NULL;
#endif
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if ( strlen(cache_type) == strlen("reiserfs") &&
             memcmp(cache_type, "reiserfs", strlen("reiserfs")) == 0 ) {
#if 0
		/* #if defined(CONFIG_REISERFS_FS) || defined(CONFIG_REISERFS_FS_MODULE) */
                ops->o_trops = &presto_reiserfs_journal_ops;
#else
                ops->o_trops = NULL;
#endif
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if ( strlen(cache_type) == strlen("xfs") &&
             memcmp(cache_type, "xfs", strlen("xfs")) == 0 ) {
#if 0
/*#if defined(CONFIG_XFS_FS) || defined (CONFIG_XFS_FS_MODULE) */
                ops->o_trops = &presto_xfs_journal_ops;
#else
                ops->o_trops = NULL;
#endif
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if ( strlen(cache_type) == strlen("obdfs") &&
             memcmp(cache_type, "obdfs", strlen("obdfs")) == 0 ) {
#if defined(CONFIG_OBDFS_FS) || defined (CONFIG_OBDFS_FS_MODULE)
                ops->o_trops = presto_obdfs_journal_ops;
#else
                ops->o_trops = NULL;
#endif
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }
}


/* find the cache for this FS */
struct filter_fs *filter_get_filter_fs(const char *cache_type)
{
        struct filter_fs *ops = NULL;
        FENTRY;

        if ( strlen(cache_type) == strlen("ext2") &&
             memcmp(cache_type, "ext2", strlen("ext2")) == 0 ) {
                ops = &filter_oppar[FILTER_FS_EXT2];
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if ( strlen(cache_type) == strlen("xfs") &&
             memcmp(cache_type, "xfs", strlen("xfs")) == 0 ) {
                ops = &filter_oppar[FILTER_FS_XFS];
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if ( strlen(cache_type) == strlen("ext3") &&
             memcmp(cache_type, "ext3", strlen("ext3")) == 0 ) {
                ops = &filter_oppar[FILTER_FS_EXT3];
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if ( strlen(cache_type) == strlen("tmpfs") &&
             memcmp(cache_type, "tmpfs", strlen("tmpfs")) == 0 ) {
                ops = &filter_oppar[FILTER_FS_TMPFS];
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if ( strlen(cache_type) == strlen("reiserfs") &&
             memcmp(cache_type, "reiserfs", strlen("reiserfs")) == 0 ) {
                ops = &filter_oppar[FILTER_FS_REISERFS];
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }
        if ( strlen(cache_type) == strlen("obdfs") &&
             memcmp(cache_type, "obdfs", strlen("obdfs")) == 0 ) {
                ops = &filter_oppar[FILTER_FS_OBDFS];
                FDEBUG(D_SUPER, "ops at %p\n", ops);
        }

        if (ops == NULL) {
                CERROR("prepare to die: unrecognized cache type for Filter\n");
        }
        FEXIT;
        return ops;
}


/*
 *  Frobnicate the InterMezzo operations
 *    this establishes the link between the InterMezzo file system
 *    and the underlying file system used for the cache.
 */

void filter_setup_super_ops(struct filter_fs *cache, struct super_operations *cache_sops, struct super_operations *filter_sops)
{
        /* Get ptr to the shared struct snapfs_ops structure. */
        struct filter_ops *props = &cache->o_fops;
        /* Get ptr to the shared struct cache_ops structure. */
        struct cache_ops *caops = &cache->o_caops;

        FENTRY;

        if ( cache->o_flags & FILTER_DID_SUPER_OPS ) {
                FEXIT;
                return;
        }
        cache->o_flags |= FILTER_DID_SUPER_OPS;

        /* Set the cache superblock operations to point to the
           superblock operations of the underlying file system.  */
        caops->cache_sops = cache_sops;

        /*
         * Copy the cache (real fs) superblock ops to the "filter"
         * superblock ops as defaults. Some will be changed below
         */
        memcpy(&props->filter_sops, cache_sops, sizeof(*cache_sops));

        /* 'put_super' unconditionally is that of filter */
        if (filter_sops->put_super) { 
                props->filter_sops.put_super = filter_sops->put_super;
        }

        if (cache_sops->read_inode) {
                props->filter_sops.read_inode = filter_sops->read_inode;
                FDEBUG(D_INODE, "setting filter_read_inode, cache_ops %p, cache %p, ri at %p\n",
                      cache, cache, props->filter_sops.read_inode);
        }

        if (cache_sops->remount_fs)
                props->filter_sops.remount_fs = filter_sops->remount_fs;
        FEXIT;
}


void filter_setup_dir_ops(struct filter_fs *cache, struct inode *inode, struct inode_operations *filter_iops, struct file_operations *filter_fops)
{
        struct inode_operations *cache_filter_iops;
        struct inode_operations *cache_iops = inode->i_op;
        struct file_operations *cache_fops = inode->i_fop;
        FENTRY;

        if ( cache->o_flags & FILTER_DID_DIR_OPS ) {
                FEXIT;
                return;
        }
        cache->o_flags |= FILTER_DID_DIR_OPS;

        /* former ops become cache_ops */
        cache->o_caops.cache_dir_iops = cache_iops;
        cache->o_caops.cache_dir_fops = cache_fops;
        FDEBUG(D_SUPER, "filter at %p, cache iops %p, iops %p\n",
               cache, cache_iops, filter_c2udiops(cache));

        /* setup our dir iops: copy and modify */
        memcpy(filter_c2udiops(cache), cache_iops, sizeof(*cache_iops));

        /* abbreviate */
        cache_filter_iops = filter_c2udiops(cache);

        /* methods that filter if cache filesystem has these ops */
        if (cache_iops->lookup && filter_iops->lookup)
                cache_filter_iops->lookup = filter_iops->lookup;
        if (cache_iops->create && filter_iops->create)
                cache_filter_iops->create = filter_iops->create;
        if (cache_iops->link && filter_iops->link)
                cache_filter_iops->link = filter_iops->link;
        if (cache_iops->unlink && filter_iops->unlink)
                cache_filter_iops->unlink = filter_iops->unlink;
        if (cache_iops->mkdir && filter_iops->mkdir)
                cache_filter_iops->mkdir = filter_iops->mkdir;
        if (cache_iops->rmdir && filter_iops->rmdir)
                cache_filter_iops->rmdir = filter_iops->rmdir;
        if (cache_iops->symlink && filter_iops->symlink)
                cache_filter_iops->symlink = filter_iops->symlink;
        if (cache_iops->rename && filter_iops->rename)
                cache_filter_iops->rename = filter_iops->rename;
        if (cache_iops->mknod && filter_iops->mknod)
                cache_filter_iops->mknod = filter_iops->mknod;
        if (cache_iops->permission && filter_iops->permission)
                cache_filter_iops->permission = filter_iops->permission;
        if (cache_iops->getattr)
                cache_filter_iops->getattr = filter_iops->getattr;
        /* Some filesystems do not use a setattr method of their own
           instead relying on inode_setattr/write_inode. We still need to
           journal these so we make setattr an unconditional operation. 
           XXX: we should probably check for write_inode. SHP
        */
        /*if (cache_iops->setattr)*/
                cache_filter_iops->setattr = filter_iops->setattr;
#ifdef CONFIG_FS_EXT_ATTR
	/* For now we assume that posix acls are handled through extended
	* attributes. If this is not the case, we must explicitly trap 
	* posix_set_acl. SHP
	*/
	if (cache_iops->set_ext_attr && filter_iops->set_ext_attr)
		cache_filter_iops->set_ext_attr = filter_iops->set_ext_attr;
#endif


        /* copy dir fops */
        memcpy(filter_c2udfops(cache), cache_fops, sizeof(*cache_fops));

        /* unconditional filtering operations */
        filter_c2udfops(cache)->ioctl = filter_fops->ioctl;

        FEXIT;
}


void filter_setup_file_ops(struct filter_fs *cache, struct inode *inode, struct inode_operations *filter_iops, struct file_operations *filter_fops)
{
        struct inode_operations *pr_iops;
        struct inode_operations *cache_iops = inode->i_op;
        struct file_operations *cache_fops = inode->i_fop;
        FENTRY;

        if ( cache->o_flags & FILTER_DID_FILE_OPS ) {
                FEXIT;
                return;
        }
        cache->o_flags |= FILTER_DID_FILE_OPS;

        /* steal the old ops */
        /* former ops become cache_ops */
        cache->o_caops.cache_file_iops = cache_iops;
        cache->o_caops.cache_file_fops = cache_fops;
        
        /* abbreviate */
        pr_iops = filter_c2ufiops(cache); 

        /* setup our dir iops: copy and modify */
        memcpy(pr_iops, cache_iops, sizeof(*cache_iops));

        /* copy dir fops */
        CERROR("*** cache file ops at %p\n", cache_fops);
        memcpy(filter_c2uffops(cache), cache_fops, sizeof(*cache_fops));

        /* assign */
        /* See comments above in filter_setup_dir_ops. SHP */
        /*if (cache_iops->setattr)*/
                pr_iops->setattr = filter_iops->setattr;
        if (cache_iops->getattr)
                pr_iops->getattr = filter_iops->getattr;
        /* XXX Should this be conditional rmr ? */
        pr_iops->permission = filter_iops->permission;
#ifdef CONFIG_FS_EXT_ATTR
    	/* For now we assume that posix acls are handled through extended
	* attributes. If this is not the case, we must explicitly trap and 
	* posix_set_acl
	*/
	if (cache_iops->set_ext_attr && filter_iops->set_ext_attr)
		pr_iops->set_ext_attr = filter_iops->set_ext_attr;
#endif


        /* unconditional filtering operations */
        filter_c2uffops(cache)->open = filter_fops->open;
        filter_c2uffops(cache)->release = filter_fops->release;
        filter_c2uffops(cache)->write = filter_fops->write;
        filter_c2uffops(cache)->ioctl = filter_fops->ioctl;

        FEXIT;
}

/* XXX in 2.3 there are "fast" and "slow" symlink ops for ext2 XXX */
void filter_setup_symlink_ops(struct filter_fs *cache, struct inode *inode, struct inode_operations *filter_iops, struct file_operations *filter_fops)
{
        struct inode_operations *pr_iops;
        struct inode_operations *cache_iops = inode->i_op;
        struct file_operations *cache_fops = inode->i_fop;
        FENTRY;

        if ( cache->o_flags & FILTER_DID_SYMLINK_OPS ) {
                FEXIT;
                return;
        }
        cache->o_flags |= FILTER_DID_SYMLINK_OPS;

        /* steal the old ops */
        cache->o_caops.cache_sym_iops = cache_iops;
        cache->o_caops.cache_sym_fops = cache_fops;

        /* abbreviate */
        pr_iops = filter_c2usiops(cache); 

        /* setup our dir iops: copy and modify */
        memcpy(pr_iops, cache_iops, sizeof(*cache_iops));

        /* See comments above in filter_setup_dir_ops. SHP */
        /* if (cache_iops->setattr) */
                pr_iops->setattr = filter_iops->setattr;
        if (cache_iops->getattr)
                pr_iops->getattr = filter_iops->getattr;

        /* assign */
        /* copy fops - careful for symlinks they might be NULL */
        if ( cache_fops ) { 
                memcpy(filter_c2usfops(cache), cache_fops, sizeof(*cache_fops));
        }

        FEXIT;
}

void filter_setup_dentry_ops(struct filter_fs *cache,
                             struct dentry_operations *cache_dop,
                             struct dentry_operations *filter_dop)
{
        if ( cache->o_flags & FILTER_DID_DENTRY_OPS ) {
                FEXIT;
                return;
        }
        cache->o_flags |= FILTER_DID_DENTRY_OPS;

        cache->o_caops.cache_dentry_ops = cache_dop;
        memcpy(&cache->o_fops.filter_dentry_ops,
               filter_dop, sizeof(*filter_dop));
        
        if (cache_dop &&  cache_dop != filter_dop && cache_dop->d_revalidate){
                CERROR("WARNING: filter overriding revalidation!\n");
        }
        return;
}
