9ns: Fix dangling negative TFs
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 26 Jul 2018 20:55:35 +0000 (16:55 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Mon, 30 Jul 2018 20:05:46 +0000 (16:05 -0400)
If you remove a directory that has negative entries (but not real children
- that would fail), those negative children would keep a reference on their
parent (the directory).  When you finally marked the directory
'disconnected', you'd have a positive refcnt, so we wouldn't free it.  We'd
have a ball of disconnected references, none of which would ever get freed.

Now that directory wasn't freed.  Same with its negative entries.  So far,
it's just wasted memory.  However, since we didn't __tf_free it, we also
kept a reference on *its parent.*  But that parent doesn't have a pointer
to the original directory (it is disconnected).  Now we're leaking
references on another directory.

Later, when you unmounted the filesystem and we tried to purge everything,
we'd hit that directory with the leaked references and panic.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/src/ns/tree_file.c

index 02b4293..369ec45 100644 (file)
@@ -341,6 +341,26 @@ static void __link_child(struct tree_file *parent, struct tree_file *child)
        wc_insert_child(parent, child);
 }
 
+/* Hold the dir's qlock.  This probably works with any directory, though we only
+ * use it when we remove a directory.  The normal way to drop negative entries
+ * involves working directly with the WC (tfs_lru_prune_neg). */
+static void __prune_dir_negatives(struct tree_file *dir)
+{
+       struct tree_file *child, *temp;
+
+       list_for_each_entry_safe(child, temp, &dir->children, siblings) {
+               if (!tree_file_is_negative(child))
+                       continue;
+               spin_lock(&child->lifetime);
+               assert(kref_refcnt(&child->kref) == 0);
+               /* This mark prevents new lookups.  We'll disconnect it shortly. */
+               child->flags |= TF_F_DISCONNECTED;
+               spin_unlock(&child->lifetime);
+               assert(child->parent == dir);
+               __disconnect_child(dir, child);
+       }
+}
+
 static void neuter_directory(struct tree_file *dir)
 {
        qlock(&dir->file.qlock);
@@ -349,6 +369,9 @@ static void neuter_directory(struct tree_file *dir)
                error(ENOTEMPTY, "can't remove dir with children");
        }
        dir->can_have_children = false;
+       /* Even if we don't have real children, we might have some negatives.  Those
+        * aren't a reason to abort an rmdir, we just need to drop them. */
+       __prune_dir_negatives(dir);
        qunlock(&dir->file.qlock);
 }