fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / fs / dcache.c
index cbf76e6..444c284 100644 (file)
@@ -32,6 +32,8 @@
 #include <linux/seqlock.h>
 #include <linux/swap.h>
 #include <linux/bootmem.h>
+#include <linux/vs_limit.h>
+#include "internal.h"
 
 
 int sysctl_vfs_cache_pressure __read_mostly = 100;
@@ -42,7 +44,7 @@ static __cacheline_aligned_in_smp DEFINE_SEQLOCK(rename_lock);
 
 EXPORT_SYMBOL(dcache_lock);
 
-static kmem_cache_t *dentry_cache __read_mostly;
+static struct kmem_cache *dentry_cache __read_mostly;
 
 #define DNAME_INLINE_LEN (sizeof(struct dentry)-offsetof(struct dentry,d_iname))
 
@@ -67,15 +69,19 @@ struct dentry_stat_t dentry_stat = {
        .age_limit = 45,
 };
 
-static void d_callback(struct rcu_head *head)
+static void __d_free(struct dentry *dentry)
 {
-       struct dentry * dentry = container_of(head, struct dentry, d_u.d_rcu);
-
        if (dname_external(dentry))
                kfree(dentry->d_name.name);
        kmem_cache_free(dentry_cache, dentry); 
 }
 
+static void d_callback(struct rcu_head *head)
+{
+       struct dentry * dentry = container_of(head, struct dentry, d_u.d_rcu);
+       __d_free(dentry);
+}
+
 /*
  * no dcache_lock, please.  The caller must decrement dentry_stat.nr_dentry
  * inside dcache_lock.
@@ -84,11 +90,11 @@ static void d_free(struct dentry *dentry)
 {
        if (dentry->d_op && dentry->d_op->d_release)
                dentry->d_op->d_release(dentry);
-       if (dentry->d_extra_attributes) {
-               kfree(dentry->d_extra_attributes);
-               dentry->d_extra_attributes = NULL;
-       }
-       call_rcu(&dentry->d_u.d_rcu, d_callback);
+       /* if dentry was never inserted into hash, immediate free is OK */
+       if (dentry->d_hash.pprev == NULL)
+               __d_free(dentry);
+       else
+               call_rcu(&dentry->d_u.d_rcu, d_callback);
 }
 
 /*
@@ -150,6 +156,7 @@ void dput(struct dentry *dentry)
        if (!dentry)
                return;
 
+       vx_dentry_dec(dentry);
 repeat:
        if (atomic_read(&dentry->d_count) == 1)
                might_sleep();
@@ -163,6 +170,8 @@ repeat:
                return;
        }
 
+       vx_dentry_dec(dentry);
+
        /*
         * AV: ->d_delete() is _NOT_ allowed to block now.
         */
@@ -273,6 +282,7 @@ static inline struct dentry * __dget_locked(struct dentry *dentry)
        if (!list_empty(&dentry->d_lru)) {
                dentry_stat.nr_unused--;
                list_del_init(&dentry->d_lru);
+               vx_dentry_inc(dentry);
        }
        return dentry;
 }
@@ -294,9 +304,9 @@ struct dentry * dget_locked(struct dentry *dentry)
  * it can be unhashed only if it has no children, or if it is the root
  * of a filesystem.
  *
- * If the inode has a DCACHE_DISCONNECTED alias, then prefer
+ * If the inode has an IS_ROOT, DCACHE_DISCONNECTED alias, then prefer
  * any other hashed alias over that one unless @want_discon is set,
- * in which case only return a DCACHE_DISCONNECTED alias.
+ * in which case only return an IS_ROOT, DCACHE_DISCONNECTED alias.
  */
 
 static struct dentry * __d_find_alias(struct inode *inode, int want_discon)
@@ -312,7 +322,8 @@ static struct dentry * __d_find_alias(struct inode *inode, int want_discon)
                prefetch(next);
                alias = list_entry(tmp, struct dentry, d_alias);
                if (S_ISDIR(inode->i_mode) || !d_unhashed(alias)) {
-                       if (alias->d_flags & DCACHE_DISCONNECTED)
+                       if (IS_ROOT(alias) &&
+                           (alias->d_flags & DCACHE_DISCONNECTED))
                                discon_alias = alias;
                        else if (!want_discon) {
                                __dget_locked(alias);
@@ -480,11 +491,12 @@ static void prune_dcache(int count, struct super_block *sb)
                        up_read(s_umount);
                }
                spin_unlock(&dentry->d_lock);
-               /* Cannot remove the first dentry, and it isn't appropriate
-                * to move it to the head of the list, so give up, and try
-                * later
+               /*
+                * Insert dentry at the head of the list as inserting at the
+                * tail leads to a cycle.
                 */
-               break;
+               list_add(&dentry->d_lru, &dentry_unused);
+               dentry_stat.nr_unused++;
        }
        spin_unlock(&dcache_lock);
 }
@@ -558,6 +570,7 @@ repeat:
 static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
 {
        struct dentry *parent;
+       unsigned detached = 0;
 
        BUG_ON(!IS_ROOT(dentry));
 
@@ -622,7 +635,7 @@ static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
                                atomic_dec(&parent->d_count);
 
                        list_del(&dentry->d_u.d_child);
-                       dentry_stat.nr_dentry--;        /* For d_free, below */
+                       detached++;
 
                        inode = dentry->d_inode;
                        if (inode) {
@@ -640,7 +653,7 @@ static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
                         * otherwise we ascend to the parent and move to the
                         * next sibling if there is one */
                        if (!parent)
-                               return;
+                               goto out;
 
                        dentry = parent;
 
@@ -649,6 +662,11 @@ static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
                dentry = list_entry(dentry->d_subdirs.next,
                                    struct dentry, d_u.d_child);
        }
+out:
+       /* several dentries were freed, need to correct nr_dentry */
+       spin_lock(&dcache_lock);
+       dentry_stat.nr_dentry -= detached;
+       spin_unlock(&dcache_lock);
 }
 
 /*
@@ -856,6 +874,9 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
        struct dentry *dentry;
        char *dname;
 
+       if (!vx_dentry_avail(1))
+               return NULL;
+
        dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL); 
        if (!dentry)
                return NULL;
@@ -884,7 +905,6 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
        dentry->d_sb = NULL;
        dentry->d_op = NULL;
        dentry->d_fsdata = NULL;
-       dentry->d_extra_attributes = NULL;
        dentry->d_mounted = 0;
 #ifdef CONFIG_PROFILING
        dentry->d_cookie = NULL;
@@ -905,6 +925,7 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
        if (parent)
                list_add(&dentry->d_u.d_child, &parent->d_subdirs);
        dentry_stat.nr_dentry++;
+       vx_dentry_inc(dentry);
        spin_unlock(&dcache_lock);
 
        return dentry;
@@ -1138,7 +1159,7 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
 {
        struct dentry *new = NULL;
 
-       if (inode) {
+       if (inode && S_ISDIR(inode->i_mode)) {
                spin_lock(&dcache_lock);
                new = __d_find_alias(inode, 1);
                if (new) {
@@ -1254,6 +1275,7 @@ struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
 
                if (!d_unhashed(dentry)) {
                        atomic_inc(&dentry->d_count);
+                       vx_dentry_inc(dentry);
                        found = dentry;
                }
                spin_unlock(&dentry->d_lock);
@@ -1472,23 +1494,21 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
  * deleted it.
  */
  
-/**
- * d_move - move a dentry
+/*
+ * d_move_locked - move a dentry
  * @dentry: entry to move
  * @target: new dentry
  *
  * Update the dcache to reflect the move of a file name. Negative
  * dcache entries should not be moved in this way.
  */
-
-void d_move(struct dentry * dentry, struct dentry * target)
+static void d_move_locked(struct dentry * dentry, struct dentry * target)
 {
        struct hlist_head *list;
 
        if (!dentry->d_inode)
                printk(KERN_WARNING "VFS: moving negative dcache entry\n");
 
-       spin_lock(&dcache_lock);
        write_seqlock(&rename_lock);
        /*
         * XXXX: do we really need to take target->d_lock?
@@ -1514,16 +1534,6 @@ already_unhashed:
        /* Unhash the target: dput() will then get rid of it */
        __d_drop(target);
 
-       /* flush any possible attributes */
-       if (dentry->d_extra_attributes) {
-               kfree(dentry->d_extra_attributes);
-               dentry->d_extra_attributes = NULL;
-       }
-       if (target->d_extra_attributes) {
-               kfree(target->d_extra_attributes);
-               target->d_extra_attributes = NULL;
-       }
-
        list_del(&dentry->d_u.d_child);
        list_del(&target->d_u.d_child);
 
@@ -1549,7 +1559,81 @@ already_unhashed:
        fsnotify_d_move(dentry);
        spin_unlock(&dentry->d_lock);
        write_sequnlock(&rename_lock);
+}
+
+/**
+ * d_move - move a dentry
+ * @dentry: entry to move
+ * @target: new dentry
+ *
+ * Update the dcache to reflect the move of a file name. Negative
+ * dcache entries should not be moved in this way.
+ */
+
+void d_move(struct dentry * dentry, struct dentry * target)
+{
+       spin_lock(&dcache_lock);
+       d_move_locked(dentry, target);
+       spin_unlock(&dcache_lock);
+}
+
+/*
+ * Helper that returns 1 if p1 is a parent of p2, else 0
+ */
+static int d_isparent(struct dentry *p1, struct dentry *p2)
+{
+       struct dentry *p;
+
+       for (p = p2; p->d_parent != p; p = p->d_parent) {
+               if (p->d_parent == p1)
+                       return 1;
+       }
+       return 0;
+}
+
+/*
+ * This helper attempts to cope with remotely renamed directories
+ *
+ * It assumes that the caller is already holding
+ * dentry->d_parent->d_inode->i_mutex and the dcache_lock
+ *
+ * Note: If ever the locking in lock_rename() changes, then please
+ * remember to update this too...
+ *
+ * On return, dcache_lock will have been unlocked.
+ */
+static struct dentry *__d_unalias(struct dentry *dentry, struct dentry *alias)
+{
+       struct mutex *m1 = NULL, *m2 = NULL;
+       struct dentry *ret;
+
+       /* If alias and dentry share a parent, then no extra locks required */
+       if (alias->d_parent == dentry->d_parent)
+               goto out_unalias;
+
+       /* Check for loops */
+       ret = ERR_PTR(-ELOOP);
+       if (d_isparent(alias, dentry))
+               goto out_err;
+
+       /* See lock_rename() */
+       ret = ERR_PTR(-EBUSY);
+       if (!mutex_trylock(&dentry->d_sb->s_vfs_rename_mutex))
+               goto out_err;
+       m1 = &dentry->d_sb->s_vfs_rename_mutex;
+       if (!mutex_trylock(&alias->d_parent->d_inode->i_mutex))
+               goto out_err;
+       m2 = &alias->d_parent->d_inode->i_mutex;
+out_unalias:
+       d_move_locked(alias, dentry);
+       ret = alias;
+out_err:
        spin_unlock(&dcache_lock);
+       if (m2)
+               mutex_unlock(m2);
+       if (m1)
+               mutex_unlock(m1);
+       return ret;
 }
 
 /*
@@ -1594,7 +1678,7 @@ static void __d_materialise_dentry(struct dentry *dentry, struct dentry *anon)
  */
 struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
 {
-       struct dentry *alias, *actual;
+       struct dentry *actual;
 
        BUG_ON(!d_unhashed(dentry));
 
@@ -1606,26 +1690,27 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
                goto found_lock;
        }
 
-       /* See if a disconnected directory already exists as an anonymous root
-        * that we should splice into the tree instead */
-       if (S_ISDIR(inode->i_mode) && (alias = __d_find_alias(inode, 1))) {
-               spin_lock(&alias->d_lock);
-
-               /* Is this a mountpoint that we could splice into our tree? */
-               if (IS_ROOT(alias))
-                       goto connect_mountpoint;
-
-               if (alias->d_name.len == dentry->d_name.len &&
-                   alias->d_parent == dentry->d_parent &&
-                   memcmp(alias->d_name.name,
-                          dentry->d_name.name,
-                          dentry->d_name.len) == 0)
-                       goto replace_with_alias;
-
-               spin_unlock(&alias->d_lock);
-
-               /* Doh! Seem to be aliasing directories for some reason... */
-               dput(alias);
+       if (S_ISDIR(inode->i_mode)) {
+               struct dentry *alias;
+
+               /* Does an aliased dentry already exist? */
+               alias = __d_find_alias(inode, 0);
+               if (alias) {
+                       actual = alias;
+                       /* Is this an anonymous mountpoint that we could splice
+                        * into our tree? */
+                       if (IS_ROOT(alias)) {
+                               spin_lock(&alias->d_lock);
+                               __d_materialise_dentry(dentry, alias);
+                               __d_drop(alias);
+                               goto found;
+                       }
+                       /* Nope, but we must(!) avoid directory aliasing */
+                       actual = __d_unalias(dentry, alias);
+                       if (IS_ERR(actual))
+                               dput(alias);
+                       goto out_nolock;
+               }
        }
 
        /* Add a unique reference */
@@ -1641,7 +1726,7 @@ found:
        _d_rehash(actual);
        spin_unlock(&actual->d_lock);
        spin_unlock(&dcache_lock);
-
+out_nolock:
        if (actual == dentry) {
                security_d_instantiate(dentry, inode);
                return NULL;
@@ -1650,16 +1735,6 @@ found:
        iput(inode);
        return actual;
 
-       /* Convert the anonymous/root alias into an ordinary dentry */
-connect_mountpoint:
-       __d_materialise_dentry(dentry, alias);
-
-       /* Replace the candidate dentry with the alias in the tree */
-replace_with_alias:
-       __d_drop(alias);
-       actual = alias;
-       goto found;
-
 shouldnt_be_hashed:
        spin_unlock(&dcache_lock);
        BUG();
@@ -1682,7 +1757,7 @@ shouldnt_be_hashed:
  *
  * "buflen" should be positive. Caller holds the dcache_lock.
  */
-char * __d_path( struct dentry *dentry, struct vfsmount *vfsmnt,
+static char * __d_path( struct dentry *dentry, struct vfsmount *vfsmnt,
                        struct dentry *root, struct vfsmount *rootmnt,
                        char *buffer, int buflen)
 {
@@ -1750,8 +1825,6 @@ Elong:
        return ERR_PTR(-ENAMETOOLONG);
 }
 
-EXPORT_SYMBOL_GPL(__d_path);
-
 /* write full pathname into buffer and return start of pathname */
 char * d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
                                char *buf, int buflen)
@@ -1980,23 +2053,6 @@ static void __init dcache_init_early(void)
                INIT_HLIST_HEAD(&dentry_hashtable[loop]);
 }
 
-void flush_dentry_attributes (void)
-{
-       struct hlist_node *tmp;
-       struct dentry *dentry;
-       int i;
-
-       spin_lock(&dcache_lock);
-       for (i = 0; i <= d_hash_mask; i++)
-               hlist_for_each_entry(dentry, tmp, dentry_hashtable+i, d_hash) {
-                       kfree(dentry->d_extra_attributes);
-                       dentry->d_extra_attributes = NULL;
-               }
-       spin_unlock(&dcache_lock);
-}
-
-EXPORT_SYMBOL_GPL(flush_dentry_attributes);
-
 static void __init dcache_init(unsigned long mempages)
 {
        int loop;
@@ -2034,16 +2090,13 @@ static void __init dcache_init(unsigned long mempages)
 }
 
 /* SLAB cache for __getname() consumers */
-kmem_cache_t *names_cachep __read_mostly;
+struct kmem_cache *names_cachep __read_mostly;
 
 /* SLAB cache for file structures */
-kmem_cache_t *filp_cachep __read_mostly;
+struct kmem_cache *filp_cachep __read_mostly;
 
 EXPORT_SYMBOL(d_genocide);
 
-extern void bdev_cache_init(void);
-extern void chrdev_init(void);
-
 void __init vfs_caches_init_early(void)
 {
        dcache_init_early();