kernfs: Implement kernfs_show()
[linux-block.git] / fs / kernfs / dir.c
index 1cc88ba6de9077c8250f4edf09d3279b507e289b..7fb5a72cc96d637d0c8b722e6b89bdccb8b8b169 100644 (file)
@@ -472,6 +472,16 @@ static void kernfs_drain(struct kernfs_node *kn)
        lockdep_assert_held_write(&root->kernfs_rwsem);
        WARN_ON_ONCE(kernfs_active(kn));
 
+       /*
+        * Skip draining if already fully drained. This avoids draining and its
+        * lockdep annotations for nodes which have never been activated
+        * allowing embedding kernfs_remove() in create error paths without
+        * worrying about draining.
+        */
+       if (atomic_read(&kn->active) == KN_DEACTIVATED_BIAS &&
+           !kernfs_should_drain_open_files(kn))
+               return;
+
        up_write(&root->kernfs_rwsem);
 
        if (kernfs_lockdep(kn)) {
@@ -480,7 +490,6 @@ static void kernfs_drain(struct kernfs_node *kn)
                        lock_contended(&kn->dep_map, _RET_IP_);
        }
 
-       /* but everyone should wait for draining */
        wait_event(root->deactivate_waitq,
                   atomic_read(&kn->active) == KN_DEACTIVATED_BIAS);
 
@@ -489,7 +498,8 @@ static void kernfs_drain(struct kernfs_node *kn)
                rwsem_release(&kn->dep_map, _RET_IP_);
        }
 
-       kernfs_drain_open_files(kn);
+       if (kernfs_should_drain_open_files(kn))
+               kernfs_drain_open_files(kn);
 
        down_write(&root->kernfs_rwsem);
 }
@@ -695,13 +705,7 @@ struct kernfs_node *kernfs_find_and_get_node_by_id(struct kernfs_root *root,
                        goto err_unlock;
        }
 
-       /*
-        * ACTIVATED is protected with kernfs_mutex but it was clear when
-        * @kn was added to idr and we just wanna see it set.  No need to
-        * grab kernfs_mutex.
-        */
-       if (unlikely(!(kn->flags & KERNFS_ACTIVATED) ||
-                    !atomic_inc_not_zero(&kn->count)))
+       if (unlikely(!kernfs_active(kn) || !atomic_inc_not_zero(&kn->count)))
                goto err_unlock;
 
        spin_unlock(&kernfs_idr_lock);
@@ -743,10 +747,7 @@ int kernfs_add_one(struct kernfs_node *kn)
                goto out_unlock;
 
        ret = -ENOENT;
-       if (parent->flags & KERNFS_EMPTY_DIR)
-               goto out_unlock;
-
-       if ((parent->flags & KERNFS_ACTIVATED) && !kernfs_active(parent))
+       if (parent->flags & (KERNFS_REMOVING | KERNFS_EMPTY_DIR))
                goto out_unlock;
 
        kn->hash = kernfs_name_hash(kn->name, kn->ns);
@@ -1304,6 +1305,21 @@ static struct kernfs_node *kernfs_next_descendant_post(struct kernfs_node *pos,
        return pos->parent;
 }
 
+static void kernfs_activate_one(struct kernfs_node *kn)
+{
+       lockdep_assert_held_write(&kernfs_root(kn)->kernfs_rwsem);
+
+       kn->flags |= KERNFS_ACTIVATED;
+
+       if (kernfs_active(kn) || (kn->flags & (KERNFS_HIDDEN | KERNFS_REMOVING)))
+               return;
+
+       WARN_ON_ONCE(kn->parent && RB_EMPTY_NODE(&kn->rb));
+       WARN_ON_ONCE(atomic_read(&kn->active) != KN_DEACTIVATED_BIAS);
+
+       atomic_sub(KN_DEACTIVATED_BIAS, &kn->active);
+}
+
 /**
  * kernfs_activate - activate a node which started deactivated
  * @kn: kernfs_node whose subtree is to be activated
@@ -1325,15 +1341,42 @@ void kernfs_activate(struct kernfs_node *kn)
        down_write(&root->kernfs_rwsem);
 
        pos = NULL;
-       while ((pos = kernfs_next_descendant_post(pos, kn))) {
-               if (pos->flags & KERNFS_ACTIVATED)
-                       continue;
+       while ((pos = kernfs_next_descendant_post(pos, kn)))
+               kernfs_activate_one(pos);
+
+       up_write(&root->kernfs_rwsem);
+}
 
-               WARN_ON_ONCE(pos->parent && RB_EMPTY_NODE(&pos->rb));
-               WARN_ON_ONCE(atomic_read(&pos->active) != KN_DEACTIVATED_BIAS);
+/**
+ * kernfs_show - show or hide a node
+ * @kn: kernfs_node to show or hide
+ * @show: whether to show or hide
+ *
+ * If @show is %false, @kn is marked hidden and deactivated. A hidden node is
+ * ignored in future activaitons. If %true, the mark is removed and activation
+ * state is restored. This function won't implicitly activate a new node in a
+ * %KERNFS_ROOT_CREATE_DEACTIVATED root which hasn't been activated yet.
+ *
+ * To avoid recursion complexities, directories aren't supported for now.
+ */
+void kernfs_show(struct kernfs_node *kn, bool show)
+{
+       struct kernfs_root *root = kernfs_root(kn);
 
-               atomic_sub(KN_DEACTIVATED_BIAS, &pos->active);
-               pos->flags |= KERNFS_ACTIVATED;
+       if (WARN_ON_ONCE(kernfs_type(kn) == KERNFS_DIR))
+               return;
+
+       down_write(&root->kernfs_rwsem);
+
+       if (show) {
+               kn->flags &= ~KERNFS_HIDDEN;
+               if (kn->flags & KERNFS_ACTIVATED)
+                       kernfs_activate_one(kn);
+       } else {
+               kn->flags |= KERNFS_HIDDEN;
+               if (kernfs_active(kn))
+                       atomic_add(KN_DEACTIVATED_BIAS, &kn->active);
+               kernfs_drain(kn);
        }
 
        up_write(&root->kernfs_rwsem);
@@ -1358,34 +1401,27 @@ static void __kernfs_remove(struct kernfs_node *kn)
 
        pr_debug("kernfs %s: removing\n", kn->name);
 
-       /* prevent any new usage under @kn by deactivating all nodes */
+       /* prevent new usage by marking all nodes removing and deactivating */
        pos = NULL;
-       while ((pos = kernfs_next_descendant_post(pos, kn)))
+       while ((pos = kernfs_next_descendant_post(pos, kn))) {
+               pos->flags |= KERNFS_REMOVING;
                if (kernfs_active(pos))
                        atomic_add(KN_DEACTIVATED_BIAS, &pos->active);
+       }
 
        /* deactivate and unlink the subtree node-by-node */
        do {
                pos = kernfs_leftmost_descendant(kn);
 
                /*
-                * kernfs_drain() drops kernfs_rwsem temporarily and @pos's
+                * kernfs_drain() may drop kernfs_rwsem temporarily and @pos's
                 * base ref could have been put by someone else by the time
                 * the function returns.  Make sure it doesn't go away
                 * underneath us.
                 */
                kernfs_get(pos);
 
-               /*
-                * Drain iff @kn was activated.  This avoids draining and
-                * its lockdep annotations for nodes which have never been
-                * activated and allows embedding kernfs_remove() in create
-                * error paths without worrying about draining.
-                */
-               if (kn->flags & KERNFS_ACTIVATED)
-                       kernfs_drain(pos);
-               else
-                       WARN_ON_ONCE(atomic_read(&kn->active) != KN_DEACTIVATED_BIAS);
+               kernfs_drain(pos);
 
                /*
                 * kernfs_unlink_sibling() succeeds once per node.  Use it