mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 12:11:40 +00:00
gfs2: Fix withdraw race
Function gfs2_withdraw() tries to synchronize concurrent callers by atomically setting the SDF_WITHDRAWN flag in the first caller, setting the SDF_WITHDRAW_IN_PROG flag to indicate that a withdraw is in progress, performing the actual withdraw, and clearing the SDF_WITHDRAW_IN_PROG flag when done. All other callers wait for the SDF_WITHDRAW_IN_PROG flag to be cleared before returning. This leaves a small window in which callers can find the SDF_WITHDRAWN flag set before the SDF_WITHDRAW_IN_PROG flag has been set, causing them to return prematurely, before the withdraw has been completed. Fix that by setting the SDF_WITHDRAWN and SDF_WITHDRAW_IN_PROG flags atomically. Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
This commit is contained in:
parent
fe0690f0a6
commit
e3da6be3d7
@ -323,19 +323,19 @@ int gfs2_withdraw(struct gfs2_sbd *sdp)
|
||||
struct lm_lockstruct *ls = &sdp->sd_lockstruct;
|
||||
const struct lm_lockops *lm = ls->ls_ops;
|
||||
|
||||
if (sdp->sd_args.ar_errors == GFS2_ERRORS_WITHDRAW &&
|
||||
test_and_set_bit(SDF_WITHDRAWN, &sdp->sd_flags)) {
|
||||
if (!test_bit(SDF_WITHDRAW_IN_PROG, &sdp->sd_flags))
|
||||
return -1;
|
||||
|
||||
wait_on_bit(&sdp->sd_flags, SDF_WITHDRAW_IN_PROG,
|
||||
TASK_UNINTERRUPTIBLE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
set_bit(SDF_WITHDRAW_IN_PROG, &sdp->sd_flags);
|
||||
|
||||
if (sdp->sd_args.ar_errors == GFS2_ERRORS_WITHDRAW) {
|
||||
unsigned long old = READ_ONCE(sdp->sd_flags), new;
|
||||
|
||||
do {
|
||||
if (old & BIT(SDF_WITHDRAWN)) {
|
||||
wait_on_bit(&sdp->sd_flags,
|
||||
SDF_WITHDRAW_IN_PROG,
|
||||
TASK_UNINTERRUPTIBLE);
|
||||
return -1;
|
||||
}
|
||||
new = old | BIT(SDF_WITHDRAWN) | BIT(SDF_WITHDRAW_IN_PROG);
|
||||
} while (unlikely(!try_cmpxchg(&sdp->sd_flags, &old, new)));
|
||||
|
||||
fs_err(sdp, "about to withdraw this file system\n");
|
||||
BUG_ON(sdp->sd_args.ar_debug);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user