* [Devel] [PATCH 0/5] dm/push_backup: event fd interface and fixes
@ 2026-11-02 23:38 Andrey Zhadchenko
2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 1/5] drivers/md/push_backup: correct initialization Andrey Zhadchenko
` (4 more replies)
0 siblings, 5 replies; 11+ messages in thread
From: Andrey Zhadchenko @ 2026-11-02 23:38 UTC (permalink / raw)
To: alexander.ivanov; +Cc: devel
The push_backup target currently makes the backup tool wait for the next
cluster range inside the "push_backup_read" target message. dm runs
target_message() under dm_get_live_table(), i.e. with md->io_barrier SRCU
read-locked, so while the tool sleeps there anything that needs
synchronize_srcu(&md->io_barrier) on that device is stuck: suspend, table
reload/remove and dm_internal_suspend_fast() from "@stats_create". An
unrelated dm-stats user hangs, and everything that later queues behind
md->suspend_lock hangs with it.
Patch 5 adds a file descriptor based interface instead. The tool gets an
anon inode fd via the "push_backup_get_fd" message and then uses plain
read()/write() of struct push_backup_range to fetch the next range and to
acknowledge it, without going through dm messages on the data path. The
fd pins struct push_backup with a kref so it survives target removal and
simply returns -ENODEV afterwards. The legacy messages are kept for
compatibility.
Patches 1-4 are fixes found while working on this:
1. init the lock, waitqueue and timer before anything that can fail so
pb_destroy() is safe from any error path;
2. clear pb->alive under pb->lock like every other user does;
3. drop the redundant mod_timer() in push_backup_write(). The timer
re-arms itself from pb->deadline_jiffies, so updating the deadline
under the lock is enough. This was also the only place that armed
the timer outside the window push_backup_stop() waits for, which
matters once writes may arrive via the fd without pb->ctl_rwsem;
4. pass absolute expiry to mod_timer(). Both remaining callers passed
a relative value, so after ~5 min of uptime the timer fired and
re-armed on every tick while a write was delayed.
https://virtuozzo.atlassian.net/browse/VSTOR-152912
Andrey Zhadchenko (5):
drivers/md/push_backup: correct initialization
drivers/md/push_backup: clear pb->alive under pb->lock
drivers/md/push_backup: do not re-arm deadline timer in
push_backup_write()
drivers/md/push_backup: fix mod_timer() usage
dm/push_backup: add event file descriptor interface
drivers/md/dm-push-backup.c | 231 ++++++++++++++++++++++------
include/uapi/linux/dm-push-backup.h | 36 +++++
2 files changed, 220 insertions(+), 47 deletions(-)
create mode 100644 include/uapi/linux/dm-push-backup.h
--
2.43.5
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 11+ messages in thread* [Devel] [PATCH hci-8.1 1/5] drivers/md/push_backup: correct initialization 2026-11-02 23:38 [Devel] [PATCH 0/5] dm/push_backup: event fd interface and fixes Andrey Zhadchenko @ 2026-11-02 23:38 ` Andrey Zhadchenko 2026-11-02 23:46 ` no-reply 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 2/5] drivers/md/push_backup: clear pb->alive under pb->lock Andrey Zhadchenko ` (3 subsequent siblings) 4 siblings, 1 reply; 11+ messages in thread From: Andrey Zhadchenko @ 2026-11-02 23:38 UTC (permalink / raw) To: alexander.ivanov; +Cc: devel Allocate pool and htable after lock, waitqueue and deadline timer init so we are sure that pb_destroy() can be called from anywhere. Otherwise we risk timer_delete_sync_try on zeroed timer struct (currently luckily fine). https://virtuozzo.atlassian.net/browse/VSTOR-152912 Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> --- drivers/md/dm-push-backup.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c index 885ea7b31cf67..9a6252f2b21e2 100644 --- a/drivers/md/dm-push-backup.c +++ b/drivers/md/dm-push-backup.c @@ -658,24 +658,22 @@ static int pb_ctr(struct dm_target *ti, unsigned int argc, char **argv) pb = kzalloc(sizeof(*pb), GFP_KERNEL); if (!pb) goto err; - - pb->pbio_pool = mempool_create_slab_pool(PUSH_BACKUP_POOL_SIZE, - pbio_cache); - pb->pending_htable = kcalloc(PB_HASH_TABLE_SIZE, - sizeof(struct hlist_head), - GFP_KERNEL); - if (!pb->pbio_pool || !pb->pending_htable) - goto err; - spin_lock_init(&pb->lock); init_rwsem(&pb->ctl_rwsem); bio_list_init(&pb->deferred_bios); INIT_LIST_HEAD(&pb->pending); timer_setup(&pb->deadline_timer, pb_timer_func, 0); - init_waitqueue_head(&pb->waitq); INIT_WORK(&pb->worker, do_pb_work); + pb->pbio_pool = mempool_create_slab_pool(PUSH_BACKUP_POOL_SIZE, + pbio_cache); + pb->pending_htable = kcalloc(PB_HASH_TABLE_SIZE, + sizeof(struct hlist_head), + GFP_KERNEL); + if (!pb->pbio_pool || !pb->pending_htable) + goto err; + ti->private = pb; pb->ti = ti; -- 2.43.5 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [Devel] [PATCH hci-8.1 1/5] drivers/md/push_backup: correct initialization 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 1/5] drivers/md/push_backup: correct initialization Andrey Zhadchenko @ 2026-11-02 23:46 ` no-reply 0 siblings, 0 replies; 11+ messages in thread From: no-reply @ 2026-11-02 23:46 UTC (permalink / raw) To: alexander.ivanov, Andrey Zhadchenko; +Cc: devel > Allocate pool and htable after lock, waitqueue and deadline > timer init so we are sure that pb_destroy() can be called > from anywhere. Otherwise we risk timer_delete_sync_try on > zeroed timer struct (currently luckily fine). > > https://virtuozzo.atlassian.net/browse/VSTOR-152912 > Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261102233849.613155-1-andrey.zhadchenko@virtuozzo.com?part=1 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* [Devel] [PATCH hci-8.1 2/5] drivers/md/push_backup: clear pb->alive under pb->lock 2026-11-02 23:38 [Devel] [PATCH 0/5] dm/push_backup: event fd interface and fixes Andrey Zhadchenko 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 1/5] drivers/md/push_backup: correct initialization Andrey Zhadchenko @ 2026-11-02 23:38 ` Andrey Zhadchenko 2026-11-02 23:47 ` no-reply 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 3/5] drivers/md/push_backup: do not re-arm deadline timer in push_backup_write() Andrey Zhadchenko ` (2 subsequent siblings) 4 siblings, 1 reply; 11+ messages in thread From: Andrey Zhadchenko @ 2026-11-02 23:38 UTC (permalink / raw) To: alexander.ivanov; +Cc: devel Although it is harmless in practice, it may potentially trigger KCSAN. Besides, most other users handle this only under the pb->lock. https://virtuozzo.atlassian.net/browse/VSTOR-152912 Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> --- drivers/md/dm-push-backup.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c index 9a6252f2b21e2..1ae4ccc208ffa 100644 --- a/drivers/md/dm-push-backup.c +++ b/drivers/md/dm-push-backup.c @@ -581,7 +581,9 @@ static int pb_message(struct dm_target *ti, unsigned int argc, char **argv, if (!pb->map) return -EBADF; + spin_lock_irq(&pb->lock); pb->alive = false; + spin_unlock_irq(&pb->lock); wake_up_interruptible(&pb->waitq); /* pb->alive = false */ } -- 2.43.5 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [Devel] [PATCH hci-8.1 2/5] drivers/md/push_backup: clear pb->alive under pb->lock 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 2/5] drivers/md/push_backup: clear pb->alive under pb->lock Andrey Zhadchenko @ 2026-11-02 23:47 ` no-reply 0 siblings, 0 replies; 11+ messages in thread From: no-reply @ 2026-11-02 23:47 UTC (permalink / raw) To: alexander.ivanov, Andrey Zhadchenko; +Cc: devel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] dm-push-backup: pb->alive KCSAN race only partly fixed, plain accesses remain -- commit 9d9907f2e7f049fda97985539a4920b9e2e49764 Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> drivers/md/push_backup: clear pb->alive under pb->lock This patch makes pb_message() take pb->lock around the store that clears pb->alive when handling push_backup_stop. The aim is to avoid a possible KCSAN report and to match other users of pb->alive. Link: https://virtuozzo.atlassian.net/browse/VSTOR-152912 > diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c > index 9a6252f2b21e..1ae4ccc208ff 100644 > --- a/drivers/md/dm-push-backup.c > +++ b/drivers/md/dm-push-backup.c > @@ -581,7 +581,9 @@ static int pb_message(struct dm_target *ti, unsigned int argc, char **argv, > if (!pb->map) > return -EBADF; > > + spin_lock_irq(&pb->lock); > pb->alive = false; > + spin_unlock_irq(&pb->lock); > wake_up_interruptible(&pb->waitq); /* pb->alive = false */ > } [Severity: Low] Does this fully avoid the KCSAN report described in the commit message ("it may potentially trigger KCSAN")? This store in pb_message() runs before ctl_rwsem is taken, so it holds pb->lock but not ctl_rwsem. A concurrent push_backup_start() holds ctl_rwsem but not pb->lock, so the two do not exclude each other: push_backup_start() if (pb->alive) return -EEXIST; That read is a plain read with no pb->lock and no READ_ONCE(), so it can still race with the store above. The wait_event condition used by push_backup_read() is also evaluated without pb->lock. It reads the flag through pb_has_event(): !READ_ONCE(pb->alive) || !READ_ONCE(pb->map_bits) || ... Here the reader is marked, but none of the writers use WRITE_ONCE(). The stores in pb_message() (this patch), push_backup_stop(), and the pb->alive = true store in the push_backup_start() setup path are all still plain stores. KCSAN can report a plain store racing with a lockless read, so taking pb->lock around only this one store may not remove the report. Would it make sense to use WRITE_ONCE() on the stores to pb->alive and READ_ONCE() on the read in push_backup_start() (or take pb->lock for that read)? The commit message could also be adjusted if the intent is only to make the locking consistent. -- Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261102233849.613155-1-andrey.zhadchenko@virtuozzo.com?part=2 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* [Devel] [PATCH hci-8.1 3/5] drivers/md/push_backup: do not re-arm deadline timer in push_backup_write() 2026-11-02 23:38 [Devel] [PATCH 0/5] dm/push_backup: event fd interface and fixes Andrey Zhadchenko 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 1/5] drivers/md/push_backup: correct initialization Andrey Zhadchenko 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 2/5] drivers/md/push_backup: clear pb->alive under pb->lock Andrey Zhadchenko @ 2026-11-02 23:38 ` Andrey Zhadchenko 2026-11-02 23:48 ` no-reply 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 4/5] drivers/md/push_backup: fix mod_timer() usage Andrey Zhadchenko 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 5/5] dm/push_backup: add event file descriptor interface Andrey Zhadchenko 4 siblings, 1 reply; 11+ messages in thread From: Andrey Zhadchenko @ 2026-11-02 23:38 UTC (permalink / raw) To: alexander.ivanov; +Cc: devel pb_timer_func() checks pb->deadline_jiffies every time it fires, so we actually don't need to re-arm the timer after setting the filed. This is also the only place that arms the timer outside the window push_backup_stop() waits for with synchronize_rcu(). Today it is harmless because write and stop are serialized by pb->ctl_rwsem, but it would re-arm the timer behind timer_delete_sync_try() once the write path is reachable without the rwsem (next patches). https://virtuozzo.atlassian.net/browse/VSTOR-152912 Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> --- drivers/md/dm-push-backup.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c index 1ae4ccc208ffa..8ce84a1a696e5 100644 --- a/drivers/md/dm-push-backup.c +++ b/drivers/md/dm-push-backup.c @@ -467,8 +467,8 @@ static int push_backup_write(struct push_backup *pb, unsigned int clu, unsigned int nr) { u64 i, key, nr_clus = pb->nr_clus; - bool finished, has_more = false; LIST_HEAD(pbio_list); + bool finished; struct pb_bio *pbio; if (!pb) @@ -519,8 +519,7 @@ static int push_backup_write(struct push_backup *pb, } } - has_more = (pb->nr_delayed != 0); - if (has_more) + if (pb->nr_delayed) pb->deadline_jiffies = get_jiffies_64() + pb->timeout_in_jiffies; else pb->deadline_jiffies = S64_MAX; @@ -529,11 +528,8 @@ static int push_backup_write(struct push_backup *pb, if (finished) wake_up_interruptible(&pb->waitq); - if (!list_empty(&pbio_list)) { + if (!list_empty(&pbio_list)) resubmit_pbios(pb, &pbio_list); - if (has_more) - mod_timer(&pb->deadline_timer, pb->timeout_in_jiffies + 1); - } return 0; } -- 2.43.5 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [Devel] [PATCH hci-8.1 3/5] drivers/md/push_backup: do not re-arm deadline timer in push_backup_write() 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 3/5] drivers/md/push_backup: do not re-arm deadline timer in push_backup_write() Andrey Zhadchenko @ 2026-11-02 23:48 ` no-reply 0 siblings, 0 replies; 11+ messages in thread From: no-reply @ 2026-11-02 23:48 UTC (permalink / raw) To: alexander.ivanov, Andrey Zhadchenko; +Cc: devel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] dm-push-backup: stale-deadline re-arm in pb_timer_func() can leave timer far in future -- commit 4c99ff329d7b8e6b90df5269d7233e727e68dc3a Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> drivers/md/push_backup: do not re-arm deadline timer in push_backup_write() This patch removes the mod_timer() call from push_backup_write(). The commit message says pb_timer_func() re-checks pb->deadline_jiffies each time it fires, so re-arming after updating the field is unnecessary. It also says this prepares for making the write path reachable without pb->ctl_rwsem. Link: https://virtuozzo.atlassian.net/browse/VSTOR-152912 > diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c > index 1ae4ccc208ff..8ce84a1a696e 100644 > --- a/drivers/md/dm-push-backup.c > +++ b/drivers/md/dm-push-backup.c [ ... ] > @@ -519,8 +519,7 @@ static int push_backup_write(struct push_backup *pb, > } > } > > - has_more = (pb->nr_delayed != 0); > - if (has_more) > + if (pb->nr_delayed) > pb->deadline_jiffies = get_jiffies_64() + pb->timeout_in_jiffies; > else > pb->deadline_jiffies = S64_MAX; [ ... ] > - if (!list_empty(&pbio_list)) { > + if (!list_empty(&pbio_list)) > resubmit_pbios(pb, &pbio_list); > - if (has_more) > - mod_timer(&pb->deadline_timer, pb->timeout_in_jiffies + 1); > - } [Severity: Medium] Can the deadline timer be left armed far in the future after this change? The commit message relies on pb_timer_func() re-checking the deadline, but that callback samples the deadline under pb->lock and then calls mod_timer() after dropping the lock: drivers/md/dm-push-backup.c:pb_timer_func() { ... spin_lock_irqsave(&pb->lock, flags); deadline = pb->deadline_jiffies; spin_unlock_irqrestore(&pb->lock, flags); if (unlikely(time_before64(now, deadline))) mod_timer(timer, deadline - now + 1); ... } Consider this sequence: 1. push_backup_write() completes the last delayed pbio and sets pb->deadline_jiffies = S64_MAX, while the timer is still armed for the old expiry. 2. The timer fires. pb_timer_func() reads deadline = S64_MAX under pb->lock and drops the lock. 3. On another CPU, postpone_if_required_for_backup() takes pb->lock, moves pb->nr_delayed from 0 to 1, and sets a fresh deadline_jiffies. It then calls mod_timer(&pb->deadline_timer, ...) after dropping the lock, so the timer is armed for the short timeout. 4. pb_timer_func() then runs its stale mod_timer(timer, deadline - now + 1) with deadline = S64_MAX. This overwrites the short expiry with a far-future one. After step 4, the only arming site left is the pb->nr_delayed == 1 transition in postpone_if_required_for_backup(). push_backup_write() no longer re-arms the timer, so nothing corrects the expiry. pb->nr_delayed stays above zero with the timer far in the future. Delayed requests would then not be released by the backup timeout if userspace stops servicing the backup. The window is narrow. It spans the gap between spin_unlock_irqrestore() and mod_timer() in pb_timer_func(). Before this patch, a later push_backup_write() with pending pbios could re-arm the timer and recover. Could the re-arm in pb_timer_func() be done in a way that cannot overwrite a newer expiry? One option is to re-read or re-arm the deadline under pb->lock. Another is to have the arming sites in postpone_if_required_for_backup() and pb_timer_func() order their updates consistently. Later patches in this series do not appear to close this window. The patch described as fixing mod_timer() usage only changes the expiry arithmetic. The event file descriptor patch does not change this path. -- Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261102233849.613155-1-andrey.zhadchenko@virtuozzo.com?part=3 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* [Devel] [PATCH hci-8.1 4/5] drivers/md/push_backup: fix mod_timer() usage 2026-11-02 23:38 [Devel] [PATCH 0/5] dm/push_backup: event fd interface and fixes Andrey Zhadchenko ` (2 preceding siblings ...) 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 3/5] drivers/md/push_backup: do not re-arm deadline timer in push_backup_write() Andrey Zhadchenko @ 2026-11-02 23:38 ` Andrey Zhadchenko 2026-11-02 23:47 ` no-reply 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 5/5] dm/push_backup: add event file descriptor interface Andrey Zhadchenko 4 siblings, 1 reply; 11+ messages in thread From: Andrey Zhadchenko @ 2026-11-02 23:38 UTC (permalink / raw) To: alexander.ivanov; +Cc: devel mod_timer() takes an absolute jiffies value, but both callers pass a relative one. After about 5 min uptime this causes the timer to fire and re-arm on every tick. This has not exploded because we still have checked pb->deadline_jiffies each time. https://virtuozzo.atlassian.net/browse/VSTOR-152912 Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> --- drivers/md/dm-push-backup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c index 8ce84a1a696e5..b1861204f6e3e 100644 --- a/drivers/md/dm-push-backup.c +++ b/drivers/md/dm-push-backup.c @@ -244,7 +244,7 @@ static void pb_timer_func(struct timer_list *timer) spin_unlock_irqrestore(&pb->lock, flags); if (unlikely(time_before64(now, deadline))) - mod_timer(timer, deadline - now + 1); + mod_timer(timer, jiffies + (deadline - now) + 1); else queue_work(pb->wq, &pb->worker); } @@ -278,7 +278,7 @@ static int postpone_if_required_for_backup(struct push_backup *pb, spin_unlock_irqrestore(&pb->lock, flags); if (queue_timer) - mod_timer(&pb->deadline_timer, pb->timeout_in_jiffies + 1); + mod_timer(&pb->deadline_timer, jiffies + pb->timeout_in_jiffies + 1); rcu_read_unlock(); if (queue_timer) -- 2.43.5 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [Devel] [PATCH hci-8.1 4/5] drivers/md/push_backup: fix mod_timer() usage 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 4/5] drivers/md/push_backup: fix mod_timer() usage Andrey Zhadchenko @ 2026-11-02 23:47 ` no-reply 0 siblings, 0 replies; 11+ messages in thread From: no-reply @ 2026-11-02 23:47 UTC (permalink / raw) To: alexander.ivanov, Andrey Zhadchenko; +Cc: devel > mod_timer() takes an absolute jiffies value, but both callers pass a > relative one. After about 5 min uptime this causes the timer to fire > and re-arm on every tick. This has not exploded because we still have > checked pb->deadline_jiffies each time. > > https://virtuozzo.atlassian.net/browse/VSTOR-152912 > Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261102233849.613155-1-andrey.zhadchenko@virtuozzo.com?part=4 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* [Devel] [PATCH hci-8.1 5/5] dm/push_backup: add event file descriptor interface 2026-11-02 23:38 [Devel] [PATCH 0/5] dm/push_backup: event fd interface and fixes Andrey Zhadchenko ` (3 preceding siblings ...) 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 4/5] drivers/md/push_backup: fix mod_timer() usage Andrey Zhadchenko @ 2026-11-02 23:38 ` Andrey Zhadchenko 2026-11-02 23:51 ` no-reply 4 siblings, 1 reply; 11+ messages in thread From: Andrey Zhadchenko @ 2026-11-02 23:38 UTC (permalink / raw) To: alexander.ivanov; +Cc: devel The backup tool currently waits for the next cluster range to back up by sleeping inside the "push_backup_read" target message. target_message() calls into the target under dm_get_live_table(), i.e. with md->io_barrier SRCU read-locked, so for the whole time the tool waits for a write to be intercepted anything doing synchronize_srcu(&md->io_barrier) on this device is stuck: dm suspend, table reload/remove and in particular dm_internal_suspend_fast() called by "@stats_create". An unrelated dm-stats consumer therefore hangs together with everything that later queues behind md->suspend_lock. Add a file descriptor based interface that does not involve dm messages on the data path: push_backup_get_fd - returns an anon inode fd in the message result read(fd, &range, 16) - next struct push_backup_range to back up; 0 when the backup is finished, -ESTALE when not running, -ENODEV after the target is destroyed write(fd, &range, 16) - acknowledge the range, same as push_backup_write This way we will avoid aforementioned problems. We can't access/uninstall fd across all processes that have it, so add kref on the push_backup struct and tie it to number of fds. Free only when everything is gone. The legacy messages keep working unchanged for compatibility but we will clean it up later. https://virtuozzo.atlassian.net/browse/VSTOR-152912 Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> --- drivers/md/dm-push-backup.c | 197 ++++++++++++++++++++++++---- include/uapi/linux/dm-push-backup.h | 36 +++++ 2 files changed, 205 insertions(+), 28 deletions(-) create mode 100644 include/uapi/linux/dm-push-backup.h diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c index b1861204f6e3e..1ea71cb82b9ed 100644 --- a/drivers/md/dm-push-backup.c +++ b/drivers/md/dm-push-backup.c @@ -15,6 +15,10 @@ #include <linux/ctype.h> #include <linux/dm-io.h> #include <linux/blk-mq.h> +#include <linux/anon_inodes.h> +#include <linux/kref.h> +#include <linux/uaccess.h> +#include <linux/dm-push-backup.h> #define DM_MSG_PREFIX "push-backup" @@ -46,6 +50,9 @@ struct push_backup { u64 clu_size; u64 nr_clus; + struct kref ref; + bool dead; + bool alive; void *map; u64 map_bits; @@ -69,6 +76,16 @@ struct push_backup { struct rw_semaphore ctl_rwsem; }; +static void pb_release(struct kref *ref) +{ + kfree(container_of(ref, struct push_backup, ref)); +} + +static void pb_put(struct push_backup *pb) +{ + kref_put(&pb->ref, pb_release); +} + static u64 pbio_first_required_for_backup_clu(struct push_backup *pb, struct pb_bio *pbio) { u64 clu; @@ -415,35 +432,41 @@ static int push_backup_stop(struct push_backup *pb, return 0; } -static int push_backup_read(struct push_backup *pb, - char *result, unsigned int maxlen) +static bool pb_has_event(struct push_backup *pb) +{ + return READ_ONCE(pb->dead) || !READ_ONCE(pb->map) || + !READ_ONCE(pb->alive) || !READ_ONCE(pb->map_bits) || + !list_empty_careful(&pb->pending); +} + +/* + * Returns: + * 1 - next range to back up is stored in @clu/@nr + * 0 - backup is finished (nothing left to back up) + * -EAGAIN - nothing pending at the moment + * -ESTALE - backup is not running (not started, stopped or expired) + * -ENODEV - target was destroyed + */ +static int pb_next_range(struct push_backup *pb, u64 *clu, u64 *nr) { - unsigned int left, right, sz = 0; struct pb_bio *pbio; + u64 left, right; int ret; - if (!pb) - return -EBADF; - if (!pb->map) - return -ESTALE; -again: - if (wait_event_interruptible(pb->waitq, - !list_empty_careful(&pb->pending) || - !pb->alive || !pb->map_bits)) - return -EINTR; - spin_lock_irq(&pb->lock); + ret = -ENODEV; + if (pb->dead) + goto unlock; ret = -ESTALE; - if (!pb->alive) + if (!pb->map || !pb->alive) goto unlock; ret = 0; if (!pb->map_bits) goto unlock; + ret = -EAGAIN; pbio = list_first_entry_or_null(&pb->pending, typeof(*pbio), list); - if (unlikely(!pbio)) { - spin_unlock_irq(&pb->lock); - goto again; - } + if (!pbio) + goto unlock; ret = -EBADMSG; left = pbio_first_required_for_backup_clu(pb, pbio); @@ -456,15 +479,47 @@ static int push_backup_read(struct push_backup *pb, else right = pb->nr_clus - 1; - DMEMIT("%u:%u", left, right - left + 1); + *clu = left; + *nr = right - left + 1; ret = 1; unlock: spin_unlock_irq(&pb->lock); return ret; } -static int push_backup_write(struct push_backup *pb, - unsigned int clu, unsigned int nr) +static int pb_wait_range(struct push_backup *pb, bool nonblock, + u64 *clu, u64 *nr) +{ + int ret; + + while (1) { + ret = pb_next_range(pb, clu, nr); + if (ret != -EAGAIN || nonblock) + return ret; + if (wait_event_interruptible(pb->waitq, pb_has_event(pb))) + return -ERESTARTSYS; + } +} + +static int push_backup_read(struct push_backup *pb, + char *result, unsigned int maxlen) +{ + unsigned int sz = 0; + u64 clu, nr; + int ret; + + if (!pb) + return -EBADF; + + ret = pb_wait_range(pb, false, &clu, &nr); + if (ret == -ERESTARTSYS) + ret = -EINTR; + if (ret == 1) + DMEMIT("%llu:%llu", clu, nr); + return ret; +} + +static int push_backup_write(struct push_backup *pb, u64 clu, u64 nr) { u64 i, key, nr_clus = pb->nr_clus; LIST_HEAD(pbio_list); @@ -475,11 +530,13 @@ static int push_backup_write(struct push_backup *pb, return -EBADF; if (clu >= nr_clus || nr > nr_clus - clu) return -E2BIG; - if (!pb->map) - return -ESTALE; spin_lock_irq(&pb->lock); - if (!pb->alive) { + if (pb->dead) { + spin_unlock_irq(&pb->lock); + return -ENODEV; + } + if (!pb->map || !pb->alive) { spin_unlock_irq(&pb->lock); return -ESTALE; } @@ -534,6 +591,73 @@ static int push_backup_write(struct push_backup *pb, return 0; } +static ssize_t pb_fd_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct push_backup *pb = file->private_data; + struct push_backup_range range; + int ret; + + if (count < sizeof(range)) + return -EINVAL; + + ret = pb_wait_range(pb, file->f_flags & O_NONBLOCK, + &range.clu, &range.nr); + if (ret <= 0) + return ret; + + if (copy_to_user(buf, &range, sizeof(range))) + return -EFAULT; + return sizeof(range); +} + +static ssize_t pb_fd_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct push_backup *pb = file->private_data; + struct push_backup_range range; + int ret; + + if (count != sizeof(range)) + return -EINVAL; + if (copy_from_user(&range, buf, sizeof(range))) + return -EFAULT; + + ret = push_backup_write(pb, range.clu, range.nr); + return ret ? : sizeof(range); +} + +static int pb_fd_release(struct inode *inode, struct file *file) +{ + pb_put(file->private_data); + return 0; +} + +static const struct file_operations pb_fops = { + .owner = THIS_MODULE, + .read = pb_fd_read, + .write = pb_fd_write, + .release = pb_fd_release, +}; + +static int push_backup_get_fd(struct push_backup *pb, + char *result, unsigned int maxlen) +{ + unsigned int sz = 0; + int fd; + + kref_get(&pb->ref); + fd = anon_inode_getfd("[push_backup]", &pb_fops, pb, + O_RDWR | O_CLOEXEC); + if (fd < 0) { + pb_put(pb); + return fd; + } + + DMEMIT("%d", fd); + return 1; +} + static int push_backup_statistics(struct push_backup *pb, char *result, unsigned int maxlen) { @@ -612,6 +736,10 @@ static int pb_message(struct dm_target *ti, unsigned int argc, char **argv, ret = push_backup_write(pb, val, val2); } else if (!strcmp(argv[0], "push_backup_statistics")) { ret = push_backup_statistics(pb, result, maxlen); + } else if (!strcmp(argv[0], "push_backup_get_fd")) { + if (argc != 1) + goto unlock; + ret = push_backup_get_fd(pb, result, maxlen); } else { ret = -ENOTSUPP; } @@ -626,18 +754,30 @@ static int pb_message(struct dm_target *ti, unsigned int argc, char **argv, } static void pb_destroy(struct push_backup *pb) { + void *map, *pending_map; + WARN_ON_ONCE(pb->nr_delayed); + spin_lock_irq(&pb->lock); + pb->dead = true; + map = pb->map; /* Is's not zero if stop was not called */ + pb->map = NULL; + pending_map = pb->pending_map; + pb->pending_map = NULL; + spin_unlock_irq(&pb->lock); + + wake_up_interruptible(&pb->waitq); /* pb->dead = true */ + WARN_ON_ONCE(timer_delete_sync_try(&pb->deadline_timer) < 0); if (pb->wq) destroy_workqueue(pb->wq); - kvfree(pb->map); /* Is's not zero if stop was not called */ - kvfree(pb->pending_map); + kvfree(map); + kvfree(pending_map); kvfree(pb->pending_htable); mempool_destroy(pb->pbio_pool); if (pb->origin_dev) dm_put_device(pb->ti, pb->origin_dev); - kfree(pb); + pb_put(pb); } /* @@ -656,6 +796,7 @@ static int pb_ctr(struct dm_target *ti, unsigned int argc, char **argv) pb = kzalloc(sizeof(*pb), GFP_KERNEL); if (!pb) goto err; + kref_init(&pb->ref); spin_lock_init(&pb->lock); init_rwsem(&pb->ctl_rwsem); bio_list_init(&pb->deferred_bios); @@ -764,7 +905,7 @@ static loff_t pb_llseek_hole(struct dm_target *ti, loff_t offset, static struct target_type pb_target = { .name = "push_backup", - .version = {1, 0, 0}, + .version = {1, 1, 0}, .features = DM_TARGET_SINGLETON, .module = THIS_MODULE, .ctr = pb_ctr, diff --git a/include/uapi/linux/dm-push-backup.h b/include/uapi/linux/dm-push-backup.h new file mode 100644 index 0000000000000..3a56f851a2d56 --- /dev/null +++ b/include/uapi/linux/dm-push-backup.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2026 Virtuozzo International GmbH. All rights reserved. + */ + +/* + * Userspace interface of the dm-push-backup target's event file descriptor. + * + * The fd is obtained with the "push_backup_get_fd" target message; the fd + * number is returned in the message result buffer. + * + * read(fd, &range, sizeof(range)): + * sizeof(range) - @range holds the next cluster range that must be backed + * up before the writes postponed on it may proceed. + * Reading does not consume anything: the same (or a wider) + * range is returned until it is acknowledged via write(). + * 0 - backup has finished, nothing is left to back up. + * -EAGAIN - O_NONBLOCK is set and no writes are pending. + * -ESTALE - backup is not running (not started, stopped or expired). + * -ENODEV - the target has been destroyed. + * + * write(fd, &range, sizeof(range)): + * acknowledge that @range has been backed up; equivalent to the + * "push_backup_write clu:nr" message. + */ +#ifndef _UAPI_LINUX_DM_PUSH_BACKUP_H +#define _UAPI_LINUX_DM_PUSH_BACKUP_H + +#include <linux/types.h> + +struct push_backup_range { + __u64 clu; /* first cluster */ + __u64 nr; /* number of clusters */ +}; + +#endif /* _UAPI_LINUX_DM_PUSH_BACKUP_H */ -- 2.43.5 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [Devel] [PATCH hci-8.1 5/5] dm/push_backup: add event file descriptor interface 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 5/5] dm/push_backup: add event file descriptor interface Andrey Zhadchenko @ 2026-11-02 23:51 ` no-reply 0 siblings, 0 replies; 11+ messages in thread From: no-reply @ 2026-11-02 23:51 UTC (permalink / raw) To: Andrey Zhadchenko, alexander.ivanov; +Cc: devel Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] dm-push-backup: fd write races pb_destroy() and uses freed pbio_pool in resubmit_pbios() - [Low] dm-push-backup: push_backup_get_fd() installs fd before result is delivered - [Low] dm-push-backup: event fd has no .poll handler, so it cannot be polled -- commit 013b902b0f480dc76bc700b18a002953deb80967 Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com> dm/push_backup: add event file descriptor interface This patch adds an anon-inode file descriptor interface to dm-push-backup. The fd is obtained with a new push_backup_get_fd target message. Userspace then uses read() to get the next cluster range to back up and write() to acknowledge it. A kref on struct push_backup is tied to the number of open fds. Link: https://virtuozzo.atlassian.net/browse/VSTOR-152912 > diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c > --- a/drivers/md/dm-push-backup.c > +++ b/drivers/md/dm-push-backup.c [ ... ] > @@ -534,6 +591,73 @@ static int push_backup_write(struct push_backup *pb, > return 0; > } > > +static ssize_t pb_fd_write(struct file *file, const char __user *buf, > + size_t count, loff_t *ppos) > +{ > + struct push_backup *pb = file->private_data; > + struct push_backup_range range; > + int ret; > + > + if (count != sizeof(range)) > + return -EINVAL; > + if (copy_from_user(&range, buf, sizeof(range))) > + return -EFAULT; > + > + ret = push_backup_write(pb, range.clu, range.nr); > + return ret ? : sizeof(range); > +} [Severity: High] Can this race with pb_destroy() and use freed memory? Before this patch, push_backup_write() was reachable only through pb_message(), which runs under dm_get_live_table() and ctl_rwsem, so it could not overlap the target destructor. Now pb_fd_write() calls it with neither. The dead check in push_backup_write() is made under pb->lock, but resubmit_pbios() runs after the lock is dropped: push_backup_write() spin_unlock_irq(&pb->lock); ... if (!list_empty(&pbio_list)) resubmit_pbios(pb, &pbio_list); resubmit_pbios() list_del_init(&pbio->list); dm_requeue_original_rq(pbio->rq); mempool_free(pbio, pb->pbio_pool); If pb_destroy() runs on another CPU after the writer has unlinked the pbios and dropped the lock, it can proceed as follows: pb_destroy() spin_lock_irq(&pb->lock); pb->dead = true; ... spin_unlock_irq(&pb->lock); ... destroy_workqueue(pb->wq); kvfree(pb->pending_htable); mempool_destroy(pb->pbio_pool); ... pb_put(pb); The writer would then call mempool_free() on a pbio_pool that mempool_destroy() has already freed. WARN_ON_ONCE(pb->nr_delayed) in pb_destroy() would not fire in this case, because the writer has already decremented nr_delayed. The new kref keeps only the struct push_backup allocation alive. It does not keep pbio_pool, pending_htable, wq or origin_dev alive. pb_destroy() does not wait for fd readers or writers that have already passed the dead check. The fd can also be inherited or passed to a process other than the one that opened it. Also, __dm_destroy() suspends the device without draining postponed requests, so pending pbios can exist when dtr runs. Is there something that serializes pb_fd_write() against pb_destroy(), for example draining in-flight fd operations before freeing the sub-resources? > +static int pb_fd_release(struct inode *inode, struct file *file) > +{ > + pb_put(file->private_data); > + return 0; > +} > + > +static const struct file_operations pb_fops = { > + .owner = THIS_MODULE, > + .read = pb_fd_read, > + .write = pb_fd_write, > + .release = pb_fd_release, > +}; [Severity: Low] The interface is described as an event file descriptor, but pb_fops has no .poll callback. Can a backup tool multiplex this fd with its other I/O? As written, it can only block in read() or busy-poll with O_NONBLOCK and get -EAGAIN. pb_has_event() and pb->waitq already exist and look like a natural basis for a poll handler. Would it make sense to add .poll before the UAPI is fixed? Adding it later is harder because read() does not consume anything and returns 0 to mean "finished". > +static int push_backup_get_fd(struct push_backup *pb, > + char *result, unsigned int maxlen) > +{ > + unsigned int sz = 0; > + int fd; > + > + kref_get(&pb->ref); > + fd = anon_inode_getfd("[push_backup]", &pb_fops, pb, > + O_RDWR | O_CLOEXEC); > + if (fd < 0) { > + pb_put(pb); > + return fd; > + } > + > + DMEMIT("%d", fd); > + return 1; > +} [Severity: Low] Can this leak the fd when the result is not delivered? anon_inode_getfd() does fd_install() irrevocably. DMEMIT() never reports truncation, so push_backup_get_fd() returns 1 even if the result buffer is too small and the fd number is truncated or omitted. The same applies if the dm-ioctl copy-out fails afterwards. In those cases the caller never learns the fd number, and the open fd continues to hold a pb kref and a module reference through pb_fops.owner until the process exits. Would it be better to check maxlen before creating the fd, or to use get_unused_fd_flags() and call fd_install() only after the result has been formatted? -- Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261102233849.613155-1-andrey.zhadchenko@virtuozzo.com?part=5 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-11-02 23:53 UTC | newest] Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-11-02 23:38 [Devel] [PATCH 0/5] dm/push_backup: event fd interface and fixes Andrey Zhadchenko 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 1/5] drivers/md/push_backup: correct initialization Andrey Zhadchenko 2026-11-02 23:46 ` no-reply 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 2/5] drivers/md/push_backup: clear pb->alive under pb->lock Andrey Zhadchenko 2026-11-02 23:47 ` no-reply 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 3/5] drivers/md/push_backup: do not re-arm deadline timer in push_backup_write() Andrey Zhadchenko 2026-11-02 23:48 ` no-reply 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 4/5] drivers/md/push_backup: fix mod_timer() usage Andrey Zhadchenko 2026-11-02 23:47 ` no-reply 2026-11-02 23:38 ` [Devel] [PATCH hci-8.1 5/5] dm/push_backup: add event file descriptor interface Andrey Zhadchenko 2026-11-02 23:51 ` no-reply
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.