vfs: show unreachable paths in getcwd and proc
[linux-2.6.git] / fs / dcache.c
index c231a63..166d35d 100644 (file)
@@ -17,7 +17,6 @@
 #include <linux/syscalls.h>
 #include <linux/string.h>
 #include <linux/mm.h>
-#include <linux/fdtable.h>
 #include <linux/fs.h>
 #include <linux/fsnotify.h>
 #include <linux/slab.h>
@@ -32,6 +31,8 @@
 #include <linux/seqlock.h>
 #include <linux/swap.h>
 #include <linux/bootmem.h>
+#include <linux/fs_struct.h>
+#include <linux/hardirq.h>
 #include "internal.h"
 
 int sysctl_vfs_cache_pressure __read_mostly = 100;
@@ -256,6 +257,7 @@ kill_it:
        if (dentry)
                goto repeat;
 }
+EXPORT_SYMBOL(dput);
 
 /**
  * d_invalidate - invalidate a dentry
@@ -313,6 +315,7 @@ int d_invalidate(struct dentry * dentry)
        spin_unlock(&dcache_lock);
        return 0;
 }
+EXPORT_SYMBOL(d_invalidate);
 
 /* This should be called _only_ with dcache_lock held */
 
@@ -327,6 +330,7 @@ struct dentry * dget_locked(struct dentry *dentry)
 {
        return __dget_locked(dentry);
 }
+EXPORT_SYMBOL(dget_locked);
 
 /**
  * d_find_alias - grab a hashed alias of inode
@@ -383,6 +387,7 @@ struct dentry * d_find_alias(struct inode *inode)
        }
        return de;
 }
+EXPORT_SYMBOL(d_find_alias);
 
 /*
  *     Try to kill dentries associated with this inode.
@@ -407,6 +412,7 @@ restart:
        }
        spin_unlock(&dcache_lock);
 }
+EXPORT_SYMBOL(d_prune_aliases);
 
 /*
  * Throw away a dentry - free the inode, dput the parent.  This requires that
@@ -481,7 +487,7 @@ restart:
                        if ((flags & DCACHE_REFERENCED)
                                && (dentry->d_flags & DCACHE_REFERENCED)) {
                                dentry->d_flags &= ~DCACHE_REFERENCED;
-                               list_move_tail(&dentry->d_lru, &referenced);
+                               list_move(&dentry->d_lru, &referenced);
                                spin_unlock(&dentry->d_lock);
                        } else {
                                list_move_tail(&dentry->d_lru, &tmp);
@@ -530,7 +536,7 @@ restart:
  */
 static void prune_dcache(int count)
 {
-       struct super_block *sb;
+       struct super_block *sb, *p = NULL;
        int w_count;
        int unused = dentry_stat.nr_unused;
        int prune_ratio;
@@ -539,13 +545,14 @@ static void prune_dcache(int count)
        if (unused == 0 || count == 0)
                return;
        spin_lock(&dcache_lock);
-restart:
        if (count >= unused)
                prune_ratio = 1;
        else
                prune_ratio = unused / count;
        spin_lock(&sb_lock);
        list_for_each_entry(sb, &super_blocks, s_list) {
+               if (list_empty(&sb->s_instances))
+                       continue;
                if (sb->s_nr_dentry_unused == 0)
                        continue;
                sb->s_count++;
@@ -583,16 +590,16 @@ restart:
                        up_read(&sb->s_umount);
                }
                spin_lock(&sb_lock);
+               if (p)
+                       __put_super(p);
                count -= pruned;
-               /*
-                * restart only when sb is no longer on the list and
-                * we have more work to do.
-                */
-               if (__put_super_and_need_restart(sb) && count > 0) {
-                       spin_unlock(&sb_lock);
-                       goto restart;
-               }
+               p = sb;
+               /* more work left to do? */
+               if (count <= 0)
+                       break;
        }
+       if (p)
+               __put_super(p);
        spin_unlock(&sb_lock);
        spin_unlock(&dcache_lock);
 }
@@ -609,6 +616,7 @@ void shrink_dcache_sb(struct super_block * sb)
 {
        __shrink_dcache_sb(sb, NULL, 0);
 }
+EXPORT_SYMBOL(shrink_dcache_sb);
 
 /*
  * destroy a single subtree of dentries for unmount
@@ -791,6 +799,7 @@ positive:
        spin_unlock(&dcache_lock);
        return 1;
 }
+EXPORT_SYMBOL(have_submounts);
 
 /*
  * Search the dentry child list for the specified parent,
@@ -875,6 +884,7 @@ void shrink_dcache_parent(struct dentry * parent)
        while ((found = select_parent(parent)) != 0)
                __shrink_dcache_sb(sb, &found, 0);
 }
+EXPORT_SYMBOL(shrink_dcache_parent);
 
 /*
  * Scan `nr' dentries and return the number which remain.
@@ -888,7 +898,7 @@ void shrink_dcache_parent(struct dentry * parent)
  *
  * In this case we return -1 to tell the caller that we baled.
  */
-static int shrink_dcache_memory(int nr, gfp_t gfp_mask)
+static int shrink_dcache_memory(struct shrinker *shrink, int nr, gfp_t gfp_mask)
 {
        if (nr) {
                if (!(gfp_mask & __GFP_FS))
@@ -967,6 +977,7 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
 
        return dentry;
 }
+EXPORT_SYMBOL(d_alloc);
 
 struct dentry *d_alloc_name(struct dentry *parent, const char *name)
 {
@@ -977,6 +988,7 @@ struct dentry *d_alloc_name(struct dentry *parent, const char *name)
        q.hash = full_name_hash(q.name, q.len);
        return d_alloc(parent, &q);
 }
+EXPORT_SYMBOL(d_alloc_name);
 
 /* the caller must hold dcache_lock */
 static void __d_instantiate(struct dentry *dentry, struct inode *inode)
@@ -1010,6 +1022,7 @@ void d_instantiate(struct dentry *entry, struct inode * inode)
        spin_unlock(&dcache_lock);
        security_d_instantiate(entry, inode);
 }
+EXPORT_SYMBOL(d_instantiate);
 
 /**
  * d_instantiate_unique - instantiate a non-aliased dentry
@@ -1106,6 +1119,7 @@ struct dentry * d_alloc_root(struct inode * root_inode)
        }
        return res;
 }
+EXPORT_SYMBOL(d_alloc_root);
 
 static inline struct hlist_head *d_hash(struct dentry *parent,
                                        unsigned long hash)
@@ -1180,7 +1194,7 @@ struct dentry *d_obtain_alias(struct inode *inode)
        iput(inode);
        return res;
 }
-EXPORT_SYMBOL_GPL(d_obtain_alias);
+EXPORT_SYMBOL(d_obtain_alias);
 
 /**
  * d_splice_alias - splice a disconnected dentry into the tree if one exists
@@ -1209,7 +1223,6 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
                        BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
                        spin_unlock(&dcache_lock);
                        security_d_instantiate(new, inode);
-                       d_rehash(dentry);
                        d_move(new, dentry);
                        iput(inode);
                } else {
@@ -1223,6 +1236,7 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
                d_add(dentry, inode);
        return new;
 }
+EXPORT_SYMBOL(d_splice_alias);
 
 /**
  * d_add_ci - lookup or allocate new dentry with case-exact name
@@ -1247,15 +1261,18 @@ struct dentry *d_add_ci(struct dentry *dentry, struct inode *inode,
        struct dentry *found;
        struct dentry *new;
 
-       /* Does a dentry matching the name exist already? */
+       /*
+        * First check if a dentry matching the name already exists,
+        * if not go ahead and create it now.
+        */
        found = d_hash_and_lookup(dentry->d_parent, name);
-       /* If not, create it now and return */
        if (!found) {
                new = d_alloc(dentry->d_parent, name);
                if (!new) {
                        error = -ENOMEM;
                        goto err_out;
                }
+
                found = d_splice_alias(inode, new);
                if (found) {
                        dput(new);
@@ -1263,67 +1280,53 @@ struct dentry *d_add_ci(struct dentry *dentry, struct inode *inode,
                }
                return new;
        }
-       /* Matching dentry exists, check if it is negative. */
+
+       /*
+        * If a matching dentry exists, and it's not negative use it.
+        *
+        * Decrement the reference count to balance the iget() done
+        * earlier on.
+        */
        if (found->d_inode) {
                if (unlikely(found->d_inode != inode)) {
                        /* This can't happen because bad inodes are unhashed. */
                        BUG_ON(!is_bad_inode(inode));
                        BUG_ON(!is_bad_inode(found->d_inode));
                }
-               /*
-                * Already have the inode and the dentry attached, decrement
-                * the reference count to balance the iget() done
-                * earlier on.  We found the dentry using d_lookup() so it
-                * cannot be disconnected and thus we do not need to worry
-                * about any NFS/disconnectedness issues here.
-                */
                iput(inode);
                return found;
        }
+
        /*
         * Negative dentry: instantiate it unless the inode is a directory and
-        * has a 'disconnected' dentry (i.e. IS_ROOT and DCACHE_DISCONNECTED),
-        * in which case d_move() that in place of the found dentry.
+        * already has a dentry.
         */
-       if (!S_ISDIR(inode->i_mode)) {
-               /* Not a directory; everything is easy. */
-               d_instantiate(found, inode);
-               return found;
-       }
        spin_lock(&dcache_lock);
-       if (list_empty(&inode->i_dentry)) {
-               /*
-                * Directory without a 'disconnected' dentry; we need to do
-                * d_instantiate() by hand because it takes dcache_lock which
-                * we already hold.
-                */
+       if (!S_ISDIR(inode->i_mode) || list_empty(&inode->i_dentry)) {
                __d_instantiate(found, inode);
                spin_unlock(&dcache_lock);
                security_d_instantiate(found, inode);
                return found;
        }
+
        /*
-        * Directory with a 'disconnected' dentry; get a reference to the
-        * 'disconnected' dentry.
+        * In case a directory already has a (disconnected) entry grab a
+        * reference to it, move it in place and use it.
         */
        new = list_entry(inode->i_dentry.next, struct dentry, d_alias);
        dget_locked(new);
        spin_unlock(&dcache_lock);
-       /* Do security vodoo. */
        security_d_instantiate(found, inode);
-       /* Move new in place of found. */
        d_move(new, found);
-       /* Balance the iget() we did above. */
        iput(inode);
-       /* Throw away found. */
        dput(found);
-       /* Use new as the actual dentry. */
        return new;
 
 err_out:
        iput(inode);
        return ERR_PTR(error);
 }
+EXPORT_SYMBOL(d_add_ci);
 
 /**
  * d_lookup - search for a dentry
@@ -1367,6 +1370,7 @@ struct dentry * d_lookup(struct dentry * parent, struct qstr * name)
        } while (read_seqretry(&rename_lock, seq));
        return dentry;
 }
+EXPORT_SYMBOL(d_lookup);
 
 struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
 {
@@ -1493,6 +1497,7 @@ int d_validate(struct dentry *dentry, struct dentry *dparent)
 out:
        return 0;
 }
+EXPORT_SYMBOL(d_validate);
 
 /*
  * When a file is deleted, we have two options:
@@ -1525,6 +1530,7 @@ void d_delete(struct dentry * dentry)
        spin_lock(&dentry->d_lock);
        isdir = S_ISDIR(dentry->d_inode->i_mode);
        if (atomic_read(&dentry->d_count) == 1) {
+               dentry->d_flags &= ~DCACHE_CANT_MOUNT;
                dentry_iput(dentry);
                fsnotify_nameremove(dentry, isdir);
                return;
@@ -1538,6 +1544,7 @@ void d_delete(struct dentry * dentry)
 
        fsnotify_nameremove(dentry, isdir);
 }
+EXPORT_SYMBOL(d_delete);
 
 static void __d_rehash(struct dentry * entry, struct hlist_head *list)
 {
@@ -1566,10 +1573,7 @@ void d_rehash(struct dentry * entry)
        spin_unlock(&entry->d_lock);
        spin_unlock(&dcache_lock);
 }
-
-#define do_switch(x,y) do { \
-       __typeof__ (x) __tmp = x; \
-       x = y; y = __tmp; } while (0)
+EXPORT_SYMBOL(d_rehash);
 
 /*
  * When switching names, the actual string doesn't strictly have to
@@ -1589,7 +1593,7 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
                        /*
                         * Both external: swap the pointers
                         */
-                       do_switch(target->d_name.name, dentry->d_name.name);
+                       swap(target->d_name.name, dentry->d_name.name);
                } else {
                        /*
                         * dentry:internal, target:external.  Steal target's
@@ -1620,7 +1624,7 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
                        return;
                }
        }
-       do_switch(dentry->d_name.len, target->d_name.len);
+       swap(dentry->d_name.len, target->d_name.len);
 }
 
 /*
@@ -1680,7 +1684,7 @@ already_unhashed:
 
        /* Switch the names.. */
        switch_names(dentry, target);
-       do_switch(dentry->d_name.hash, target->d_name.hash);
+       swap(dentry->d_name.hash, target->d_name.hash);
 
        /* ... and switch the parents */
        if (IS_ROOT(dentry)) {
@@ -1688,7 +1692,7 @@ already_unhashed:
                target->d_parent = target;
                INIT_LIST_HEAD(&target->d_u.d_child);
        } else {
-               do_switch(dentry->d_parent, target->d_parent);
+               swap(dentry->d_parent, target->d_parent);
 
                /* And add them back to the (new) parent lists */
                list_add(&target->d_u.d_child, &target->d_parent->d_subdirs);
@@ -1716,6 +1720,7 @@ void d_move(struct dentry * dentry, struct dentry * target)
        d_move_locked(dentry, target);
        spin_unlock(&dcache_lock);
 }
+EXPORT_SYMBOL(d_move);
 
 /**
  * d_ancestor - search for an ancestor
@@ -1789,7 +1794,7 @@ static void __d_materialise_dentry(struct dentry *dentry, struct dentry *anon)
        struct dentry *dparent, *aparent;
 
        switch_names(dentry, anon);
-       do_switch(dentry->d_name.hash, anon->d_name.hash);
+       swap(dentry->d_name.hash, anon->d_name.hash);
 
        dparent = dentry->d_parent;
        aparent = anon->d_parent;
@@ -1882,6 +1887,7 @@ shouldnt_be_hashed:
        spin_unlock(&dcache_lock);
        BUG();
 }
+EXPORT_SYMBOL_GPL(d_materialise_unique);
 
 static int prepend(char **buffer, int *buflen, const char *str, int namelen)
 {
@@ -1899,47 +1905,30 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
 }
 
 /**
- * __d_path - return the path of a dentry
+ * Prepend path string to a buffer
+ *
  * @path: the dentry/vfsmount to report
  * @root: root vfsmnt/dentry (may be modified by this function)
- * @buffer: buffer to return value in
- * @buflen: buffer length
+ * @buffer: pointer to the end of the buffer
+ * @buflen: pointer to buffer length
  *
- * Convert a dentry into an ASCII path name. If the entry has been deleted
- * the string " (deleted)" is appended. Note that this is ambiguous.
- *
- * Returns the buffer or an error code if the path was too long.
- *
- * "buflen" should be positive. Caller holds the dcache_lock.
+ * Caller holds the dcache_lock.
  *
  * If path is not reachable from the supplied root, then the value of
  * root is changed (without modifying refcounts).
  */
-char *__d_path(const struct path *path, struct path *root,
-              char *buffer, int buflen)
+static int prepend_path(const struct path *path, struct path *root,
+                       char **buffer, int *buflen)
 {
        struct dentry *dentry = path->dentry;
        struct vfsmount *vfsmnt = path->mnt;
-       char *end = buffer + buflen;
-       char *retval;
+       bool slash = false;
+       int error = 0;
 
        spin_lock(&vfsmount_lock);
-       prepend(&end, &buflen, "\0", 1);
-       if (!IS_ROOT(dentry) && d_unhashed(dentry) &&
-               (prepend(&end, &buflen, " (deleted)", 10) != 0))
-                       goto Elong;
-
-       if (buflen < 1)
-               goto Elong;
-       /* Get '/' right */
-       retval = end-1;
-       *retval = '/';
-
-       for (;;) {
+       while (dentry != root->dentry || vfsmnt != root->mnt) {
                struct dentry * parent;
 
-               if (dentry == root->dentry && vfsmnt == root->mnt)
-                       break;
                if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
                        /* Global root? */
                        if (vfsmnt->mnt_parent == vfsmnt) {
@@ -1951,28 +1940,88 @@ char *__d_path(const struct path *path, struct path *root,
                }
                parent = dentry->d_parent;
                prefetch(parent);
-               if ((prepend_name(&end, &buflen, &dentry->d_name) != 0) ||
-                   (prepend(&end, &buflen, "/", 1) != 0))
-                       goto Elong;
-               retval = end;
+               error = prepend_name(buffer, buflen, &dentry->d_name);
+               if (!error)
+                       error = prepend(buffer, buflen, "/", 1);
+               if (error)
+                       break;
+
+               slash = true;
                dentry = parent;
        }
 
 out:
+       if (!error && !slash)
+               error = prepend(buffer, buflen, "/", 1);
+
        spin_unlock(&vfsmount_lock);
-       return retval;
+       return error;
 
 global_root:
-       retval += 1;    /* hit the slash */
-       if (prepend_name(&retval, &buflen, &dentry->d_name) != 0)
-               goto Elong;
+       /*
+        * Filesystems needing to implement special "root names"
+        * should do so with ->d_dname()
+        */
+       if (IS_ROOT(dentry) &&
+           (dentry->d_name.len != 1 || dentry->d_name.name[0] != '/')) {
+               WARN(1, "Root dentry has weird name <%.*s>\n",
+                    (int) dentry->d_name.len, dentry->d_name.name);
+       }
        root->mnt = vfsmnt;
        root->dentry = dentry;
        goto out;
+}
 
-Elong:
-       retval = ERR_PTR(-ENAMETOOLONG);
-       goto out;
+/**
+ * __d_path - return the path of a dentry
+ * @path: the dentry/vfsmount to report
+ * @root: root vfsmnt/dentry (may be modified by this function)
+ * @buffer: buffer to return value in
+ * @buflen: buffer length
+ *
+ * Convert a dentry into an ASCII path name.
+ *
+ * Returns a pointer into the buffer or an error code if the
+ * path was too long.
+ *
+ * "buflen" should be positive. Caller holds the dcache_lock.
+ *
+ * If path is not reachable from the supplied root, then the value of
+ * root is changed (without modifying refcounts).
+ */
+char *__d_path(const struct path *path, struct path *root,
+              char *buf, int buflen)
+{
+       char *res = buf + buflen;
+       int error;
+
+       prepend(&res, &buflen, "\0", 1);
+       error = prepend_path(path, root, &res, &buflen);
+       if (error)
+               return ERR_PTR(error);
+
+       return res;
+}
+
+/*
+ * same as __d_path but appends "(deleted)" for unlinked files.
+ */
+static int path_with_deleted(const struct path *path, struct path *root,
+                                char **buf, int *buflen)
+{
+       prepend(buf, buflen, "\0", 1);
+       if (d_unlinked(path->dentry)) {
+               int error = prepend(buf, buflen, " (deleted)", 10);
+               if (error)
+                       return error;
+       }
+
+       return prepend_path(path, root, buf, buflen);
+}
+
+static int prepend_unreachable(char **buffer, int *buflen)
+{
+       return prepend(buffer, buflen, "(unreachable)", 13);
 }
 
 /**
@@ -1984,15 +2033,19 @@ Elong:
  * Convert a dentry into an ASCII path name. If the entry has been deleted
  * the string " (deleted)" is appended. Note that this is ambiguous.
  *
- * Returns the buffer or an error code if the path was too long.
+ * Returns a pointer into the buffer or an error code if the path was
+ * too long. Note: Callers should use the returned pointer, not the passed
+ * in buffer, to use the name! The implementation often starts at an offset
+ * into the buffer, and may leave 0 bytes at the start.
  *
  * "buflen" should be positive.
  */
 char *d_path(const struct path *path, char *buf, int buflen)
 {
-       char *res;
+       char *res = buf + buflen;
        struct path root;
        struct path tmp;
+       int error;
 
        /*
         * We have various synthetic filesystems that never get mounted.  On
@@ -2004,17 +2057,50 @@ char *d_path(const struct path *path, char *buf, int buflen)
        if (path->dentry->d_op && path->dentry->d_op->d_dname)
                return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
 
-       read_lock(&current->fs->lock);
-       root = current->fs->root;
-       path_get(&root);
-       read_unlock(&current->fs->lock);
+       get_fs_root(current->fs, &root);
        spin_lock(&dcache_lock);
        tmp = root;
-       res = __d_path(path, &tmp, buf, buflen);
+       error = path_with_deleted(path, &tmp, &res, &buflen);
+       if (error)
+               res = ERR_PTR(error);
        spin_unlock(&dcache_lock);
        path_put(&root);
        return res;
 }
+EXPORT_SYMBOL(d_path);
+
+/**
+ * d_path_with_unreachable - return the path of a dentry
+ * @path: path to report
+ * @buf: buffer to return value in
+ * @buflen: buffer length
+ *
+ * The difference from d_path() is that this prepends "(unreachable)"
+ * to paths which are unreachable from the current process' root.
+ */
+char *d_path_with_unreachable(const struct path *path, char *buf, int buflen)
+{
+       char *res = buf + buflen;
+       struct path root;
+       struct path tmp;
+       int error;
+
+       if (path->dentry->d_op && path->dentry->d_op->d_dname)
+               return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
+
+       get_fs_root(current->fs, &root);
+       spin_lock(&dcache_lock);
+       tmp = root;
+       error = path_with_deleted(path, &tmp, &res, &buflen);
+       if (!error && !path_equal(&tmp, &root))
+               error = prepend_unreachable(&res, &buflen);
+       spin_unlock(&dcache_lock);
+       path_put(&root);
+       if (error)
+               res =  ERR_PTR(error);
+
+       return res;
+}
 
 /*
  * Helper function for dentry_operations.d_dname() members
@@ -2040,16 +2126,12 @@ char *dynamic_dname(struct dentry *dentry, char *buffer, int buflen,
 /*
  * Write full pathname from the root of the filesystem into the buffer.
  */
-char *dentry_path(struct dentry *dentry, char *buf, int buflen)
+char *__dentry_path(struct dentry *dentry, char *buf, int buflen)
 {
        char *end = buf + buflen;
        char *retval;
 
-       spin_lock(&dcache_lock);
        prepend(&end, &buflen, "\0", 1);
-       if (!IS_ROOT(dentry) && d_unhashed(dentry) &&
-               (prepend(&end, &buflen, "//deleted", 9) != 0))
-                       goto Elong;
        if (buflen < 1)
                goto Elong;
        /* Get '/' right */
@@ -2067,7 +2149,28 @@ char *dentry_path(struct dentry *dentry, char *buf, int buflen)
                retval = end;
                dentry = parent;
        }
+       return retval;
+Elong:
+       return ERR_PTR(-ENAMETOOLONG);
+}
+EXPORT_SYMBOL(__dentry_path);
+
+char *dentry_path(struct dentry *dentry, char *buf, int buflen)
+{
+       char *p = NULL;
+       char *retval;
+
+       spin_lock(&dcache_lock);
+       if (d_unlinked(dentry)) {
+               p = buf + buflen;
+               if (prepend(&p, &buflen, "//deleted", 10) != 0)
+                       goto Elong;
+               buflen++;
+       }
+       retval = __dentry_path(dentry, buf, buflen);
        spin_unlock(&dcache_lock);
+       if (!IS_ERR(retval) && p)
+               *p = '/';       /* restore '/' overriden with '\0' */
        return retval;
 Elong:
        spin_unlock(&dcache_lock);
@@ -2092,7 +2195,7 @@ Elong:
  *             return NULL;
  *     }
  */
-asmlinkage long sys_getcwd(char __user *buf, unsigned long size)
+SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
 {
        int error;
        struct path pwd, root;
@@ -2101,28 +2204,30 @@ asmlinkage long sys_getcwd(char __user *buf, unsigned long size)
        if (!page)
                return -ENOMEM;
 
-       read_lock(&current->fs->lock);
-       pwd = current->fs->pwd;
-       path_get(&pwd);
-       root = current->fs->root;
-       path_get(&root);
-       read_unlock(&current->fs->lock);
+       get_fs_root_and_pwd(current->fs, &root, &pwd);
 
        error = -ENOENT;
-       /* Has the current directory has been unlinked? */
        spin_lock(&dcache_lock);
-       if (IS_ROOT(pwd.dentry) || !d_unhashed(pwd.dentry)) {
+       if (!d_unlinked(pwd.dentry)) {
                unsigned long len;
                struct path tmp = root;
-               char * cwd;
+               char *cwd = page + PAGE_SIZE;
+               int buflen = PAGE_SIZE;
 
-               cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE);
+               prepend(&cwd, &buflen, "\0", 1);
+               error = prepend_path(&pwd, &tmp, &cwd, &buflen);
                spin_unlock(&dcache_lock);
 
-               error = PTR_ERR(cwd);
-               if (IS_ERR(cwd))
+               if (error)
                        goto out;
 
+               /* Unreachable from current root */
+               if (!path_equal(&tmp, &root)) {
+                       error = prepend_unreachable(&cwd, &buflen);
+                       if (error)
+                               goto out;
+               }
+
                error = -ERANGE;
                len = PAGE_SIZE + page - cwd;
                if (len <= size) {
@@ -2161,7 +2266,6 @@ int is_subdir(struct dentry *new_dentry, struct dentry *old_dentry)
        int result;
        unsigned long seq;
 
-       /* FIXME: This is old behavior, needed? Please check callers. */
        if (new_dentry == old_dentry)
                return 1;
 
@@ -2183,6 +2287,30 @@ int is_subdir(struct dentry *new_dentry, struct dentry *old_dentry)
        return result;
 }
 
+int path_is_under(struct path *path1, struct path *path2)
+{
+       struct vfsmount *mnt = path1->mnt;
+       struct dentry *dentry = path1->dentry;
+       int res;
+       spin_lock(&vfsmount_lock);
+       if (mnt != path2->mnt) {
+               for (;;) {
+                       if (mnt->mnt_parent == mnt) {
+                               spin_unlock(&vfsmount_lock);
+                               return 0;
+                       }
+                       if (mnt->mnt_parent == path2->mnt)
+                               break;
+                       mnt = mnt->mnt_parent;
+               }
+               dentry = mnt->mnt_mountpoint;
+       }
+       res = is_subdir(dentry, path2->dentry);
+       spin_unlock(&vfsmount_lock);
+       return res;
+}
+EXPORT_SYMBOL(path_is_under);
+
 void d_genocide(struct dentry *root)
 {
        struct dentry *this_parent = root;
@@ -2240,6 +2368,7 @@ ino_t find_inode_number(struct dentry *dir, struct qstr *name)
        }
        return ino;
 }
+EXPORT_SYMBOL(find_inode_number);
 
 static __initdata unsigned long dhash_entries;
 static int __init set_dhash_entries(char *str)
@@ -2309,9 +2438,7 @@ static void __init dcache_init(void)
 
 /* SLAB cache for __getname() consumers */
 struct kmem_cache *names_cachep __read_mostly;
-
-/* SLAB cache for file structures */
-struct kmem_cache *filp_cachep __read_mostly;
+EXPORT_SYMBOL(names_cachep);
 
 EXPORT_SYMBOL(d_genocide);
 
@@ -2334,9 +2461,6 @@ void __init vfs_caches_init(unsigned long mempages)
        names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
                        SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
 
-       filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0,
-                       SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
-
        dcache_init();
        inode_init();
        files_init(mempages);
@@ -2344,26 +2468,3 @@ void __init vfs_caches_init(unsigned long mempages)
        bdev_cache_init();
        chrdev_init();
 }
-
-EXPORT_SYMBOL(d_alloc);
-EXPORT_SYMBOL(d_alloc_root);
-EXPORT_SYMBOL(d_delete);
-EXPORT_SYMBOL(d_find_alias);
-EXPORT_SYMBOL(d_instantiate);
-EXPORT_SYMBOL(d_invalidate);
-EXPORT_SYMBOL(d_lookup);
-EXPORT_SYMBOL(d_move);
-EXPORT_SYMBOL_GPL(d_materialise_unique);
-EXPORT_SYMBOL(d_path);
-EXPORT_SYMBOL(d_prune_aliases);
-EXPORT_SYMBOL(d_rehash);
-EXPORT_SYMBOL(d_splice_alias);
-EXPORT_SYMBOL(d_add_ci);
-EXPORT_SYMBOL(d_validate);
-EXPORT_SYMBOL(dget_locked);
-EXPORT_SYMBOL(dput);
-EXPORT_SYMBOL(find_inode_number);
-EXPORT_SYMBOL(have_submounts);
-EXPORT_SYMBOL(names_cachep);
-EXPORT_SYMBOL(shrink_dcache_parent);
-EXPORT_SYMBOL(shrink_dcache_sb);