/*
 * Direcotry operations for Coda filesystem
 * Original version: (C) 1996 P. Braam and M. Callahan
 * Rewritten for Linux 2.1. (C) 1997 Carnegie Mellon University
 * 
 * Carnegie Mellon encourages users to contribute improvements to
 * the Coda project. Contact Peter Braam (coda@cs.cmu.edu).
 */

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/errno.h>
#include <linux/locks.h>
#include <asm/segment.h>
#include <asm/uaccess.h>
#include <linux/string.h>

#include <linux/coda.h>
#include <linux/coda_linux.h>
#include <linux/coda_psdev.h>
#include <linux/coda_cnode.h>
#include <linux/coda_namecache.h>

/* dir inode-ops */
static int coda_create(struct inode *dir, struct dentry *new, int mode);
static int coda_lookup(struct inode *dir, struct dentry *target);
static int coda_link(struct inode *old_inode, struct inode *dir_inode, 
		     struct dentry *entry);
static int coda_unlink(struct inode *dir_inode, struct dentry *entry);
static int coda_symlink(struct inode *dir_inode, struct dentry *entry,
			const char *symname);
static int coda_mkdir(struct inode *dir_inode, struct dentry *entry, int mode);
static int coda_rmdir(struct inode *dir_inode, struct dentry *entry);
static int coda_rename(struct inode *old_inode, struct dentry *old_dentry, 
                       struct inode *new_inode, struct dentry *new_dentry);

/* dir file-ops */
static int coda_readdir(struct file *file, void *dirent, filldir_t filldir);

/* support routines */
static int coda_venus_readdir(struct file *filp, void *dirent, 
			      filldir_t filldir);



struct inode_operations coda_dir_inode_operations =
{
	&coda_dir_operations,
	coda_create,	        /* create */
	coda_lookup,	        /* lookup */
	coda_link,	        /* link */
	coda_unlink,            /* unlink */
	coda_symlink,	        /* symlink */
	coda_mkdir,	        /* mkdir */
	coda_rmdir,   	        /* rmdir */
	NULL,		        /* mknod */
	coda_rename,	        /* rename */
	NULL,	                /* readlink */
	NULL,	                /* follow_link */
	NULL,	                /* readpage */
	NULL,		        /* writepage */
	NULL,		        /* bmap */
	NULL,	                /* truncate */
	coda_permission,        /* permission */
	NULL,                   /* smap */
	NULL,                   /* update page */
        NULL                    /* revalidate */
};

struct file_operations coda_dir_operations = {
        NULL,                   /* lseek */
        NULL,                   /* read -- bad  */
        NULL,                   /* write */
        coda_readdir,           /* readdir */
        NULL,                   /* select */
        NULL,                   /* ioctl */
        NULL,                   /* mmap */
        coda_open,              /* open */
        coda_release,           /* release */
        NULL,                   /* fsync */
};


/* inode operations for directories */
/* acces routines: lookup, readlink, permission */
static int coda_lookup(struct inode *dir, struct dentry *entry)
{
        struct cnode *dircnp, *savedcnp;
	struct inode *res_inode = NULL;
	struct ViceFid resfid;
	int type;
	int error = 0;
	const char *name = entry->d_name.name;
	size_t length = entry->d_name.len;
	char str[50];
	
        ENTRY;
        CDEBUG(D_INODE, "name %s, len %d in ino %ld\n", 
	       name, length, dir->i_ino);

	if (!dir || !S_ISDIR(dir->i_mode)) {
		printk("coda_lookup: inode is NULL or not a directory\n");
		return -ENOENT;
	}

	dircnp = ITOC(dir);
	CHECK_CNODE(dircnp);

	if ( length > CFS_MAXNAMLEN ) {
	        printk("name too long: lookup, %s (%s)\n", 
		       coda_f2s(&dircnp->c_fid, str), name);
		return -ENAMETOOLONG;
	}
	
	CDEBUG(D_INODE, "lookup: %s in %s\n", name, 
	       coda_f2s(&dircnp->c_fid, str));

        /* control object, create inode on the fly */
        if ( coda_isroot(dir) && (CFS_CONTROLLEN == length) && 
	     (strncmp(name, CFS_CONTROL, CFS_CONTROLLEN) == 0) ) {
	        error = coda_cnode_makectl(&res_inode, dir->i_sb);
		CDEBUG(D_SPECIAL, 
		       "Lookup on CTL object; iput of ino %ld, count %d\n", 
		       dir->i_ino, dir->i_count);
                goto exit;
        }

        /* do we have it already in name cache */
	if ( (savedcnp = cfsnc_lookup(dircnp, name, length)) != NULL ) {
		CHECK_CNODE(savedcnp);
		res_inode = CTOI(savedcnp);
		iget(res_inode->i_sb, res_inode->i_ino);
		CDEBUG(D_INODE, "cache hit for ino: %ld, count: %d!\n",
		       res_inode->i_ino, res_inode->i_count);
		goto exit;
	}
	CDEBUG(D_INODE, "name not found in cache!\n");

        /* name not cached */
        error = venus_lookup(dir->i_sb, &(dircnp->c_fid), 
				(const char *)name, length, &type, &resfid);

	res_inode = NULL;
	if (!error) {
	    	error = coda_cnode_make(&res_inode, &resfid, dir->i_sb);
		if (error)
			return -EACCES;
		/* put the thing in the name cache */
		savedcnp = ITOC(res_inode);
		CHECK_CNODE(savedcnp);
		CDEBUG(D_INODE, "ABOUT to enter into cache.\n");
		cfsnc_enter(dircnp, name, length, savedcnp);
		CDEBUG(D_INODE, "entered in cache\n");
	} else if (error != -ENOENT) {
	        CDEBUG(D_INODE, "error for %s(%s)%d\n",
		       coda_f2s(&dircnp->c_fid, str), name, error);
		return error;
	}
	CDEBUG(D_INODE, "lookup: %s is (%s) type %d result %d\n",
	       name, coda_f2s(&resfid, str), type, error);

	/* at last we have our inode number from Venus, 
	   now allocate storage for
	   the cnode and do iget, and fill in the attributes */


exit:
	entry->d_time = 0;
	entry->d_op = NULL;
	d_add(entry, res_inode);
        EXIT;
        return 0;
}



int coda_permission(struct inode *inode, int mask)
{
        struct cnode *cp;
        int error;
        int mode = inode->i_mode;
	char str[50];
 
        ENTRY;

        if ( mask == 0 ) {
                EXIT;
                return 0;
        }

        /* we should be able to trust what is in the mode
           although Venus should be told to return the 
           correct modes to the kernel */
        if ( coda_access_cache == 1 ) { 
            if (current->fsuid == inode->i_uid)
                mode >>= 6;
        	else if (in_group_p(inode->i_gid))
                mode >>= 3;
        	if (((mode & mask & 0007) == mask) )
                return 0;
	}

        cp = ITOC(inode);
        CHECK_CNODE(cp);

        CDEBUG(D_INODE, "mask is %o\n", mask);
        error = venus_access(inode->i_sb, &(cp->c_fid), mask);
    
        CDEBUG(D_INODE, "fid: %s, ino: %ld (mask: %o) error: %d\n", 
	       coda_f2s(&(cp->c_fid), str), inode->i_ino, mask, error);

        return error; 
}



/* creation routines: create, mkdir, link, symlink */

static int coda_create(struct inode *dir, struct dentry *de, int mode)
{
        int error=0;
        struct cnode *dircnp;
	const char *name=de->d_name.name;
	int length=de->d_name.len;
	struct inode *result = NULL;
	struct ViceFid newfid;
	struct coda_vattr attrs;

	CDEBUG(D_INODE, "name: %s, length %d, mode %o\n",name, length, mode);

        if (!dir || !S_ISDIR(dir->i_mode)) {
                printk("coda_create: inode is null or not a directory\n");
                return -ENOENT;
        }
	dircnp = ITOC(dir);
        CHECK_CNODE(dircnp);

        if ( length > CFS_MAXNAMLEN ) {
		char str[50];
		printk("name too long: create, %s(%s)\n", 
		       coda_f2s(&dircnp->c_fid, str), name);
		return -ENAMETOOLONG;
        }

	error = venus_create(dir->i_sb, &(dircnp->c_fid), name, length, 
				0, mode, &newfid, &attrs);

        if ( error ) {
		char str[50];
		CDEBUG(D_INODE, "create: %s, result %d\n",
		       coda_f2s(&newfid, str), error); 
		return error;
	}

	error = coda_cnode_make(&result, &newfid, dir->i_sb);
	if ( error ) {
		result = NULL;
		return error;
	}

	/* invalidate the directory cnode's attributes */
	dircnp->c_flags &= ~C_VATTR;
	cfsnc_zapfid(&(dircnp->c_fid));

	d_instantiate(de, result);
        return 0;
}			     

static int coda_mkdir(struct inode *dir, struct dentry *de, int mode)
{
        struct cnode *dircnp;
	struct inode *inode;
	struct coda_vattr attr;
	const char *name = de->d_name.name;
	int len = de->d_name.len;
	int error;
	struct ViceFid newfid;
	char fidstr[50];

	if (!dir || !S_ISDIR(dir->i_mode)) {
		printk("coda_mkdir: inode is NULL or not a directory\n");
		return -ENOENT;
	}
        dircnp = ITOC(dir);
        CHECK_CNODE(dircnp);

        if ( len > CFS_MAXNAMLEN )
                return -ENAMETOOLONG;

	CDEBUG(D_INODE, "mkdir %s (len %d) in %s, mode %o.\n", 
	       name, len, coda_f2s(&(dircnp->c_fid), fidstr), mode);

	attr.va_mode = mode;
	error = venus_mkdir(dir->i_sb, &(dircnp->c_fid), 
			       name, len, &newfid, &attr);
        
        if ( error ) {
	        CDEBUG(D_INODE, "mkdir error: %s result %d\n", 
		       coda_f2s(&newfid, fidstr), error); 
                return error;
        }
         
	CDEBUG(D_INODE, "mkdir: new dir has fid %s.\n", 
	       coda_f2s(&newfid, fidstr)); 

	error = coda_cnode_make(&inode, &newfid, dir->i_sb);
	if ( error ) 
	       return error;
	
	/* invalidate the directory cnode's attributes */
	dircnp->c_flags &= ~C_VATTR;
	cfsnc_zapfid(&(dircnp->c_fid));

	dir->i_nlink++;
	d_instantiate(de, inode);
        return 0;
}

static int coda_link(struct inode *inode, struct inode *dir_inode, 
	  struct dentry *de)
{
        const char * name = de->d_name.name;
	int len = de->d_name.len;
        struct cnode *dir_cnp, *cnp;
	char str[50];
	int error;

        ENTRY;

        dir_cnp = ITOC(dir_inode);
        CHECK_CNODE(dir_cnp);
        cnp = ITOC(inode);
        CHECK_CNODE(cnp);

	CDEBUG(D_INODE, "old: fid: %s\n", coda_f2s(&(cnp->c_fid), str));
	CDEBUG(D_INODE, "directory: %s\n", coda_f2s(&(dir_cnp->c_fid), str));

        if ( len > CFS_MAXNAMLEN ) {
                printk("coda_link: name too long. \n");
                return -ENAMETOOLONG;
        }

        /* Check for link to/from control object. */

        error = venus_link(dir_inode->i_sb,&(cnp->c_fid), &(dir_cnp->c_fid), 
			   (const char *)name, len);

	if (  ! error ) { 
	      dir_cnp->c_flags &= ~C_VATTR;
	      cfsnc_zapfid(&(dir_cnp->c_fid));
	      cfsnc_zapfid(&(cnp->c_fid));

	      inode->i_nlink++;
	      d_instantiate(de, inode);
	}
        CDEBUG(D_INODE, "link result %d\n",error);
	EXIT;
        return(error);
}


static int 
coda_symlink(struct inode *dir_inode, struct dentry *de,
             const char *symname)
{
        const char *name = de->d_name.name;
	int len = de->d_name.len;
        struct cnode *dir_cnp = ITOC(dir_inode);
	int symlen;
        int error=0;
        
        ENTRY;

	error = -ENAMETOOLONG;
	if ( len > CFS_MAXNAMLEN ) { 
	        return error;
	}

	symlen = strlen(symname);
	if ( symlen > CFS_MAXNAMLEN ) { 
	        return error;
	}
        CDEBUG(D_INODE, "symname: %s, length: %d\n", symname, symlen);

	error = venus_symlink(dir_inode->i_sb, &(dir_cnp->c_fid), name, len, 
			      symname, symlen);

	if ( !error ) {
	  d_drop(de);
	}

        CDEBUG(D_INODE, "in symlink result %d\n",error);
        EXIT;
        return error;
}

/* destruction routines: unlink, rmdir */

int coda_unlink(struct inode *dir, struct dentry *de)
{
        struct cnode *dircnp;
        int error;
	const char *name = de->d_name.name;
	int len = de->d_name.len;
	char fidstr[50];

	ENTRY;

        dircnp = ITOC(dir);
        CHECK_CNODE(dircnp);

        CDEBUG(D_INODE, " %s in %s, ino %ld\n", name , 
	       coda_f2s(&(dircnp->c_fid), fidstr), dir->i_ino);

        /* this file should no longer be in the namecache! */
        cfsnc_zapfile(dircnp, (const char *)name, len);

        error = venus_remove(dir->i_sb, &(dircnp->c_fid), name, len);

        if ( error ) {
                CDEBUG(D_INODE, "upc returned error %d\n", error);
                return error;
        }

        /* cache management */
	dircnp->c_flags &= ~C_VATTR;
	cfsnc_zapfid(&(dircnp->c_fid));
	
	de->d_inode->i_nlink--;
	d_delete(de);

        return 0;
}

int coda_rmdir(struct inode *dir, struct dentry *de)
{
        struct cnode *dircnp;
	const char *name = de->d_name.name;
	int len = de->d_name.len;
        int error;

	if (!dir || !S_ISDIR(dir->i_mode)) {
		printk("coda_rmdir: inode is NULL or not a directory\n");
		return -ENOENT;
	}
        dircnp = ITOC(dir);
        CHECK_CNODE(dircnp);

	if (len > CFS_MAXNAMLEN)
		return -ENAMETOOLONG;

        /* this directory name should no longer be in the namecache */
        cfsnc_zapfile(dircnp, (const char *)name, len);

	error = venus_rmdir(dir->i_sb, &(dircnp->c_fid), name, len);

        if ( error ) {
                CDEBUG(D_INODE, "upc returned error %d\n", error);
                return error;
        }

	dircnp->c_flags &= ~C_VATTR;
	cfsnc_zapfid(&(dircnp->c_fid));

	dir->i_nlink--;
	d_delete(de);

        return 0;
}

/* rename */
static int coda_rename(struct inode *old_dir, struct dentry *old_dentry, 
		       struct inode *new_dir, struct dentry *new_dentry)
{
        const char *old_name = old_dentry->d_name.name;
        const char *new_name = new_dentry->d_name.name;
	int old_length = old_dentry->d_name.len;
	int new_length = new_dentry->d_name.len;
	struct inode *old_inode = old_dentry->d_inode;
	struct inode *new_inode = new_dentry->d_inode;
        struct cnode *new_cnp, *old_cnp;
        int error, rehash = 0, update = 1;
ENTRY;
        old_cnp = ITOC(old_dir);
        CHECK_CNODE(old_cnp);
        new_cnp = ITOC(new_dir);
        CHECK_CNODE(new_cnp);

        CDEBUG(D_INODE, "old: %s, (%d length, %d strlen), new: %s (%d length, %d strlen).\n", old_name, old_length, strlen(old_name), new_name, new_length, strlen(new_name));

        if ( (old_length > CFS_MAXNAMLEN) || new_length > CFS_MAXNAMLEN ) {
                return -ENAMETOOLONG;
        }

        /* the old file should go from the namecache */
        cfsnc_zapfile(old_cnp, (const char *)old_name, old_length);
        cfsnc_zapfile(new_cnp, (const char *)new_name, new_length);

        /* cross directory moves */
	if (new_dir != old_dir  &&
	    S_ISDIR(old_inode->i_mode) && 
	    old_dentry->d_count > 1)
	        shrink_dcache_parent(old_dentry);
		
	/* We must prevent any new references to the
	 * target while the rename is in progress, so
	 * we unhash the dentry.  */
	if (!list_empty(&new_dentry->d_hash)) {
	        d_drop(new_dentry);
		rehash = 1;
	}

        error = venus_rename(old_dir->i_sb, &(old_cnp->c_fid), 
			     &(new_cnp->c_fid), old_length, new_length, 
			     (const char *) old_name, (const char *)new_name);

	if (rehash) {
		d_add(new_dentry, new_inode);
	}

        if ( error ) {
                CDEBUG(D_INODE, "returned error %d\n", error);
                return error;
        }
	/* Update the dcache if needed */
	if (update)
	        d_move(old_dentry, new_dentry);
         
        CDEBUG(D_INODE, "result %d\n", error); 

	EXIT;
	return 0;
}



/* file operations for directories */
int coda_readdir(struct file *file, void *dirent,  filldir_t filldir)
{
        int result = 0;
        struct cnode *cnp;
        struct file open_file;
	struct dentry open_dentry;
	struct inode *inode=file->f_dentry->d_inode;

        ENTRY;

        if (!inode || !inode->i_sb || !S_ISDIR(inode->i_mode)) {
                printk("coda_readdir: inode is NULL or not a directory\n");
                return -EBADF;
        }

        cnp = ITOC(inode);
        CHECK_CNODE(cnp);
        
        if ( !cnp->c_ovp ) {
                CDEBUG(D_FILE, "open inode pointer = NULL.\n");
                return -ENODEV;
        }

	coda_prepare_openfile(inode, file, cnp->c_ovp, &open_file,
			      &open_dentry);
        if ( S_ISREG(cnp->c_ovp->i_mode) ) {
                /* Venus: we must read Venus dirents from the file */
                result = coda_venus_readdir(&open_file, dirent, filldir);
        } else {
                /* potemkin case: we are handed a directory inode */
                result = open_file.f_op->readdir(&open_file, dirent, filldir);
        }
	coda_restore_codafile(inode, file, cnp->c_ovp, &open_file);
	return result;
        EXIT;
}

/* ask venus to cache the file and return the inode of the container file,
   put this inode pointer in the cnode for future read/writes */
int coda_open(struct inode *i, struct file *f)
{
        ino_t ino;
	dev_t dev;
        struct cnode *cnp;
        int error = 0;
        struct inode *cont_inode = NULL;
        unsigned short flags = f->f_flags;

        ENTRY;
        
        CDEBUG(D_SPECIAL, "OPEN inode number: %ld, flags %o.\n", 
	       f->f_dentry->d_inode->i_ino, flags);

        if ( flags & O_CREAT ) {
                flags &= ~O_EXCL; /* taken care of by coda_create ?? */
        }
        cnp = ITOC(i);
        CHECK_CNODE(cnp);

	error = venus_open(i->i_sb, &(cnp->c_fid), flags, &ino, &dev); 
	if (error) {
	        CDEBUG(D_FILE, "venus: dev %d, inode %ld, out->result %d\n",
		       dev, ino, error);
		return error;
	}

        /* coda_upcall returns ino number of cached object, get inode */
        CDEBUG(D_FILE, "cache file dev %d, ino %ld\n", dev, ino);

        if ( ! cnp->c_ovp ) {
                error = coda_inode_grab(dev, ino, &cont_inode);
                
                if ( error ){
                        printk("coda_open: coda_inode_grab error %d.", error);
                        if (cont_inode) iput(cont_inode);
			return error;
                }
                CDEBUG(D_FILE, "GRAB: coda_inode_grab: ino %ld, ops at %x\n", 
		       cont_inode->i_ino, (int)cont_inode->i_op);
                cnp->c_ovp = cont_inode; 
		cnp->c_odentry.d_inode = cont_inode;
        } 
        cnp->c_ocount++;

        /* if opened for writing flush cache entry.  */
        if ( flags & (O_WRONLY | O_RDWR) ) {
	        cfsnc_zapfid(&(cnp->c_fid));
	} 

        CDEBUG(D_FILE, "result %d, coda i->i_count is %d for ino %ld\n", 
	       error, i->i_count, i->i_ino);
        CDEBUG(D_FILE, "cache ino: %ld, count %d, ops %x\n", 
	       cnp->c_ovp->i_ino, cnp->c_ovp->i_count,
	       (int)(cnp->c_ovp->i_op));
        EXIT;
        return 0;
}

int coda_release(struct inode *i, struct file *f)
{
        struct cnode *cnp;
        int error;
        unsigned short flags = f->f_flags;

        ENTRY;

        cnp =ITOC(i);
        CHECK_CNODE(cnp);
        CDEBUG(D_FILE,  
	       "RELEASE coda (ino %ld, ct %d) cache (ino %ld, ct %d)\n",
               i->i_ino, i->i_count, (cnp->c_ovp ? cnp->c_ovp->i_ino : 0),
               (cnp->c_ovp ? cnp->c_ovp->i_count : -99));


        /* even when c_ocount=0 we cannot put c_ovp to
         * NULL since the file may be mmapped.
	 * See code in inode.c (coda_put_inode) for
	 * further handling of close.
	 */

        --cnp->c_ocount;

        if (flags & (O_WRONLY | O_RDWR)) {
                --cnp->c_owrite;
        }

	error = venus_release(i->i_sb, &(cnp->c_fid), flags);

        CDEBUG(D_FILE, "coda_release: result: %d\n", error);
        return error;
}

/* support routines */
/* 
 * this structure is manipulated by filldir in vfs layer.
 * the count holds the remaining amount of space in the getdents buffer,
 * beyond the current_dir pointer.
 */

struct getdents_callback {
	struct linux_dirent * current_dir;
	struct linux_dirent * previous;
	int count;
	int error;
};

static int coda_venus_readdir(struct file *filp, void *getdent, 
			      filldir_t filldir)
{
        int result = 0,  offset, count, pos, error = 0;
	int errfill;
        caddr_t buff = NULL;
        struct venus_dirent *vdirent;
        struct getdents_callback *dents_callback;
        int string_offset;
        char debug[255];

        ENTRY;        

        /* we also need the ofset of the string in the dirent struct */
        string_offset = sizeof ( char )* 2  + sizeof(unsigned int) + 
                        sizeof(unsigned short);

        dents_callback = (struct getdents_callback *) getdent;

        count =  dents_callback->count;
        CODA_ALLOC(buff, void *, count);
        if ( ! buff ) { 
                printk("coda_venus_readdir: out of memory.\n");
                return -ENOMEM;
        }

        /* we use this routine to read the file into our buffer */
        result = read_exec(filp->f_dentry, filp->f_pos, buff, count, 1);
        if ( result < 0) {
                printk("coda_venus_readdir: cannot read directory %d.\n",
		       result);
                error = result;
                goto exit;
        }
        if ( result == 0) {
                error = result;
                goto exit;
        }

        /* Parse and write into user space. Filldir tells us when done! */
        offset = filp->f_pos;
        pos = 0;
        CDEBUG(D_FILE, "offset %d, count %d.\n", offset, count);

        while ( pos + string_offset < result ) {
                vdirent = (struct venus_dirent *) (buff + pos);

                /* test if the name is fully in the buffer */
                if ( pos + string_offset + (int) vdirent->d_namlen >= result ){
                        break;
                }
                
                /* now we are certain that we can read the entry from buff */

                /* for debugging, get the string out */
                memcpy(debug, vdirent->d_name, vdirent->d_namlen);
                *(debug + vdirent->d_namlen) = '\0';

                /* if we don't have a null entry, copy it */
                if ( vdirent->d_fileno ) {
                        int namlen  = vdirent->d_namlen;
                        off_t offs  = filp->f_pos; 
                        ino_t ino   = vdirent->d_fileno;
                        char *name  = vdirent->d_name;
                        /* adjust count */
                        count = dents_callback->count;

			errfill = filldir(dents_callback,  name, namlen, 
					  offs, ino); 
CDEBUG(D_FILE, "ino %ld, namlen %d, reclen %d, type %d, pos %d, string_offs %d, name %s, offset %d, count %d.\n", vdirent->d_fileno, vdirent->d_namlen, vdirent->d_reclen, vdirent->d_type, pos,  string_offset, debug, (u_int) offs, dents_callback->count);

		      /* errfill means no space for filling in this round */
                      if ( errfill < 0 ) break;
                }
                /* next one */
                filp->f_pos += (unsigned int) vdirent->d_reclen;
                pos += (unsigned int) vdirent->d_reclen;
        } 

exit:
        CODA_FREE(buff, count);
        return error;
}
