From feafa9f682a18ff395f8c12924a8619b3bd09acf Mon Sep 17 00:00:00 2001 From: Peter Rajnoha Date: Mon, 8 Jun 2026 12:09:01 +0200 Subject: [PATCH 1/8] Incremental: move wait_for() to end of non-container path Move the symlink wait to the common exit path, after udev is unblocked and the uevent is sent. The re-add loop does not need the symlink. Add a flag to distinguish successful array start from the "not enough disks" case to avoid spurious timeouts. Signed-off-by: Peter Rajnoha --- Incremental.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Incremental.c b/Incremental.c index f717c9c3..39715357 100644 --- a/Incremental.c +++ b/Incremental.c @@ -107,6 +107,7 @@ int Incremental(struct mddev_dev *devlist, struct context *c, int have_target; char *devname = devlist->devname; int journal_device_missing = 0; + bool array_started = false; if (!stat_is_blkdev(devname, &rdev)) return rv; @@ -604,8 +605,7 @@ int Incremental(struct mddev_dev *devlist, struct context *c, } else if (c->verbose >= 0) pr_err("%s attached to %s, which has been started.\n", devname, chosen_name); - rv = 0; - wait_for(chosen_name, mdfd); + array_started = true; /* We just started the array, so some devices * might have been evicted from the array * because their event counts were too old. @@ -638,8 +638,6 @@ int Incremental(struct mddev_dev *devlist, struct context *c, free(avail); if (dfd >= 0) close(dfd); - if (mdfd >= 0) - close(mdfd); if (policy) dev_policy_free(policy); udev_unblock(); @@ -647,6 +645,10 @@ int Incremental(struct mddev_dev *devlist, struct context *c, sysfs_uevent(sra, "change"); sysfs_free(sra); } + if (array_started) + wait_for(chosen_name, mdfd); + if (mdfd >= 0) + close(mdfd); return rv; out_unlock: map_unlock(&map); From a1dce17b59796b8548c2e31b7a0ae83c860aacb8 Mon Sep 17 00:00:00 2001 From: Peter Rajnoha Date: Mon, 8 Jun 2026 12:10:09 +0200 Subject: [PATCH 2/8] Incremental: move wait_for() after sysfs_uevent() in container path Waiting for the symlink before sending the uevent causes a race: udev may see the initial add event before metadata is set, skip symlink creation, and cause a ~3.6s timeout. Move the symlink wait after the uevent so udev re-evaluates with correct metadata first. Signed-off-by: Peter Rajnoha --- Incremental.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Incremental.c b/Incremental.c index 39715357..5935ed7f 100644 --- a/Incremental.c +++ b/Incremental.c @@ -470,12 +470,10 @@ int Incremental(struct mddev_dev *devlist, struct context *c, chosen_name, info.array.working_disks, info.array.working_disks == 1?"":"s"); sysfs_rules_apply(chosen_name, &info, st); - wait_for(chosen_name, mdfd); if (st->ss->external) strcpy(devnm, fd2devnm(mdfd)); if (st->ss->load_container) rv = st->ss->load_container(st, mdfd, NULL); - close(mdfd); udev_unblock(); sysfs_uevent(sra, "change"); sysfs_free(sra); @@ -486,7 +484,8 @@ int Incremental(struct mddev_dev *devlist, struct context *c, * so that it can eg. try to rebuild degraded array */ if (st->ss->external) ping_monitor(devnm); - udev_unblock(); + wait_for(chosen_name, mdfd); + close(mdfd); return rv; } From 493745efc8fae63b5967405abe55f2038b53e512 Mon Sep 17 00:00:00 2001 From: Peter Rajnoha Date: Mon, 8 Jun 2026 12:10:27 +0200 Subject: [PATCH 3/8] Assemble: move wait_for() after sysfs_rules_apply() Sysfs rule application does not need the symlink. Move the symlink wait after it so it is the last step before return. Signed-off-by: Peter Rajnoha --- Assemble.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assemble.c b/Assemble.c index c7563ab2..2d1ff448 100644 --- a/Assemble.c +++ b/Assemble.c @@ -2221,8 +2221,8 @@ int assemble_container_content(struct supertype *st, int mdfd, set_array_assembly_status(c, result, INCR_NO, &array); else { set_array_assembly_status(c, result, INCR_YES, &array); - wait_for(chosen_name, mdfd); sysfs_rules_apply(chosen_name, content, st); + wait_for(chosen_name, mdfd); } return err; From 3e5e1459ce7af5eb789841e29f9ec04a6f7d0e6a Mon Sep 17 00:00:00 2001 From: Peter Rajnoha Date: Mon, 22 Jun 2026 11:52:50 +0200 Subject: [PATCH 4/8] create_mddev: call udev_unblock() on failure paths Several error paths return without unblocking udev, leaking the blocking file on disk. Consolidate cleanup into a single error exit so all post-block failures unblock properly. Remove the redundant safety net in the caller now that create_mddev() handles its own cleanup. Signed-off-by: Peter Rajnoha --- Incremental.c | 1 - mdopen.c | 15 +++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Incremental.c b/Incremental.c index 5935ed7f..bf13e81a 100644 --- a/Incremental.c +++ b/Incremental.c @@ -1653,7 +1653,6 @@ static int Incremental_container(struct supertype *st, char *devname, release: map_free(map); sysfs_free(list); - udev_unblock(); sysfs_uevent(&info, "change"); return rv; } diff --git a/mdopen.c b/mdopen.c index 9af0284b..83a00262 100644 --- a/mdopen.c +++ b/mdopen.c @@ -327,14 +327,14 @@ int create_mddev(char *dev, char *name, int trustworthy, stb.st_rdev != devnm2devid(devnm)) { pr_err("%s exists but looks wrong, please fix\n", devname); - return -1; + goto error; } } else { if (mknod(devname, S_IFBLK|0600, devnm2devid(devnm)) != 0) { pr_err("failed to create %s\n", devname); - return -1; + goto error; } if (chown(devname, ci->uid, ci->gid)) perror("chown"); @@ -343,7 +343,7 @@ int create_mddev(char *dev, char *name, int trustworthy, if (stat(devname, &stb) < 0) { pr_err("failed to stat %s\n", devname); - return -1; + goto error; } add_dev(devname, &stb, 0, NULL); } @@ -379,10 +379,17 @@ int create_mddev(char *dev, char *name, int trustworthy, } } mdfd = open_dev_excl(devnm); - if (mdfd < 0) + if (mdfd < 0) { pr_err("unexpected failure opening %s\n", devname); + goto error; + } return mdfd; + +error: + if (block_udev) + udev_unblock(); + return -1; } /* Open this and check that it is an md device. From 9c97f4efc275ee36e819e876f222597202dccd4e Mon Sep 17 00:00:00 2001 From: Peter Rajnoha Date: Thu, 18 Jun 2026 14:46:58 +0200 Subject: [PATCH 5/8] Incremental: remove redundant sysfs_uevent() from container release path The release path sends a uevent unconditionally, but subarrays already get their own after assembly and error paths have nothing to signal. Signed-off-by: Peter Rajnoha --- Incremental.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Incremental.c b/Incremental.c index bf13e81a..7ec6a167 100644 --- a/Incremental.c +++ b/Incremental.c @@ -1653,7 +1653,6 @@ static int Incremental_container(struct supertype *st, char *devname, release: map_free(map); sysfs_free(list); - sysfs_uevent(&info, "change"); return rv; } From 40f161ade8a1187c8511396d89413cf32b8d345e Mon Sep 17 00:00:00 2001 From: Peter Rajnoha Date: Fri, 19 Jun 2026 09:04:58 +0200 Subject: [PATCH 6/8] Incremental: call udev_unblock() on skipped container members When a container member is skipped by the filter, udev is not unblocked, leaking the blocking file on disk. Signed-off-by: Peter Rajnoha --- Incremental.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Incremental.c b/Incremental.c index 7ec6a167..7307522e 100644 --- a/Incremental.c +++ b/Incremental.c @@ -1614,6 +1614,7 @@ static int Incremental_container(struct supertype *st, char *devname, } if (only && (!mp || strcmp(mp->devnm, only) != 0)) { + udev_unblock(); close_fd(&mdfd); continue; } From e854411cf0d544f6595f70cc10948e68a690a17a Mon Sep 17 00:00:00 2001 From: Peter Rajnoha Date: Thu, 25 Jun 2026 14:22:08 +0200 Subject: [PATCH 7/8] Introduce udev_ready() wrapper for udev_unblock() + sysfs_uevent() Add udev_ready() that combines udev_unblock() with sysfs_uevent() for the common "device is ready" path. This replaces the recurring pattern of calling both functions in sequence when an array has been fully set up and is ready for udev to re-evaluate. Call sites that only need udev_unblock() (error/abort/skip paths) or only sysfs_uevent() (container mid-path in Create) are left unchanged, as they represent distinct use cases. Signed-off-by: Peter Rajnoha --- Create.c | 3 +-- Incremental.c | 11 +++++------ udev.c | 12 ++++++++++++ udev.h | 1 + 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Create.c b/Create.c index 3a4eff77..8af618c2 100644 --- a/Create.c +++ b/Create.c @@ -1331,8 +1331,7 @@ int Create(struct supertype *st, struct mddev_ident *ident, int subdevs, pr_err("not starting array - not enough devices.\n"); } close(mdfd); - udev_unblock(); - sysfs_uevent(&info, "change"); + udev_ready(&info); dev_policy_free(custom_pols); return 0; diff --git a/Incremental.c b/Incremental.c index 7307522e..3187bed0 100644 --- a/Incremental.c +++ b/Incremental.c @@ -474,8 +474,7 @@ int Incremental(struct mddev_dev *devlist, struct context *c, strcpy(devnm, fd2devnm(mdfd)); if (st->ss->load_container) rv = st->ss->load_container(st, mdfd, NULL); - udev_unblock(); - sysfs_uevent(sra, "change"); + udev_ready(sra); sysfs_free(sra); if (!rv) rv = Incremental_container(st, chosen_name, c, NULL); @@ -639,10 +638,11 @@ int Incremental(struct mddev_dev *devlist, struct context *c, close(dfd); if (policy) dev_policy_free(policy); - udev_unblock(); if (sra) { - sysfs_uevent(sra, "change"); + udev_ready(sra); sysfs_free(sra); + } else { + udev_unblock(); } if (array_started) wait_for(chosen_name, mdfd); @@ -1626,8 +1626,7 @@ static int Incremental_container(struct supertype *st, char *devname, sysname = fd2devnm(mdfd); strncpy(info.sys_name, sysname, sizeof(sysname) - 1); close_fd(&mdfd); - udev_unblock(); - sysfs_uevent(&info, "change"); + udev_ready(&info); } if (c->export && result) { char sep = '='; diff --git a/udev.c b/udev.c index f857c723..af30463e 100644 --- a/udev.c +++ b/udev.c @@ -202,3 +202,15 @@ void udev_unblock(void) free(unblock_path); unblock_path = NULL; } + +/* + * udev_ready() - Unblock udev and signal that the device is ready. + * + * Removes the blocking file and sends a synthetic change uevent + * so udev re-evaluates the device in its final state. + */ +void udev_ready(struct mdinfo *sra) +{ + udev_unblock(); + sysfs_uevent(sra, "change"); +} diff --git a/udev.h b/udev.h index ae0a3617..db2ef1d3 100644 --- a/udev.h +++ b/udev.h @@ -36,5 +36,6 @@ enum udev_status udev_wait_for_events(int seconds); enum udev_status udev_block(char *devnm); void udev_unblock(void); +void udev_ready(struct mdinfo *sra); #endif From fc587cbc880685c093b87fcfb7dcd4aa1a5fc493 Mon Sep 17 00:00:00 2001 From: Peter Rajnoha Date: Thu, 25 Jun 2026 14:25:52 +0200 Subject: [PATCH 8/8] Guard udev_unblock()/sysfs_uevent() based on udev blocking state Move the udev_is_available() check from create_mddev() to its callers in Incremental.c and Create.c, letting them track in a local udev_blocked variable whether udev was actually blocked. All udev_unblock() and udev_ready() calls are then guarded by this flag, preventing spurious calls when udev is not available or when the code path never called create_mddev() (e.g. second disk arriving for an already-existing IMSM container). Add a diagnostic to udev_unblock() that warns if called without a prior udev_block(), catching any remaining misuse. Signed-off-by: Peter Rajnoha --- Create.c | 13 +++++++++---- Incremental.c | 28 ++++++++++++++++++---------- mdopen.c | 9 ++++----- udev.c | 8 ++++++-- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/Create.c b/Create.c index 8af618c2..acf75aba 100644 --- a/Create.c +++ b/Create.c @@ -523,6 +523,7 @@ int Create(struct supertype *st, struct mddev_ident *ident, int subdevs, int container_fd = -1; int need_mdmon = 0; unsigned long long bitmapsize; + bool udev_blocked; struct mdinfo info; int did_default = 0; int do_default_layout = 0; @@ -1029,7 +1030,8 @@ int Create(struct supertype *st, struct mddev_ident *ident, int subdevs, /* We need to create the device */ map_lock(&map); - mdfd = create_mddev(ident->devname, ident->name, LOCAL, chosen_name, 1); + udev_blocked = udev_is_available(); + mdfd = create_mddev(ident->devname, ident->name, LOCAL, chosen_name, udev_blocked); if (mdfd < 0) { map_unlock(&map); return 1; @@ -1043,7 +1045,8 @@ int Create(struct supertype *st, struct mddev_ident *ident, int subdevs, pr_err("Array name %s is in use already.\n", chosen_name); close(mdfd); map_unlock(&map); - udev_unblock(); + if (udev_blocked) + udev_unblock(); return 1; } @@ -1331,13 +1334,15 @@ int Create(struct supertype *st, struct mddev_ident *ident, int subdevs, pr_err("not starting array - not enough devices.\n"); } close(mdfd); - udev_ready(&info); + if (udev_blocked) + udev_ready(&info); dev_policy_free(custom_pols); return 0; abort: - udev_unblock(); + if (udev_blocked) + udev_unblock(); map_lock(&map); abort_locked: map_remove(&map, fd2devnm(mdfd)); diff --git a/Incremental.c b/Incremental.c index 3187bed0..2cf625ef 100644 --- a/Incremental.c +++ b/Incremental.c @@ -108,6 +108,7 @@ int Incremental(struct mddev_dev *devlist, struct context *c, char *devname = devlist->devname; int journal_device_missing = 0; bool array_started = false; + bool udev_blocked = false; if (!stat_is_blkdev(devname, &rdev)) return rv; @@ -287,8 +288,9 @@ int Incremental(struct mddev_dev *devlist, struct context *c, goto out_unlock; /* Couldn't find an existing array, maybe make a new one */ + udev_blocked = udev_is_available(); mdfd = create_mddev(match ? match->devname : NULL, name_to_use, trustworthy, - chosen_name, 1); + chosen_name, udev_blocked); if (mdfd < 0) goto out_unlock; @@ -474,7 +476,8 @@ int Incremental(struct mddev_dev *devlist, struct context *c, strcpy(devnm, fd2devnm(mdfd)); if (st->ss->load_container) rv = st->ss->load_container(st, mdfd, NULL); - udev_ready(sra); + if (udev_blocked) + udev_ready(sra); sysfs_free(sra); if (!rv) rv = Incremental_container(st, chosen_name, c, NULL); @@ -638,12 +641,13 @@ int Incremental(struct mddev_dev *devlist, struct context *c, close(dfd); if (policy) dev_policy_free(policy); - if (sra) { - udev_ready(sra); - sysfs_free(sra); - } else { - udev_unblock(); + if (udev_blocked) { + if (sra) + udev_ready(sra); + else + udev_unblock(); } + sysfs_free(sra); if (array_started) wait_for(chosen_name, mdfd); if (mdfd >= 0) @@ -1532,6 +1536,7 @@ static int Incremental_container(struct supertype *st, char *devname, char *sysname; struct map_ent *mp; struct mddev_ident *match = NULL; + bool udev_blocked = false; /* do not activate arrays blocked by metadata handler */ if (ra->array.state & (1 << MD_SB_BLOCK_VOLUME)) { @@ -1602,8 +1607,9 @@ static int Incremental_container(struct supertype *st, char *devname, if (match) trustworthy = LOCAL; + udev_blocked = udev_is_available(); mdfd = create_mddev(match ? match->devname : NULL, ra->name, trustworthy, - chosen_name, 1); + chosen_name, udev_blocked); if (!is_fd_valid(mdfd)) { pr_err("create_mddev failed with chosen name %s: %s.\n", @@ -1614,7 +1620,8 @@ static int Incremental_container(struct supertype *st, char *devname, } if (only && (!mp || strcmp(mp->devnm, only) != 0)) { - udev_unblock(); + if (udev_blocked) + udev_unblock(); close_fd(&mdfd); continue; } @@ -1626,7 +1633,8 @@ static int Incremental_container(struct supertype *st, char *devname, sysname = fd2devnm(mdfd); strncpy(info.sys_name, sysname, sizeof(sysname) - 1); close_fd(&mdfd); - udev_ready(&info); + if (udev_blocked) + udev_ready(&info); } if (c->export && result) { char sep = '='; diff --git a/mdopen.c b/mdopen.c index 83a00262..f0e344d5 100644 --- a/mdopen.c +++ b/mdopen.c @@ -135,9 +135,6 @@ int create_mddev(char *dev, char *name, int trustworthy, return -1; } - if (!udev_is_available()) - block_udev = 0; - if (chosen == NULL) chosen = cbuf; @@ -273,7 +270,8 @@ int create_mddev(char *dev, char *name, int trustworthy, return -1; if (!create_named_array(devnm)) { devnm[0] = 0; - udev_unblock(); + if (block_udev) + udev_unblock(); } } if (num >= 0) { @@ -282,7 +280,8 @@ int create_mddev(char *dev, char *name, int trustworthy, return -1; if (!create_named_array(devnm)) { devnm[0] = 0; - udev_unblock(); + if (block_udev) + udev_unblock(); } } if (devnm[0] == 0) { diff --git a/udev.c b/udev.c index af30463e..ce2d4850 100644 --- a/udev.c +++ b/udev.c @@ -197,8 +197,12 @@ enum udev_status udev_block(char *devnm) */ void udev_unblock(void) { - if (unblock_path) - unlink(unblock_path); + if (!unblock_path) { + pr_err("Called without prior udev block.\n"); + return; + } + + unlink(unblock_path); free(unblock_path); unblock_path = NULL; }