commit b2a1be83b85f183f582f07fac0279d40fc62a4db
Author: Qu Wenruo <wqu@suse.com>
Date:   Wed Mar 15 14:06:54 2023 +0800

    btrfs-progs: mkfs: keep file descriptors open during whole time
    
    [BUG]
    There is an internal bug report that, after mkfs.btrfs there is a chance
    that no /dev/disk/by-uuid/<uuid> symlink is not created at all.
    
    [CAUSE]
    That uuid symlink is created by udev, which listens to inotify
    IN_CLOSE_WRITE events from all block devices.
    
    After such IN_CLOSE_WRITE event is triggered, udev would *disable*
    inotify for that block device, and do a blkid scan on it.
    After the blkid scan is done, re-enables the inotify listening.
    
    This means normally mkfs tools should open the fd, do all the writes,
    and close the fd after everything is done.
    
    But unfortunately for mkfs.btrfs, it's not the case, we have a lot of
    phases separated by different close() calls:
    
      open_ctree() would open fds of each involved device
      and close them at close_ctree()
      Only after close_ctree() we have a valid superblock -\
                                                           |
    |<------- A -------->|<--------- B --------->|<------- C ------->|
              |                      |
              |                      `- open a new fd for make_btrfs()
              |                         and close it before open_ctree()
              |                         The device contains invalid sb.
              |
              `- open a new fd for each device, then call
                 btrfs_prepare_device(), then close the fd.
                 The device would contain no valid superblock.
    
    If at the close() of phase A udev event is triggered, while doing udev
    scan we go into phase C (but before the new valid super blocks written),
    udev would only see no superblock or invalid superblock.
    
    Then phase C finished, udev resumes its inotify listening, but at this
    time mkfs is finished, while udev only sees the premature data from
    phase A, and misses the IN_CLOSE_WRITE events from phase C.
    
    [FIX]
    Instead of opening and closing a new fd for each device, re-use the fd
    opened during prepare_one_device(), and close all the fds until
    close_ctree() is called.
    
    By this, although we may still have race between close_ctree() and
    explicit close() calls, at least udev can always see the properly
    written super blocks.
    
    To compensate the change, some extra cleanups are made:
    
    - Do not touch @device_count
      Which makes later prepare_ctx iteration much easier.
    
    - Remove top-level @fd variable
      Instead go with prepare_ctx[i].fd.
    
    - Do not open with O_RDWR in test_dev_for_mkfs()
      as test_dev_for_mkfs() would close the fd, if we go O_RDWR, it can
      cause the udev race.
    
    Reviewed-by: Anand Jain <anand.jain@oracle.com>
    Signed-off-by: Qu Wenruo <wqu@suse.com>
    Signed-off-by: David Sterba <dsterba@suse.com>

Index: btrfs-progs-v5.14/mkfs/common.c
===================================================================
--- btrfs-progs-v5.14.orig/mkfs/common.c
+++ btrfs-progs-v5.14/mkfs/common.c
@@ -939,8 +939,11 @@ int test_dev_for_mkfs(const char *file,
 	ret = test_status_for_mkfs(file, force_overwrite);
 	if (ret)
 		return 1;
-	/* check if the device is busy */
-	fd = open(file, O_RDWR|O_EXCL);
+	/*
+	 * Check if the device is busy. Open it in read-only mode to avoid triggering
+	 * udev events.
+	 */
+	fd = open(file, O_RDONLY | O_EXCL);
 	if (fd < 0) {
 		error("unable to open %s: %m", file);
 		return 1;
Index: btrfs-progs-v5.14/mkfs/main.c
===================================================================
--- btrfs-progs-v5.14.orig/mkfs/main.c
+++ btrfs-progs-v5.14/mkfs/main.c
@@ -68,10 +68,11 @@ static bool opt_zoned = true;
 static int opt_oflags = O_RDWR;
 
 struct prepare_device_progress {
-       char *file;
-       u64 dev_block_count;
-       u64 block_count;
-       int ret;
+	int fd;
+	char *file;
+	u64 dev_block_count;
+	u64 block_count;
+	int ret;
 };
 
 static int create_metadata_block_groups(struct btrfs_root *root, int mixed,
@@ -882,23 +883,21 @@ fail:
 static void *prepare_one_device(void *ctx)
 {
 	struct prepare_device_progress *prepare_ctx = ctx;
-	int fd;
 
-	fd = open(prepare_ctx->file, opt_oflags);
-	if (fd < 0) {
+	prepare_ctx->fd = open(prepare_ctx->file, opt_oflags);
+	if (prepare_ctx->fd < 0) {
 		error("unable to open %s: %m", prepare_ctx->file);
 		prepare_ctx->ret = -errno;
 		return NULL;
 	}
-	prepare_ctx->ret = btrfs_prepare_device(fd, prepare_ctx->file,
+	prepare_ctx->ret = btrfs_prepare_device(prepare_ctx->fd,
+				prepare_ctx->file,
 				&prepare_ctx->dev_block_count,
 				prepare_ctx->block_count,
 				(bconf.verbose ? PREP_DEVICE_VERBOSE : 0) |
 				(opt_zero_end ? PREP_DEVICE_ZERO_END : 0) |
 				(opt_discard ? PREP_DEVICE_DISCARD : 0) |
 				(opt_zoned ? PREP_DEVICE_ZONED : 0));
-	close(fd);
-
 	return NULL;
 }
 
@@ -1255,6 +1254,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv
 	if (source_dir_set) {
 		int oflags = O_RDWR;
 		struct stat statbuf;
+		int fd;
 
 		if (path_exists(file) == 0)
 			oflags |= O_CREAT;
@@ -1421,7 +1421,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv
 	mkfs_cfg.csum_type = csum_type;
 	mkfs_cfg.zone_size = zone_size(file);
 
-	ret = make_btrfs(fd, &mkfs_cfg);
+	ret = make_btrfs(prepare_ctx[0].fd, &mkfs_cfg);
 	if (ret) {
 		errno = -ret;
 		error("error during mkfs: %m");
@@ -1435,8 +1435,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv
 		error("open ctree failed");
 		goto error;
 	}
-	close(fd);
-	fd = -1;
+
 	root = fs_info->fs_root;
 
 	ret = create_metadata_block_groups(root, mixed, &allocation);
@@ -1477,17 +1476,8 @@ int BOX_MAIN(mkfs)(int argc, char **argv
 
 	if (dev_cnt == 0)
 		goto raid_groups;
-
-	while (dev_cnt-- > 0) {
-		int dev_index = argc - saved_optind - dev_cnt - 1;
-		file = argv[optind++];
-
-		fd = open(file, opt_oflags);
-		if (fd < 0) {
-			error("unable to open %s: %m", file);
-			goto error;
-		}
-		ret = btrfs_device_already_in_root(root, fd,
+	for (i = 1; i < dev_cnt; i++) {
+		ret = btrfs_device_already_in_root(root, prepare_ctx[i].fd,
 						   BTRFS_SUPER_INFO_OFFSET);
 		if (ret) {
 			error("skipping duplicate device %s in the filesystem",
@@ -1495,19 +1485,20 @@ int BOX_MAIN(mkfs)(int argc, char **argv
 			close(fd);
 			continue;
 		}
-		dev_block_count = prepare_ctx[dev_index].dev_block_count;
+		dev_block_count = prepare_ctx[i].dev_block_count;
 
-		if (prepare_ctx[dev_index].ret) {
-			errno = -prepare_ctx[dev_index].ret;
-			error("unable to prepare device %s: %m",
-					prepare_ctx[dev_index].file);
+		if (prepare_ctx[i].ret) {
+			errno = -prepare_ctx[i].ret;
+			error("unable to prepare device %s: %m", prepare_ctx[i].file);
 			goto error;
 		}
 
-		ret = btrfs_add_to_fsid(trans, root, fd, file, dev_block_count,
-					sectorsize, sectorsize, sectorsize);
+		ret = btrfs_add_to_fsid(trans, root, prepare_ctx[i].fd,
+				prepare_ctx[i].file, dev_block_count,
+				sectorsize, sectorsize, sectorsize);
 		if (ret) {
-			error("unable to add %s to filesystem: %d", file, ret);
+			error("unable to add %s to filesystem: %d",
+					prepare_ctx[i].file, ret);
 			goto error;
 		}
 		if (verbose >= 2) {
@@ -1651,15 +1642,20 @@ out:
 	}
 
 	btrfs_close_all_devices();
+	if (prepare_ctx) {
+		for (i = 0; i < dev_cnt; i++)
+			close(prepare_ctx[i].fd);
+	}
 	free(t_prepare);
 	free(prepare_ctx);
 	free(label);
 
 	return !!ret;
 error:
-	if (fd > 0)
-		close(fd);
-
+	if (prepare_ctx) {
+		for (i = 0; i < dev_cnt; i++)
+			close(prepare_ctx[i].fd);
+	}
 	free(t_prepare);
 	free(prepare_ctx);
 	free(label);
