fs: brlock vfsmount_lock
[deliverable/linux.git] / fs / dcache.c
index d09d93819b4d45413bb7f1e73669fba4f8225c5c..83293be4814965373d4c81e5b7d91bc63f3a55c6 100644 (file)
@@ -1332,31 +1332,13 @@ EXPORT_SYMBOL(d_add_ci);
  * d_lookup - search for a dentry
  * @parent: parent dentry
  * @name: qstr of name we wish to find
+ * Returns: dentry, or NULL
  *
- * Searches the children of the parent dentry for the name in question. If
- * the dentry is found its reference count is incremented and the dentry
- * is returned. The caller must use dput to free the entry when it has
- * finished using it. %NULL is returned on failure.
- *
- * __d_lookup is dcache_lock free. The hash list is protected using RCU.
- * Memory barriers are used while updating and doing lockless traversal. 
- * To avoid races with d_move while rename is happening, d_lock is used.
- *
- * Overflows in memcmp(), while d_move, are avoided by keeping the length
- * and name pointer in one structure pointed by d_qstr.
- *
- * rcu_read_lock() and rcu_read_unlock() are used to disable preemption while
- * lookup is going on.
- *
- * The dentry unused LRU is not updated even if lookup finds the required dentry
- * in there. It is updated in places such as prune_dcache, shrink_dcache_sb,
- * select_parent and __dget_locked. This laziness saves lookup from dcache_lock
- * acquisition.
- *
- * d_lookup() is protected against the concurrent renames in some unrelated
- * directory using the seqlockt_t rename_lock.
+ * d_lookup searches the children of the parent dentry for the name in
+ * question. If the dentry is found its reference count is incremented and the
+ * dentry is returned. The caller must use dput to free the entry when it has
+ * finished using it. %NULL is returned if the dentry does not exist.
  */
-
 struct dentry * d_lookup(struct dentry * parent, struct qstr * name)
 {
        struct dentry * dentry = NULL;
@@ -1372,6 +1354,21 @@ struct dentry * d_lookup(struct dentry * parent, struct qstr * name)
 }
 EXPORT_SYMBOL(d_lookup);
 
+/*
+ * __d_lookup - search for a dentry (racy)
+ * @parent: parent dentry
+ * @name: qstr of name we wish to find
+ * Returns: dentry, or NULL
+ *
+ * __d_lookup is like d_lookup, however it may (rarely) return a
+ * false-negative result due to unrelated rename activity.
+ *
+ * __d_lookup is slightly faster by avoiding rename_lock read seqlock,
+ * however it must be used carefully, eg. with a following d_lookup in
+ * the case of failure.
+ *
+ * __d_lookup callers must be commented.
+ */
 struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
 {
        unsigned int len = name->len;
@@ -1382,6 +1379,19 @@ struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
        struct hlist_node *node;
        struct dentry *dentry;
 
+       /*
+        * The hash list is protected using RCU.
+        *
+        * Take d_lock when comparing a candidate dentry, to avoid races
+        * with d_move().
+        *
+        * It is possible that concurrent renames can mess up our list
+        * walk here and result in missing our dentry, resulting in the
+        * false-negative result. d_lookup() protects against concurrent
+        * renames using rename_lock seqlock.
+        *
+        * See Documentation/vfs/dcache-locking.txt for more details.
+        */
        rcu_read_lock();
        
        hlist_for_each_entry_rcu(dentry, node, head, d_hash) {
@@ -1396,8 +1406,8 @@ struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
 
                /*
                 * Recheck the dentry after taking the lock - d_move may have
-                * changed things.  Don't bother checking the hash because we're
-                * about to compare the whole name anyway.
+                * changed things. Don't bother checking the hash because
+                * we're about to compare the whole name anyway.
                 */
                if (dentry->d_parent != parent)
                        goto next;
@@ -1925,7 +1935,7 @@ static int prepend_path(const struct path *path, struct path *root,
        bool slash = false;
        int error = 0;
 
-       spin_lock(&vfsmount_lock);
+       br_read_lock(vfsmount_lock);
        while (dentry != root->dentry || vfsmnt != root->mnt) {
                struct dentry * parent;
 
@@ -1954,7 +1964,7 @@ out:
        if (!error && !slash)
                error = prepend(buffer, buflen, "/", 1);
 
-       spin_unlock(&vfsmount_lock);
+       br_read_unlock(vfsmount_lock);
        return error;
 
 global_root:
@@ -1976,11 +1986,10 @@ global_root:
  * __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
+ * @buf: buffer to return value in
  * @buflen: 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.
+ * Convert a dentry into an ASCII path name.
  *
  * Returns a pointer into the buffer or an error code if the
  * path was too long.
@@ -1997,12 +2006,6 @@ char *__d_path(const struct path *path, struct path *root,
        int error;
 
        prepend(&res, &buflen, "\0", 1);
-       if (d_unlinked(path->dentry)) {
-               error = prepend(&res, &buflen, " (deleted)", 10);
-               if (error)
-                       return ERR_PTR(error);
-       }
-
        error = prepend_path(path, root, &res, &buflen);
        if (error)
                return ERR_PTR(error);
@@ -2010,6 +2013,27 @@ char *__d_path(const struct path *path, struct path *root,
        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);
+}
+
 /**
  * d_path - return the path of a dentry
  * @path: path to report
@@ -2028,9 +2052,10 @@ char *__d_path(const struct path *path, struct path *root,
  */
 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
@@ -2045,13 +2070,48 @@ char *d_path(const struct path *path, char *buf, int buflen)
        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
  */
@@ -2161,15 +2221,23 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
        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) {
@@ -2234,11 +2302,12 @@ 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);
+
+       br_read_lock(vfsmount_lock);
        if (mnt != path2->mnt) {
                for (;;) {
                        if (mnt->mnt_parent == mnt) {
-                               spin_unlock(&vfsmount_lock);
+                               br_read_unlock(vfsmount_lock);
                                return 0;
                        }
                        if (mnt->mnt_parent == path2->mnt)
@@ -2248,7 +2317,7 @@ int path_is_under(struct path *path1, struct path *path2)
                dentry = mnt->mnt_mountpoint;
        }
        res = is_subdir(dentry, path2->dentry);
-       spin_unlock(&vfsmount_lock);
+       br_read_unlock(vfsmount_lock);
        return res;
 }
 EXPORT_SYMBOL(path_is_under);
This page took 0.02802 seconds and 5 git commands to generate.