mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Christian Borntraeger <borntraeger@linux.ibm.com>
To: Qu Wenruo <quwenruo.btrfs@gmx.com>, Chris Mason <clm@fb.com>,
	David Sterba <dsterba@suse.com>, Qu Wenruo <wqu@suse.com>
Cc: linux-btrfs@vger.kernel.org,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>
Subject: Re: Lockdep circular dependency: btrfs_swap_activate() calls sysfs_notify()
Date: Fri, 31 Jul 2026 13:14:50 +0200	[thread overview]
Message-ID: <6a5c7341-6e88-4eaf-8311-0de81b3d1015@linux.ibm.com> (raw)
In-Reply-To: <11be35c2-7ede-4c83-b21f-39e52c3b04a2@gmx.com>



Am 31.07.26 um 13:12 schrieb Qu Wenruo:
> 
> 
> 在 2026/7/31 20:27, Christian Borntraeger 写道:
>> One or more of the following files ( btrfs-swapon-kernfs-repro.sh ) violates IBM policy and all attachment(s) have been removed from the message.

Oh.


Lets try this:
#!/bin/bash
# Reproducer for the lockdep circular dependency
#
#   &root->kernfs_rwsem --> sb_pagefaults --> &ei->i_mmap_lock
#
# btrfs_swap_activate() calls btrfs_exclop_finish() -> sysfs_notify() ->
# kernfs_rwsem while holding the inode's i_mmap_lock for write, while
# kernfs_fop_readdir() holds kernfs_rwsem across filldir64() (-> mmap_lock)
# and a btrfs write fault goes mmap_lock -> sb_pagefaults -> i_mmap_lock.
#
#
# Must be run as root, on a kernel with CONFIG_PROVE_LOCKING and
# CONFIG_DEBUG_ATOMIC_SLEEP.  Run it on an unpatched kernel to see the splat,
# and on a patched one to confirm it is gone.
#
# Usage: ./btrfs-swapon-kernfs-repro.sh [existing-btrfs-mountpoint]
#
# With no argument a 512M loopback btrfs image is created under /var/tmp and
# removed again at the end.  With an argument, that existing single-device
# btrfs mount is used and only the test files inside it are removed.
#
# Exit codes: 0 = no splat (expected with the fix), 1 = reproduced,
#             2 = inconclusive (setup problem, or lockdep already off).

set -u

IMG=""
LOOP=""
MNT=""
OWN_FS=0
SWAPFILE=""
SWAP_ON=0
SWAPON_OK=0

msg()  { printf '[repro] %s\n' "$*"; }
fail() { printf '[repro] ERROR: %s\n' "$*" >&2; exit 2; }

cleanup() {
	set +e
	[ "$SWAP_ON" = 1 ] && { msg "swapoff $SWAPFILE"; swapoff "$SWAPFILE"; }
	if [ "$OWN_FS" = 1 ]; then
		[ -n "$MNT" ]  && umount "$MNT" 2>/dev/null && rmdir "$MNT"
		[ -n "$LOOP" ] && losetup -d "$LOOP" 2>/dev/null
		[ -n "$IMG" ]  && rm -f "$IMG"
	elif [ -n "$MNT" ]; then
		# Only ever the two files this script created, never a bare path.
		[ -n "$SWAPFILE" ] && rm -f "$SWAPFILE"
		rm -f "$MNT/lockdep-mmap.dat"
	fi
}
trap cleanup EXIT

# ---------------------------------------------------------------- checks ---

[ "$(id -u)" = 0 ] || fail "must be run as root"

for t in mkfs.btrfs losetup mkswap swapon swapoff chattr dmesg \
	 truncate stat find dd sync mount umount; do
	command -v "$t" >/dev/null || fail "missing tool: $t"
done

[ -e /proc/lockdep_stats ] || fail \
	"no /proc/lockdep_stats -- kernel lacks CONFIG_PROVE_LOCKING, nothing to see"

# A previous splat disables lockdep for the rest of the boot ("INFO: lockdep
# is turned off").  Nothing will be reported after that, so bail out early
# rather than report a bogus PASS.
if grep -qE '^ *debug_locks: *0' /proc/lockdep_stats; then
	fail "lockdep is already turned off (earlier splat this boot) -- reboot first"
fi

# Edge #1 of the cycle needs the might_fault() annotation in filldir64().
KCONF=""
for c in /proc/config.gz "/boot/config-$(uname -r)"; do
	[ -e "$c" ] && { KCONF="$c"; break; }
done
if [ -n "$KCONF" ]; then
	if [ "${KCONF##*.}" = gz ]; then RDCONF="zcat"; else RDCONF="cat"; fi
	$RDCONF "$KCONF" | grep -q '^CONFIG_DEBUG_ATOMIC_SLEEP=y' || msg \
		"WARNING: CONFIG_DEBUG_ATOMIC_SLEEP is not set -- filldir64() will not
          annotate mmap_lock and the cycle cannot be detected"
else
	msg "note: could not find the kernel config, not checking DEBUG_ATOMIC_SLEEP"
fi

MMAP_HELPER=""
if command -v python3 >/dev/null; then
	MMAP_HELPER=python3
elif command -v cc >/dev/null; then
	MMAP_HELPER=cc
else
	fail "need python3 or cc to do the mmap write fault"
fi

msg "kernel $(uname -r), lockdep active"

# Cursor for the verdict.  Taken before the first step, not just before
# swapon: if some earlier boot activity already registered the
# i_mmap_lock -> kernfs_rwsem edge, the cycle is completed by the mmap or
# the readdir step below instead, and the splat has to be caught there too.
DMESG_LINES=$(dmesg | wc -l)

# ------------------------------------------------------------ test btrfs ---

if [ $# -ge 1 ]; then
	MNT="$1"
	[ -d "$MNT" ] || fail "$MNT is not a directory"
	[ "$(stat -f -c %T "$MNT")" = btrfs ] || fail "$MNT is not btrfs"
	msg "using existing btrfs at $MNT"
else
	OWN_FS=1
	IMG=$(mktemp /var/tmp/btrfs-swapon-repro.XXXXXX.img) || fail "mktemp failed"
	MNT=$(mktemp -d /var/tmp/btrfs-swapon-repro.XXXXXX.mnt) || fail "mktemp failed"
	msg "creating 512M btrfs image $IMG"
	truncate -s 512M "$IMG"                     || fail "truncate failed"
	LOOP=$(losetup --find --show "$IMG")        || fail "losetup failed"
	mkfs.btrfs -q -f "$LOOP"                    || fail "mkfs.btrfs failed"
	# No compression: a compressed swap file is rejected by btrfs.
	mount -o compress=no "$LOOP" "$MNT"         || fail "mount failed"
	msg "mounted $LOOP on $MNT"
fi

# ------------------------------------ edges #2/#3: mmap write fault on btrfs --
# handle_mm_fault -> do_page_mkwrite -> btrfs_page_mkwrite
#   -> sb_start_pagefault()            [sb_pagefaults]
#   -> down_read(&BTRFS_I(inode)->i_mmap_lock)
# Both the do_fault (first touch is a write) and the do_wp_page (read first,
# then write) variants are exercised.

MMAPFILE="$MNT/lockdep-mmap.dat"
dd if=/dev/zero of="$MMAPFILE" bs=4096 count=64 status=none || fail "dd failed"
sync

msg "dirtying a btrfs file mapping (mmap_lock -> sb_pagefaults -> i_mmap_lock)"
if [ "$MMAP_HELPER" = python3 ]; then
	python3 - "$MMAPFILE" <<-'EOF' || fail "mmap helper failed"
		import mmap, os, sys
		fd = os.open(sys.argv[1], os.O_RDWR)
		m = mmap.mmap(fd, 4096 * 64)
		m[0:8] = b"lockdep!"          # write fault on an absent page (do_fault)
		_ = m[8192]                   # populate clean ...
		m[8192:8200] = b"lockdep!"    # ... then dirty it (do_wp_page)
		m.flush()
		m.close()
		os.close(fd)
	EOF
else
	HELPER=$(mktemp -d)
	cat > "$HELPER/mw.c" <<-'EOF'
		#include <fcntl.h>
		#include <stdio.h>
		#include <sys/mman.h>
		#include <unistd.h>
		int main(int argc, char **argv)
		{
			size_t len = 4096 * 64;
			char *p;
			int fd = open(argv[1], O_RDWR);

			if (fd < 0)
				return perror("open"), 1;
			p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
			if (p == MAP_FAILED)
				return perror("mmap"), 1;
			p[0] = 'x';                  /* do_fault */
			(void)*(volatile char *)(p + 8192);
			p[8192] = 'x';               /* do_wp_page */
			msync(p, len, MS_SYNC);
			munmap(p, len);
			close(fd);
			return 0;
		}
	EOF
	cc -O2 -o "$HELPER/mw" "$HELPER/mw.c" || fail "compiling the mmap helper failed"
	"$HELPER/mw" "$MMAPFILE" || fail "mmap helper failed"
	rm -rf "$HELPER"
fi

# ------------------------------------------- edge #1: kernfs readdir + fault --
# kernfs_fop_readdir() holds kernfs_rwsem for read across the whole emit loop
# and calls filldir64() -> __might_fault() -> mmap_lock.  Any getdents on a
# sysfs directory registers it.

msg "reading sysfs directories (kernfs_rwsem -> mmap_lock)"
for d in /sys/fs/btrfs /sys/class/net /sys/block /sys/devices/system/cpu; do
	[ -d "$d" ] && ls -fU "$d" >/dev/null 2>&1
done
find /sys/fs/btrfs -maxdepth 3 >/dev/null 2>&1

# ------------------------------------------- edge #0: swapon on btrfs (bang) --
# btrfs_swap_activate() holds i_mmap_lock for write across
# btrfs_exclop_finish() -> sysfs_notify() -> kernfs_find_and_get_ns()
#   -> down_read(&root->kernfs_rwsem)
#
# The swap file has to satisfy every btrfs_swap_activate() precondition:
# NODATACOW and NODATASUM (both from chattr +C on a still empty file), not
# compressed, no holes, no inline extent, not shared.  fallocate() is not
# used on purpose -- the extents are written out for real.

SWAPFILE="$MNT/lockdep-swapfile"
msg "creating swap file $SWAPFILE"
rm -f "$SWAPFILE"
touch "$SWAPFILE"                 || fail "touch failed"
chattr +C "$SWAPFILE"             || fail "chattr +C failed (needs NODATACOW)"
chmod 600 "$SWAPFILE"
dd if=/dev/zero of="$SWAPFILE" bs=1M count=64 status=none || fail "dd failed"
sync
mkswap "$SWAPFILE" >/dev/null     || fail "mkswap failed"

msg "swapon -- this is the call that closes the cycle"
if swapon -p 0 "$SWAPFILE"; then
	SWAP_ON=1
	SWAPON_OK=1
	msg "swapon succeeded"
else
	msg "swapon FAILED -- check the btrfs_warn above for which precondition"
	msg "(the lockdep edge is taken on the error paths too, so keep reading)"
fi

sleep 1

# ------------------------------------------------------------------ verdict --

NEW=$(dmesg | tail -n +$((DMESG_LINES + 1)))

if printf '%s\n' "$NEW" | grep -q "possible circular locking dependency"; then
	printf '%s\n' "$NEW" | sed -n '/possible circular locking dependency/,$p'
	if printf '%s\n' "$NEW" | grep -q "kernfs_rwsem"; then
		msg "REPRODUCED: the expected cycle, kernfs_rwsem is in the chain"
		exit 1
	fi
	msg "INCONCLUSIVE: a circular dependency was reported, but without"
	msg "kernfs_rwsem -- that is a different bug, compare against more.txt"
	exit 2
fi

msg "no lockdep splat"
if grep -qE '^ *debug_locks: *0' /proc/lockdep_stats; then
	msg "INCONCLUSIVE: lockdep turned itself off during the run"
	exit 2
fi
if [ "$SWAPON_OK" = 0 ]; then
	msg "INCONCLUSIVE: swapon did not get far enough, fix that first"
	exit 2
fi
msg "PASS: the i_mmap_lock -> kernfs_rwsem edge was not taken"
msg "(expected on a kernel carrying btrfs-swapon-kernfs-fix.patch)"
exit 0

  reply	other threads:[~2026-07-31 11:14 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-31 10:57 Christian Borntraeger
2026-07-31 11:12 ` Qu Wenruo
2026-07-31 11:14   ` Christian Borntraeger [this message]
2026-08-03  9:07     ` Christian Borntraeger
2026-08-03  9:44       ` Qu Wenruo

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=6a5c7341-6e88-4eaf-8311-0de81b3d1015@linux.ibm.com \
    --to=borntraeger@linux.ibm.com \
    --cc=clm@fb.com \
    --cc=dsterba@suse.com \
    --cc=linux-btrfs@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=quwenruo.btrfs@gmx.com \
    --cc=wqu@suse.com \
    /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®