From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C055E43C7B4 for ; Thu, 24 Sep 2026 22:36:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790289364; cv=none; b=rd39gPz+U2iDWUAIBQt4siQLCF5XrX8jlw0WWHGf/vtmdhoWcyRIpKyOM7p3Ni4jP032E7SXMMZWsGn+HT3jU64aw96oPEg6pomcoeLDsDcnMBDmDK+kerYSwK/zXFUTD7PR2FO3XvGtm26qUsZQMBn0g7jvb+dvIaufOAC4rDs= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790289364; c=relaxed/simple; bh=vVPl82I0C3uQBrS9LYdp8YNP0IB2qNwWdH923lGVrU0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=TcL9qw0ZnTsRdth2GKiDSID8gfmawgRCY+JVBuf85E0vJCq2HIw2gcc5BRideMdEE/QDpNzn3YWbz/88NArUImo3kZR2TwUvQPSdpCn6TcMexEHo5YmMXIIF57iMX/sLTsIQVpFLqSEEbaBS41aC0tk3PCmlbntYZfR6WnsaTFY= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=BNaqAm9G; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="BNaqAm9G" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E335B1F000FF; Thu, 24 Sep 2026 22:36:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1790289362; bh=9J03Qe1gtni+OJzxo2cbcj3X+Rg95uG3mC4saM7hVbw=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=BNaqAm9GNIqA59bheSwcW0zTLlBWWDJykBXC0ie9VDAeu/sSQR96GB2WiX8MaQiIG zlokroREG86xokKdQ+mXj7+Z7iGzQS/amwqAmWF83MKhI2pG44YtSwlZLF/FUu1ki9 57Y419PAypUxc+qUPuNPhqa8o3c8SJInD47oPhHVvPuvRbs5CF+MGf0hg+8TpXVnIA 7PnKJUUqwGnSHgFUH9W5hSoMKe+FxJsoIjPhx/ixbl4KxKhQb0W5Zl2YCMj9i+N7E6 ZTS2AQTgbEYylJswoxT/dK4ClQuu6+C35iKMKVJSV65TZtMMsIFT6G8oA22PQLR7x/ A7OWI01t92tlg== From: Christian Brauner Date: Fri, 25 Sep 2026 00:35:47 +0200 Subject: [PATCH RFC v2 7/8] selftests/filesystems: check the two-step cycle over crossed loop images Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260925-work-mount-knullfs-v2-7-c4aebaa186e9@kernel.org> References: <20260925-work-mount-knullfs-v2-0-c4aebaa186e9@kernel.org> In-Reply-To: <20260925-work-mount-knullfs-v2-0-c4aebaa186e9@kernel.org> To: Linus Torvalds Cc: Jann Horn , Jan Kara , Amir Goldstein , linux-fsdevel@vger.kernel.org, Alexander Viro , "Christian Brauner (Amutable)" X-Mailer: b4 0.17-dev-db0b7 X-Developer-Signature: v=1; a=openpgp-sha256; l=4326; i=brauner@kernel.org; h=from:subject:message-id; bh=vVPl82I0C3uQBrS9LYdp8YNP0IB2qNwWdH923lGVrU0=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWRtXXpgrSZHGnNNvf6T4P++Jpv8Z5of/X52tofqjlcpA b4RdhVSHaUsDGJcDLJiiiwO7Sbhcst5KjYbZWrAzGFlAhnCwMUpABOJPcrwP8A3eZq34EoVR7Zl 2ktOBF6d8N5lXUP8Wq+K5XaXv2xgLWJkmHxd1q0kcUOV9LV7b1OMWPnOLLk+uSCC6+77rjnrrn3 cyAsA X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Two tmpfs mounts in a child namespace, each carrying the image of the loop mount below the other. Check that both loop devices are released after: - rmdir of the first tmpfs' mountpoint from the parent namespace, which leaves the loop mount below it connected while its image's mount lives - rmdir of the second, which takes it down with the loop mount whose image is on the first, by then dead, tmpfs Each dead tmpfs then owns a loop mount whose filesystem pins the other. Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/mount_cycle/loop_cycle_test.c | 88 ++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c index 67fc3e420c92..4f4c88397861 100644 --- a/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c +++ b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c @@ -204,6 +204,34 @@ static int loop_mount(const char *img, const char *mp) return n; } +/* + * Two tmpfs mounts, each carrying the image of the loop mount below the + * other: the loop mount below /mnt_dir/vol has its image on /mnt_dir/vol2 + * and the other way round. + */ +static int crossed_child(int to_parent, int from_parent) +{ + int n[2]; + char c; + + if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return CHILD_NS; + if (mkdir("/mnt_dir/vol", 0755) || mount("tmpfs", "/mnt_dir/vol", "tmpfs", 0, NULL) || + mkdir("/mnt_dir/vol2", 0755) || mount("tmpfs", "/mnt_dir/vol2", "tmpfs", 0, NULL)) + return CHILD_NS; + n[0] = loop_mount("/mnt_dir/vol2/img", "/mnt_dir/vol/mnt"); + if (n[0] < 0) + return -n[0]; + n[1] = loop_mount("/mnt_dir/vol/img", "/mnt_dir/vol2/mnt"); + if (n[1] < 0) + return -n[1]; + if (write(to_parent, n, sizeof(n)) != sizeof(n)) + return CHILD_PIPE; + if (read(from_parent, &c, 1) != 1) + return CHILD_PIPE; + return CHILD_OK; +} + /* * In its own mount namespace the child mounts a tmpfs on /mnt_dir/vol, * puts a filesystem image on it, binds a loop device to the image and @@ -374,4 +402,64 @@ TEST_F(loop_cycle, dissolved_tree_loop_mount_released) assert_loop_released(_metadata, self); } +/* + * The cycle in two steps: rmdir of /mnt_dir/vol leaves the loop mount + * below it connected while its image's mount, /mnt_dir/vol2, is alive; + * then rmdir of /mnt_dir/vol2 takes that one with the loop mount whose + * image is on the dead /mnt_dir/vol. Each dead mount now owns a loop + * mount whose filesystem pins the other. + */ +TEST_F(loop_cycle, crossed_images_released) +{ + int to_parent[2], to_child[2]; + char sysfs[2][64], dev[2][32]; + int status, n[2] = { -1, -1 }; + pid_t pid; + + ASSERT_EQ(pipe(to_parent), 0); + ASSERT_EQ(pipe(to_child), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_parent[0]); + close(to_child[1]); + _exit(crossed_child(to_parent[1], to_child[0])); + } + close(to_parent[1]); + close(to_child[0]); + + if (read(to_parent[0], n, sizeof(n)) != sizeof(n)) { + waitpid(pid, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == CHILD_MOUNT) + SKIP(return, "test requires a FAT filesystem"); + ASSERT_EQ(status, 0); + } + for (int i = 0; i < 2; i++) { + snprintf(dev[i], sizeof(dev[i]), "/dev/loop%d", n[i]); + snprintf(sysfs[i], sizeof(sysfs[i]), "/sys/block/loop%d/loop/backing_file", n[i]); + } + + /* step one: the mount with the first loop mount below it goes */ + ASSERT_EQ(rmdir("/mnt_dir/vol"), 0); + + /* step two: the other one, with the loop mount whose image is on the first */ + ASSERT_EQ(rmdir("/mnt_dir/vol2"), 0); + + /* the child leaves: its namespace and every reference it held are gone */ + ASSERT_EQ(write(to_child[1], "", 1), 1); + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_EQ(status, 0); + close(to_parent[0]); + close(to_child[1]); + + /* nothing can reach the four mounts any more */ + for (int i = 0; i < 2; i++) { + ASSERT_FALSE(mounted_anywhere(dev[i])); + strcpy(self->dev, dev[i]); + strcpy(self->sysfs, sysfs[i]); + assert_loop_released(_metadata, self); + } +} + TEST_HARNESS_MAIN -- 2.53.0