From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail.openvz.org (mail.openvz.org [69.168.225.77]) by lore.virtuozzo.com (Postfix) with ESMTPS id D3FA68017D for ; Tue, 3 Nov 2026 12:44:03 +0000 (UTC) Received: from mail.openvz.org (localhost [127.0.0.1]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 6A3CgpDw005230; Tue, 3 Nov 2026 15:42:51 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 6A3CgpDw005230 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="icUEdPb4" Received: from mail-wr1-f72.google.com (mail-wr1-f72.google.com [209.85.221.72]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 6A3CgOlG005171 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Tue, 3 Nov 2026 15:42:25 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 6A3CgOlG005171 Received: by mail-wr1-f72.google.com with SMTP id ffacd0b85a97d-48e1ad3cb66so2825140f8f.3 for ; Tue, 03 Nov 2026 04:42:24 -0800 (PST) X-Gm-Message-State: APuU4Z/hsg8MgGrmKt8cLsY97Vi+fFvDf1jA9Ts46WRhRcKcLpMsEJja u/zjkd8iXNSyo+swsI2wT42XEii7/rb86TYGfF0UDlNCiwT2oEWGDoSEopidt5Fnj1d7p2wmJDM lbIg9wm3uM0L+p91sNE3D7aUnnShJSg0fRKzjwK3HQ2htifC2dDdmjQ== X-Gm-Gg: AYBFou3QX7x2yJJgaUC0JsYWyg7v+V58+C02zXIsf4KGrX6YC//c8eCFSZ6NvXlxmTw FhrDkAtC/byXLjZCNPSyFj7DD2JRAEqhkLRvN/QvC7LQJbNj53VDK/2lnP2YhPoahQyre3N/t2o 3O4GbvI/xoHSD/AOgqRGn1RehVtM/A0ZyLLBHmNqILsNfjdo1J70Yahd9O0MGazyvkA87DYEAOQ GNh426+R7Z4dLFmWnxJarTLwAZJw5OsstnvONNOURTqYlhZafD7b6/iOIyKza2DOw30szVwy0GD 1HwBWV90iwcIdkX+agbdeYq4CY/7zlR9fGEpwgkX7KBD3l+oZrAGgwI//Q3V/T8+EFR9seo6o2J ga81dzv7FhVRdb9s6mispExfpPY1Yrg== X-Received: by 2002:a05:600c:19d1:b0:4a2:677:ad8e with SMTP id 5b1f17b1804b1-4ac1970b9f7mr288813475e9.34.1793709744559; Tue, 03 Nov 2026 04:42:24 -0800 (PST) X-Received: by 2002:a05:600c:19d1:b0:4a2:677:ad8e with SMTP id 5b1f17b1804b1-4ac1970b9f7mr288812495e9.34.1793709743600; Tue, 03 Nov 2026 04:42:23 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1793709743; cv=none; d=google.com; s=arc-20260327; b=WNIa3ttlRritZWB2LL26WYp4ymAkgA+BuVeDqTsY4WKNdI++JqMiCan2yS3gth9W5s 3bFYhQwaZ8xF5KDGj8IOgvuIQdQBAsPhkintaqJjDbCTVIl39lav11GrmERljiW9YbF5 /tG9qWRBsp8OvTmZDIBqp//nFGMOGhYsZEHgqVriTZIQfhBiBA5ZZRIsPD3BPYGIwLek GKf0tn7ilcuBXKL3GULNssa0JpIr/rzRWvA7oWD+d+aR8CRa+/igIFHTqTB7E0J2hfC1 z5YmxzjxGnRo5/kRkfRg1KVt9S4L8S5bP8UDConpjgMiQ9MWmzxA6jwU/KvoegEZ1h9p Y7+A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=F84MXPuSebaDgg7RAqOWIOsvFy260bUw5YST1IqgCik=; fh=ElTTnnNtphHTCCOZ+58STyaWac7y7ytLDJZIF4HMMJ4=; b=eJQ51Prz9HrVLmXdCIvXwK43fnIN8FoyzbnV+z0Eso+WpwmUwL8Dulp4TrFxugqxF/ Dz/YL2KWHr4kqj1a5s1PwS7kBHG34UNd3RC9L+M7hjJpRvz3pVylQwzj9qF/RX9THUkb /swEHQikdefVw0yfV02R1v8AXJi5C074+BIlBz1ygfY0KjJ7fRoUvn4rSIni38qX/Hjr m1ci4rNjR9Dvf3zdZwTBGCmSDRP/bOCyplCibu+NfgndJDo8CqylXrwynqsWwQ0iajKs EgHnMebuiPhAKzUqPlF+7V+C1iBM8TNPFZLq4eHQvYArQTdT7F9H4q5po3XdJtnIw/w8 m71Q==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=icUEdPb4; spf=pass (google.com: domain of andrey.zhadchenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=andrey.zhadchenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com Received: from relay.virtuozzo.com (relay.virtuozzo.com. [130.117.225.111]) by mx.google.com with ESMTPS id ffacd0b85a97d-48e71ba324csi23205720f8f.78.2026.11.03.04.42.23 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Nov 2026 04:42:23 -0800 (PST) Received-SPF: pass (google.com: domain of andrey.zhadchenko@virtuozzo.com designates 130.117.225.111 as permitted sender) client-ip=130.117.225.111; Authentication-Results: mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=icUEdPb4; spf=pass (google.com: domain of andrey.zhadchenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=andrey.zhadchenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=virtuozzo.com; s=relay; h=MIME-Version:Message-ID:Date:Subject:From: Content-Type; bh=F84MXPuSebaDgg7RAqOWIOsvFy260bUw5YST1IqgCik=; b=icUEdPb4wSTO WG3yNQ2/skww5QxF2+Tp+97ePacza2bHkHj6yrhsytpnj7iY44Q6BvwSWhrHbAz/qoB2x0nYIy0dB 4JTHHZB+fttTxv88t3e9/oQHJeLPPAefknfWjA1+3+ZRC2d8H95bn2dTql57cCl1+kaU6+j1mmebj Yo0IP1Mf597FshPoGXJNDQu6fVLCxmeqZkBUTSMR3G2WbtG4uN7iWJ/enpFx2M8AFX0nZ4VZoIcAf kRs9RghyyJ0pqsjK3Uk4PKMoKc5FTCaG8OpRv4OsKueCy5GloaJX93zOFHapcPECUpWdSXufLKHad mGJRmMUs/3KOKFmnv75h4w==; Received: from [130.117.225.5] (helo=vz9-demens-1.aci.vzint.dev) by relay.virtuozzo.com with esmtp (Exim 4.96) (envelope-from ) id 1xODmO-00FAc4-0H; Tue, 03 Nov 2026 13:42:13 +0100 From: Andrey Zhadchenko To: khorenko@virtuozzo.com, alexander.ivanov@virtuozzo.com Date: Tue, 3 Nov 2026 15:38:02 +0300 Message-ID: <20261103123802.846676-8-andrey.zhadchenko@virtuozzo.com> X-Mailer: git-send-email 2.43.5 In-Reply-To: <20261103123802.846676-1-andrey.zhadchenko@virtuozzo.com> References: <20261103123802.846676-1-andrey.zhadchenko@virtuozzo.com> MIME-Version: 1.0 X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH hci-8.1 v5 7/7] drivers/md/push_backup: add event file descriptor interface X-BeenThere: devel@openvz.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: OpenVZ development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: devel-bounces@openvz.org Errors-To: devel-bounces@openvz.org 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 dm_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 The fd is bound to the target, not to a backup session, and keeps working across stop and a later start. 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 are kept for compatibility but we will clean them up later. They now share the code with the fd, which changes two things: pb_message() parsed u64 but passed unsigned int to push_backup_write(), so "push_backup_write 4294967296:1" acked cluster 0 and now fails with -E2BIG, and push_backup_read printed "%u" ranges, which was wrong on devices with 2^32 or more clusters. Also free the pbio before requeuing its request in resubmit_pbios(). In-flight requests pin the mapped device (dm_start_request() -> dm_get()), not the target, so once the last request is requeued the target may be destroyed together with pbio_pool. The dm messages run under the live table and are safe, but the fd write path holds no md reference. Bump the target version to 1.1.0. https://virtuozzo.atlassian.net/browse/VSTOR-152912 Signed-off-by: Andrey Zhadchenko Reviewed-by: Alexander Ivanov --- v2: free the pbio before dm_requeue_original_rq() in resubmit_pbios(), the requeue may drop the last md reference and let .dtr run v3: install the fd only after its number fits into the result buffer (reserve fd -> DMEMIT -> overflow check -> anon_inode_getfile -> fd_install); rename pb->dead to pb->destroyed; prefix the UAPI type as struct dm_push_backup_range; read() requires exactly sizeof(range) like write(); subject prefix aligned with the rest of the series v4: commit message typos v5: header: the fd survives stop/start, -1/errno, EINTR, EFAULT, EBADMSG and write() errors, O_CLOEXEC, "dmsetup message" is of no use; pb_destroy() NULLs the pointers it frees; blank line after the declaration in resubmit_pbios(); commit message: legacy u64 parsing changes, correct resubmit_pbios() rationale, version bump to 1.1.0 drivers/md/dm-push-backup.c | 218 ++++++++++++++++++++++++---- include/uapi/linux/dm-push-backup.h | 48 ++++++ 2 files changed, 237 insertions(+), 29 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 52ba80b703ff1..c85e8446895d7 100644 --- a/drivers/md/dm-push-backup.c +++ b/drivers/md/dm-push-backup.c @@ -15,6 +15,11 @@ #include #include #include +#include +#include +#include +#include +#include #define DM_MSG_PREFIX "push-backup" @@ -46,6 +51,9 @@ struct push_backup { u64 clu_size; u64 nr_clus; + struct kref ref; + bool destroyed; + bool alive; void *map; u64 map_bits; @@ -69,6 +77,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; @@ -196,9 +214,11 @@ static void resubmit_pbios(struct push_backup *pb, struct list_head *list) struct pb_bio *pbio; while ((pbio = list_first_entry_or_null(list, struct pb_bio, list)) != NULL) { + struct request *rq = pbio->rq; + list_del_init(&pbio->list); - dm_requeue_original_rq(pbio->rq); mempool_free(pbio, pb->pbio_pool); + dm_requeue_original_rq(rq); } } @@ -411,35 +431,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->destroyed) || !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->destroyed) + 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); @@ -452,15 +478,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); @@ -471,11 +529,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->destroyed) { + spin_unlock_irq(&pb->lock); + return -ENODEV; + } + if (!pb->map || !pb->alive) { spin_unlock_irq(&pb->lock); return -ESTALE; } @@ -530,6 +590,84 @@ 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 dm_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 dm_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; + struct file *file; + int fd; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + return fd; + + DMEMIT("%d", fd); + if (sz + 1 >= maxlen) { + put_unused_fd(fd); + return 1; + } + + kref_get(&pb->ref); + file = anon_inode_getfile("[push_backup]", &pb_fops, pb, O_RDWR); + if (IS_ERR(file)) { + pb_put(pb); + put_unused_fd(fd); + return PTR_ERR(file); + } + + fd_install(fd, file); + return 1; +} + static int push_backup_statistics(struct push_backup *pb, char *result, unsigned int maxlen) { @@ -608,6 +746,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; } @@ -622,18 +764,35 @@ 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->destroyed = true; + map = pb->map; /* It'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); + timer_shutdown_sync(&pb->deadline_timer); if (pb->wq) destroy_workqueue(pb->wq); - kvfree(pb->map); /* Is's not zero if stop was not called */ - kvfree(pb->pending_map); + pb->wq = NULL; + kvfree(map); + kvfree(pending_map); kvfree(pb->pending_htable); + pb->pending_htable = NULL; mempool_destroy(pb->pbio_pool); + pb->pbio_pool = NULL; if (pb->origin_dev) dm_put_device(pb->ti, pb->origin_dev); - kfree(pb); + pb->origin_dev = NULL; + pb->ti = NULL; + pb_put(pb); } /* @@ -652,6 +811,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); @@ -761,7 +921,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..7e24835f2c83a --- /dev/null +++ b/include/uapi/linux/dm-push-backup.h @@ -0,0 +1,48 @@ +/* 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 + * is installed into the calling process and the number is returned in the + * message result buffer. + * + * The fd is bound to the target and push_backup_stop does not invalidate + * it. + * + * Both read() and write() operate on exactly one struct dm_push_backup_range + * and fail with EINVAL for any other count. On failure they return -1 and + * set errno as usual. + * + * 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. + * EINTR - interrupted by a signal while waiting. + * EFAULT - @range is not accessible. + * EBADMSG - internal inconsistency of the pending state. + * + * write(fd, &range, sizeof(range)): + * acknowledge that @range has been backed up; equivalent to the + * "push_backup_write clu:nr" message. Fails with E2BIG if @range is out + * of the device, ESTALE and ENODEV as read(), and EFAULT. + */ +#ifndef _UAPI_LINUX_DM_PUSH_BACKUP_H +#define _UAPI_LINUX_DM_PUSH_BACKUP_H + +#include + +struct dm_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