• NeilBrown's avatar
    md: fix test in md_write_start() · febaf832
    NeilBrown authored
    commit 81fe48e9 upstream.
    
    md_write_start() needs to clear the in_sync flag is it is set, or if
    there might be a race with set_in_sync() such that the later will
    set it very soon.  In the later case it is sufficient to take the
    spinlock to synchronize with set_in_sync(), and then set the flag
    if needed.
    
    The current test is incorrect.
    It should be:
      if "flag is set" or "race is possible"
    
    "flag is set" is trivially "mddev->in_sync".
    "race is possible" should be tested by "mddev->sync_checkers".
    
    If sync_checkers is 0, then there can be no race.  set_in_sync() will
    wait in percpu_ref_switch_to_atomic_sync() for an RCU grace period,
    and as md_write_start() holds the rcu_read_lock(), set_in_sync() will
    be sure ot see the update to writes_pending.
    
    If sync_checkers is > 0, there could be race.  If md_write_start()
    happened entirely between
    		if (!mddev->in_sync &&
    		    percpu_ref_is_zero(&mddev->writes_pending)) {
    and
    			mddev->in_sync = 1;
    in set_in_sync(), then it would not see that is_sync had been set,
    and set_in_sync() would not see that writes_pending had been
    incremented.
    
    This bug means that in_sync is sometimes not set when it should be.
    Consequently there is a small chance that the array will be marked as
    "clean" when in fact it is inconsistent.
    
    Fixes: 4ad23a97 ("MD: use per-cpu counter for writes_pending")
    Signed-off-by: default avatarNeilBrown <neilb@suse.com>
    Signed-off-by: default avatarShaohua Li <shli@fb.com>
    Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
    febaf832
md.c 241 KB