/* -*- 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 Tacitus Systems
 *  Copyright (C) 2000 Peter J. Braam
 *
 *   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/smp_lock.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/intermezzo_fs.h>
#include <linux/intermezzo_psdev.h>

static inline void presto_relock_sem(struct inode *dir) 
{
        /* the lock from sys_mkdir / lookup_create */
        down(&dir->i_sem);
        /* the rest is done by the do_{create,mkdir, ...} */
}

static inline void presto_relock_other(struct inode *dir) 
{
        /* vfs_mkdir locks */
        down(&dir->i_zombie);
        lock_kernel(); 
}

static inline void presto_fulllock(struct inode *dir) 
{
        /* the lock from sys_mkdir / lookup_create */
        down(&dir->i_sem);
        /* vfs_mkdir locks */
        down(&dir->i_zombie);
        lock_kernel(); 
}

static inline void presto_unlock(struct inode *dir) 
{
        /* vfs_mkdir locks */
        unlock_kernel(); 
        up(&dir->i_zombie);
        /* the lock from sys_mkdir / lookup_create */
        up(&dir->i_sem);
}


/*
 * these are initialized in super.c
 */
extern int presto_permission(struct inode *inode, int mask);
static int izo_authorized_uid = 0;

int izo_dentry_is_ilookup(struct dentry *dentry, ino_t *id,
                          unsigned int *generation)
{
        char tmpname[64];
        char *next;

        ENTRY;
        /* prefix is 7 characters: '...ino:' */
        if ( dentry->d_name.len < 7 || dentry->d_name.len > 64 ||
             memcmp(dentry->d_name.name, PRESTO_ILOOKUP_MAGIC, 7) != 0 ) {
                EXIT;
                return 0;
        }

        memcpy(tmpname, dentry->d_name.name + 7, dentry->d_name.len - 7);
        *(tmpname + dentry->d_name.len - 7) = '\0';

        /* name is of the form ...ino:<inode number>:<generation> */
        *id = simple_strtoul(tmpname, &next, 16);
        if ( *next == PRESTO_ILOOKUP_SEP ) {
                *generation = simple_strtoul(next + 1, 0, 16);
                CDEBUG(D_INODE, "ino string: %s, Id = %lx (%lu), "
                       "generation %x (%d)\n",
                       tmpname, *id, *id, *generation, *generation);
                EXIT;
                return 1;
        } else {
                EXIT;
                return 0;
        }
}

struct dentry *presto_tmpfs_ilookup(struct inode *dir, 
                                    struct dentry *dentry,
                                    ino_t ino, 
                                    unsigned int generation)
{
        return dentry; 
}


inline int presto_can_ilookup(void)
{
        return (current->euid == izo_authorized_uid ||
                capable(CAP_DAC_READ_SEARCH));
}

struct dentry *presto_iget_ilookup(struct inode *dir, 
                                          struct dentry *dentry,
                                          ino_t ino, 
                                          unsigned int generation)
{
        struct inode *inode;
        int error;

        ENTRY;

        if ( !presto_can_ilookup() ) {
                CERROR("ilookup denied: euid %u, authorized_uid %u\n",
                       current->euid, izo_authorized_uid);
                return ERR_PTR(-EPERM);
        }
        error = -ENOENT;
        inode = iget(dir->i_sb, ino);
        if (!inode) { 
                CERROR("fatal: NULL inode ino %lu\n", ino); 
                goto cleanup_iput;
        }
        if (is_bad_inode(inode) || inode->i_nlink == 0) {
                CERROR("fatal: bad inode ino %lu, links %d\n", ino, inode->i_nlink); 
                goto cleanup_iput;
        }
        if (inode->i_generation != generation) {
                CERROR("fatal: bad generation %u (want %u)\n",
                       inode->i_generation, generation);
                goto cleanup_iput;
        }

        d_instantiate(dentry, inode);
        dentry->d_flags |= DCACHE_NFSD_DISCONNECTED; /* NFS hack */

        EXIT;
        return NULL;

cleanup_iput:
        if (inode)
                iput(inode);
        return ERR_PTR(error);
}

struct dentry *presto_add_ilookup_dentry(struct dentry *parent,
                                         struct dentry *real)
{
        struct inode *inode = real->d_inode;
        struct dentry *de;
        char buf[32];
        char *ptr = buf;
        struct dentry *inodir;
        struct presto_dentry_data *dd;

        inodir = lookup_one_len("..iopen..", parent,  strlen("..iopen..")); 
        if (!inodir || IS_ERR(inodir) || !inodir->d_inode ) { 
                CERROR("%s: bad ..iopen.. lookup\n", __FUNCTION__); 
                return NULL; 
        }
        inodir->d_inode->i_op = &presto_dir_iops;

        snprintf(ptr, 32, "...ino:%lx:%x", inode->i_ino, inode->i_generation);

        de = lookup_one_len(ptr, inodir,  strlen(ptr)); 
        if (!de || IS_ERR(de)) {
                CERROR("%s: bad ...ino lookup %ld\n", 
                       __FUNCTION__, PTR_ERR(de)); 
                dput(inodir);
                return NULL; 
        }

        dd = presto_d2d(real);
        if (!dd) 
                BUG();

        /* already exists */
        if (de->d_inode)
                BUG();
#if 0 
                if (de->d_inode != inode ) { 
                        CERROR("XX de->d_inode %ld, inode %ld\n", 
                               de->d_inode->i_ino, inode->i_ino); 
                        BUG();
                }
                if (dd->dd_inodentry) { 
                        CERROR("inodentry exists %ld \n", inode->i_ino);
                        BUG();
                }
                dput(inodir);
                return de;
        }
#endif 

        if (presto_d2d(de)) 
                BUG();

        atomic_inc(&inode->i_count);
        de->d_op = &presto_dentry_ops;
        d_add(de, inode);
        if (!de->d_op)
                CERROR("DD: no ops dentry %p, dd %p\n", de, dd);
        dd->dd_inodentry = de;
        dd->dd_count++;
        de->d_fsdata = dd;

        dput(inodir);
        return de;
}

struct dentry *presto_lookup(struct inode * dir, struct dentry *dentry)
{
        int rc = 0;
        struct dentry *de;
        struct presto_cache *cache;
        int minor;
        ino_t ino;
        unsigned int generation;
        struct inode_operations *iops;
        int is_ilookup = 0;

        ENTRY;
        cache = presto_get_cache(dir);
        if (cache == NULL) {
                CERROR("InterMezzo BUG: no cache in presto_lookup "
                       "(dir ino: %ld)!\n", dir->i_ino);
                EXIT;
                return NULL;
        }
        minor = presto_c2m(cache);

        iops = filter_c2cdiops(cache->cache_filter);
        if (!iops || !iops->lookup) {
                CERROR("InterMezzo BUG: filesystem has no lookup\n");
                EXIT;
                return NULL;
        }


        CDEBUG(D_CACHE, "dentry %p, dir ino: %ld, name: %*s, islento: %d\n",
               dentry, dir->i_ino, dentry->d_name.len, dentry->d_name.name,
               ISLENTO(minor));

        if (dentry->d_fsdata)
                CERROR("DD -- BAD dentry %p has data\n", dentry);
                       
        dentry->d_fsdata = NULL;
#if 0
        if (ext2_check_for_iopen(dir, dentry))
                de = NULL;
        else {
#endif
                if ( izo_dentry_is_ilookup(dentry, &ino, &generation) ) { 
                        de = cache->cache_filter->o_trops->tr_ilookup
                                (dir, dentry, ino, generation);
                        is_ilookup = 1;
                } else
                        de = iops->lookup(dir, dentry);
#if 0
        }
#endif

        if ( IS_ERR(de) ) {
                CERROR("dentry lookup error %ld\n", PTR_ERR(de));
                return de;
        }

        /* some file systems have no read_inode: set methods here */
        if (dentry->d_inode)
                presto_set_ops(dentry->d_inode, cache->cache_filter);
		/* dentry->d_op is now hooked in dcache.c:presto_set_dd */

        /* In lookup we will tolerate EROFS return codes from presto_set_dd
         * to placate NFS. EROFS indicates that a fileset was not found but
         * we should still be able to continue through a lookup.
         * Anything else is a hard error and must be returned to VFS. */
        if (!is_ilookup)
                rc = presto_set_dd(dentry);
        if (rc && rc != -EROFS) {
                CERROR("presto_set_dd failed (dir %ld, name %*s): %d\n",
                       dir->i_ino, dentry->d_name.len, dentry->d_name.name, rc);
                return ERR_PTR(rc);
        }

        EXIT;
        return NULL;
}

static inline int presto_check_set_fsdata (struct dentry *de)
{
        if (presto_d2d(de) == NULL) {
#ifdef PRESTO_NO_NFS
                CERROR("dentry without fsdata: %p: %*s\n", de, 
                                de->d_name.len, de->d_name.name);
                BUG();
#endif
                return presto_set_dd (de);
        }

        return 0;
}

int presto_setattr(struct dentry *de, struct iattr *iattr)
{
        int error;
        struct presto_cache *cache;
        struct presto_file_set *fset;
        struct lento_vfs_context info = { 0, 0, 0 };

        ENTRY;

        error = presto_prep(de, &cache, &fset);
        if ( error ) {
                EXIT;
                return error;
        }

        if (!iattr->ia_valid)
                CDEBUG(D_INODE, "presto_setattr: iattr is not valid\n");

        CDEBUG(D_INODE, "valid %#x, mode %#o, uid %u, gid %u, size %Lu, "
               "atime %lu mtime %lu ctime %lu flags %d\n",
               iattr->ia_valid, iattr->ia_mode, iattr->ia_uid, iattr->ia_gid,
               iattr->ia_size, iattr->ia_atime, iattr->ia_mtime,
               iattr->ia_ctime, iattr->ia_attr_flags);
        
        if ( presto_get_permit(de->d_inode) < 0 ) {
                EXIT;
                return -EROFS;
        }

        if (!ISLENTO(presto_c2m(cache)))
                info.flags = LENTO_FL_KML;
        info.flags |= LENTO_FL_IGNORE_TIME;
        error = presto_do_setattr(fset, de, iattr, &info);
        presto_put_permit(de->d_inode);
        return error;
}

/*
 *  Now the meat: the fs operations that require journaling
 *
 *
 *  XXX: some of these need modifications for hierarchical filesets
 */

int presto_prep(struct dentry *dentry, struct presto_cache **cache,
                struct presto_file_set **fset)
{       
        int rc;

        /* NFS might pass us dentries which have not gone through lookup.
         * Test and set d_fsdata for such dentries
         */
        rc = presto_check_set_fsdata (dentry);
        if (rc) return rc;

        *fset = presto_fset(dentry);
        if ( *fset == NULL ) {
                CERROR("No file set for dentry at %p: %*s\n", dentry,
                                dentry->d_name.len, dentry->d_name.name);
                return -EROFS;
        }

        *cache = (*fset)->fset_cache;
        if ( *cache == NULL ) {
                CERROR("PRESTO: BAD, BAD: cannot find cache\n");
                return -EBADF;
        }

        CDEBUG(D_PIOCTL, "---> cache flags %x, fset flags %x\n",
              (*cache)->cache_flags, (*fset)->fset_flags);
        if( presto_is_read_only(*fset) ) {
                CERROR("PRESTO: cannot modify read-only fileset, minor %d.\n",
                       presto_c2m(*cache));
                return -EROFS;
        }
        return 0;
}

static int presto_create(struct inode * dir, struct dentry * dentry, int mode)
{
        int error;
        struct presto_cache *cache;
        struct dentry *parent = dentry->d_parent;
        struct lento_vfs_context info;
        struct presto_file_set *fset;

        ENTRY;
        error = presto_check_set_fsdata(dentry);
        if ( error ) {
                EXIT;
                return error;
        }

        error = presto_prep(dentry->d_parent, &cache, &fset);
        if ( error ) {
                EXIT;
                return error;
        }
        presto_unlock(dir);

        /* Does blocking and non-blocking behavious need to be 
           checked for.  Without blocking (return 1), the permit
           was acquired without reintegration
        */
        if ( presto_get_permit(dir) < 0 ) {
                EXIT;
                presto_fulllock(dir);
                return -EROFS;
        }

        presto_relock_sem(dir);
        parent = dentry->d_parent; 
        memset(&info, 0, sizeof(info));
        if (!ISLENTO(presto_c2m(cache)))
                info.flags = LENTO_FL_KML;
        info.flags |= LENTO_FL_IGNORE_TIME;
        error = presto_do_create(fset, parent, dentry, mode, &info);

        presto_relock_other(dir);
        presto_put_permit(dir);
        EXIT;
        return error;
}

static int presto_link(struct dentry *old_dentry, struct inode *dir,
                struct dentry *new_dentry)
{
        int error;
        struct presto_cache *cache, *new_cache;
        struct presto_file_set *fset, *new_fset;
        struct dentry *parent = new_dentry->d_parent;
        struct lento_vfs_context info;

        ENTRY;
        error = presto_prep(old_dentry, &cache, &fset);
        if ( error ) {
                EXIT;
                return error;
        }

        error = presto_check_set_fsdata(new_dentry);
        if ( error ) {
                EXIT;
                return error;
        }

        error = presto_prep(new_dentry->d_parent, &new_cache, &new_fset);
        if ( error ) {
                EXIT;
                return error;
        }

        if (fset != new_fset) { 
                EXIT;
                return -EXDEV;
        }

        presto_unlock(dir);
        if ( presto_get_permit(old_dentry->d_inode) < 0 ) {
                EXIT;
                presto_fulllock(dir);
                return -EROFS;
        }

        if ( presto_get_permit(dir) < 0 ) {
                EXIT;
                presto_fulllock(dir);
                return -EROFS;
        }

        presto_relock_sem(dir);
        parent = new_dentry->d_parent;

        memset(&info, 0, sizeof(info));
        if (!ISLENTO(presto_c2m(cache)))
                info.flags = LENTO_FL_KML;
        info.flags |= LENTO_FL_IGNORE_TIME;
        error = presto_do_link(fset, old_dentry, parent,
                               new_dentry, &info);

#if 0
        /* XXX for links this is not right */
        if (cache->cache_filter->o_trops->tr_add_ilookup ) { 
                struct dentry *d;
                d = cache->cache_filter->o_trops->tr_add_ilookup
                        (dir->i_sb->s_root, new_dentry, 1); 
        }
#endif 

        presto_relock_other(dir);
        presto_put_permit(dir);
        presto_put_permit(old_dentry->d_inode);
        return error;
}

static int presto_mkdir(struct inode * dir, struct dentry * dentry, int mode)
{
        int error;
        struct presto_file_set *fset;
        struct presto_cache *cache;
        struct dentry *parent = dentry->d_parent;
        struct lento_vfs_context info;

        ENTRY;

        error = presto_check_set_fsdata(dentry);
        if ( error  ) {
                EXIT;
                return error;
        }

        error = presto_prep(dentry->d_parent, &cache, &fset);
        if ( error  ) {
                EXIT;
                return error;
        }

        presto_unlock(dir); 

        if ( presto_get_permit(dir) < 0 ) {
                EXIT;
                presto_fulllock(dir);
                return -EROFS;
        }

        memset(&info, 0, sizeof(info));
        if (!ISLENTO(presto_c2m(cache)))
                info.flags = LENTO_FL_KML;
        info.flags |= LENTO_FL_IGNORE_TIME;

        presto_relock_sem(dir); 
        parent = dentry->d_parent;
        error = presto_do_mkdir(fset, parent, dentry, mode, &info);
        presto_relock_other(dir); 
        presto_put_permit(dir);
        return error;
}



static int presto_symlink(struct inode *dir, struct dentry *dentry,
                   const char *name)
{
        int error;
        struct presto_cache *cache;
        struct presto_file_set *fset;
        struct dentry *parent = dentry->d_parent;
        struct lento_vfs_context info;

        ENTRY;
        error = presto_check_set_fsdata(dentry);
        if ( error ) {
                EXIT;
                return error;
        }

        error = presto_prep(dentry->d_parent, &cache, &fset);
        if ( error ) {
                EXIT;
                return error;
        }

        presto_unlock(dir);
        if ( presto_get_permit(dir) < 0 ) {
                EXIT;
                presto_fulllock(dir);
                return -EROFS;
        }

        presto_relock_sem(dir);
        parent = dentry->d_parent;
        memset(&info, 0, sizeof(info));
        if (!ISLENTO(presto_c2m(cache)))
                info.flags = LENTO_FL_KML;
        info.flags |= LENTO_FL_IGNORE_TIME;
        error = presto_do_symlink(fset, parent, dentry, name, &info);
        presto_relock_other(dir);
        presto_put_permit(dir);
        return error;
}

int presto_unlink(struct inode *dir, struct dentry *dentry)
{
        int error;
        struct presto_cache *cache;
        struct presto_file_set *fset;
        struct dentry *parent = dentry->d_parent;
        struct lento_vfs_context info;

        ENTRY;
        error = presto_check_set_fsdata(dentry);
        if ( error ) {
                EXIT;
                return error;
        }

        error = presto_prep(dentry->d_parent, &cache, &fset);
        if ( error  ) {
                EXIT;
                return error;
        }

        presto_unlock(dir);
        if ( presto_get_permit(dir) < 0 ) {
                EXIT;
                presto_fulllock(dir);
                return -EROFS;
        }

        presto_relock_sem(dir);
        parent = dentry->d_parent;
        memset(&info, 0, sizeof(info));
        if (!ISLENTO(presto_c2m(cache)))
                info.flags = LENTO_FL_KML;
        info.flags |= LENTO_FL_IGNORE_TIME;

        error = presto_do_unlink(fset, parent, dentry, &info);

        presto_relock_other(dir);
        presto_put_permit(dir);
        return error;
}

static int presto_rmdir(struct inode *dir, struct dentry *dentry)
{
        int error;
        struct presto_cache *cache;
        struct presto_file_set *fset;
        struct dentry *parent = dentry->d_parent;
        struct lento_vfs_context info;

        ENTRY;
        CDEBUG(D_FILE, "prepping presto\n");
        error = presto_check_set_fsdata(dentry);

        if ( error ) {
                EXIT;
                return error;
        }

        error = presto_prep(dentry->d_parent, &cache, &fset);
        if ( error ) {
                EXIT;
                return error;
        }

        CDEBUG(D_FILE, "unlocking\n");
        /* We need to dget() before the dput in double_unlock, to ensure we
         * still have dentry references.  double_lock doesn't do dget for us.
         */
        unlock_kernel();
        if (d_unhashed(dentry))
                d_rehash(dentry);
        double_up(&dir->i_zombie, &dentry->d_inode->i_zombie);
        double_up(&dir->i_sem, &dentry->d_inode->i_sem);

        CDEBUG(D_FILE, "getting permit\n");
        if ( presto_get_permit(parent->d_inode) < 0 ) {
                EXIT;
                double_down(&dir->i_sem, &dentry->d_inode->i_sem);
                double_down(&dir->i_zombie, &dentry->d_inode->i_zombie);
                
                lock_kernel();
                return -EROFS;
        }
        CDEBUG(D_FILE, "locking\n");

        double_down(&dir->i_sem, &dentry->d_inode->i_sem);
        parent = dentry->d_parent;
        memset(&info, 0, sizeof(info));
        if (!ISLENTO(presto_c2m(cache)))
                info.flags = LENTO_FL_KML;
        info.flags |= LENTO_FL_IGNORE_TIME;
        error = presto_do_rmdir(fset, parent, dentry, &info);
        presto_put_permit(parent->d_inode);
        lock_kernel();
        EXIT;
        return error;
}

static int presto_mknod(struct inode * dir, struct dentry * dentry, int mode, int rdev)
{
        int error;
        struct presto_cache *cache;
        struct presto_file_set *fset;
        struct dentry *parent = dentry->d_parent;
        struct lento_vfs_context info;

        ENTRY;
        error = presto_check_set_fsdata(dentry);
        if ( error ) {
                EXIT;
                return error;
        }

        error = presto_prep(dentry->d_parent, &cache, &fset);
        if ( error  ) {
                EXIT;
                return error;
        }

        presto_unlock(dir);
        if ( presto_get_permit(dir) < 0 ) {
                EXIT;
                presto_fulllock(dir);
                return -EROFS;
        }
        
        presto_relock_sem(dir);
        parent = dentry->d_parent;
        memset(&info, 0, sizeof(info));
        if (!ISLENTO(presto_c2m(cache)))
                info.flags = LENTO_FL_KML;
        info.flags |= LENTO_FL_IGNORE_TIME;
        error = presto_do_mknod(fset, parent, dentry, mode, rdev, &info);
        presto_relock_other(dir);
        presto_put_permit(dir);
        EXIT;
        return error;
}

inline void presto_triple_unlock(struct inode *old_dir, struct inode *new_dir, 
                                 struct dentry *old_dentry, 
                                 struct dentry *new_dentry, int triple)
{
        /* rename_dir case */ 
        if (S_ISDIR(old_dentry->d_inode->i_mode)) { 
                if (triple) {                   
                        triple_up(&old_dir->i_zombie,
                                  &new_dir->i_zombie,
                                  &new_dentry->d_inode->i_zombie);
                } else { 
                        double_up(&old_dir->i_zombie,
                                  &new_dir->i_zombie);
                }
                up(&old_dir->i_sb->s_vfs_rename_sem);
        } else /* this case is rename_other */
                double_up(&old_dir->i_zombie, &new_dir->i_zombie);
        /* done by do_rename */
        unlock_kernel();
        double_up(&old_dir->i_sem, &new_dir->i_sem);
}

inline void presto_triple_fulllock(struct inode *old_dir, 
                                   struct inode *new_dir, 
                                   struct dentry *old_dentry, 
                                   struct dentry *new_dentry, int triple)
{
        /* done by do_rename */
        double_down(&old_dir->i_sem, &new_dir->i_sem);
        lock_kernel();
        /* rename_dir case */ 
        if (S_ISDIR(old_dentry->d_inode->i_mode)) { 
                down(&old_dir->i_sb->s_vfs_rename_sem);
                if (triple) {                   
                        triple_down(&old_dir->i_zombie,
                                  &new_dir->i_zombie,
                                  &new_dentry->d_inode->i_zombie);
                } else { 
                        double_down(&old_dir->i_zombie,
                                  &new_dir->i_zombie);
                }
        } else /* this case is rename_other */
                double_down(&old_dir->i_zombie, &new_dir->i_zombie);
}

inline void presto_triple_relock_sem(struct inode *old_dir, 
                                   struct inode *new_dir, 
                                   struct dentry *old_dentry, 
                                   struct dentry *new_dentry, int triple)
{
        /* done by do_rename */
        double_down(&old_dir->i_sem, &new_dir->i_sem);
        lock_kernel();
}

inline void presto_triple_relock_other(struct inode *old_dir, 
                                   struct inode *new_dir, 
                                   struct dentry *old_dentry, 
                                   struct dentry *new_dentry, int triple)
{
        /* rename_dir case */ 
        if (S_ISDIR(old_dentry->d_inode->i_mode)) { 
                down(&old_dir->i_sb->s_vfs_rename_sem);
                if (triple) {                   
                        triple_down(&old_dir->i_zombie,
                                  &new_dir->i_zombie,
                                  &new_dentry->d_inode->i_zombie);
                } else { 
                        double_down(&old_dir->i_zombie,
                                  &new_dir->i_zombie);
                }
        } else /* this case is rename_other */
                double_down(&old_dir->i_zombie, &new_dir->i_zombie);
}


// XXX this can be optimized: renamtes across filesets only require 
//     multiple KML records, but can locally be executed normally. 
int presto_rename(struct inode *old_dir, struct dentry *old_dentry,
                  struct inode *new_dir, struct dentry *new_dentry)
{
        int error;
        struct presto_cache *cache, *new_cache;
        struct presto_file_set *fset, *new_fset;
        struct lento_vfs_context info;
        struct dentry *old_parent = old_dentry->d_parent;
        struct dentry *new_parent = new_dentry->d_parent;
        int triple;

        ENTRY;
        error = presto_prep(old_dentry, &cache, &fset);
        if ( error ) {
                EXIT;
                return error;
        }
        error = presto_prep(new_parent, &new_cache, &new_fset);
        if ( error ) {
                EXIT;
                return error;
        }

        if ( fset != new_fset ) {
                EXIT;
                return -EXDEV;
        }

        /* We need to do dget before the dput in double_unlock, to ensure we
         * still have dentry references.  double_lock doesn't do dget for us.
         */

        triple = (S_ISDIR(old_dentry->d_inode->i_mode) && new_dentry->d_inode)?
                1:0;

        presto_triple_unlock(old_dir, new_dir, old_dentry, new_dentry, triple); 

        if ( presto_get_permit(old_dir) < 0 ) {
                EXIT;
                presto_triple_fulllock(old_dir, new_dir, old_dentry, new_dentry, triple); 
                return -EROFS;
        }
        if ( presto_get_permit(new_dir) < 0 ) {
                EXIT;
                presto_triple_fulllock(old_dir, new_dir, old_dentry, new_dentry, triple); 
                return -EROFS;
        }

        presto_triple_relock_sem(old_dir, new_dir, old_dentry, new_dentry, triple); 
        memset(&info, 0, sizeof(info));
        if (!ISLENTO(presto_c2m(cache)))
                info.flags = LENTO_FL_KML;
        info.flags |= LENTO_FL_IGNORE_TIME;
        error = do_rename(fset, old_parent, old_dentry, new_parent,
                          new_dentry, &info);
        presto_triple_relock_other(old_dir, new_dir, old_dentry, new_dentry, triple); 

        presto_put_permit(new_dir);
        presto_put_permit(old_dir);
        return error;
}

/* basically this allows the ilookup processes access to all files for
 * reading, while not making ilookup totally insecure.  This could all
 * go away if we could set the CAP_DAC_READ_SEARCH capability for the client.
 */
/* If posix acls are available, the underlying cache fs will export the
 * appropriate permission function. Thus we do not worry here about ACLs
 * or EAs. -SHP
 */
int presto_permission(struct inode *inode, int mask)
{
        unsigned short mode = inode->i_mode;
        struct presto_cache *cache;
        int rc;

        ENTRY;
        if ( presto_can_ilookup() && !(mask & S_IWOTH)) {
                CDEBUG(D_CACHE, "ilookup on %ld OK\n", inode->i_ino);
                EXIT;
                return 0;
        }

        cache = presto_get_cache(inode);

        if ( cache ) {
                /* we only override the file/dir permission operations */
                struct inode_operations *fiops = filter_c2cfiops(cache->cache_filter);
                struct inode_operations *diops = filter_c2cdiops(cache->cache_filter);

                if ( S_ISREG(mode) && fiops && fiops->permission ) {
                        EXIT;
                        return fiops->permission(inode, mask);
                }
                if ( S_ISDIR(mode) && diops && diops->permission ) {
                        EXIT;
                        return diops->permission(inode, mask);
                }
        }

        /* The cache filesystem doesn't have its own permission function,
         * but we don't want to duplicate the VFS code here.  In order
         * to avoid looping from permission calling this function again,
         * we temporarily override the permission operation while we call
         * the VFS permission function.
         */
        inode->i_op->permission = NULL;
        rc = permission(inode, mask);
        inode->i_op->permission = &presto_permission;

        EXIT;
        return rc;
}


int presto_ioctl(struct inode *inode, struct file *file,
                        unsigned int cmd, unsigned long arg)
{
        char buf[1024];
        struct izo_ioctl_data *data = NULL;
        struct presto_dentry_data *dd;
        int rc;

        ENTRY;

        /* Try the filesystem's ioctl first, and return if it succeeded. */
        dd = presto_d2d(file->f_dentry); 
        if (dd && dd->dd_fset) { 
                int (*cache_ioctl)(struct inode *, struct file *, unsigned int, unsigned long ) = filter_c2cdfops(dd->dd_fset->fset_cache->cache_filter)->ioctl;
                rc = -ENOTTY;
                if (cache_ioctl)
                        rc = cache_ioctl(inode, file, cmd, arg);
                if (rc != -ENOTTY) {
                        EXIT;
                        return rc;
                }
        }

        if (current->euid != 0 && current->euid != izo_authorized_uid) {
                EXIT;
                return -EPERM;
        }

        memset(buf, 0, sizeof(buf));
        
        if (izo_ioctl_getdata(buf, buf + 1024, (void *)arg)) { 
                CERROR("intermezzo ioctl: data error\n");
                return -EINVAL;
        }
        data = (struct izo_ioctl_data *)buf;
        
        switch(cmd) {
        case IZO_IOC_REINTKML: { 
                int rc;
                int cperr;
                rc = kml_reint_rec(file, data);

                EXIT;
                cperr = copy_to_user((char *)arg, data, sizeof(*data));
                if (cperr) { 
                        CERROR("WARNING: cperr %d\n", cperr); 
                        rc = -EFAULT;
                }
                return rc;
        }

        case IZO_IOC_GET_RCVD: {
                struct izo_rcvd_rec rec;
                struct presto_file_set *fset;
                int rc;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                rc = izo_rcvd_get(&rec, fset, data->ioc_uuid);
                if (rc < 0) {
                        EXIT;
                        return rc;
                }

                EXIT;
                return copy_to_user((char *)arg, &rec, sizeof(rec))? -EFAULT : 0;
        }

        case IZO_IOC_REPSTATUS: {
                __u64 client_kmlsize;
                struct izo_rcvd_rec *lr_client;
                struct izo_rcvd_rec rec;
                struct presto_file_set *fset;
                int minor;
                int rc;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                minor = presto_f2m(fset);

                client_kmlsize = data->ioc_kmlsize;
                lr_client =  (struct izo_rcvd_rec *) data->ioc_pbuf1;

                rc = izo_repstatus(fset, client_kmlsize, 
                                       lr_client, &rec);
                if (rc < 0) {
                        EXIT;
                        return rc;
                }

                EXIT;
                return copy_to_user((char *)arg, &rec, sizeof(rec))? -EFAULT : 0;
        }

        case IZO_IOC_GET_CHANNEL: {
                struct presto_file_set *fset;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                
                data->ioc_dev = fset->fset_cache->cache_psdev->uc_minor;
                CDEBUG(D_PSDEV, "CHANNEL %d\n", data->ioc_dev); 
                EXIT;
                return copy_to_user((char *)arg, data, sizeof(*data))? -EFAULT : 0;
        }

        case IZO_IOC_SET_IOCTL_UID:
                izo_authorized_uid = data->ioc_uid;
                EXIT;
                return 0;

        case IZO_IOC_SET_PID:
                rc = izo_psdev_setpid(data->ioc_dev);
                EXIT;
                return rc;

        case IZO_IOC_SET_CHANNEL:
                rc = izo_psdev_setchannel(file, data->ioc_dev);
                EXIT;
                return rc;

        case IZO_IOC_GET_KML_SIZE: {
                struct presto_file_set *fset;
                __u64 kmlsize;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }

                kmlsize = presto_kml_offset(fset) + fset->fset_kml_logical_off;

                EXIT;
                return copy_to_user((char *)arg, &kmlsize, sizeof(kmlsize))?-EFAULT : 0;
        }

        case IZO_IOC_PURGE_FILE_DATA: {
                struct presto_file_set *fset;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }

                rc = izo_purge_file(fset, data->ioc_inlbuf1);
                EXIT;
                return rc;
        }

        case IZO_IOC_GET_FILEID: {
                rc = izo_get_fileid(file, data);
                EXIT;
                if (rc)
                        return rc;
                return copy_to_user((char *)arg, data, sizeof(*data))? -EFAULT : 0;
        }

        case IZO_IOC_SET_FILEID: {
                rc = izo_set_fileid(file, data);
                EXIT;
                if (rc)
                        return rc;
                return copy_to_user((char *)arg, data, sizeof(*data))? -EFAULT  : 0;
        }

        case IZO_IOC_ADJUST_LML: { 
                struct lento_vfs_context *info; 
                info = (struct lento_vfs_context *)data->ioc_inlbuf1;
                rc = presto_adjust_lml(file, info); 
                EXIT;
                return rc;
        }

        case IZO_IOC_CONNECT: {
                struct presto_file_set *fset;
                int minor;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                minor = presto_f2m(fset);

                rc = izo_upc_connect(minor, data->ioc_ino,
                                     data->ioc_generation, data->ioc_uuid,
                                     data->ioc_flags);
                EXIT;
                return rc;
        }

        case IZO_IOC_GO_FETCH_KML: {
                struct presto_file_set *fset;
                int minor;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                minor = presto_f2m(fset);

                rc = izo_upc_go_fetch_kml(minor, fset->fset_name,
                                          data->ioc_uuid, data->ioc_kmlsize);
                EXIT;
                return rc;
        }

        case IZO_IOC_REVOKE_PERMIT:
                if (data->ioc_flags)
                        rc = izo_revoke_permit(file->f_dentry, data->ioc_uuid);
                else
                        rc = izo_revoke_permit(file->f_dentry, NULL);
                EXIT;
                return rc;

        case IZO_IOC_CLEAR_FSET:
                rc = izo_clear_fsetroot(file->f_dentry);
                EXIT;
                return rc;

        case IZO_IOC_CLEAR_ALL_FSETS: { 
                struct presto_file_set *fset;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }

                rc = izo_clear_all_fsetroots(fset->fset_cache);
                EXIT;
                return rc;
        }

        case IZO_IOC_SET_FSET:
                /*
                 * Mark this dentry as being a fileset root.
                 */
                rc = presto_set_fsetroot_from_ioc(file->f_dentry, 
                                                  data->ioc_inlbuf1,
                                                  data->ioc_flags);
                EXIT;
                return rc;


        case IZO_IOC_MARK: {
                int res = 0;  /* resulting flags - returned to user */
                int error;

                CDEBUG(D_DOWNCALL, "mark inode: %ld, and: %x, or: %x, what %d\n",
                       file->f_dentry->d_inode->i_ino, data->ioc_and_flag,
                       data->ioc_or_flag, data->ioc_mark_what);

                switch (data->ioc_mark_what) {
                case MARK_DENTRY:               
                        error = izo_mark_dentry(file->f_dentry,
                                                   data->ioc_and_flag,
                                                   data->ioc_or_flag, &res);
                        break;
                case MARK_FSET:
                        error = izo_mark_fset(file->f_dentry,
                                                 data->ioc_and_flag,
                                                 data->ioc_or_flag, &res);
                        break;
                case MARK_CACHE:
                        error = izo_mark_cache(file->f_dentry,
                                                  data->ioc_and_flag,
                                                  data->ioc_or_flag, &res);
                        break;
                case MARK_GETFL: {
                        int fflags, cflags;
                        data->ioc_and_flag = 0xffffffff;
                        data->ioc_or_flag = 0; 
                        error = izo_mark_dentry(file->f_dentry,
                                                   data->ioc_and_flag,
                                                   data->ioc_or_flag, &res);
                        if (error) 
                                break;
                        error = izo_mark_fset(file->f_dentry,
                                                 data->ioc_and_flag,
                                                 data->ioc_or_flag, &fflags);
                        if (error) 
                                break;
                        error = izo_mark_cache(file->f_dentry,
                                                  data->ioc_and_flag,
                                                  data->ioc_or_flag,
                                                  &cflags);

                        if (error) 
                                break;
                        data->ioc_and_flag = fflags;
                        data->ioc_or_flag = cflags;
                        break;
                }
                default:
                        error = -EINVAL;
                }

                if (error) { 
                        EXIT;
                        return error;
                }
                data->ioc_mark_what = res;
                CDEBUG(D_DOWNCALL, "mark inode: %ld, and: %x, or: %x, what %x\n",
                       file->f_dentry->d_inode->i_ino, data->ioc_and_flag,
                       data->ioc_or_flag, data->ioc_mark_what);

                EXIT;
                return copy_to_user((char *)arg, data, sizeof(*data))? -EFAULT : 0;
        }
#if 0
        case IZO_IOC_CLIENT_MAKE_BRANCH: {
                struct presto_file_set *fset;
                int minor;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                minor = presto_f2m(fset);

                rc = izo_upc_client_make_branch(minor, fset->fset_name,
                                                data->ioc_inlbuf1,
                                                data->ioc_inlbuf2);
                EXIT;
                return rc;
        }
#endif
        case IZO_IOC_SERVER_MAKE_BRANCH: {
                struct presto_file_set *fset;
                int minor;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                minor = presto_f2m(fset);

                izo_upc_server_make_branch(minor, data->ioc_inlbuf1);
                EXIT;
                return 0;
        }
        case IZO_IOC_SET_KMLSIZE: {
                struct presto_file_set *fset;
                int minor;
                struct izo_rcvd_rec rec;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                minor = presto_f2m(fset);

                rc = izo_upc_set_kmlsize(minor, fset->fset_name, data->ioc_uuid,
                                         data->ioc_kmlsize);

                if (rc != 0) {
                        EXIT;
                        return rc;
                }

                rc = izo_rcvd_get(&rec, fset, data->ioc_uuid);
                if (rc == -EINVAL) {
                        /* We don't know anything about this uuid yet; no
                         * worries. */
                        memset(&rec, 0, sizeof(rec));
                } else if (rc <= 0) {
                        CERROR("InterMezzo: error reading last_rcvd: %d\n", rc);
                        EXIT;
                        return rc;
                }
                rec.lr_remote_offset = data->ioc_kmlsize;
                rc = izo_rcvd_write(fset, &rec);
                if (rc <= 0) {
                        CERROR("InterMezzo: error writing last_rcvd: %d\n", rc);
                        EXIT;
                        return rc;
                }
                EXIT;
                return rc;
        }
        case IZO_IOC_BRANCH_UNDO: {
                struct presto_file_set *fset;
                int minor;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                minor = presto_f2m(fset);

                rc = izo_upc_branch_undo(minor, fset->fset_name,
                                         data->ioc_inlbuf1);
                EXIT;
                return rc;
        }
        case IZO_IOC_BRANCH_REDO: {
                struct presto_file_set *fset;
                int minor;

                fset = presto_fset(file->f_dentry);
                if (fset == NULL) {
                        EXIT;
                        return -ENODEV;
                }
                minor = presto_f2m(fset);

                rc = izo_upc_branch_redo(minor, fset->fset_name,
                                         data->ioc_inlbuf1);
                EXIT;
                return rc;
        }

        case TCGETS:
                EXIT;
                return -EINVAL;

        default:
                EXIT;
                return -EINVAL;
                
        }
        EXIT;
        return 0;
}

struct file_operations presto_dir_fops = {
        .ioctl =  presto_ioctl
};

struct inode_operations presto_dir_iops = {
        .create       = presto_create,
        .lookup       = presto_lookup,
        .link         = presto_link,
        .unlink       = presto_unlink,
        .symlink      = presto_symlink,
        .mkdir        = presto_mkdir,
        .rmdir        = presto_rmdir,
        .mknod        = presto_mknod,
        .rename       = presto_rename,
        .permission   = presto_permission,
        .setattr      = presto_setattr,
#ifdef CONFIG_FS_EXT_ATTR
        .set_ext_attr = presto_set_ext_attr,
#endif
};


