From: Christian Brauner <brauner@kernel.org>
To: Jan Kara <jack@suse.cz>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>,
Josef Bacik <josef@toxicpanda.com>,
linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
"Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH 3/5] super: let super_lock() wait for a superblock being shut down
Date: Fri, 23 Oct 2026 11:41:03 +0200 [thread overview]
Message-ID: <20261023-work-super-lock_helpers-v1-3-d0fcb3f2f275@kernel.org> (raw)
In-Reply-To: <20261023-work-super-lock_helpers-v1-0-d0fcb3f2f275@kernel.org>
Everyone who needs an unmount to be over before they can carry on
currently calls super_lock() and, if that fails, calls
super_wait_shutdown() or super_wait_shutdown_killable() after it. That's
six callers open-coding the same two step dance, and they have to make
sure to pick the shutdown wait that matches the lock they took.
Add SUPER_LOCK_WAIT_SHUTDOWN which makes super_lock() wait for SB_DYING
when it finds the superblock going away. It still returns -ENOENT after
the wait. It sleeps in the same state as the rest of super_lock(), so
SUPER_LOCK_KILLABLE now covers all of the waiting. get_active_super()
passes the flags through for the freezing callers. Then remove
super_wait_shutdown() and super_wait_shutdown_killable().
__iterate_supers() is the odd one out. It takes s_umount uninterruptibly
but waits for the shutdown killably. Keep it that way for now and let it
call super_wait() directly.
Note that the freezing callers used to wait for the shutdown even if
super_lock() succeeded and atomic_inc_not_zero() in get_active_super()
failed. That can't happen. The last active reference is dropped with
s_umount held and s_umount stays held until SB_DYING is set. So once
super_lock() succeeded there's always an active reference to take.
No functional changes.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/super.c | 118 +++++++++++++++++++++++--------------------------------------
1 file changed, 45 insertions(+), 73 deletions(-)
diff --git a/fs/super.c b/fs/super.c
index 0185c1f16ba3..0f3308fb99aa 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -58,6 +58,7 @@ static char *sb_writers_name[SB_FREEZE_LEVELS] = {
enum super_lock_flags_t {
SUPER_LOCK_EXCL = (1U << 0),
SUPER_LOCK_KILLABLE = (1U << 1),
+ SUPER_LOCK_WAIT_SHUTDOWN = (1U << 2),
};
static inline int __super_lock(struct super_block *sb,
@@ -130,13 +131,19 @@ static int super_wait(struct super_block *sb, unsigned int sb_flags,
* creation will succeed and SB_BORN is set by vfs_get_tree() or we're
* woken and we'll see SB_DYING.
*
- * A superblock which is being shut down is not waited for, see
- * super_wait_shutdown() and super_wait_shutdown_killable() for that.
- *
* With SUPER_LOCK_EXCL s_umount is taken for writing, otherwise for reading.
- * With SUPER_LOCK_KILLABLE both the wait and taking s_umount can be cut short
- * by a fatal signal. That is only for callers which give up altogether when
- * they are killed.
+ *
+ * A superblock which is being shut down is not waited for unless
+ * SUPER_LOCK_WAIT_SHUTDOWN is passed. That is for those who have nothing to
+ * do on a superblock which is going away, but who promise their caller that
+ * what it had to write has been written, or who must not carry on while the
+ * filesystem is still being written out. The wait is over once
+ * generic_shutdown_super() is done with @sb, and -ENOENT is still returned.
+ *
+ * With SUPER_LOCK_KILLABLE all the waiting can be cut short by a fatal
+ * signal. That is only for callers which give up altogether when they are
+ * killed. If what comes after the wait happens either way, the wait must
+ * not be cut short.
*
* The caller must have acquired a temporary reference on @sb->s_passive.
*
@@ -162,7 +169,7 @@ static __must_check int super_lock(struct super_block *sb,
* and it can be a long one: the filesystem is written out first.
*/
if (!atomic_read(&sb->s_active))
- return -ENOENT;
+ goto dying;
/* wait until the superblock is ready or dying */
ret = super_wait(sb, SB_BORN | SB_DYING, state);
@@ -171,7 +178,7 @@ static __must_check int super_lock(struct super_block *sb,
/* Don't pointlessly acquire s_umount. */
if (super_flags(sb, SB_DYING))
- return -ENOENT;
+ goto dying;
ret = __super_lock(sb, flags);
if (ret)
@@ -184,45 +191,19 @@ static __must_check int super_lock(struct super_block *sb,
*/
if (sb->s_flags & SB_DYING) {
super_unlock(sb, flags);
- return -ENOENT;
+ goto dying;
}
WARN_ON_ONCE(!(sb->s_flags & SB_BORN));
return 0;
-}
-/**
- * super_wait_shutdown_killable - wait for a superblock being shut down
- * @sb: superblock super_lock() failed on
- *
- * For those who have nothing to do on a superblock which is going away, but
- * who promise their caller that what it had to write has been written.
- *
- * Only for callers which give up altogether when they are killed. If what
- * comes after the wait happens either way, the wait must not be cut short.
- *
- * The caller must have acquired a temporary reference on @sb->s_passive.
- *
- * Return: 0 once generic_shutdown_super() is done with @sb, -ERESTARTSYS if
- * the task was killed first.
- */
-static int super_wait_shutdown_killable(struct super_block *sb)
-{
- return super_wait(sb, SB_DYING, TASK_KILLABLE);
-}
-
-/**
- * super_wait_shutdown - wait for a superblock being shut down
- * @sb: superblock super_lock() failed on
- *
- * For callers of which the rest happens whether they are killed or not, and
- * must not happen while the filesystem is still being written out.
- *
- * The caller must have acquired a temporary reference on @sb->s_passive.
- */
-static void super_wait_shutdown(struct super_block *sb)
-{
- super_wait(sb, SB_DYING, TASK_UNINTERRUPTIBLE);
+dying:
+ if (flags & SUPER_LOCK_WAIT_SHUTDOWN) {
+ ret = super_wait(sb, SB_DYING, state);
+ if (ret)
+ return ret;
+ }
+ return -ENOENT;
}
/* wake waiters */
@@ -1055,7 +1036,8 @@ static int __iterate_supers(void (*f)(struct super_block *, void *), void *arg,
f(sb, arg);
super_unlock_shared(sb);
} else if (flags & SUPER_ITER_WAIT_SHUTDOWN) {
- ret = super_wait_shutdown_killable(sb);
+ /* whoever shuts it down writes it out, wait for that */
+ ret = super_wait(sb, SB_DYING, TASK_KILLABLE);
}
if (p)
@@ -1285,11 +1267,9 @@ static void do_emergency_remount_callback(struct super_block *sb, void *unused)
{
struct fs_context *fc = NULL;
- if (super_lock(sb, SUPER_LOCK_EXCL)) {
- /* the unmount writes the filesystem out, wait for that */
- super_wait_shutdown(sb);
+ /* the unmount writes the filesystem out, wait for that */
+ if (super_lock(sb, SUPER_LOCK_EXCL | SUPER_LOCK_WAIT_SHUTDOWN))
return;
- }
/* Make sure no reference put under s_umount is the last one. */
atomic_inc(&sb->s_active);
@@ -1329,11 +1309,12 @@ void emergency_remount(void)
}
}
-static inline bool get_active_super(struct super_block *sb)
+static inline bool get_active_super(struct super_block *sb,
+ enum super_lock_flags_t flags)
{
bool active = false;
- if (!super_lock(sb, SUPER_LOCK_EXCL)) {
+ if (!super_lock(sb, SUPER_LOCK_EXCL | flags)) {
active = atomic_inc_not_zero(&sb->s_active);
super_unlock_excl(sb);
}
@@ -1342,7 +1323,7 @@ static inline bool get_active_super(struct super_block *sb)
static void do_thaw_all_callback(struct super_block *sb, void *unused)
{
- if (!get_active_super(sb))
+ if (!get_active_super(sb, 0))
return;
/* fs_bdev_thaw() acquires s_umount so it must not be held here */
@@ -1388,11 +1369,9 @@ static void filesystems_freeze_callback(struct super_block *sb, void *freeze_all
if (!freeze_all_ptr && !(sb->s_type->fs_flags & FS_POWER_FREEZE))
return;
- if (!get_active_super(sb)) {
- /* an unmount from a workqueue isn't stopped by the freezer */
- super_wait_shutdown(sb);
+ /* an unmount from a workqueue isn't stopped by the freezer */
+ if (!get_active_super(sb, SUPER_LOCK_WAIT_SHUTDOWN))
return;
- }
if (sb->s_op->freeze_super)
sb->s_op->freeze_super(sb, FREEZE_EXCL | FREEZE_HOLDER_KERNEL,
@@ -1419,7 +1398,7 @@ static void filesystems_thaw_callback(struct super_block *sb, void *unused)
if (!sb->s_op->freeze_fs && !sb->s_op->freeze_super)
return;
- if (!get_active_super(sb))
+ if (!get_active_super(sb, 0))
return;
if (sb->s_op->thaw_super)
@@ -1660,12 +1639,9 @@ static void fs_bdev_mark_dead(struct block_device *bdev, bool surprise)
mutex_unlock(&bdev->bd_holder_lock);
for_each_dev_super(sb, &cursor) {
- if (super_lock(sb, 0)) {
- /* don't take the device away from under an unmount */
- if (!surprise)
- super_wait_shutdown(sb);
+ /* don't take the device away from under an unmount */
+ if (super_lock(sb, surprise ? 0 : SUPER_LOCK_WAIT_SHUTDOWN))
continue;
- }
if (sb->s_root && (sb->s_flags & SB_ACTIVE)) {
if (!sb->s_op->remove_bdev ||
sb->s_op->remove_bdev(sb, bdev)) {
@@ -1691,20 +1667,18 @@ static void fs_bdev_sync(struct block_device *bdev)
for_each_dev_super_killable(sb, &cursor) {
int ret;
- ret = super_lock(sb, SUPER_LOCK_KILLABLE);
- if (!ret) {
- if (sb->s_root && (sb->s_flags & SB_ACTIVE))
- sync_filesystem(sb);
- super_unlock_shared(sb);
- continue;
- }
+ ret = super_lock(sb, SUPER_LOCK_KILLABLE |
+ SUPER_LOCK_WAIT_SHUTDOWN);
if (ret == -ENOENT)
- ret = super_wait_shutdown_killable(sb);
+ continue;
if (ret) {
super_dev_walk_end(&cursor);
put_super(sb);
return;
}
+ if (sb->s_root && (sb->s_flags & SB_ACTIVE))
+ sync_filesystem(sb);
+ super_unlock_shared(sb);
}
}
@@ -1733,11 +1707,9 @@ static int fs_bdev_freeze(struct block_device *bdev)
mutex_unlock(&bdev->bd_holder_lock);
for_each_dev_super(sb, &cursor) {
- if (!get_active_super(sb)) {
- /* an unmount writes to the device until it is done */
- super_wait_shutdown(sb);
+ /* an unmount writes to the device until it is done */
+ if (!get_active_super(sb, SUPER_LOCK_WAIT_SHUTDOWN))
continue;
- }
err = fs_super_freeze(sb);
if (err && !error)
error = err;
@@ -1791,7 +1763,7 @@ static int fs_bdev_thaw(struct block_device *bdev)
mutex_unlock(&bdev->bd_holder_lock);
for_each_dev_super(sb, &cursor) {
- if (!get_active_super(sb))
+ if (!get_active_super(sb, 0))
continue;
err = fs_super_thaw(sb);
if (err && !error)
--
2.53.0
next prev parent reply other threads:[~2026-10-23 9:42 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-23 9:41 [PATCH 0/5] super: rework super_lock() Christian Brauner
2026-10-23 9:41 ` [PATCH 1/5] super: add super_wait() Christian Brauner
2026-10-23 9:41 ` [PATCH 2/5] super: let super_lock() take flags Christian Brauner
2026-10-23 9:41 ` Christian Brauner [this message]
2026-10-23 9:41 ` [PATCH 4/5] super: make sync(2)'s wait for s_umount killable Christian Brauner
2026-10-23 9:41 ` [PATCH 5/5] super: add a guard for super_lock() Christian Brauner
2026-10-23 13:54 ` [PATCH 0/5] super: rework super_lock() Josef Bacik
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261023-work-super-lock_helpers-v1-3-d0fcb3f2f275@kernel.org \
--to=brauner@kernel.org \
--cc=jack@suse.cz \
--cc=josef@toxicpanda.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®