__put_super() required the caller to hold sb_lock, so put_super()
wrapped it. The per-device superblock table introduced later drops its
passive references from contexts that do not hold sb_lock, so make
put_super() self-locking: drop the count first and take sb_lock only for
the final list_del.

With the count now dropped outside sb_lock a superblock can briefly sit
on @super_blocks with s_passive == 0 before it is unlinked, so the list
walkers (__iterate_supers(), iterate_supers_type(), user_get_super())
switch to refcount_inc_not_zero() and skip it.

Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
 fs/super.c | 63 ++++++++++++++++++++++++++++----------------------------------
 1 file changed, 28 insertions(+), 35 deletions(-)

diff --git a/fs/super.c b/fs/super.c
index 25dd72b550e0..a771a0ad4c9a 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -403,12 +403,17 @@ static struct super_block *alloc_super(struct 
file_system_type *type, int flags,
 /* Superblock refcounting  */
 
 /*
- * Drop a superblock's refcount.  The caller must hold sb_lock.
+ * Drop a superblock's passive reference.  Must be called WITHOUT sb_lock held;
+ * put_super() acquires sb_lock itself when the final reference is dropped.
  */
-static void __put_super(struct super_block *s)
+void put_super(struct super_block *s)
 {
        if (refcount_dec_and_test(&s->s_passive)) {
+
+               spin_lock(&sb_lock);
                list_del_init(&s->s_list);
+               spin_unlock(&sb_lock);
+
                WARN_ON(s->s_dentry_lru.node);
                WARN_ON(s->s_inode_lru.node);
                WARN_ON(s->s_mounts);
@@ -416,20 +421,6 @@ static void __put_super(struct super_block *s)
        }
 }
 
-/**
- *     put_super       -       drop a temporary reference to superblock
- *     @sb: superblock in question
- *
- *     Drops a temporary reference, frees superblock if there's no
- *     references left.
- */
-void put_super(struct super_block *sb)
-{
-       spin_lock(&sb_lock);
-       __put_super(sb);
-       spin_unlock(&sb_lock);
-}
-
 static void kill_super_notify(struct super_block *sb)
 {
        lockdep_assert_not_held(&sb->s_umount);
@@ -478,11 +469,7 @@ void deactivate_locked_super(struct super_block *s)
 
                kill_super_notify(s);
 
-               /*
-                * Since list_lru_destroy() may sleep, we cannot call it from
-                * put_super(), where we hold the sb_lock. Therefore we destroy
-                * the lru lists right now.
-                */
+               /* list_lru_destroy() may sleep; put_super() callers may not. */
                list_lru_destroy(&s->s_dentry_lru);
                list_lru_destroy(&s->s_inode_lru);
 
@@ -851,14 +838,17 @@ static void __iterate_supers(void (*f)(struct super_block 
*, void *), void *arg,
        struct super_block *sb, *p = NULL;
        bool excl = flags & SUPER_ITER_EXCL;
 
-       guard(spinlock)(&sb_lock);
+       spin_lock(&sb_lock);
 
        for (sb = first_super(flags);
             !list_entry_is_head(sb, &super_blocks, s_list);
             sb = next_super(sb, flags)) {
                if (super_flags(sb, SB_DYING))
                        continue;
-               refcount_inc(&sb->s_passive);
+
+               if (!refcount_inc_not_zero(&sb->s_passive))
+                       continue;
+
                spin_unlock(&sb_lock);
 
                if (flags & SUPER_ITER_UNLOCKED) {
@@ -868,13 +858,14 @@ static void __iterate_supers(void (*f)(struct super_block 
*, void *), void *arg,
                        super_unlock(sb, excl);
                }
 
-               spin_lock(&sb_lock);
                if (p)
-                       __put_super(p);
+                       put_super(p);
                p = sb;
+               spin_lock(&sb_lock);
        }
+       spin_unlock(&sb_lock);
        if (p)
-               __put_super(p);
+               put_super(p);
 }
 
 void iterate_supers(void (*f)(struct super_block *, void *), void *arg)
@@ -903,7 +894,9 @@ void iterate_supers_type(struct file_system_type *type,
                if (super_flags(sb, SB_DYING))
                        continue;
 
-               refcount_inc(&sb->s_passive);
+               if (!refcount_inc_not_zero(&sb->s_passive))
+                       continue;
+
                spin_unlock(&sb_lock);
 
                locked = super_lock_shared(sb);
@@ -912,14 +905,14 @@ void iterate_supers_type(struct file_system_type *type,
                        super_unlock_shared(sb);
                }
 
-               spin_lock(&sb_lock);
                if (p)
-                       __put_super(p);
+                       put_super(p);
                p = sb;
+               spin_lock(&sb_lock);
        }
-       if (p)
-               __put_super(p);
        spin_unlock(&sb_lock);
+       if (p)
+               put_super(p);
 }
 
 EXPORT_SYMBOL(iterate_supers_type);
@@ -935,15 +928,17 @@ struct super_block *user_get_super(dev_t dev, bool excl)
                if (sb->s_dev != dev)
                        continue;
 
-               refcount_inc(&sb->s_passive);
+               if (!refcount_inc_not_zero(&sb->s_passive))
+                       continue;
+
                spin_unlock(&sb_lock);
 
                locked = super_lock(sb, excl);
                if (locked)
                        return sb;
 
+               put_super(sb);
                spin_lock(&sb_lock);
-               __put_super(sb);
                break;
        }
        spin_unlock(&sb_lock);
@@ -1368,9 +1363,7 @@ static struct super_block *bdev_super_lock(struct 
block_device *bdev, bool excl)
        lockdep_assert_not_held(&bdev->bd_disk->open_mutex);
 
        /* Make sure sb doesn't go away from under us */
-       spin_lock(&sb_lock);
        refcount_inc(&sb->s_passive);
-       spin_unlock(&sb_lock);
 
        mutex_unlock(&bdev->bd_holder_lock);
 

-- 
2.47.3


Reply via email to