diff options
Diffstat (limited to 'fs/coda')
-rw-r--r-- | fs/coda/Makefile | 12 | ||||
-rw-r--r-- | fs/coda/cache.c | 120 | ||||
-rw-r--r-- | fs/coda/cnode.c | 172 | ||||
-rw-r--r-- | fs/coda/coda_linux.c | 197 | ||||
-rw-r--r-- | fs/coda/dir.c | 704 | ||||
-rw-r--r-- | fs/coda/file.c | 300 | ||||
-rw-r--r-- | fs/coda/inode.c | 321 | ||||
-rw-r--r-- | fs/coda/pioctl.c | 95 | ||||
-rw-r--r-- | fs/coda/psdev.c | 462 | ||||
-rw-r--r-- | fs/coda/symlink.c | 55 | ||||
-rw-r--r-- | fs/coda/sysctl.c | 254 | ||||
-rw-r--r-- | fs/coda/upcall.c | 914 |
12 files changed, 3606 insertions, 0 deletions
diff --git a/fs/coda/Makefile b/fs/coda/Makefile new file mode 100644 index 000000000000..6c22e61da397 --- /dev/null +++ b/fs/coda/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the Linux Coda filesystem routines. +# + +obj-$(CONFIG_CODA_FS) += coda.o + +coda-objs := psdev.o cache.o cnode.o inode.o dir.o file.o upcall.o \ + coda_linux.o symlink.o pioctl.o sysctl.o + +# If you want debugging output, please uncomment the following line. + +# EXTRA_CFLAGS += -DDEBUG -DDEBUG_SMB_MALLOC=1 diff --git a/fs/coda/cache.c b/fs/coda/cache.c new file mode 100644 index 000000000000..80072fd9b7fa --- /dev/null +++ b/fs/coda/cache.c @@ -0,0 +1,120 @@ +/* + * Cache operations for Coda. + * For Linux 2.1: (C) 1997 Carnegie Mellon University + * For Linux 2.3: (C) 2000 Carnegie Mellon University + * + * Carnegie Mellon encourages users of this code to contribute improvements + * to the Coda project http://www.coda.cs.cmu.edu/ <coda@cs.cmu.edu>. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/time.h> +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <asm/uaccess.h> +#include <linux/string.h> +#include <linux/list.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_psdev.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_cache.h> + +static atomic_t permission_epoch = ATOMIC_INIT(0); + +/* replace or extend an acl cache hit */ +void coda_cache_enter(struct inode *inode, int mask) +{ + struct coda_inode_info *cii = ITOC(inode); + + cii->c_cached_epoch = atomic_read(&permission_epoch); + if (cii->c_uid != current->fsuid) { + cii->c_uid = current->fsuid; + cii->c_cached_perm = mask; + } else + cii->c_cached_perm |= mask; +} + +/* remove cached acl from an inode */ +void coda_cache_clear_inode(struct inode *inode) +{ + struct coda_inode_info *cii = ITOC(inode); + cii->c_cached_perm = 0; +} + +/* remove all acl caches */ +void coda_cache_clear_all(struct super_block *sb) +{ + struct coda_sb_info *sbi; + + sbi = coda_sbp(sb); + if (!sbi) BUG(); + + atomic_inc(&permission_epoch); +} + + +/* check if the mask has been matched against the acl already */ +int coda_cache_check(struct inode *inode, int mask) +{ + struct coda_inode_info *cii = ITOC(inode); + int hit; + + hit = (mask & cii->c_cached_perm) == mask && + cii->c_uid == current->fsuid && + cii->c_cached_epoch == atomic_read(&permission_epoch); + + return hit; +} + + +/* Purging dentries and children */ +/* The following routines drop dentries which are not + in use and flag dentries which are in use to be + zapped later. + + The flags are detected by: + - coda_dentry_revalidate (for lookups) if the flag is C_PURGE + - coda_dentry_delete: to remove dentry from the cache when d_count + falls to zero + - an inode method coda_revalidate (for attributes) if the + flag is C_VATTR +*/ + +/* this won't do any harm: just flag all children */ +static void coda_flag_children(struct dentry *parent, int flag) +{ + struct list_head *child; + struct dentry *de; + + spin_lock(&dcache_lock); + list_for_each(child, &parent->d_subdirs) + { + de = list_entry(child, struct dentry, d_child); + /* don't know what to do with negative dentries */ + if ( ! de->d_inode ) + continue; + coda_flag_inode(de->d_inode, flag); + } + spin_unlock(&dcache_lock); + return; +} + +void coda_flag_inode_children(struct inode *inode, int flag) +{ + struct dentry *alias_de; + + if ( !inode || !S_ISDIR(inode->i_mode)) + return; + + alias_de = d_find_alias(inode); + if (!alias_de) + return; + coda_flag_children(alias_de, flag); + shrink_dcache_parent(alias_de); + dput(alias_de); +} + diff --git a/fs/coda/cnode.c b/fs/coda/cnode.c new file mode 100644 index 000000000000..23aeef5aa814 --- /dev/null +++ b/fs/coda/cnode.c @@ -0,0 +1,172 @@ +/* cnode related routines for the coda kernel code + (C) 1996 Peter Braam + */ + +#include <linux/types.h> +#include <linux/string.h> +#include <linux/time.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_psdev.h> + +static inline int coda_fideq(struct CodaFid *fid1, struct CodaFid *fid2) +{ + return memcmp(fid1, fid2, sizeof(*fid1)) == 0; +} + +static struct inode_operations coda_symlink_inode_operations = { + .readlink = generic_readlink, + .follow_link = page_follow_link_light, + .put_link = page_put_link, + .setattr = coda_setattr, +}; + +/* cnode.c */ +static void coda_fill_inode(struct inode *inode, struct coda_vattr *attr) +{ + coda_vattr_to_iattr(inode, attr); + + if (S_ISREG(inode->i_mode)) { + inode->i_op = &coda_file_inode_operations; + inode->i_fop = &coda_file_operations; + } else if (S_ISDIR(inode->i_mode)) { + inode->i_op = &coda_dir_inode_operations; + inode->i_fop = &coda_dir_operations; + } else if (S_ISLNK(inode->i_mode)) { + inode->i_op = &coda_symlink_inode_operations; + inode->i_data.a_ops = &coda_symlink_aops; + inode->i_mapping = &inode->i_data; + } else + init_special_inode(inode, inode->i_mode, huge_decode_dev(attr->va_rdev)); +} + +static int coda_test_inode(struct inode *inode, void *data) +{ + struct CodaFid *fid = (struct CodaFid *)data; + return coda_fideq(&(ITOC(inode)->c_fid), fid); +} + +static int coda_set_inode(struct inode *inode, void *data) +{ + struct CodaFid *fid = (struct CodaFid *)data; + ITOC(inode)->c_fid = *fid; + return 0; +} + +static int coda_fail_inode(struct inode *inode, void *data) +{ + return -1; +} + +struct inode * coda_iget(struct super_block * sb, struct CodaFid * fid, + struct coda_vattr * attr) +{ + struct inode *inode; + struct coda_inode_info *cii; + unsigned long hash = coda_f2i(fid); + + inode = iget5_locked(sb, hash, coda_test_inode, coda_set_inode, fid); + + if (!inode) + return ERR_PTR(-ENOMEM); + + if (inode->i_state & I_NEW) { + cii = ITOC(inode); + /* we still need to set i_ino for things like stat(2) */ + inode->i_ino = hash; + cii->c_mapcount = 0; + unlock_new_inode(inode); + } + + /* always replace the attributes, type might have changed */ + coda_fill_inode(inode, attr); + return inode; +} + +/* this is effectively coda_iget: + - get attributes (might be cached) + - get the inode for the fid using vfs iget + - link the two up if this is needed + - fill in the attributes +*/ +int coda_cnode_make(struct inode **inode, struct CodaFid *fid, struct super_block *sb) +{ + struct coda_vattr attr; + int error; + + /* We get inode numbers from Venus -- see venus source */ + error = venus_getattr(sb, fid, &attr); + if ( error ) { + *inode = NULL; + return error; + } + + *inode = coda_iget(sb, fid, &attr); + if ( IS_ERR(*inode) ) { + printk("coda_cnode_make: coda_iget failed\n"); + return PTR_ERR(*inode); + } + return 0; +} + + +void coda_replace_fid(struct inode *inode, struct CodaFid *oldfid, + struct CodaFid *newfid) +{ + struct coda_inode_info *cii; + unsigned long hash = coda_f2i(newfid); + + cii = ITOC(inode); + + if (!coda_fideq(&cii->c_fid, oldfid)) + BUG(); + + /* replace fid and rehash inode */ + /* XXX we probably need to hold some lock here! */ + remove_inode_hash(inode); + cii->c_fid = *newfid; + inode->i_ino = hash; + __insert_inode_hash(inode, hash); +} + +/* convert a fid to an inode. */ +struct inode *coda_fid_to_inode(struct CodaFid *fid, struct super_block *sb) +{ + struct inode *inode; + unsigned long hash = coda_f2i(fid); + + if ( !sb ) { + printk("coda_fid_to_inode: no sb!\n"); + return NULL; + } + + inode = iget5_locked(sb, hash, coda_test_inode, coda_fail_inode, fid); + if ( !inode ) + return NULL; + + /* we should never see newly created inodes because we intentionally + * fail in the initialization callback */ + BUG_ON(inode->i_state & I_NEW); + + return inode; +} + +/* the CONTROL inode is made without asking attributes from Venus */ +int coda_cnode_makectl(struct inode **inode, struct super_block *sb) +{ + int error = -ENOMEM; + + *inode = new_inode(sb); + if (*inode) { + (*inode)->i_ino = CTL_INO; + (*inode)->i_op = &coda_ioctl_inode_operations; + (*inode)->i_fop = &coda_ioctl_operations; + (*inode)->i_mode = 0444; + error = 0; + } + + return error; +} + diff --git a/fs/coda/coda_linux.c b/fs/coda/coda_linux.c new file mode 100644 index 000000000000..5597080cb811 --- /dev/null +++ b/fs/coda/coda_linux.c @@ -0,0 +1,197 @@ +/* + * Inode 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/time.h> +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/errno.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_fs_i.h> + +/* initialize the debugging variables */ +int coda_fake_statfs; + +/* print a fid */ +char * coda_f2s(struct CodaFid *f) +{ + static char s[60]; +#ifdef CONFIG_CODA_FS_OLD_API + sprintf(s, "(%08x.%08x.%08x)", f->opaque[0], f->opaque[1], f->opaque[2]); +#else + sprintf(s, "(%08x.%08x.%08x.%08x)", f->opaque[0], f->opaque[1], f->opaque[2], f->opaque[3]); +#endif + return s; +} + +/* recognize special .CONTROL name */ +int coda_iscontrol(const char *name, size_t length) +{ + return ((CODA_CONTROLLEN == length) && + (strncmp(name, CODA_CONTROL, CODA_CONTROLLEN) == 0)); +} + +/* recognize /coda inode */ +int coda_isroot(struct inode *i) +{ + return ( i->i_sb->s_root->d_inode == i ); +} + +unsigned short coda_flags_to_cflags(unsigned short flags) +{ + unsigned short coda_flags = 0; + + if ((flags & O_ACCMODE) == O_RDONLY) + coda_flags |= C_O_READ; + + if ((flags & O_ACCMODE) == O_RDWR) + coda_flags |= C_O_READ | C_O_WRITE; + + if ((flags & O_ACCMODE) == O_WRONLY) + coda_flags |= C_O_WRITE; + + if (flags & O_TRUNC) + coda_flags |= C_O_TRUNC; + + if (flags & O_CREAT) + coda_flags |= C_O_CREAT; + + if (flags & O_EXCL) + coda_flags |= C_O_EXCL; + + return coda_flags; +} + + +/* utility functions below */ +void coda_vattr_to_iattr(struct inode *inode, struct coda_vattr *attr) +{ + int inode_type; + /* inode's i_flags, i_ino are set by iget + XXX: is this all we need ?? + */ + switch (attr->va_type) { + case C_VNON: + inode_type = 0; + break; + case C_VREG: + inode_type = S_IFREG; + break; + case C_VDIR: + inode_type = S_IFDIR; + break; + case C_VLNK: + inode_type = S_IFLNK; + break; + default: + inode_type = 0; + } + inode->i_mode |= inode_type; + + if (attr->va_mode != (u_short) -1) + inode->i_mode = attr->va_mode | inode_type; + if (attr->va_uid != -1) + inode->i_uid = (uid_t) attr->va_uid; + if (attr->va_gid != -1) + inode->i_gid = (gid_t) attr->va_gid; + if (attr->va_nlink != -1) + inode->i_nlink = attr->va_nlink; + if (attr->va_size != -1) + inode->i_size = attr->va_size; + if (attr->va_blocksize != -1) + inode->i_blksize = attr->va_blocksize; + if (attr->va_size != -1) + inode->i_blocks = (attr->va_size + 511) >> 9; + if (attr->va_atime.tv_sec != -1) + inode->i_atime = attr->va_atime; + if (attr->va_mtime.tv_sec != -1) + inode->i_mtime = attr->va_mtime; + if (attr->va_ctime.tv_sec != -1) + inode->i_ctime = attr->va_ctime; +} + + +/* + * BSD sets attributes that need not be modified to -1. + * Linux uses the valid field to indicate what should be + * looked at. The BSD type field needs to be deduced from linux + * mode. + * So we have to do some translations here. + */ + +void coda_iattr_to_vattr(struct iattr *iattr, struct coda_vattr *vattr) +{ + unsigned int valid; + + /* clean out */ + vattr->va_mode = (umode_t) -1; + vattr->va_uid = (vuid_t) -1; + vattr->va_gid = (vgid_t) -1; + vattr->va_size = (off_t) -1; + vattr->va_atime.tv_sec = (time_t) -1; + vattr->va_atime.tv_nsec = (time_t) -1; + vattr->va_mtime.tv_sec = (time_t) -1; + vattr->va_mtime.tv_nsec = (time_t) -1; + vattr->va_ctime.tv_sec = (time_t) -1; + vattr->va_ctime.tv_nsec = (time_t) -1; + vattr->va_type = C_VNON; + vattr->va_fileid = -1; + vattr->va_gen = -1; + vattr->va_bytes = -1; + vattr->va_nlink = -1; + vattr->va_blocksize = -1; + vattr->va_rdev = -1; + vattr->va_flags = 0; + + /* determine the type */ +#if 0 + mode = iattr->ia_mode; + if ( S_ISDIR(mode) ) { + vattr->va_type = C_VDIR; + } else if ( S_ISREG(mode) ) { + vattr->va_type = C_VREG; + } else if ( S_ISLNK(mode) ) { + vattr->va_type = C_VLNK; + } else { + /* don't do others */ + vattr->va_type = C_VNON; + } +#endif + + /* set those vattrs that need change */ + valid = iattr->ia_valid; + if ( valid & ATTR_MODE ) { + vattr->va_mode = iattr->ia_mode; + } + if ( valid & ATTR_UID ) { + vattr->va_uid = (vuid_t) iattr->ia_uid; + } + if ( valid & ATTR_GID ) { + vattr->va_gid = (vgid_t) iattr->ia_gid; + } + if ( valid & ATTR_SIZE ) { + vattr->va_size = iattr->ia_size; + } + if ( valid & ATTR_ATIME ) { + vattr->va_atime = iattr->ia_atime; + } + if ( valid & ATTR_MTIME ) { + vattr->va_mtime = iattr->ia_mtime; + } + if ( valid & ATTR_CTIME ) { + vattr->va_ctime = iattr->ia_ctime; + } +} + diff --git a/fs/coda/dir.c b/fs/coda/dir.c new file mode 100644 index 000000000000..2391766e9c7c --- /dev/null +++ b/fs/coda/dir.c @@ -0,0 +1,704 @@ + +/* + * Directory 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/time.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/smp_lock.h> + +#include <asm/uaccess.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_psdev.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_cache.h> +#include <linux/coda_proc.h> + +/* dir inode-ops */ +static int coda_create(struct inode *dir, struct dentry *new, int mode, struct nameidata *nd); +static struct dentry *coda_lookup(struct inode *dir, struct dentry *target, struct nameidata *nd); +static int coda_link(struct dentry *old_dentry, 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); + +/* dentry ops */ +static int coda_dentry_revalidate(struct dentry *de, struct nameidata *nd); +static int coda_dentry_delete(struct dentry *); + +/* support routines */ +static int coda_venus_readdir(struct file *filp, filldir_t filldir, + void *dirent, struct dentry *dir); +int coda_fsync(struct file *, struct dentry *dentry, int datasync); + +/* same as fs/bad_inode.c */ +static int coda_return_EIO(void) +{ + return -EIO; +} +#define CODA_EIO_ERROR ((void *) (coda_return_EIO)) + +static struct dentry_operations coda_dentry_operations = +{ + .d_revalidate = coda_dentry_revalidate, + .d_delete = coda_dentry_delete, +}; + +struct inode_operations coda_dir_inode_operations = +{ + .create = coda_create, + .lookup = coda_lookup, + .link = coda_link, + .unlink = coda_unlink, + .symlink = coda_symlink, + .mkdir = coda_mkdir, + .rmdir = coda_rmdir, + .mknod = CODA_EIO_ERROR, + .rename = coda_rename, + .permission = coda_permission, + .getattr = coda_getattr, + .setattr = coda_setattr, +}; + +struct file_operations coda_dir_operations = { + .llseek = generic_file_llseek, + .read = generic_read_dir, + .readdir = coda_readdir, + .open = coda_open, + .flush = coda_flush, + .release = coda_release, + .fsync = coda_fsync, +}; + + +/* inode operations for directories */ +/* access routines: lookup, readlink, permission */ +static struct dentry *coda_lookup(struct inode *dir, struct dentry *entry, struct nameidata *nd) +{ + struct inode *res_inode = NULL; + struct CodaFid resfid = { { 0, } }; + int dropme = 0; /* to indicate entry should not be cached */ + int type = 0; + int error = 0; + const char *name = entry->d_name.name; + size_t length = entry->d_name.len; + + if ( length > CODA_MAXNAMLEN ) { + printk("name too long: lookup, %s (%*s)\n", + coda_i2s(dir), (int)length, name); + return ERR_PTR(-ENAMETOOLONG); + } + + lock_kernel(); + /* control object, create inode on the fly */ + if (coda_isroot(dir) && coda_iscontrol(name, length)) { + error = coda_cnode_makectl(&res_inode, dir->i_sb); + dropme = 1; + goto exit; + } + + error = venus_lookup(dir->i_sb, coda_i2f(dir), + (const char *)name, length, &type, &resfid); + + res_inode = NULL; + if (!error) { + if (type & CODA_NOCACHE) { + type &= (~CODA_NOCACHE); + dropme = 1; + } + + error = coda_cnode_make(&res_inode, &resfid, dir->i_sb); + if (error) { + unlock_kernel(); + return ERR_PTR(error); + } + } else if (error != -ENOENT) { + unlock_kernel(); + return ERR_PTR(error); + } + +exit: + entry->d_time = 0; + entry->d_op = &coda_dentry_operations; + d_add(entry, res_inode); + if ( dropme ) { + d_drop(entry); + coda_flag_inode(res_inode, C_VATTR); + } + unlock_kernel(); + return NULL; +} + + +int coda_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + int error = 0; + + if (!mask) + return 0; + + lock_kernel(); + + coda_vfs_stat.permission++; + + if (coda_cache_check(inode, mask)) + goto out; + + error = venus_access(inode->i_sb, coda_i2f(inode), mask); + + if (!error) + coda_cache_enter(inode, mask); + + out: + unlock_kernel(); + + return error; +} + + +static inline void coda_dir_changed(struct inode *dir, int link) +{ +#ifdef REQUERY_VENUS_FOR_MTIME + /* invalidate the directory cnode's attributes so we refetch the + * attributes from venus next time the inode is referenced */ + coda_flag_inode(dir, C_VATTR); +#else + /* optimistically we can also act as if our nose bleeds. The + * granularity of the mtime is coarse anyways so we might actually be + * right most of the time. Note: we only do this for directories. */ + dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC; +#endif + if (link) + dir->i_nlink += link; +} + +/* creation routines: create, mknod, mkdir, link, symlink */ +static int coda_create(struct inode *dir, struct dentry *de, int mode, struct nameidata *nd) +{ + int error=0; + const char *name=de->d_name.name; + int length=de->d_name.len; + struct inode *inode; + struct CodaFid newfid; + struct coda_vattr attrs; + + lock_kernel(); + coda_vfs_stat.create++; + + if (coda_isroot(dir) && coda_iscontrol(name, length)) { + unlock_kernel(); + return -EPERM; + } + + error = venus_create(dir->i_sb, coda_i2f(dir), name, length, + 0, mode, &newfid, &attrs); + + if ( error ) { + unlock_kernel(); + d_drop(de); + return error; + } + + inode = coda_iget(dir->i_sb, &newfid, &attrs); + if ( IS_ERR(inode) ) { + unlock_kernel(); + d_drop(de); + return PTR_ERR(inode); + } + + /* invalidate the directory cnode's attributes */ + coda_dir_changed(dir, 0); + unlock_kernel(); + d_instantiate(de, inode); + return 0; +} + +static int coda_mkdir(struct inode *dir, struct dentry *de, int mode) +{ + struct inode *inode; + struct coda_vattr attrs; + const char *name = de->d_name.name; + int len = de->d_name.len; + int error; + struct CodaFid newfid; + + lock_kernel(); + coda_vfs_stat.mkdir++; + + if (coda_isroot(dir) && coda_iscontrol(name, len)) { + unlock_kernel(); + return -EPERM; + } + + attrs.va_mode = mode; + error = venus_mkdir(dir->i_sb, coda_i2f(dir), + name, len, &newfid, &attrs); + + if ( error ) { + unlock_kernel(); + d_drop(de); + return error; + } + + inode = coda_iget(dir->i_sb, &newfid, &attrs); + if ( IS_ERR(inode) ) { + unlock_kernel(); + d_drop(de); + return PTR_ERR(inode); + } + + /* invalidate the directory cnode's attributes */ + coda_dir_changed(dir, 1); + unlock_kernel(); + d_instantiate(de, inode); + return 0; +} + +/* try to make de an entry in dir_inodde linked to source_de */ +static int coda_link(struct dentry *source_de, struct inode *dir_inode, + struct dentry *de) +{ + struct inode *inode = source_de->d_inode; + const char * name = de->d_name.name; + int len = de->d_name.len; + int error; + + lock_kernel(); + coda_vfs_stat.link++; + + if (coda_isroot(dir_inode) && coda_iscontrol(name, len)) { + unlock_kernel(); + return -EPERM; + } + + error = venus_link(dir_inode->i_sb, coda_i2f(inode), + coda_i2f(dir_inode), (const char *)name, len); + + if (error) { + d_drop(de); + goto out; + } + + coda_dir_changed(dir_inode, 0); + atomic_inc(&inode->i_count); + d_instantiate(de, inode); + inode->i_nlink++; + +out: + unlock_kernel(); + 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; + int symlen; + int error=0; + + lock_kernel(); + coda_vfs_stat.symlink++; + + if (coda_isroot(dir_inode) && coda_iscontrol(name, len)) { + unlock_kernel(); + return -EPERM; + } + + symlen = strlen(symname); + if ( symlen > CODA_MAXPATHLEN ) { + unlock_kernel(); + return -ENAMETOOLONG; + } + + /* + * This entry is now negative. Since we do not create + * an inode for the entry we have to drop it. + */ + d_drop(de); + error = venus_symlink(dir_inode->i_sb, coda_i2f(dir_inode), name, len, + symname, symlen); + + /* mtime is no good anymore */ + if ( !error ) + coda_dir_changed(dir_inode, 0); + + unlock_kernel(); + return error; +} + +/* destruction routines: unlink, rmdir */ +int coda_unlink(struct inode *dir, struct dentry *de) +{ + int error; + const char *name = de->d_name.name; + int len = de->d_name.len; + + lock_kernel(); + coda_vfs_stat.unlink++; + + error = venus_remove(dir->i_sb, coda_i2f(dir), name, len); + if ( error ) { + unlock_kernel(); + return error; + } + + coda_dir_changed(dir, 0); + de->d_inode->i_nlink--; + unlock_kernel(); + + return 0; +} + +int coda_rmdir(struct inode *dir, struct dentry *de) +{ + const char *name = de->d_name.name; + int len = de->d_name.len; + int error; + + lock_kernel(); + coda_vfs_stat.rmdir++; + + if (!d_unhashed(de)) { + unlock_kernel(); + return -EBUSY; + } + error = venus_rmdir(dir->i_sb, coda_i2f(dir), name, len); + + if ( error ) { + unlock_kernel(); + return error; + } + + coda_dir_changed(dir, -1); + de->d_inode->i_nlink--; + d_delete(de); + unlock_kernel(); + + 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; + int link_adjust = 0; + int error; + + lock_kernel(); + coda_vfs_stat.rename++; + + error = venus_rename(old_dir->i_sb, coda_i2f(old_dir), + coda_i2f(new_dir), old_length, new_length, + (const char *) old_name, (const char *)new_name); + + if ( !error ) { + if ( new_dentry->d_inode ) { + if ( S_ISDIR(new_dentry->d_inode->i_mode) ) + link_adjust = 1; + + coda_dir_changed(old_dir, -link_adjust); + coda_dir_changed(new_dir, link_adjust); + coda_flag_inode(new_dentry->d_inode, C_VATTR); + } else { + coda_flag_inode(old_dir, C_VATTR); + coda_flag_inode(new_dir, C_VATTR); + } + } + unlock_kernel(); + + return error; +} + + +/* file operations for directories */ +int coda_readdir(struct file *coda_file, void *dirent, filldir_t filldir) +{ + struct dentry *coda_dentry = coda_file->f_dentry; + struct coda_file_info *cfi; + struct file *host_file; + struct inode *host_inode; + int ret; + + cfi = CODA_FTOC(coda_file); + BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); + host_file = cfi->cfi_container; + + coda_vfs_stat.readdir++; + + host_inode = host_file->f_dentry->d_inode; + down(&host_inode->i_sem); + host_file->f_pos = coda_file->f_pos; + + if (!host_file->f_op->readdir) { + /* Venus: we must read Venus dirents from the file */ + ret = coda_venus_readdir(host_file, filldir, dirent, coda_dentry); + } else { + /* potemkin case: we were handed a directory inode. */ + /* Yuk, we can't call vfs_readdir because we are already + * holding the inode semaphore. */ + ret = -ENOTDIR; + if (!host_file->f_op || !host_file->f_op->readdir) + goto out; + + ret = -ENOENT; + if (!IS_DEADDIR(host_inode)) { + ret = host_file->f_op->readdir(host_file, filldir, dirent); + file_accessed(host_file); + } + } +out: + coda_file->f_pos = host_file->f_pos; + up(&host_inode->i_sem); + + return ret; +} + +static inline unsigned int CDT2DT(unsigned char cdt) +{ + unsigned int dt; + + switch(cdt) { + case CDT_UNKNOWN: dt = DT_UNKNOWN; break; + case CDT_FIFO: dt = DT_FIFO; break; + case CDT_CHR: dt = DT_CHR; break; + case CDT_DIR: dt = DT_DIR; break; + case CDT_BLK: dt = DT_BLK; break; + case CDT_REG: dt = DT_REG; break; + case CDT_LNK: dt = DT_LNK; break; + case CDT_SOCK: dt = DT_SOCK; break; + case CDT_WHT: dt = DT_WHT; break; + default: dt = DT_UNKNOWN; break; + } + return dt; +} + +/* support routines */ +static int coda_venus_readdir(struct file *filp, filldir_t filldir, + void *dirent, struct dentry *dir) +{ + int result = 0; /* # of entries returned */ + struct venus_dirent *vdir; + unsigned long vdir_size = + (unsigned long)(&((struct venus_dirent *)0)->d_name); + unsigned int type; + struct qstr name; + ino_t ino; + int ret, i; + + vdir = (struct venus_dirent *)kmalloc(sizeof(*vdir), GFP_KERNEL); + if (!vdir) return -ENOMEM; + + i = filp->f_pos; + switch(i) { + case 0: + ret = filldir(dirent, ".", 1, 0, dir->d_inode->i_ino, DT_DIR); + if (ret < 0) break; + result++; + filp->f_pos++; + /* fallthrough */ + case 1: + ret = filldir(dirent, "..", 2, 1, dir->d_parent->d_inode->i_ino, DT_DIR); + if (ret < 0) break; + result++; + filp->f_pos++; + /* fallthrough */ + default: + while (1) { + /* read entries from the directory file */ + ret = kernel_read(filp, filp->f_pos - 2, (char *)vdir, + sizeof(*vdir)); + if (ret < 0) { + printk("coda_venus_readdir: read dir failed %d\n", ret); + break; + } + if (ret == 0) break; /* end of directory file reached */ + + /* catch truncated reads */ + if (ret < vdir_size || ret < vdir_size + vdir->d_namlen) { + printk("coda_venus_readdir: short read: %ld\n", + filp->f_dentry->d_inode->i_ino); + ret = -EBADF; + break; + } + /* validate whether the directory file actually makes sense */ + if (vdir->d_reclen < vdir_size + vdir->d_namlen) { + printk("coda_venus_readdir: Invalid dir: %ld\n", + filp->f_dentry->d_inode->i_ino); + ret = -EBADF; + break; + } + + name.len = vdir->d_namlen; + name.name = vdir->d_name; + + /* Make sure we skip '.' and '..', we already got those */ + if (name.name[0] == '.' && (name.len == 1 || + (vdir->d_name[1] == '.' && name.len == 2))) + vdir->d_fileno = name.len = 0; + + /* skip null entries */ + if (vdir->d_fileno && name.len) { + /* try to look up this entry in the dcache, that way + * userspace doesn't have to worry about breaking + * getcwd by having mismatched inode numbers for + * internal volume mountpoints. */ + ino = find_inode_number(dir, &name); + if (!ino) ino = vdir->d_fileno; + + type = CDT2DT(vdir->d_type); + ret = filldir(dirent, name.name, name.len, filp->f_pos, + ino, type); + /* failure means no space for filling in this round */ + if (ret < 0) break; + result++; + } + /* we'll always have progress because d_reclen is unsigned and + * we've already established it is non-zero. */ + filp->f_pos += vdir->d_reclen; + } + } + kfree(vdir); + return result ? result : ret; +} + +/* called when a cache lookup succeeds */ +static int coda_dentry_revalidate(struct dentry *de, struct nameidata *nd) +{ + struct inode *inode = de->d_inode; + struct coda_inode_info *cii; + + if (!inode) + return 1; + lock_kernel(); + if (coda_isroot(inode)) + goto out; + if (is_bad_inode(inode)) + goto bad; + + cii = ITOC(de->d_inode); + if (!(cii->c_flags & (C_PURGE | C_FLUSH))) + goto out; + + shrink_dcache_parent(de); + + /* propagate for a flush */ + if (cii->c_flags & C_FLUSH) + coda_flag_inode_children(inode, C_FLUSH); + + if (atomic_read(&de->d_count) > 1) + /* pretend it's valid, but don't change the flags */ + goto out; + + /* clear the flags. */ + cii->c_flags &= ~(C_VATTR | C_PURGE | C_FLUSH); + +bad: + unlock_kernel(); + return 0; +out: + unlock_kernel(); + return 1; +} + +/* + * This is the callback from dput() when d_count is going to 0. + * We use this to unhash dentries with bad inodes. + */ +static int coda_dentry_delete(struct dentry * dentry) +{ + int flags; + + if (!dentry->d_inode) + return 0; + + flags = (ITOC(dentry->d_inode)->c_flags) & C_PURGE; + if (is_bad_inode(dentry->d_inode) || flags) { + return 1; + } + return 0; +} + + + +/* + * This is called when we want to check if the inode has + * changed on the server. Coda makes this easy since the + * cache manager Venus issues a downcall to the kernel when this + * happens + */ +int coda_revalidate_inode(struct dentry *dentry) +{ + struct coda_vattr attr; + int error = 0; + int old_mode; + ino_t old_ino; + struct inode *inode = dentry->d_inode; + struct coda_inode_info *cii = ITOC(inode); + + lock_kernel(); + if ( !cii->c_flags ) + goto ok; + + if (cii->c_flags & (C_VATTR | C_PURGE | C_FLUSH)) { + error = venus_getattr(inode->i_sb, &(cii->c_fid), &attr); + if ( error ) + goto return_bad; + + /* this inode may be lost if: + - it's ino changed + - type changes must be permitted for repair and + missing mount points. + */ + old_mode = inode->i_mode; + old_ino = inode->i_ino; + coda_vattr_to_iattr(inode, &attr); + + if ((old_mode & S_IFMT) != (inode->i_mode & S_IFMT)) { + printk("Coda: inode %ld, fid %s changed type!\n", + inode->i_ino, coda_f2s(&(cii->c_fid))); + } + + /* the following can happen when a local fid is replaced + with a global one, here we lose and declare the inode bad */ + if (inode->i_ino != old_ino) + goto return_bad; + + coda_flag_inode_children(inode, C_FLUSH); + cii->c_flags &= ~(C_VATTR | C_PURGE | C_FLUSH); + } + +ok: + unlock_kernel(); + return 0; + +return_bad: + unlock_kernel(); + return -EIO; +} diff --git a/fs/coda/file.c b/fs/coda/file.c new file mode 100644 index 000000000000..e6bc022568f3 --- /dev/null +++ b/fs/coda/file.c @@ -0,0 +1,300 @@ +/* + * File operations for Coda. + * Original version: (C) 1996 Peter Braam + * Rewritten for Linux 2.1: (C) 1997 Carnegie Mellon University + * + * Carnegie Mellon encourages users of this code to contribute improvements + * to the Coda project. Contact Peter Braam <coda@cs.cmu.edu>. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/time.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/smp_lock.h> +#include <linux/string.h> +#include <asm/uaccess.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_psdev.h> +#include <linux/coda_proc.h> + +/* if CODA_STORE fails with EOPNOTSUPP, venus clearly doesn't support + * CODA_STORE/CODA_RELEASE and we fall back on using the CODA_CLOSE upcall */ +static int use_coda_close; + +static ssize_t +coda_file_read(struct file *coda_file, char __user *buf, size_t count, loff_t *ppos) +{ + struct coda_file_info *cfi; + struct file *host_file; + + cfi = CODA_FTOC(coda_file); + BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); + host_file = cfi->cfi_container; + + if (!host_file->f_op || !host_file->f_op->read) + return -EINVAL; + + return host_file->f_op->read(host_file, buf, count, ppos); +} + +static ssize_t +coda_file_sendfile(struct file *coda_file, loff_t *ppos, size_t count, + read_actor_t actor, void *target) +{ + struct coda_file_info *cfi; + struct file *host_file; + + cfi = CODA_FTOC(coda_file); + BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); + host_file = cfi->cfi_container; + + if (!host_file->f_op || !host_file->f_op->sendfile) + return -EINVAL; + + return host_file->f_op->sendfile(host_file, ppos, count, actor, target); +} + +static ssize_t +coda_file_write(struct file *coda_file, const char __user *buf, size_t count, loff_t *ppos) +{ + struct inode *host_inode, *coda_inode = coda_file->f_dentry->d_inode; + struct coda_file_info *cfi; + struct file *host_file; + ssize_t ret; + + cfi = CODA_FTOC(coda_file); + BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); + host_file = cfi->cfi_container; + + if (!host_file->f_op || !host_file->f_op->write) + return -EINVAL; + + host_inode = host_file->f_dentry->d_inode; + down(&coda_inode->i_sem); + + ret = host_file->f_op->write(host_file, buf, count, ppos); + + coda_inode->i_size = host_inode->i_size; + coda_inode->i_blocks = (coda_inode->i_size + 511) >> 9; + coda_inode->i_mtime = coda_inode->i_ctime = CURRENT_TIME_SEC; + up(&coda_inode->i_sem); + + return ret; +} + +static int +coda_file_mmap(struct file *coda_file, struct vm_area_struct *vma) +{ + struct coda_file_info *cfi; + struct coda_inode_info *cii; + struct file *host_file; + struct inode *coda_inode, *host_inode; + + cfi = CODA_FTOC(coda_file); + BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); + host_file = cfi->cfi_container; + + if (!host_file->f_op || !host_file->f_op->mmap) + return -ENODEV; + + coda_inode = coda_file->f_dentry->d_inode; + host_inode = host_file->f_dentry->d_inode; + coda_file->f_mapping = host_file->f_mapping; + if (coda_inode->i_mapping == &coda_inode->i_data) + coda_inode->i_mapping = host_inode->i_mapping; + + /* only allow additional mmaps as long as userspace isn't changing + * the container file on us! */ + else if (coda_inode->i_mapping != host_inode->i_mapping) + return -EBUSY; + + /* keep track of how often the coda_inode/host_file has been mmapped */ + cii = ITOC(coda_inode); + cii->c_mapcount++; + cfi->cfi_mapcount++; + + return host_file->f_op->mmap(host_file, vma); +} + +int coda_open(struct inode *coda_inode, struct file *coda_file) +{ + struct file *host_file = NULL; + int error; + unsigned short flags = coda_file->f_flags & (~O_EXCL); + unsigned short coda_flags = coda_flags_to_cflags(flags); + struct coda_file_info *cfi; + + coda_vfs_stat.open++; + + cfi = kmalloc(sizeof(struct coda_file_info), GFP_KERNEL); + if (!cfi) { + unlock_kernel(); + return -ENOMEM; + } + + lock_kernel(); + + error = venus_open(coda_inode->i_sb, coda_i2f(coda_inode), coda_flags, + &host_file); + if (error || !host_file) { + kfree(cfi); + unlock_kernel(); + return error; + } + + host_file->f_flags |= coda_file->f_flags & (O_APPEND | O_SYNC); + + cfi->cfi_magic = CODA_MAGIC; + cfi->cfi_mapcount = 0; + cfi->cfi_container = host_file; + + BUG_ON(coda_file->private_data != NULL); + coda_file->private_data = cfi; + + unlock_kernel(); + return 0; +} + +int coda_flush(struct file *coda_file) +{ + unsigned short flags = coda_file->f_flags & ~O_EXCL; + unsigned short coda_flags = coda_flags_to_cflags(flags); + struct coda_file_info *cfi; + struct inode *coda_inode; + int err = 0, fcnt; + + lock_kernel(); + + coda_vfs_stat.flush++; + + /* last close semantics */ + fcnt = file_count(coda_file); + if (fcnt > 1) + goto out; + + /* No need to make an upcall when we have not made any modifications + * to the file */ + if ((coda_file->f_flags & O_ACCMODE) == O_RDONLY) + goto out; + + if (use_coda_close) + goto out; + + cfi = CODA_FTOC(coda_file); + BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); + + coda_inode = coda_file->f_dentry->d_inode; + + err = venus_store(coda_inode->i_sb, coda_i2f(coda_inode), coda_flags, + coda_file->f_uid); + + if (err == -EOPNOTSUPP) { + use_coda_close = 1; + err = 0; + } + +out: + unlock_kernel(); + return err; +} + +int coda_release(struct inode *coda_inode, struct file *coda_file) +{ + unsigned short flags = (coda_file->f_flags) & (~O_EXCL); + unsigned short coda_flags = coda_flags_to_cflags(flags); + struct coda_file_info *cfi; + struct coda_inode_info *cii; + struct inode *host_inode; + int err = 0; + + lock_kernel(); + coda_vfs_stat.release++; + + if (!use_coda_close) { + err = venus_release(coda_inode->i_sb, coda_i2f(coda_inode), + coda_flags); + if (err == -EOPNOTSUPP) { + use_coda_close = 1; + err = 0; + } + } + + cfi = CODA_FTOC(coda_file); + BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); + + if (use_coda_close) + err = venus_close(coda_inode->i_sb, coda_i2f(coda_inode), + coda_flags, coda_file->f_uid); + + host_inode = cfi->cfi_container->f_dentry->d_inode; + cii = ITOC(coda_inode); + + /* did we mmap this file? */ + if (coda_inode->i_mapping == &host_inode->i_data) { + cii->c_mapcount -= cfi->cfi_mapcount; + if (!cii->c_mapcount) + coda_inode->i_mapping = &coda_inode->i_data; + } + + fput(cfi->cfi_container); + kfree(coda_file->private_data); + coda_file->private_data = NULL; + + unlock_kernel(); + return err; +} + +int coda_fsync(struct file *coda_file, struct dentry *coda_dentry, int datasync) +{ + struct file *host_file; + struct dentry *host_dentry; + struct inode *host_inode, *coda_inode = coda_dentry->d_inode; + struct coda_file_info *cfi; + int err = 0; + + if (!(S_ISREG(coda_inode->i_mode) || S_ISDIR(coda_inode->i_mode) || + S_ISLNK(coda_inode->i_mode))) + return -EINVAL; + + cfi = CODA_FTOC(coda_file); + BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC); + host_file = cfi->cfi_container; + + coda_vfs_stat.fsync++; + + if (host_file->f_op && host_file->f_op->fsync) { + host_dentry = host_file->f_dentry; + host_inode = host_dentry->d_inode; + down(&host_inode->i_sem); + err = host_file->f_op->fsync(host_file, host_dentry, datasync); + up(&host_inode->i_sem); + } + + if ( !err && !datasync ) { + lock_kernel(); + err = venus_fsync(coda_inode->i_sb, coda_i2f(coda_inode)); + unlock_kernel(); + } + + return err; +} + +struct file_operations coda_file_operations = { + .llseek = generic_file_llseek, + .read = coda_file_read, + .write = coda_file_write, + .mmap = coda_file_mmap, + .open = coda_open, + .flush = coda_flush, + .release = coda_release, + .fsync = coda_fsync, + .sendfile = coda_file_sendfile, +}; + diff --git a/fs/coda/inode.c b/fs/coda/inode.c new file mode 100644 index 000000000000..04a73fb4848f --- /dev/null +++ b/fs/coda/inode.c @@ -0,0 +1,321 @@ +/* + * Super block/filesystem wide operations + * + * Copyright (C) 1996 Peter J. Braam <braam@maths.ox.ac.uk> and + * Michael Callahan <callahan@maths.ox.ac.uk> + * + * Rewritten for Linux 2.1. Peter Braam <braam@cs.cmu.edu> + * Copyright (C) Carnegie Mellon University + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/smp_lock.h> +#include <linux/file.h> +#include <linux/vfs.h> + +#include <asm/system.h> +#include <asm/uaccess.h> + +#include <linux/fs.h> +#include <linux/vmalloc.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_psdev.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_cache.h> + +/* VFS super_block ops */ +static void coda_clear_inode(struct inode *); +static void coda_put_super(struct super_block *); +static int coda_statfs(struct super_block *sb, struct kstatfs *buf); + +static kmem_cache_t * coda_inode_cachep; + +static struct inode *coda_alloc_inode(struct super_block *sb) +{ + struct coda_inode_info *ei; + ei = (struct coda_inode_info *)kmem_cache_alloc(coda_inode_cachep, SLAB_KERNEL); + if (!ei) + return NULL; + memset(&ei->c_fid, 0, sizeof(struct CodaFid)); + ei->c_flags = 0; + ei->c_uid = 0; + ei->c_cached_perm = 0; + return &ei->vfs_inode; +} + +static void coda_destroy_inode(struct inode *inode) +{ + kmem_cache_free(coda_inode_cachep, ITOC(inode)); +} + +static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags) +{ + struct coda_inode_info *ei = (struct coda_inode_info *) foo; + + if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) + inode_init_once(&ei->vfs_inode); +} + +int coda_init_inodecache(void) +{ + coda_inode_cachep = kmem_cache_create("coda_inode_cache", + sizeof(struct coda_inode_info), + 0, SLAB_RECLAIM_ACCOUNT, + init_once, NULL); + if (coda_inode_cachep == NULL) + return -ENOMEM; + return 0; +} + +void coda_destroy_inodecache(void) +{ + if (kmem_cache_destroy(coda_inode_cachep)) + printk(KERN_INFO "coda_inode_cache: not all structures were freed\n"); +} + +static int coda_remount(struct super_block *sb, int *flags, char *data) +{ + *flags |= MS_NODIRATIME; + return 0; +} + +/* exported operations */ +static struct super_operations coda_super_operations = +{ + .alloc_inode = coda_alloc_inode, + .destroy_inode = coda_destroy_inode, + .clear_inode = coda_clear_inode, + .put_super = coda_put_super, + .statfs = coda_statfs, + .remount_fs = coda_remount, +}; + +static int get_device_index(struct coda_mount_data *data) +{ + struct file *file; + struct inode *inode; + int idx; + + if(data == NULL) { + printk("coda_read_super: Bad mount data\n"); + return -1; + } + + if(data->version != CODA_MOUNT_VERSION) { + printk("coda_read_super: Bad mount version\n"); + return -1; + } + + file = fget(data->fd); + inode = NULL; + if(file) + inode = file->f_dentry->d_inode; + + if(!inode || !S_ISCHR(inode->i_mode) || + imajor(inode) != CODA_PSDEV_MAJOR) { + if(file) + fput(file); + + printk("coda_read_super: Bad file\n"); + return -1; + } + + idx = iminor(inode); + fput(file); + + if(idx < 0 || idx >= MAX_CODADEVS) { + printk("coda_read_super: Bad minor number\n"); + return -1; + } + + return idx; +} + +static int coda_fill_super(struct super_block *sb, void *data, int silent) +{ + struct inode *root = NULL; + struct coda_sb_info *sbi = NULL; + struct venus_comm *vc = NULL; + struct CodaFid fid; + int error; + int idx; + + idx = get_device_index((struct coda_mount_data *) data); + + /* Ignore errors in data, for backward compatibility */ + if(idx == -1) + idx = 0; + + printk(KERN_INFO "coda_read_super: device index: %i\n", idx); + + vc = &coda_comms[idx]; + if (!vc->vc_inuse) { + printk("coda_read_super: No pseudo device\n"); + return -EINVAL; + } + + if ( vc->vc_sb ) { + printk("coda_read_super: Device already mounted\n"); + return -EBUSY; + } + + sbi = kmalloc(sizeof(struct coda_sb_info), GFP_KERNEL); + if(!sbi) { + return -ENOMEM; + } + + vc->vc_sb = sb; + + sbi->sbi_vcomm = vc; + + sb->s_fs_info = sbi; + sb->s_flags |= MS_NODIRATIME; /* probably even noatime */ + sb->s_blocksize = 1024; /* XXXXX what do we put here?? */ + sb->s_blocksize_bits = 10; + sb->s_magic = CODA_SUPER_MAGIC; + sb->s_op = &coda_super_operations; + + /* get root fid from Venus: this needs the root inode */ + error = venus_rootfid(sb, &fid); + if ( error ) { + printk("coda_read_super: coda_get_rootfid failed with %d\n", + error); + goto error; + } + printk("coda_read_super: rootfid is %s\n", coda_f2s(&fid)); + + /* make root inode */ + error = coda_cnode_make(&root, &fid, sb); + if ( error || !root ) { + printk("Failure of coda_cnode_make for root: error %d\n", error); + goto error; + } + + printk("coda_read_super: rootinode is %ld dev %s\n", + root->i_ino, root->i_sb->s_id); + sb->s_root = d_alloc_root(root); + if (!sb->s_root) + goto error; + return 0; + + error: + if (sbi) { + kfree(sbi); + if(vc) + vc->vc_sb = NULL; + } + if (root) + iput(root); + + return -EINVAL; +} + +static void coda_put_super(struct super_block *sb) +{ + struct coda_sb_info *sbi; + + sbi = coda_sbp(sb); + sbi->sbi_vcomm->vc_sb = NULL; + + printk("Coda: Bye bye.\n"); + kfree(sbi); +} + +static void coda_clear_inode(struct inode *inode) +{ + coda_cache_clear_inode(inode); +} + +int coda_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) +{ + int err = coda_revalidate_inode(dentry); + if (!err) + generic_fillattr(dentry->d_inode, stat); + return err; +} + +int coda_setattr(struct dentry *de, struct iattr *iattr) +{ + struct inode *inode = de->d_inode; + struct coda_vattr vattr; + int error; + + lock_kernel(); + + memset(&vattr, 0, sizeof(vattr)); + + inode->i_ctime = CURRENT_TIME_SEC; + coda_iattr_to_vattr(iattr, &vattr); + vattr.va_type = C_VNON; /* cannot set type */ + + /* Venus is responsible for truncating the container-file!!! */ + error = venus_setattr(inode->i_sb, coda_i2f(inode), &vattr); + + if ( !error ) { + coda_vattr_to_iattr(inode, &vattr); + coda_cache_clear_inode(inode); + } + + unlock_kernel(); + + return error; +} + +struct inode_operations coda_file_inode_operations = { + .permission = coda_permission, + .getattr = coda_getattr, + .setattr = coda_setattr, +}; + +static int coda_statfs(struct super_block *sb, struct kstatfs *buf) +{ + int error; + + lock_kernel(); + + error = venus_statfs(sb, buf); + + unlock_kernel(); + + if (error) { + /* fake something like AFS does */ + buf->f_blocks = 9000000; + buf->f_bfree = 9000000; + buf->f_bavail = 9000000; + buf->f_files = 9000000; + buf->f_ffree = 9000000; + } + + /* and fill in the rest */ + buf->f_type = CODA_SUPER_MAGIC; + buf->f_bsize = 1024; + buf->f_namelen = CODA_MAXNAMLEN; + + return 0; +} + +/* init_coda: used by filesystems.c to register coda */ + +static struct super_block *coda_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return get_sb_nodev(fs_type, flags, data, coda_fill_super); +} + +struct file_system_type coda_fs_type = { + .owner = THIS_MODULE, + .name = "coda", + .get_sb = coda_get_sb, + .kill_sb = kill_anon_super, + .fs_flags = FS_BINARY_MOUNTDATA, +}; + diff --git a/fs/coda/pioctl.c b/fs/coda/pioctl.c new file mode 100644 index 000000000000..127714936c66 --- /dev/null +++ b/fs/coda/pioctl.c @@ -0,0 +1,95 @@ +/* + * Pioctl operations for Coda. + * Original version: (C) 1996 Peter Braam + * Rewritten for Linux 2.1: (C) 1997 Carnegie Mellon University + * + * Carnegie Mellon encourages users of this code to contribute improvements + * to the Coda project. Contact Peter Braam <coda@cs.cmu.edu>. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/time.h> +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/namei.h> +#include <linux/module.h> +#include <asm/uaccess.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_psdev.h> + +/* pioctl ops */ +static int coda_ioctl_permission(struct inode *inode, int mask, + struct nameidata *nd); +static int coda_pioctl(struct inode * inode, struct file * filp, + unsigned int cmd, unsigned long user_data); + +/* exported from this file */ +struct inode_operations coda_ioctl_inode_operations = +{ + .permission = coda_ioctl_permission, + .setattr = coda_setattr, +}; + +struct file_operations coda_ioctl_operations = { + .owner = THIS_MODULE, + .ioctl = coda_pioctl, +}; + +/* the coda pioctl inode ops */ +static int coda_ioctl_permission(struct inode *inode, int mask, + struct nameidata *nd) +{ + return 0; +} + +static int coda_pioctl(struct inode * inode, struct file * filp, + unsigned int cmd, unsigned long user_data) +{ + struct nameidata nd; + int error; + struct PioctlData data; + struct inode *target_inode = NULL; + struct coda_inode_info *cnp; + + /* get the Pioctl data arguments from user space */ + if (copy_from_user(&data, (void __user *)user_data, sizeof(data))) { + return -EINVAL; + } + + /* + * Look up the pathname. Note that the pathname is in + * user memory, and namei takes care of this + */ + if ( data.follow ) { + error = user_path_walk(data.path, &nd); + } else { + error = user_path_walk_link(data.path, &nd); + } + + if ( error ) { + return error; + } else { + target_inode = nd.dentry->d_inode; + } + + /* return if it is not a Coda inode */ + if ( target_inode->i_sb != inode->i_sb ) { + path_release(&nd); + return -EINVAL; + } + + /* now proceed to make the upcall */ + cnp = ITOC(target_inode); + + error = venus_pioctl(inode->i_sb, &(cnp->c_fid), cmd, &data); + + path_release(&nd); + return error; +} + diff --git a/fs/coda/psdev.c b/fs/coda/psdev.c new file mode 100644 index 000000000000..ef001a9313e6 --- /dev/null +++ b/fs/coda/psdev.c @@ -0,0 +1,462 @@ +/* + * An implementation of a loadable kernel mode driver providing + * multiple kernel/user space bidirectional communications links. + * + * Author: Alan Cox <alan@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Adapted to become the Linux 2.0 Coda pseudo device + * Peter Braam <braam@maths.ox.ac.uk> + * Michael Callahan <mjc@emmy.smith.edu> + * + * Changes for Linux 2.1 + * Copyright (c) 1997 Carnegie-Mellon University + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/time.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/fcntl.h> +#include <linux/delay.h> +#include <linux/skbuff.h> +#include <linux/proc_fs.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/smp_lock.h> +#include <linux/device.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/poll.h> +#include <asm/uaccess.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_psdev.h> +#include <linux/coda_proc.h> + +#define upc_free(r) kfree(r) + +/* + * Coda stuff + */ +extern struct file_system_type coda_fs_type; + +/* statistics */ +int coda_hard; /* allows signals during upcalls */ +unsigned long coda_timeout = 30; /* .. secs, then signals will dequeue */ + + +struct venus_comm coda_comms[MAX_CODADEVS]; +static struct class_simple *coda_psdev_class; + +/* + * Device operations + */ + +static unsigned int coda_psdev_poll(struct file *file, poll_table * wait) +{ + struct venus_comm *vcp = (struct venus_comm *) file->private_data; + unsigned int mask = POLLOUT | POLLWRNORM; + + poll_wait(file, &vcp->vc_waitq, wait); + if (!list_empty(&vcp->vc_pending)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static int coda_psdev_ioctl(struct inode * inode, struct file * filp, + unsigned int cmd, unsigned long arg) +{ + unsigned int data; + + switch(cmd) { + case CIOC_KERNEL_VERSION: + data = CODA_KERNEL_VERSION; + return put_user(data, (int __user *) arg); + default: + return -ENOTTY; + } + + return 0; +} + +/* + * Receive a message written by Venus to the psdev + */ + +static ssize_t coda_psdev_write(struct file *file, const char __user *buf, + size_t nbytes, loff_t *off) +{ + struct venus_comm *vcp = (struct venus_comm *) file->private_data; + struct upc_req *req = NULL; + struct upc_req *tmp; + struct list_head *lh; + struct coda_in_hdr hdr; + ssize_t retval = 0, count = 0; + int error; + + /* Peek at the opcode, uniquefier */ + if (copy_from_user(&hdr, buf, 2 * sizeof(u_long))) + return -EFAULT; + + if (DOWNCALL(hdr.opcode)) { + struct super_block *sb = NULL; + union outputArgs *dcbuf; + int size = sizeof(*dcbuf); + + sb = vcp->vc_sb; + if ( !sb ) { + count = nbytes; + goto out; + } + + if ( nbytes < sizeof(struct coda_out_hdr) ) { + printk("coda_downcall opc %d uniq %d, not enough!\n", + hdr.opcode, hdr.unique); + count = nbytes; + goto out; + } + if ( nbytes > size ) { + printk("Coda: downcall opc %d, uniq %d, too much!", + hdr.opcode, hdr.unique); + nbytes = size; + } + CODA_ALLOC(dcbuf, union outputArgs *, nbytes); + if (copy_from_user(dcbuf, buf, nbytes)) { + CODA_FREE(dcbuf, nbytes); + retval = -EFAULT; + goto out; + } + + /* what downcall errors does Venus handle ? */ + lock_kernel(); + error = coda_downcall(hdr.opcode, dcbuf, sb); + unlock_kernel(); + + CODA_FREE(dcbuf, nbytes); + if (error) { + printk("psdev_write: coda_downcall error: %d\n", error); + retval = error; + goto out; + } + count = nbytes; + goto out; + } + + /* Look for the message on the processing queue. */ + lock_kernel(); + list_for_each(lh, &vcp->vc_processing) { + tmp = list_entry(lh, struct upc_req , uc_chain); + if (tmp->uc_unique == hdr.unique) { + req = tmp; + list_del(&req->uc_chain); + break; + } + } + unlock_kernel(); + + if (!req) { + printk("psdev_write: msg (%d, %d) not found\n", + hdr.opcode, hdr.unique); + retval = -ESRCH; + goto out; + } + + /* move data into response buffer. */ + if (req->uc_outSize < nbytes) { + printk("psdev_write: too much cnt: %d, cnt: %ld, opc: %d, uniq: %d.\n", + req->uc_outSize, (long)nbytes, hdr.opcode, hdr.unique); + nbytes = req->uc_outSize; /* don't have more space! */ + } + if (copy_from_user(req->uc_data, buf, nbytes)) { + req->uc_flags |= REQ_ABORT; + wake_up(&req->uc_sleep); + retval = -EFAULT; + goto out; + } + + /* adjust outsize. is this useful ?? */ + req->uc_outSize = nbytes; + req->uc_flags |= REQ_WRITE; + count = nbytes; + + /* Convert filedescriptor into a file handle */ + if (req->uc_opcode == CODA_OPEN_BY_FD) { + struct coda_open_by_fd_out *outp = + (struct coda_open_by_fd_out *)req->uc_data; + outp->fh = fget(outp->fd); + } + + wake_up(&req->uc_sleep); +out: + return(count ? count : retval); +} + +/* + * Read a message from the kernel to Venus + */ + +static ssize_t coda_psdev_read(struct file * file, char __user * buf, + size_t nbytes, loff_t *off) +{ + DECLARE_WAITQUEUE(wait, current); + struct venus_comm *vcp = (struct venus_comm *) file->private_data; + struct upc_req *req; + ssize_t retval = 0, count = 0; + + if (nbytes == 0) + return 0; + + lock_kernel(); + + add_wait_queue(&vcp->vc_waitq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + while (list_empty(&vcp->vc_pending)) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + } + + set_current_state(TASK_RUNNING); + remove_wait_queue(&vcp->vc_waitq, &wait); + + if (retval) + goto out; + + req = list_entry(vcp->vc_pending.next, struct upc_req,uc_chain); + list_del(&req->uc_chain); + + /* Move the input args into userspace */ + count = req->uc_inSize; + if (nbytes < req->uc_inSize) { + printk ("psdev_read: Venus read %ld bytes of %d in message\n", + (long)nbytes, req->uc_inSize); + count = nbytes; + } + + if (copy_to_user(buf, req->uc_data, count)) + retval = -EFAULT; + + /* If request was not a signal, enqueue and don't free */ + if (!(req->uc_flags & REQ_ASYNC)) { + req->uc_flags |= REQ_READ; + list_add(&(req->uc_chain), vcp->vc_processing.prev); + goto out; + } + + CODA_FREE(req->uc_data, sizeof(struct coda_in_hdr)); + upc_free(req); +out: + unlock_kernel(); + return (count ? count : retval); +} + +static int coda_psdev_open(struct inode * inode, struct file * file) +{ + struct venus_comm *vcp; + int idx; + + lock_kernel(); + idx = iminor(inode); + if(idx >= MAX_CODADEVS) { + unlock_kernel(); + return -ENODEV; + } + + vcp = &coda_comms[idx]; + if(vcp->vc_inuse) { + unlock_kernel(); + return -EBUSY; + } + + if (!vcp->vc_inuse++) { + INIT_LIST_HEAD(&vcp->vc_pending); + INIT_LIST_HEAD(&vcp->vc_processing); + init_waitqueue_head(&vcp->vc_waitq); + vcp->vc_sb = NULL; + vcp->vc_seq = 0; + } + + file->private_data = vcp; + + unlock_kernel(); + return 0; +} + + +static int coda_psdev_release(struct inode * inode, struct file * file) +{ + struct venus_comm *vcp = (struct venus_comm *) file->private_data; + struct upc_req *req, *tmp; + + lock_kernel(); + if ( !vcp->vc_inuse ) { + unlock_kernel(); + printk("psdev_release: Not open.\n"); + return -1; + } + + if (--vcp->vc_inuse) { + unlock_kernel(); + return 0; + } + + /* Wakeup clients so they can return. */ + list_for_each_entry_safe(req, tmp, &vcp->vc_pending, uc_chain) { + /* Async requests need to be freed here */ + if (req->uc_flags & REQ_ASYNC) { + CODA_FREE(req->uc_data, sizeof(struct coda_in_hdr)); + upc_free(req); + continue; + } + req->uc_flags |= REQ_ABORT; + wake_up(&req->uc_sleep); + } + + list_for_each_entry(req, &vcp->vc_processing, uc_chain) { + req->uc_flags |= REQ_ABORT; + wake_up(&req->uc_sleep); + } + + unlock_kernel(); + return 0; +} + + +static struct file_operations coda_psdev_fops = { + .owner = THIS_MODULE, + .read = coda_psdev_read, + .write = coda_psdev_write, + .poll = coda_psdev_poll, + .ioctl = coda_psdev_ioctl, + .open = coda_psdev_open, + .release = coda_psdev_release, +}; + +static int init_coda_psdev(void) +{ + int i, err = 0; + if (register_chrdev(CODA_PSDEV_MAJOR, "coda", &coda_psdev_fops)) { + printk(KERN_ERR "coda_psdev: unable to get major %d\n", + CODA_PSDEV_MAJOR); + return -EIO; + } + coda_psdev_class = class_simple_create(THIS_MODULE, "coda"); + if (IS_ERR(coda_psdev_class)) { + err = PTR_ERR(coda_psdev_class); + goto out_chrdev; + } + devfs_mk_dir ("coda"); + for (i = 0; i < MAX_CODADEVS; i++) { + class_simple_device_add(coda_psdev_class, MKDEV(CODA_PSDEV_MAJOR,i), + NULL, "cfs%d", i); + err = devfs_mk_cdev(MKDEV(CODA_PSDEV_MAJOR, i), + S_IFCHR|S_IRUSR|S_IWUSR, "coda/%d", i); + if (err) + goto out_class; + } + coda_sysctl_init(); + goto out; + +out_class: + for (i = 0; i < MAX_CODADEVS; i++) + class_simple_device_remove(MKDEV(CODA_PSDEV_MAJOR, i)); + class_simple_destroy(coda_psdev_class); +out_chrdev: + unregister_chrdev(CODA_PSDEV_MAJOR, "coda"); +out: + return err; +} + + +MODULE_AUTHOR("Peter J. Braam <braam@cs.cmu.edu>"); +MODULE_LICENSE("GPL"); + +extern int coda_init_inodecache(void); +extern void coda_destroy_inodecache(void); +static int __init init_coda(void) +{ + int status; + int i; + printk(KERN_INFO "Coda Kernel/Venus communications, " +#ifdef CONFIG_CODA_FS_OLD_API + "v5.3.20" +#else + "v6.0.0" +#endif + ", coda@cs.cmu.edu\n"); + + status = coda_init_inodecache(); + if (status) + goto out2; + status = init_coda_psdev(); + if ( status ) { + printk("Problem (%d) in init_coda_psdev\n", status); + goto out1; + } + + status = register_filesystem(&coda_fs_type); + if (status) { + printk("coda: failed to register filesystem!\n"); + goto out; + } + return 0; +out: + for (i = 0; i < MAX_CODADEVS; i++) { + class_simple_device_remove(MKDEV(CODA_PSDEV_MAJOR, i)); + devfs_remove("coda/%d", i); + } + class_simple_destroy(coda_psdev_class); + devfs_remove("coda"); + unregister_chrdev(CODA_PSDEV_MAJOR, "coda"); + coda_sysctl_clean(); +out1: + coda_destroy_inodecache(); +out2: + return status; +} + +static void __exit exit_coda(void) +{ + int err, i; + + err = unregister_filesystem(&coda_fs_type); + if ( err != 0 ) { + printk("coda: failed to unregister filesystem\n"); + } + for (i = 0; i < MAX_CODADEVS; i++) { + class_simple_device_remove(MKDEV(CODA_PSDEV_MAJOR, i)); + devfs_remove("coda/%d", i); + } + class_simple_destroy(coda_psdev_class); + devfs_remove("coda"); + unregister_chrdev(CODA_PSDEV_MAJOR, "coda"); + coda_sysctl_clean(); + coda_destroy_inodecache(); +} + +module_init(init_coda); +module_exit(exit_coda); + diff --git a/fs/coda/symlink.c b/fs/coda/symlink.c new file mode 100644 index 000000000000..b35e5bbd9c99 --- /dev/null +++ b/fs/coda/symlink.c @@ -0,0 +1,55 @@ +/* + * Symlink inode 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/time.h> +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/pagemap.h> +#include <linux/smp_lock.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_psdev.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_proc.h> + +static int coda_symlink_filler(struct file *file, struct page *page) +{ + struct inode *inode = page->mapping->host; + int error; + struct coda_inode_info *cii; + unsigned int len = PAGE_SIZE; + char *p = kmap(page); + + lock_kernel(); + cii = ITOC(inode); + coda_vfs_stat.follow_link++; + + error = venus_readlink(inode->i_sb, &cii->c_fid, p, &len); + unlock_kernel(); + if (error) + goto fail; + SetPageUptodate(page); + kunmap(page); + unlock_page(page); + return 0; + +fail: + SetPageError(page); + kunmap(page); + unlock_page(page); + return error; +} + +struct address_space_operations coda_symlink_aops = { + .readpage = coda_symlink_filler, +}; diff --git a/fs/coda/sysctl.c b/fs/coda/sysctl.c new file mode 100644 index 000000000000..f0b10757288f --- /dev/null +++ b/fs/coda/sysctl.c @@ -0,0 +1,254 @@ +/* + * Sysctl 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). + * + * CODA operation statistics + * (c) March, 1998 Zhanyong Wan <zhanyong.wan@yale.edu> + * + */ + +#include <linux/config.h> +#include <linux/time.h> +#include <linux/mm.h> +#include <linux/sysctl.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/ctype.h> +#include <linux/bitops.h> +#include <asm/uaccess.h> +#include <linux/utsname.h> +#include <linux/module.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_psdev.h> +#include <linux/coda_cache.h> +#include <linux/coda_proc.h> + +static struct ctl_table_header *fs_table_header; + +#define FS_CODA 1 /* Coda file system */ + +#define CODA_TIMEOUT 3 /* timeout on upcalls to become intrble */ +#define CODA_HARD 5 /* mount type "hard" or "soft" */ +#define CODA_VFS 6 /* vfs statistics */ +#define CODA_CACHE_INV 9 /* cache invalidation statistics */ +#define CODA_FAKE_STATFS 10 /* don't query venus for actual cache usage */ + +struct coda_vfs_stats coda_vfs_stat; +static struct coda_cache_inv_stats coda_cache_inv_stat; + +static void reset_coda_vfs_stats( void ) +{ + memset( &coda_vfs_stat, 0, sizeof( coda_vfs_stat ) ); +} + +static void reset_coda_cache_inv_stats( void ) +{ + memset( &coda_cache_inv_stat, 0, sizeof( coda_cache_inv_stat ) ); +} + +static int do_reset_coda_vfs_stats( ctl_table * table, int write, + struct file * filp, void __user * buffer, + size_t * lenp, loff_t * ppos ) +{ + if ( write ) { + reset_coda_vfs_stats(); + + *ppos += *lenp; + } else { + *lenp = 0; + } + + return 0; +} + +static int do_reset_coda_cache_inv_stats( ctl_table * table, int write, + struct file * filp, + void __user * buffer, + size_t * lenp, loff_t * ppos ) +{ + if ( write ) { + reset_coda_cache_inv_stats(); + + *ppos += *lenp; + } else { + *lenp = 0; + } + + return 0; +} + +static int coda_vfs_stats_get_info( char * buffer, char ** start, + off_t offset, int length) +{ + int len=0; + off_t begin; + struct coda_vfs_stats * ps = & coda_vfs_stat; + + /* this works as long as we are below 1024 characters! */ + len += sprintf( buffer, + "Coda VFS statistics\n" + "===================\n\n" + "File Operations:\n" + "\topen\t\t%9d\n" + "\tflush\t\t%9d\n" + "\trelease\t\t%9d\n" + "\tfsync\t\t%9d\n\n" + "Dir Operations:\n" + "\treaddir\t\t%9d\n\n" + "Inode Operations\n" + "\tcreate\t\t%9d\n" + "\tlookup\t\t%9d\n" + "\tlink\t\t%9d\n" + "\tunlink\t\t%9d\n" + "\tsymlink\t\t%9d\n" + "\tmkdir\t\t%9d\n" + "\trmdir\t\t%9d\n" + "\trename\t\t%9d\n" + "\tpermission\t%9d\n", + + /* file operations */ + ps->open, + ps->flush, + ps->release, + ps->fsync, + + /* dir operations */ + ps->readdir, + + /* inode operations */ + ps->create, + ps->lookup, + ps->link, + ps->unlink, + ps->symlink, + ps->mkdir, + ps->rmdir, + ps->rename, + ps->permission); + + begin = offset; + *start = buffer + begin; + len -= begin; + + if ( len > length ) + len = length; + if ( len < 0 ) + len = 0; + + return len; +} + +static int coda_cache_inv_stats_get_info( char * buffer, char ** start, + off_t offset, int length) +{ + int len=0; + off_t begin; + struct coda_cache_inv_stats * ps = & coda_cache_inv_stat; + + /* this works as long as we are below 1024 characters! */ + len += sprintf( buffer, + "Coda cache invalidation statistics\n" + "==================================\n\n" + "flush\t\t%9d\n" + "purge user\t%9d\n" + "zap_dir\t\t%9d\n" + "zap_file\t%9d\n" + "zap_vnode\t%9d\n" + "purge_fid\t%9d\n" + "replace\t\t%9d\n", + ps->flush, + ps->purge_user, + ps->zap_dir, + ps->zap_file, + ps->zap_vnode, + ps->purge_fid, + ps->replace ); + + begin = offset; + *start = buffer + begin; + len -= begin; + + if ( len > length ) + len = length; + if ( len < 0 ) + len = 0; + + return len; +} + +static ctl_table coda_table[] = { + {CODA_TIMEOUT, "timeout", &coda_timeout, sizeof(int), 0644, NULL, &proc_dointvec}, + {CODA_HARD, "hard", &coda_hard, sizeof(int), 0644, NULL, &proc_dointvec}, + {CODA_VFS, "vfs_stats", NULL, 0, 0644, NULL, &do_reset_coda_vfs_stats}, + {CODA_CACHE_INV, "cache_inv_stats", NULL, 0, 0644, NULL, &do_reset_coda_cache_inv_stats}, + {CODA_FAKE_STATFS, "fake_statfs", &coda_fake_statfs, sizeof(int), 0600, NULL, &proc_dointvec}, + { 0 } +}; + +static ctl_table fs_table[] = { + {FS_CODA, "coda", NULL, 0, 0555, coda_table}, + {0} +}; + + +#ifdef CONFIG_PROC_FS + +/* + target directory structure: + /proc/fs (see linux/fs/proc/root.c) + /proc/fs/coda + /proc/fs/coda/{vfs_stats, + +*/ + +static struct proc_dir_entry* proc_fs_coda; + +#endif + +#define coda_proc_create(name,get_info) \ + create_proc_info_entry(name, 0, proc_fs_coda, get_info) + +void coda_sysctl_init(void) +{ + reset_coda_vfs_stats(); + reset_coda_cache_inv_stats(); + +#ifdef CONFIG_PROC_FS + proc_fs_coda = proc_mkdir("coda", proc_root_fs); + if (proc_fs_coda) { + proc_fs_coda->owner = THIS_MODULE; + coda_proc_create("vfs_stats", coda_vfs_stats_get_info); + coda_proc_create("cache_inv_stats", coda_cache_inv_stats_get_info); + } +#endif + +#ifdef CONFIG_SYSCTL + if ( !fs_table_header ) + fs_table_header = register_sysctl_table(fs_table, 0); +#endif +} + +void coda_sysctl_clean(void) +{ + +#ifdef CONFIG_SYSCTL + if ( fs_table_header ) { + unregister_sysctl_table(fs_table_header); + fs_table_header = NULL; + } +#endif + +#ifdef CONFIG_PROC_FS + remove_proc_entry("cache_inv_stats", proc_fs_coda); + remove_proc_entry("vfs_stats", proc_fs_coda); + remove_proc_entry("coda", proc_root_fs); +#endif +} diff --git a/fs/coda/upcall.c b/fs/coda/upcall.c new file mode 100644 index 000000000000..1bae99650a91 --- /dev/null +++ b/fs/coda/upcall.c @@ -0,0 +1,914 @@ +/* + * Mostly platform independent upcall operations to Venus: + * -- upcalls + * -- upcall routines + * + * Linux 2.0 version + * Copyright (C) 1996 Peter J. Braam <braam@maths.ox.ac.uk>, + * Michael Callahan <callahan@maths.ox.ac.uk> + * + * Redone for Linux 2.1 + * Copyright (C) 1997 Carnegie Mellon University + * + * Carnegie Mellon University encourages users of this code to contribute + * improvements to the Coda project. Contact Peter Braam <coda@cs.cmu.edu>. + */ + +#include <asm/system.h> +#include <linux/signal.h> + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/time.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <asm/uaccess.h> +#include <linux/vmalloc.h> +#include <linux/vfs.h> + +#include <linux/coda.h> +#include <linux/coda_linux.h> +#include <linux/coda_psdev.h> +#include <linux/coda_fs_i.h> +#include <linux/coda_cache.h> +#include <linux/coda_proc.h> + +#define upc_alloc() kmalloc(sizeof(struct upc_req), GFP_KERNEL) +#define upc_free(r) kfree(r) + +static int coda_upcall(struct coda_sb_info *mntinfo, int inSize, int *outSize, + union inputArgs *buffer); + +static void *alloc_upcall(int opcode, int size) +{ + union inputArgs *inp; + + CODA_ALLOC(inp, union inputArgs *, size); + if (!inp) + return ERR_PTR(-ENOMEM); + + inp->ih.opcode = opcode; + inp->ih.pid = current->pid; + inp->ih.pgid = process_group(current); +#ifdef CONFIG_CODA_FS_OLD_API + memset(&inp->ih.cred, 0, sizeof(struct coda_cred)); + inp->ih.cred.cr_fsuid = current->fsuid; +#else + inp->ih.uid = current->fsuid; +#endif + return (void*)inp; +} + +#define UPARG(op)\ +do {\ + inp = (union inputArgs *)alloc_upcall(op, insize); \ + if (IS_ERR(inp)) { return PTR_ERR(inp); }\ + outp = (union outputArgs *)(inp); \ + outsize = insize; \ +} while (0) + +#define INSIZE(tag) sizeof(struct coda_ ## tag ## _in) +#define OUTSIZE(tag) sizeof(struct coda_ ## tag ## _out) +#define SIZE(tag) max_t(unsigned int, INSIZE(tag), OUTSIZE(tag)) + + +/* the upcalls */ +int venus_rootfid(struct super_block *sb, struct CodaFid *fidp) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + + insize = SIZE(root); + UPARG(CODA_ROOT); + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + if (error) { + printk("coda_get_rootfid: error %d\n", error); + } else { + *fidp = outp->coda_root.VFid; + } + + CODA_FREE(inp, insize); + return error; +} + +int venus_getattr(struct super_block *sb, struct CodaFid *fid, + struct coda_vattr *attr) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + + insize = SIZE(getattr); + UPARG(CODA_GETATTR); + inp->coda_getattr.VFid = *fid; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + *attr = outp->coda_getattr.attr; + + CODA_FREE(inp, insize); + return error; +} + +int venus_setattr(struct super_block *sb, struct CodaFid *fid, + struct coda_vattr *vattr) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + + insize = SIZE(setattr); + UPARG(CODA_SETATTR); + + inp->coda_setattr.VFid = *fid; + inp->coda_setattr.attr = *vattr; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_lookup(struct super_block *sb, struct CodaFid *fid, + const char *name, int length, int * type, + struct CodaFid *resfid) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + int offset; + + offset = INSIZE(lookup); + insize = max_t(unsigned int, offset + length +1, OUTSIZE(lookup)); + UPARG(CODA_LOOKUP); + + inp->coda_lookup.VFid = *fid; + inp->coda_lookup.name = offset; + inp->coda_lookup.flags = CLU_CASE_SENSITIVE; + /* send Venus a null terminated string */ + memcpy((char *)(inp) + offset, name, length); + *((char *)inp + offset + length) = '\0'; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + *resfid = outp->coda_lookup.VFid; + *type = outp->coda_lookup.vtype; + + CODA_FREE(inp, insize); + return error; +} + +int venus_store(struct super_block *sb, struct CodaFid *fid, int flags, + vuid_t uid) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; +#ifdef CONFIG_CODA_FS_OLD_API + struct coda_cred cred = { 0, }; + cred.cr_fsuid = uid; +#endif + + insize = SIZE(store); + UPARG(CODA_STORE); + +#ifdef CONFIG_CODA_FS_OLD_API + memcpy(&(inp->ih.cred), &cred, sizeof(cred)); +#else + inp->ih.uid = uid; +#endif + + inp->coda_store.VFid = *fid; + inp->coda_store.flags = flags; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_release(struct super_block *sb, struct CodaFid *fid, int flags) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + + insize = SIZE(release); + UPARG(CODA_RELEASE); + + inp->coda_release.VFid = *fid; + inp->coda_release.flags = flags; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_close(struct super_block *sb, struct CodaFid *fid, int flags, + vuid_t uid) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; +#ifdef CONFIG_CODA_FS_OLD_API + struct coda_cred cred = { 0, }; + cred.cr_fsuid = uid; +#endif + + insize = SIZE(release); + UPARG(CODA_CLOSE); + +#ifdef CONFIG_CODA_FS_OLD_API + memcpy(&(inp->ih.cred), &cred, sizeof(cred)); +#else + inp->ih.uid = uid; +#endif + + inp->coda_close.VFid = *fid; + inp->coda_close.flags = flags; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_open(struct super_block *sb, struct CodaFid *fid, + int flags, struct file **fh) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + + insize = SIZE(open_by_fd); + UPARG(CODA_OPEN_BY_FD); + + inp->coda_open.VFid = *fid; + inp->coda_open.flags = flags; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + *fh = outp->coda_open_by_fd.fh; + + CODA_FREE(inp, insize); + return error; +} + +int venus_mkdir(struct super_block *sb, struct CodaFid *dirfid, + const char *name, int length, + struct CodaFid *newfid, struct coda_vattr *attrs) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + int offset; + + offset = INSIZE(mkdir); + insize = max_t(unsigned int, offset + length + 1, OUTSIZE(mkdir)); + UPARG(CODA_MKDIR); + + inp->coda_mkdir.VFid = *dirfid; + inp->coda_mkdir.attr = *attrs; + inp->coda_mkdir.name = offset; + /* Venus must get null terminated string */ + memcpy((char *)(inp) + offset, name, length); + *((char *)inp + offset + length) = '\0'; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + *attrs = outp->coda_mkdir.attr; + *newfid = outp->coda_mkdir.VFid; + + CODA_FREE(inp, insize); + return error; +} + + +int venus_rename(struct super_block *sb, struct CodaFid *old_fid, + struct CodaFid *new_fid, size_t old_length, + size_t new_length, const char *old_name, + const char *new_name) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + int offset, s; + + offset = INSIZE(rename); + insize = max_t(unsigned int, offset + new_length + old_length + 8, + OUTSIZE(rename)); + UPARG(CODA_RENAME); + + inp->coda_rename.sourceFid = *old_fid; + inp->coda_rename.destFid = *new_fid; + inp->coda_rename.srcname = offset; + + /* Venus must receive an null terminated string */ + s = ( old_length & ~0x3) +4; /* round up to word boundary */ + memcpy((char *)(inp) + offset, old_name, old_length); + *((char *)inp + offset + old_length) = '\0'; + + /* another null terminated string for Venus */ + offset += s; + inp->coda_rename.destname = offset; + s = ( new_length & ~0x3) +4; /* round up to word boundary */ + memcpy((char *)(inp) + offset, new_name, new_length); + *((char *)inp + offset + new_length) = '\0'; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_create(struct super_block *sb, struct CodaFid *dirfid, + const char *name, int length, int excl, int mode, + struct CodaFid *newfid, struct coda_vattr *attrs) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + int offset; + + offset = INSIZE(create); + insize = max_t(unsigned int, offset + length + 1, OUTSIZE(create)); + UPARG(CODA_CREATE); + + inp->coda_create.VFid = *dirfid; + inp->coda_create.attr.va_mode = mode; + inp->coda_create.excl = excl; + inp->coda_create.mode = mode; + inp->coda_create.name = offset; + + /* Venus must get null terminated string */ + memcpy((char *)(inp) + offset, name, length); + *((char *)inp + offset + length) = '\0'; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + *attrs = outp->coda_create.attr; + *newfid = outp->coda_create.VFid; + + CODA_FREE(inp, insize); + return error; +} + +int venus_rmdir(struct super_block *sb, struct CodaFid *dirfid, + const char *name, int length) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + int offset; + + offset = INSIZE(rmdir); + insize = max_t(unsigned int, offset + length + 1, OUTSIZE(rmdir)); + UPARG(CODA_RMDIR); + + inp->coda_rmdir.VFid = *dirfid; + inp->coda_rmdir.name = offset; + memcpy((char *)(inp) + offset, name, length); + *((char *)inp + offset + length) = '\0'; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_remove(struct super_block *sb, struct CodaFid *dirfid, + const char *name, int length) +{ + union inputArgs *inp; + union outputArgs *outp; + int error=0, insize, outsize, offset; + + offset = INSIZE(remove); + insize = max_t(unsigned int, offset + length + 1, OUTSIZE(remove)); + UPARG(CODA_REMOVE); + + inp->coda_remove.VFid = *dirfid; + inp->coda_remove.name = offset; + memcpy((char *)(inp) + offset, name, length); + *((char *)inp + offset + length) = '\0'; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_readlink(struct super_block *sb, struct CodaFid *fid, + char *buffer, int *length) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + int retlen; + char *result; + + insize = max_t(unsigned int, + INSIZE(readlink), OUTSIZE(readlink)+ *length + 1); + UPARG(CODA_READLINK); + + inp->coda_readlink.VFid = *fid; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + if (! error) { + retlen = outp->coda_readlink.count; + if ( retlen > *length ) + retlen = *length; + *length = retlen; + result = (char *)outp + (long)outp->coda_readlink.data; + memcpy(buffer, result, retlen); + *(buffer + retlen) = '\0'; + } + + CODA_FREE(inp, insize); + return error; +} + + + +int venus_link(struct super_block *sb, struct CodaFid *fid, + struct CodaFid *dirfid, const char *name, int len ) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + int offset; + + offset = INSIZE(link); + insize = max_t(unsigned int, offset + len + 1, OUTSIZE(link)); + UPARG(CODA_LINK); + + inp->coda_link.sourceFid = *fid; + inp->coda_link.destFid = *dirfid; + inp->coda_link.tname = offset; + + /* make sure strings are null terminated */ + memcpy((char *)(inp) + offset, name, len); + *((char *)inp + offset + len) = '\0'; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_symlink(struct super_block *sb, struct CodaFid *fid, + const char *name, int len, + const char *symname, int symlen) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + int offset, s; + + offset = INSIZE(symlink); + insize = max_t(unsigned int, offset + len + symlen + 8, OUTSIZE(symlink)); + UPARG(CODA_SYMLINK); + + /* inp->coda_symlink.attr = *tva; XXXXXX */ + inp->coda_symlink.VFid = *fid; + + /* Round up to word boundary and null terminate */ + inp->coda_symlink.srcname = offset; + s = ( symlen & ~0x3 ) + 4; + memcpy((char *)(inp) + offset, symname, symlen); + *((char *)inp + offset + symlen) = '\0'; + + /* Round up to word boundary and null terminate */ + offset += s; + inp->coda_symlink.tname = offset; + s = (len & ~0x3) + 4; + memcpy((char *)(inp) + offset, name, len); + *((char *)inp + offset + len) = '\0'; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_fsync(struct super_block *sb, struct CodaFid *fid) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + + insize=SIZE(fsync); + UPARG(CODA_FSYNC); + + inp->coda_fsync.VFid = *fid; + error = coda_upcall(coda_sbp(sb), sizeof(union inputArgs), + &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + +int venus_access(struct super_block *sb, struct CodaFid *fid, int mask) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + + insize = SIZE(access); + UPARG(CODA_ACCESS); + + inp->coda_access.VFid = *fid; + inp->coda_access.flags = mask; + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + CODA_FREE(inp, insize); + return error; +} + + +int venus_pioctl(struct super_block *sb, struct CodaFid *fid, + unsigned int cmd, struct PioctlData *data) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + int iocsize; + + insize = VC_MAXMSGSIZE; + UPARG(CODA_IOCTL); + + /* build packet for Venus */ + if (data->vi.in_size > VC_MAXDATASIZE) { + error = -EINVAL; + goto exit; + } + + if (data->vi.out_size > VC_MAXDATASIZE) { + error = -EINVAL; + goto exit; + } + + inp->coda_ioctl.VFid = *fid; + + /* the cmd field was mutated by increasing its size field to + * reflect the path and follow args. We need to subtract that + * out before sending the command to Venus. */ + inp->coda_ioctl.cmd = (cmd & ~(PIOCPARM_MASK << 16)); + iocsize = ((cmd >> 16) & PIOCPARM_MASK) - sizeof(char *) - sizeof(int); + inp->coda_ioctl.cmd |= (iocsize & PIOCPARM_MASK) << 16; + + /* in->coda_ioctl.rwflag = flag; */ + inp->coda_ioctl.len = data->vi.in_size; + inp->coda_ioctl.data = (char *)(INSIZE(ioctl)); + + /* get the data out of user space */ + if ( copy_from_user((char*)inp + (long)inp->coda_ioctl.data, + data->vi.in, data->vi.in_size) ) { + error = -EINVAL; + goto exit; + } + + error = coda_upcall(coda_sbp(sb), SIZE(ioctl) + data->vi.in_size, + &outsize, inp); + + if (error) { + printk("coda_pioctl: Venus returns: %d for %s\n", + error, coda_f2s(fid)); + goto exit; + } + + if (outsize < (long)outp->coda_ioctl.data + outp->coda_ioctl.len) { + error = -EINVAL; + goto exit; + } + + /* Copy out the OUT buffer. */ + if (outp->coda_ioctl.len > data->vi.out_size) { + error = -EINVAL; + goto exit; + } + + /* Copy out the OUT buffer. */ + if (copy_to_user(data->vi.out, + (char *)outp + (long)outp->coda_ioctl.data, + outp->coda_ioctl.len)) { + error = -EFAULT; + goto exit; + } + + exit: + CODA_FREE(inp, insize); + return error; +} + +int venus_statfs(struct super_block *sb, struct kstatfs *sfs) +{ + union inputArgs *inp; + union outputArgs *outp; + int insize, outsize, error; + + insize = max_t(unsigned int, INSIZE(statfs), OUTSIZE(statfs)); + UPARG(CODA_STATFS); + + error = coda_upcall(coda_sbp(sb), insize, &outsize, inp); + + if (!error) { + sfs->f_blocks = outp->coda_statfs.stat.f_blocks; + sfs->f_bfree = outp->coda_statfs.stat.f_bfree; + sfs->f_bavail = outp->coda_statfs.stat.f_bavail; + sfs->f_files = outp->coda_statfs.stat.f_files; + sfs->f_ffree = outp->coda_statfs.stat.f_ffree; + } else { + printk("coda_statfs: Venus returns: %d\n", error); + } + + CODA_FREE(inp, insize); + return error; +} + +/* + * coda_upcall and coda_downcall routines. + * + */ + +static inline void coda_waitfor_upcall(struct upc_req *vmp, + struct venus_comm *vcommp) +{ + DECLARE_WAITQUEUE(wait, current); + + vmp->uc_posttime = jiffies; + + add_wait_queue(&vmp->uc_sleep, &wait); + for (;;) { + if ( !coda_hard && vmp->uc_opcode != CODA_CLOSE ) + set_current_state(TASK_INTERRUPTIBLE); + else + set_current_state(TASK_UNINTERRUPTIBLE); + + /* venus died */ + if ( !vcommp->vc_inuse ) + break; + + /* got a reply */ + if ( vmp->uc_flags & ( REQ_WRITE | REQ_ABORT ) ) + break; + + if ( !coda_hard && vmp->uc_opcode != CODA_CLOSE && signal_pending(current) ) { + /* if this process really wants to die, let it go */ + if ( sigismember(&(current->pending.signal), SIGKILL) || + sigismember(&(current->pending.signal), SIGINT) ) + break; + /* signal is present: after timeout always return + really smart idea, probably useless ... */ + if ( jiffies - vmp->uc_posttime > coda_timeout * HZ ) + break; + } + schedule(); + } + remove_wait_queue(&vmp->uc_sleep, &wait); + set_current_state(TASK_RUNNING); + + return; +} + + +/* + * coda_upcall will return an error in the case of + * failed communication with Venus _or_ will peek at Venus + * reply and return Venus' error. + * + * As venus has 2 types of errors, normal errors (positive) and internal + * errors (negative), normal errors are negated, while internal errors + * are all mapped to -EINTR, while showing a nice warning message. (jh) + * + */ +static int coda_upcall(struct coda_sb_info *sbi, + int inSize, int *outSize, + union inputArgs *buffer) +{ + struct venus_comm *vcommp; + union outputArgs *out; + struct upc_req *req; + int error = 0; + + vcommp = sbi->sbi_vcomm; + if ( !vcommp->vc_inuse ) { + printk("No pseudo device in upcall comms at %p\n", vcommp); + return -ENXIO; + } + + /* Format the request message. */ + req = upc_alloc(); + if (!req) { + printk("Failed to allocate upc_req structure\n"); + return -ENOMEM; + } + req->uc_data = (void *)buffer; + req->uc_flags = 0; + req->uc_inSize = inSize; + req->uc_outSize = *outSize ? *outSize : inSize; + req->uc_opcode = ((union inputArgs *)buffer)->ih.opcode; + req->uc_unique = ++vcommp->vc_seq; + init_waitqueue_head(&req->uc_sleep); + + /* Fill in the common input args. */ + ((union inputArgs *)buffer)->ih.unique = req->uc_unique; + + /* Append msg to pending queue and poke Venus. */ + list_add(&(req->uc_chain), vcommp->vc_pending.prev); + + wake_up_interruptible(&vcommp->vc_waitq); + /* We can be interrupted while we wait for Venus to process + * our request. If the interrupt occurs before Venus has read + * the request, we dequeue and return. If it occurs after the + * read but before the reply, we dequeue, send a signal + * message, and return. If it occurs after the reply we ignore + * it. In no case do we want to restart the syscall. If it + * was interrupted by a venus shutdown (psdev_close), return + * ENODEV. */ + + /* Go to sleep. Wake up on signals only after the timeout. */ + coda_waitfor_upcall(req, vcommp); + + if (vcommp->vc_inuse) { /* i.e. Venus is still alive */ + /* Op went through, interrupt or not... */ + if (req->uc_flags & REQ_WRITE) { + out = (union outputArgs *)req->uc_data; + /* here we map positive Venus errors to kernel errors */ + error = -out->oh.result; + *outSize = req->uc_outSize; + goto exit; + } + if ( !(req->uc_flags & REQ_READ) && signal_pending(current)) { + /* Interrupted before venus read it. */ + list_del(&(req->uc_chain)); + /* perhaps the best way to convince the app to + give up? */ + error = -EINTR; + goto exit; + } + if ( (req->uc_flags & REQ_READ) && signal_pending(current) ) { + /* interrupted after Venus did its read, send signal */ + union inputArgs *sig_inputArgs; + struct upc_req *sig_req; + + list_del(&(req->uc_chain)); + error = -ENOMEM; + sig_req = upc_alloc(); + if (!sig_req) goto exit; + + CODA_ALLOC((sig_req->uc_data), char *, sizeof(struct coda_in_hdr)); + if (!sig_req->uc_data) { + upc_free(sig_req); + goto exit; + } + + error = -EINTR; + sig_inputArgs = (union inputArgs *)sig_req->uc_data; + sig_inputArgs->ih.opcode = CODA_SIGNAL; + sig_inputArgs->ih.unique = req->uc_unique; + + sig_req->uc_flags = REQ_ASYNC; + sig_req->uc_opcode = sig_inputArgs->ih.opcode; + sig_req->uc_unique = sig_inputArgs->ih.unique; + sig_req->uc_inSize = sizeof(struct coda_in_hdr); + sig_req->uc_outSize = sizeof(struct coda_in_hdr); + + /* insert at head of queue! */ + list_add(&(sig_req->uc_chain), &vcommp->vc_pending); + wake_up_interruptible(&vcommp->vc_waitq); + } else { + printk("Coda: Strange interruption..\n"); + error = -EINTR; + } + } else { /* If venus died i.e. !VC_OPEN(vcommp) */ + printk("coda_upcall: Venus dead on (op,un) (%d.%d) flags %d\n", + req->uc_opcode, req->uc_unique, req->uc_flags); + error = -ENODEV; + } + + exit: + upc_free(req); + return error; +} + +/* + The statements below are part of the Coda opportunistic + programming -- taken from the Mach/BSD kernel code for Coda. + You don't get correct semantics by stating what needs to be + done without guaranteeing the invariants needed for it to happen. + When will be have time to find out what exactly is going on? (pjb) +*/ + + +/* + * There are 7 cases where cache invalidations occur. The semantics + * of each is listed here: + * + * CODA_FLUSH -- flush all entries from the name cache and the cnode cache. + * CODA_PURGEUSER -- flush all entries from the name cache for a specific user + * This call is a result of token expiration. + * + * The next arise as the result of callbacks on a file or directory. + * CODA_ZAPFILE -- flush the cached attributes for a file. + + * CODA_ZAPDIR -- flush the attributes for the dir and + * force a new lookup for all the children + of this dir. + + * + * The next is a result of Venus detecting an inconsistent file. + * CODA_PURGEFID -- flush the attribute for the file + * purge it and its children from the dcache + * + * The last allows Venus to replace local fids with global ones + * during reintegration. + * + * CODA_REPLACE -- replace one CodaFid with another throughout the name cache */ + +int coda_downcall(int opcode, union outputArgs * out, struct super_block *sb) +{ + /* Handle invalidation requests. */ + if ( !sb || !sb->s_root || !sb->s_root->d_inode) + return 0; + + switch (opcode) { + + case CODA_FLUSH : { + coda_cache_clear_all(sb); + shrink_dcache_sb(sb); + coda_flag_inode(sb->s_root->d_inode, C_FLUSH); + return(0); + } + + case CODA_PURGEUSER : { + coda_cache_clear_all(sb); + return(0); + } + + case CODA_ZAPDIR : { + struct inode *inode; + struct CodaFid *fid = &out->coda_zapdir.CodaFid; + + inode = coda_fid_to_inode(fid, sb); + if (inode) { + coda_flag_inode_children(inode, C_PURGE); + coda_flag_inode(inode, C_VATTR); + iput(inode); + } + + return(0); + } + + case CODA_ZAPFILE : { + struct inode *inode; + struct CodaFid *fid = &out->coda_zapfile.CodaFid; + inode = coda_fid_to_inode(fid, sb); + if ( inode ) { + coda_flag_inode(inode, C_VATTR); + iput(inode); + } + return 0; + } + + case CODA_PURGEFID : { + struct inode *inode; + struct CodaFid *fid = &out->coda_purgefid.CodaFid; + inode = coda_fid_to_inode(fid, sb); + if ( inode ) { + coda_flag_inode_children(inode, C_PURGE); + + /* catch the dentries later if some are still busy */ + coda_flag_inode(inode, C_PURGE); + d_prune_aliases(inode); + + iput(inode); + } + return 0; + } + + case CODA_REPLACE : { + struct inode *inode; + struct CodaFid *oldfid = &out->coda_replace.OldFid; + struct CodaFid *newfid = &out->coda_replace.NewFid; + inode = coda_fid_to_inode(oldfid, sb); + if ( inode ) { + coda_replace_fid(inode, oldfid, newfid); + iput(inode); + } + return 0; + } + } + return 0; +} + |