Merge git://git.kernel.org/pub/scm/linux/kernel/git/sfrench/cifs-2.6
[safe/jmp/linux-2.6] / fs / coda / dir.c
index 71f2ea6..4bb9d0a 100644 (file)
@@ -25,7 +25,6 @@
 #include <linux/coda_psdev.h>
 #include <linux/coda_fs_i.h>
 #include <linux/coda_cache.h>
-#include <linux/coda_proc.h>
 
 #include "coda_int.h"
 
@@ -43,15 +42,15 @@ 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);
+static int coda_readdir(struct file *file, void *buf, 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);
+static int coda_venus_readdir(struct file *coda_file, void *buf,
+                             filldir_t filldir);
 
 /* same as fs/bad_inode.c */
 static int coda_return_EIO(void)
@@ -60,13 +59,13 @@ static int coda_return_EIO(void)
 }
 #define CODA_EIO_ERROR ((void *) (coda_return_EIO))
 
-static struct dentry_operations coda_dentry_operations =
+static const struct dentry_operations coda_dentry_operations =
 {
        .d_revalidate   = coda_dentry_revalidate,
        .d_delete       = coda_dentry_delete,
 };
 
-struct inode_operations coda_dir_inode_operations =
+const struct inode_operations coda_dir_inode_operations =
 {
        .create         = coda_create,
        .lookup         = coda_lookup,
@@ -87,7 +86,6 @@ const struct file_operations coda_dir_operations = {
        .read           = generic_read_dir,
        .readdir        = coda_readdir,
        .open           = coda_open,
-       .flush          = coda_flush,
        .release        = coda_release,
        .fsync          = coda_fsync,
 };
@@ -97,71 +95,61 @@ const struct file_operations coda_dir_operations = {
 /* 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 inode *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", 
+
+       if (length > CODA_MAXNAMLEN) {
+               printk(KERN_ERR "name too long: lookup, %s (%*s)\n",
                       coda_i2s(dir), (int)length, name);
                return ERR_PTR(-ENAMETOOLONG);
        }
 
+       /* control object, create inode on the fly */
+       if (coda_isroot(dir) && coda_iscontrol(name, length)) {
+               error = coda_cnode_makectl(&inode, dir->i_sb);
+               type = CODA_NOCACHE;
+               goto exit;
+       }
+
        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);
+       error = venus_lookup(dir->i_sb, coda_i2f(dir), name, length,
+                            &type, &resfid);
+       if (!error)
+               error = coda_cnode_make(&inode, &resfid, dir->i_sb);
 
-       res_inode = NULL;
-       if (!error) {
-               if (type & CODA_NOCACHE) {
-                       type &= (~CODA_NOCACHE);
-                       dropme = 1;
-               }
+       unlock_kernel();
 
-               error = coda_cnode_make(&res_inode, &resfid, dir->i_sb);
-               if (error) {
-                       unlock_kernel();
-                       return ERR_PTR(error);
-               }
-       } else if (error != -ENOENT) {
-               unlock_kernel();
+       if (error && error != -ENOENT)
                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;
+
+       if (inode && (type & CODA_NOCACHE))
+               coda_flag_inode(inode, C_VATTR | C_PURGE);
+
+       return d_splice_alias(inode, entry);
 }
 
 
-int coda_permission(struct inode *inode, int mask, struct nameidata *nd)
+int coda_permission(struct inode *inode, int mask)
 {
         int error = 0;
+
+       mask &= MAY_READ | MAY_WRITE | MAY_EXEC;
  
        if (!mask)
                return 0; 
 
-       lock_kernel();
+       if ((mask & MAY_EXEC) && !execute_ok(inode))
+               return -EACCES;
 
-       coda_vfs_stat.permission++;
+       lock_kernel();
 
        if (coda_cache_check(inode, mask))
                goto out; 
@@ -173,12 +161,11 @@ int coda_permission(struct inode *inode, int mask, struct nameidata *nd)
 
  out:
        unlock_kernel();
-
-        return error; 
+       return error;
 }
 
 
-static inline void coda_dir_changed(struct inode *dir, int link)
+static inline void coda_dir_update_mtime(struct inode *dir)
 {
 #ifdef REQUERY_VENUS_FOR_MTIME
        /* invalidate the directory cnode's attributes so we refetch the
@@ -186,12 +173,27 @@ static inline void coda_dir_changed(struct inode *dir, int link)
        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. */
+        * 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;
+}
+
+/* we have to wrap inc_nlink/drop_nlink because sometimes userspace uses a
+ * trick to fool GNU find's optimizations. If we can't be sure of the link
+ * (because of volume mount points) we set i_nlink to 1 which forces find
+ * to consider every child as a possible directory. We should also never
+ * see an increment or decrement for deleted directories where i_nlink == 0 */
+static inline void coda_dir_inc_nlink(struct inode *dir)
+{
+       if (dir->i_nlink >= 2)
+               inc_nlink(dir);
+}
+
+static inline void coda_dir_drop_nlink(struct inode *dir)
+{
+       if (dir->i_nlink > 2)
+               drop_nlink(dir);
 }
 
 /* creation routines: create, mknod, mkdir, link, symlink */
@@ -205,7 +207,6 @@ static int coda_create(struct inode *dir, struct dentry *de, int mode, struct na
        struct coda_vattr attrs;
 
        lock_kernel();
-       coda_vfs_stat.create++;
 
        if (coda_isroot(dir) && coda_iscontrol(name, length)) {
                unlock_kernel();
@@ -229,10 +230,10 @@ static int coda_create(struct inode *dir, struct dentry *de, int mode, struct na
        }
 
        /* invalidate the directory cnode's attributes */
-       coda_dir_changed(dir, 0);
+       coda_dir_update_mtime(dir);
        unlock_kernel();
        d_instantiate(de, inode);
-        return 0;
+       return 0;
 }
 
 static int coda_mkdir(struct inode *dir, struct dentry *de, int mode)
@@ -245,7 +246,6 @@ static int coda_mkdir(struct inode *dir, struct dentry *de, int mode)
        struct CodaFid newfid;
 
        lock_kernel();
-       coda_vfs_stat.mkdir++;
 
        if (coda_isroot(dir) && coda_iscontrol(name, len)) {
                unlock_kernel();
@@ -268,12 +268,13 @@ static int coda_mkdir(struct inode *dir, struct dentry *de, int mode)
                d_drop(de);
                return PTR_ERR(inode);
        }
-       
+
        /* invalidate the directory cnode's attributes */
-       coda_dir_changed(dir, 1);
+       coda_dir_inc_nlink(dir);
+       coda_dir_update_mtime(dir);
        unlock_kernel();
        d_instantiate(de, inode);
-        return 0;
+       return 0;
 }
 
 /* try to make de an entry in dir_inodde linked to source_de */ 
@@ -286,7 +287,6 @@ static int coda_link(struct dentry *source_de, struct inode *dir_inode,
        int error;
 
        lock_kernel();
-       coda_vfs_stat.link++;
 
        if (coda_isroot(dir_inode) && coda_iscontrol(name, len)) {
                unlock_kernel();
@@ -296,16 +296,16 @@ static int coda_link(struct dentry *source_de, struct inode *dir_inode,
        error = venus_link(dir_inode->i_sb, coda_i2f(inode),
                           coda_i2f(dir_inode), (const char *)name, len);
 
-       if (error) { 
+       if (error) {
                d_drop(de);
                goto out;
        }
 
-       coda_dir_changed(dir_inode, 0);
+       coda_dir_update_mtime(dir_inode);
        atomic_inc(&inode->i_count);
        d_instantiate(de, inode);
-       inode->i_nlink++;
-        
+       inc_nlink(inode);
+
 out:
        unlock_kernel();
        return(error);
@@ -318,10 +318,9 @@ static int coda_symlink(struct inode *dir_inode, struct dentry *de,
         const char *name = de->d_name.name;
        int len = de->d_name.len;
        int symlen;
-        int error=0;
-        
+       int error = 0;
+
        lock_kernel();
-       coda_vfs_stat.symlink++;
 
        if (coda_isroot(dir_inode) && coda_iscontrol(name, len)) {
                unlock_kernel();
@@ -336,101 +335,92 @@ static int coda_symlink(struct inode *dir_inode, struct dentry *de,
 
        /*
         * This entry is now negative. Since we do not create
-        * an inode for the entry we have to drop it. 
+        * 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, 
+       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);
+               coda_dir_update_mtime(dir_inode);
 
        unlock_kernel();
-        return error;
+       return error;
 }
 
 /* destruction routines: unlink, rmdir */
-int coda_unlink(struct inode *dir, struct dentry *de)
+static 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 ) {
+       error = venus_remove(dir->i_sb, coda_i2f(dir), name, len);
+       if ( error ) {
                unlock_kernel();
-                return error;
-        }
+               return error;
+       }
 
-       coda_dir_changed(dir, 0);
-       de->d_inode->i_nlink--;
+       coda_dir_update_mtime(dir);
+       drop_nlink(de->d_inode);
        unlock_kernel();
-
-        return 0;
+       return 0;
 }
 
-int coda_rmdir(struct inode *dir, struct dentry *de)
+static int coda_rmdir(struct inode *dir, struct dentry *de)
 {
        const char *name = de->d_name.name;
        int len = de->d_name.len;
-        int error;
+       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) {
+               /* VFS may delete the child */
+               if (de->d_inode)
+                   de->d_inode->i_nlink = 0;
 
-        if ( error ) {
-               unlock_kernel();
-                return error;
-        }
-
-       coda_dir_changed(dir, -1);
-       de->d_inode->i_nlink--;
-       d_delete(de);
+               /* fix the link count of the parent */
+               coda_dir_drop_nlink(dir);
+               coda_dir_update_mtime(dir);
+       }
        unlock_kernel();
-
-        return 0;
+       return error;
 }
 
 /* rename */
-static int coda_rename(struct inode *old_dir, struct dentry *old_dentry, 
+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;
+       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;
+       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, 
+       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 ( !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);
+                       if ( S_ISDIR(new_dentry->d_inode->i_mode) ) {
+                               coda_dir_drop_nlink(old_dir);
+                               coda_dir_inc_nlink(new_dir);
+                       }
+                       coda_dir_update_mtime(old_dir);
+                       coda_dir_update_mtime(new_dir);
                        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();
 
@@ -439,44 +429,41 @@ static int coda_rename(struct inode *old_dir, struct dentry *old_dentry,
 
 
 /* file operations for directories */
-int coda_readdir(struct file *coda_file, void *dirent, filldir_t filldir)
+static int coda_readdir(struct file *coda_file, void *buf, 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;
-       mutex_lock(&host_inode->i_mutex);
-       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;
+       if (!host_file->f_op)
+               return -ENOTDIR;
+
+       if (host_file->f_op->readdir)
+       {
+               /* potemkin case: we were handed a directory inode.
+                * We can't use vfs_readdir because we have to keep the file
+                * position in sync between the coda_file and the host_file.
+                * and as such we need grab the inode mutex. */
+               struct inode *host_inode = host_file->f_path.dentry->d_inode;
+
+               mutex_lock(&host_inode->i_mutex);
+               host_file->f_pos = coda_file->f_pos;
 
                ret = -ENOENT;
                if (!IS_DEADDIR(host_inode)) {
-                       ret = host_file->f_op->readdir(host_file, filldir, dirent);
+                       ret = host_file->f_op->readdir(host_file, buf, filldir);
                        file_accessed(host_file);
                }
+
+               coda_file->f_pos = host_file->f_pos;
+               mutex_unlock(&host_inode->i_mutex);
        }
-out:
-       coda_file->f_pos = host_file->f_pos;
-       mutex_unlock(&host_inode->i_mutex);
+       else /* Venus: we must read Venus dirents from a file */
+               ret = coda_venus_readdir(coda_file, buf, filldir);
 
        return ret;
 }
@@ -501,57 +488,68 @@ static inline unsigned int CDT2DT(unsigned char cdt)
 }
 
 /* support routines */
-static int coda_venus_readdir(struct file *filp, filldir_t filldir,
-                             void *dirent, struct dentry *dir)
+static int coda_venus_readdir(struct file *coda_file, void *buf,
+                             filldir_t filldir)
 {
        int result = 0; /* # of entries returned */
+       struct coda_file_info *cfi;
+       struct coda_inode_info *cii;
+       struct file *host_file;
+       struct dentry *de;
        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;
+       int ret;
 
-       vdir = (struct venus_dirent *)kmalloc(sizeof(*vdir), GFP_KERNEL);
+       cfi = CODA_FTOC(coda_file);
+       BUG_ON(!cfi || cfi->cfi_magic != CODA_MAGIC);
+       host_file = cfi->cfi_container;
+
+       de = coda_file->f_path.dentry;
+       cii = ITOC(de->d_inode);
+
+       vdir = 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;
+       if (coda_file->f_pos == 0) {
+               ret = filldir(buf, ".", 1, 0, de->d_inode->i_ino, DT_DIR);
+               if (ret < 0)
+                       goto out;
                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;
+               coda_file->f_pos++;
+       }
+       if (coda_file->f_pos == 1) {
+               ret = filldir(buf, "..", 2, 1, de->d_parent->d_inode->i_ino, DT_DIR);
+               if (ret < 0)
+                       goto out;
                result++;
-               filp->f_pos++;
-               /* fallthrough */
-       default:
+               coda_file->f_pos++;
+       }
        while (1) {
                /* read entries from the directory file */
-               ret = kernel_read(filp, filp->f_pos - 2, (char *)vdir,
+               ret = kernel_read(host_file, coda_file->f_pos - 2, (char *)vdir,
                                  sizeof(*vdir));
                if (ret < 0) {
-                       printk("coda_venus_readdir: read dir failed %d\n", ret);
+                       printk(KERN_ERR "coda readdir: read dir %s failed %d\n",
+                              coda_f2s(&cii->c_fid), 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);
+                       printk(KERN_ERR "coda readdir: short read on %s\n",
+                              coda_f2s(&cii->c_fid));
                        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);
+                       printk(KERN_ERR "coda readdir: invalid dir %s\n",
+                              coda_f2s(&cii->c_fid));
                        ret = -EBADF;
                        break;
                }
@@ -570,21 +568,21 @@ static int coda_venus_readdir(struct file *filp, filldir_t filldir,
                         * userspace doesn't have to worry about breaking
                         * getcwd by having mismatched inode numbers for
                         * internal volume mountpoints. */
-                       ino = find_inode_number(dir, &name);
+                       ino = find_inode_number(de, &name);
                        if (!ino) ino = vdir->d_fileno;
 
                        type = CDT2DT(vdir->d_type);
-                       ret = filldir(dirent, name.name, name.len, filp->f_pos,
-                                     ino, type); 
+                       ret = filldir(buf, name.name, name.len,
+                                     coda_file->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;
+               coda_file->f_pos += vdir->d_reclen;
        }
-       } 
+out:
        kfree(vdir);
        return result ? result : ret;
 }