From patchwork Tue Apr 4 14:08:25 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergei Shtepa X-Patchwork-Id: 79789 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:b0ea:0:b0:3b6:4342:cba0 with SMTP id b10csp435682vqo; Wed, 5 Apr 2023 09:27:22 -0700 (PDT) X-Google-Smtp-Source: AKy350bvy9vrsu/TphUImiobQ45PiE3+5a3C+dncJc6+uMDieQldf+c4BHIWlPkAzfRfeYmmy4qQ X-Received: by 2002:a17:906:ecb5:b0:931:d350:9aef with SMTP id qh21-20020a170906ecb500b00931d3509aefmr3753291ejb.25.1680712042566; Wed, 05 Apr 2023 09:27:22 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1680712042; cv=none; d=google.com; s=arc-20160816; b=TjC6vSAO/uHLROuutc1PutEp6vbybNbu/BrqvZkuq3Y5aNjcP6s/G4JsUt4iDGfu2Y sWKq7wzZZeDQQUSauokZiA6e4PesaNjCCdJ1EM7TB91LIhB9YR/PWcXshO6vPqXzLQmV dAtBivPTeKKuCy5epKPePpw6xp2rz6bjb4y/8tOb2elcQAfSdrID8bGgRjDgF+XGdFYO UUP+3aljzBidcxdz9qQDSHcTF1tfv1cgjAkmzP6ica9NesJluV89jm7osj46K6vpSEpD rlgYDcMsQonF8BAKTx/sS7XDZudK7Q/KX48+1yPntNkwzIbKh/nToSogG5DPfXDn+qTx wEbw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=aqBezH/TItv53h8zriybwSj1fO3L4xhtSNqGkkUsBIo=; b=G6docAs5QG9nZ0Vsal8MLFJsoyaT6PhrfLfAOLTbExeYMHPAvxlYocD+ckZQUxMy90 qfHSOibkdrOhGAkUDopE8ymXnZCmZOK5K1f6QoUN6Oi53vPCFnqkJ4sJY7YqrONhDfnq aw3iUGqr1XGu7Z9UIhSHa6wqEzhdUhXz4qMIQ58XfrWCGl9dOZ4kFYhGiMH+QeBYs7PF ZYZSV5lSqEq9X3stPrSFGN6lpZwy5+C6VRhJdnONWVZiegL8h0JnV8FkAqH6nAXBdVYY ciu39Q56ujN9jvPbcyQa24QIbC103Ns5mnQpedfNyHwhFu+MSSyu1e1pQoamI+FNCCAg ZaWA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b=QVaBi6uL; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id bh2-20020a170906a0c200b009372a2a6e7fsi1739861ejb.563.2023.04.05.09.26.58; Wed, 05 Apr 2023 09:27:22 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b=QVaBi6uL; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230307AbjDEQOm (ORCPT + 99 others); Wed, 5 Apr 2023 12:14:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43164 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229881AbjDEQOg (ORCPT ); Wed, 5 Apr 2023 12:14:36 -0400 Received: from mx2.veeam.com (mx2.veeam.com [64.129.123.6]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B411372A5; Wed, 5 Apr 2023 09:14:12 -0700 (PDT) Received: from mx1.veeam.com (mx1.veeam.com [172.18.34.147]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx2.veeam.com (Postfix) with ESMTPS id 899BC41AA3; Wed, 5 Apr 2023 12:13:40 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=veeam.com; s=mx2-2022; t=1680711220; bh=aqBezH/TItv53h8zriybwSj1fO3L4xhtSNqGkkUsBIo=; h=From:To:CC:Subject:Date:In-Reply-To:References:From; b=QVaBi6uLLL0vy286rAmPkG/r+IBkTEBxkh+cagf7nvUMyoTcQm76Sg37CK0oYTk2W hu/NKFW+nh6Wxd7fta9V2aTn0F1wzgkxMlfMGTEthMNpswO7TeD6mimJcMwjFM97FK sykcuKkjNB10FGXy4viUSR05YT99bdX+aLpKgIUme0uu42uON8KsrF/9E+UDNk4nV+ q/AIwU9oMx7yWdUSOTyoB5ul/y7gtrbkjUWaRAWW/QxzIM6zuXNhlrI0tcvSFK0kYD RTzlb41ldDhWjTjb6l4haQeu8YGj3P+BMg0R615vDOTEHG8M2iiV7Z87OP8Hv4ZLgt j6oVGIzgj9Q0Q== Received: from mx4.veeam.com (mx4.amust.local [172.31.224.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.veeam.com (Postfix) with ESMTPS id CA0FA423FD; Wed, 5 Apr 2023 06:09:15 -0400 (EDT) Received: from mail.veeam.com (prgmbx01.amust.local [172.24.128.102]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx4.veeam.com (Postfix) with ESMTPS id 4AED1C1A3C; Tue, 4 Apr 2023 17:09:02 +0300 (MSK) Received: from ssh-deb10-ssd-vb.amust.local (172.24.10.107) by prgmbx01.amust.local (172.24.128.102) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.26; Tue, 4 Apr 2023 16:08:58 +0200 From: Sergei Shtepa To: , , , CC: , , , , , , , , , , , , Subject: [PATCH v3 01/11] documentation: Block Device Filtering Mechanism Date: Tue, 4 Apr 2023 16:08:25 +0200 Message-ID: <20230404140835.25166-2-sergei.shtepa@veeam.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20230404140835.25166-1-sergei.shtepa@veeam.com> References: <20230404140835.25166-1-sergei.shtepa@veeam.com> MIME-Version: 1.0 X-Originating-IP: [172.24.10.107] X-ClientProxiedBy: prgmbx02.amust.local (172.24.128.103) To prgmbx01.amust.local (172.24.128.102) X-EsetResult: clean, is OK X-EsetId: 37303A2924031554657367 X-Veeam-MMEX: True X-Spam-Status: No, score=-0.2 required=5.0 tests=DKIMWL_WL_HIGH,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1762354310970947513?= X-GMAIL-MSGID: =?utf-8?q?1762354310970947513?= The document contains: * Describes the purpose of the mechanism * A little historical background on the capabilities of handling I/O units of the Linux kernel * Brief description of the design * Reference to interface description Signed-off-by: Sergei Shtepa --- Documentation/block/blkfilter.rst | 64 +++++++++++++++++++++++++++++++ Documentation/block/index.rst | 1 + MAINTAINERS | 6 +++ 3 files changed, 71 insertions(+) create mode 100644 Documentation/block/blkfilter.rst diff --git a/Documentation/block/blkfilter.rst b/Documentation/block/blkfilter.rst new file mode 100644 index 000000000000..084340e4a440 --- /dev/null +++ b/Documentation/block/blkfilter.rst @@ -0,0 +1,64 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================================ +Block Device Filtering Mechanism +================================ + +The block device filtering mechanism is an API that allows to attach block +device filters. Block device filters allow perform additional processing +for I/O units. + +Introduction +============ + +The idea of handling I/O units on block devices is not new. Back in the +2.6 kernel, there was an undocumented possibility of handling I/O units +by substituting the make_request_fn() function, which belonged to the +request_queue structure. But none of the in-tree kernel modules used this +feature, and it was eliminated in the 5.10 kernel. + +The block device filtering mechanism returns the ability to handle I/O units. +It is possible to safely attach filter to a block device "on the fly" without +changing the structure of block devices stack. + +It supports attaching one filter to one block device, because there is only +one filter implementation in the kernel yet. +See Documentation/block/blksnap.rst. + +Design +====== + +The block device filtering mechanism provides registration and unregistration +for filter operations. The struct blkfilter_operations contains a pointer to +the callback functions for the filter. After registering the filter operations, +filter can be managed using block device ioctl BLKFILTER_ATTACH, +BLKFILTER_DETACH and BLKFILTER_CTL. + +When the filter is attached, the callback function is called for each I/O unit +for a block device, providing I/O unit filtering. Depending on the result of +filtering the I/O unit, it can either be passed for subsequent processing by +the block layer, or skipped. + +The filter can be implemented as a loadable module. In this case, module +unloading is blocked while the filter is attached to at least one of the block +devices. + +Interface description +===================== + +The ioctl BLKFILTER_ATTACH and BLKFILTER_DETACH use structure blkfilter_name. +It allows to attach a filter to a block device or detach it. + +The ioctl BLKFILTER_CTL use structure blkfilter_ctl. It allows to send a +filter-specific command. + +.. kernel-doc:: include/uapi/linux/blk-filter.h + +To register in the system, the filter creates its own account, which contains +callback functions, unique filter name and module owner. This filter account is +used by the registration functions. + +.. kernel-doc:: include/linux/blk-filter.h + +.. kernel-doc:: block/blk-filter.c + :export: diff --git a/Documentation/block/index.rst b/Documentation/block/index.rst index 102953166429..e56d89db7b85 100644 --- a/Documentation/block/index.rst +++ b/Documentation/block/index.rst @@ -10,6 +10,7 @@ Block bfq-iosched biovecs blk-mq + blkfilter cmdline-partition data-integrity deadline-iosched diff --git a/MAINTAINERS b/MAINTAINERS index 1dc8bd26b6cf..2cbe4331ac97 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3571,6 +3571,12 @@ M: Jan-Simon Moeller S: Maintained F: drivers/leds/leds-blinkm.c +BLOCK DEVICE FILTERING MECHANISM +M: Sergei Shtepa +L: linux-block@vger.kernel.org +S: Supported +F: Documentation/block/blkfilter.rst + BLOCK LAYER M: Jens Axboe L: linux-block@vger.kernel.org From patchwork Tue Apr 4 14:08:26 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergei Shtepa X-Patchwork-Id: 79787 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:b0ea:0:b0:3b6:4342:cba0 with SMTP id b10csp429087vqo; Wed, 5 Apr 2023 09:18:26 -0700 (PDT) X-Google-Smtp-Source: AKy350aIqUZLacpRbzAQJUQ0LYnhZTQ9eYbLAXvVFST+C8umCKH1EVG2aq8Nz/qxUCdqpwNe45LZ X-Received: by 2002:a17:906:1dc7:b0:930:8fd7:789f with SMTP id v7-20020a1709061dc700b009308fd7789fmr3550820ejh.38.1680711506480; Wed, 05 Apr 2023 09:18:26 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1680711506; cv=none; d=google.com; s=arc-20160816; b=QmhtH8JadFDp8jgzly5S5DzU1uyqxHam3Fr3GdqlVTMnsAlgNEVW3rdI+D6QGbXQzB XId2E7+k0K1Sf8lqex+0rL9BGNEIT0lDEuJHWdg+3QQ3Qseh5bdL/AShZIaw0Ho3YXYt uerl2I1ly1mLTLflWfM7Zlp+OOoqJQinO6004rUkV06d9A9Hk0rOB9sLYw6ESPWBSEBG Yo8HpblyqkSTqFV5g2DM3iowDH6DMAhJmpJ4iQcZ/ybY+yR3JQo/Oq8rZHFHg2M9DB2J 5WaOsTcIAHHsCcUw6T5i4YUvrV5nJ9bO1iQeFxf9xjKouuMN04jXXRnGM5ESUz2kzAEc xgmg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=3UR7des7d1SNvYKVMBIh+qNln+2BT+04uvAOd7yuI2M=; b=rLFaY6/4ULwfeAYox24rQZ6jJlQy+KwysUMRZziQfd231nhi5FAfCVCLDPFjt1+1Kw PNlbOW2JZZ703rxc44NGNLRh7Z9lEr6ca8aJxtsTJC3Z4WvU+r/rJiWKdGqXcra8Z85A PWp0aJNe4gkNtOEKwHDvHKbrgBTXNWPvLdEKZbpZHIQHVwEUPyJS8EnSsVrrdRx2GbEd WPQKJWitNflvhtvcUG7ZUg7TBprgsFnkC53IrYyq9y5BPgPrac4nR4i0bLQSGwgIEXzm oL85TovofaXZF8/wLvKMnF4LB4l1vS3EggjcDfDSSHDWUHUYLFVg6jCb/1UXa98pCbfd g2qQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b=RIn3+U5j; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id n20-20020a1709061d1400b0093149d8926csi1437802ejh.335.2023.04.05.09.17.35; Wed, 05 Apr 2023 09:18:26 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b=RIn3+U5j; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230042AbjDEQQR (ORCPT + 99 others); Wed, 5 Apr 2023 12:16:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44090 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232896AbjDEQPw (ORCPT ); Wed, 5 Apr 2023 12:15:52 -0400 Received: from mx2.veeam.com (mx2.veeam.com [64.129.123.6]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B98727DAC; Wed, 5 Apr 2023 09:15:09 -0700 (PDT) Received: from mx1.veeam.com (mx1.veeam.com [172.18.34.147]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx2.veeam.com (Postfix) with ESMTPS id 601D440FFD; Wed, 5 Apr 2023 12:13:42 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=veeam.com; s=mx2-2022; t=1680711222; bh=3UR7des7d1SNvYKVMBIh+qNln+2BT+04uvAOd7yuI2M=; h=From:To:CC:Subject:Date:In-Reply-To:References:From; b=RIn3+U5jfhLQPg7zRfvsc7fK79NALYNvCq4O6/oPagmitiWZ7B2wDCCLFlXAP+LR+ z2uKPTyIUEEdZfx/9JeZlVdmA9RJVJhtO2IZ99bG8jQZSqa0ct/Mh1G8MUyfGEvPRk MKIRN+m7/95OUFkeL5gKOAubKH34DQHNY82brfnxaOToZO/FsHRll99hx7rW8WVCcl +FK7hNt54IxmfOxlZu2YPHPRd/zNxbrJ3Ka8miSsvrXjRQ0vPO5h0bv++yX97cnYqg PnYEiXSISZcIX8fSGK+KuaHxdbuT0QImTaOk0KbUvT7ngJCEE6qSuQU29LsNkOFPtk mr6GLnSZrsE/A== Received: from mx4.veeam.com (mx4.amust.local [172.31.224.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.veeam.com (Postfix) with ESMTPS id 5664A42411; Wed, 5 Apr 2023 06:09:16 -0400 (EDT) Received: from mail.veeam.com (prgmbx01.amust.local [172.24.128.102]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx4.veeam.com (Postfix) with ESMTPS id 65A047D65D; Tue, 4 Apr 2023 17:09:03 +0300 (MSK) Received: from ssh-deb10-ssd-vb.amust.local (172.24.10.107) by prgmbx01.amust.local (172.24.128.102) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.26; Tue, 4 Apr 2023 16:08:59 +0200 From: Sergei Shtepa To: , , , CC: , , , , , , , , , , , , Subject: [PATCH v3 02/11] block: Block Device Filtering Mechanism Date: Tue, 4 Apr 2023 16:08:26 +0200 Message-ID: <20230404140835.25166-3-sergei.shtepa@veeam.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20230404140835.25166-1-sergei.shtepa@veeam.com> References: <20230404140835.25166-1-sergei.shtepa@veeam.com> MIME-Version: 1.0 X-Originating-IP: [172.24.10.107] X-ClientProxiedBy: prgmbx02.amust.local (172.24.128.103) To prgmbx01.amust.local (172.24.128.102) X-EsetResult: clean, is OK X-EsetId: 37303A2924031554657367 X-Veeam-MMEX: True X-Spam-Status: No, score=-0.2 required=5.0 tests=DKIMWL_WL_HIGH,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS,T_FILL_THIS_FORM_SHORT autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1762353748599246551?= X-GMAIL-MSGID: =?utf-8?q?1762353748599246551?= The block device filtering mechanism is an API that allows to attach block device filters. Block device filters allow perform additional processing for I/O units. The idea of handling I/O units on block devices is not new. Back in the 2.6 kernel, there was an undocumented possibility of handling I/O units by substituting the make_request_fn() function, which belonged to the request_queue structure. But none of the in-tree kernel modules used this feature, and it was eliminated in the 5.10 kernel. The block device filtering mechanism returns the ability to handle I/O units. It is possible to safely attach filter to a block device "on the fly" without changing the structure of block devices stack. Co-developed-by: Christoph Hellwig Signed-off-by: Christoph Hellwig Signed-off-by: Sergei Shtepa --- MAINTAINERS | 3 + block/Makefile | 2 +- block/bdev.c | 1 + block/blk-core.c | 40 ++++++- block/blk-filter.c | 199 ++++++++++++++++++++++++++++++++ block/blk.h | 10 ++ block/genhd.c | 2 + block/ioctl.c | 7 ++ block/partitions/core.c | 2 + include/linux/blk-filter.h | 51 ++++++++ include/linux/blk_types.h | 2 + include/linux/blkdev.h | 1 + include/uapi/linux/blk-filter.h | 35 ++++++ include/uapi/linux/fs.h | 5 + 14 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 block/blk-filter.c create mode 100644 include/linux/blk-filter.h create mode 100644 include/uapi/linux/blk-filter.h diff --git a/MAINTAINERS b/MAINTAINERS index 2cbe4331ac97..fb6b7abe83e1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3576,6 +3576,9 @@ M: Sergei Shtepa L: linux-block@vger.kernel.org S: Supported F: Documentation/block/blkfilter.rst +F: block/blk-filter.c +F: include/linux/blk-filter.h +F: include/uapi/linux/blk-filter.h BLOCK LAYER M: Jens Axboe diff --git a/block/Makefile b/block/Makefile index 4e01bb71ad6e..d4671c7e499c 100644 --- a/block/Makefile +++ b/block/Makefile @@ -9,7 +9,7 @@ obj-y := bdev.o fops.o bio.o elevator.o blk-core.o blk-sysfs.o \ blk-lib.o blk-mq.o blk-mq-tag.o blk-stat.o \ blk-mq-sysfs.o blk-mq-cpumap.o blk-mq-sched.o ioctl.o \ genhd.o ioprio.o badblocks.o partitions/ blk-rq-qos.o \ - disk-events.o blk-ia-ranges.o + disk-events.o blk-ia-ranges.o blk-filter.o obj-$(CONFIG_BOUNCE) += bounce.o obj-$(CONFIG_BLK_DEV_BSG_COMMON) += bsg.o diff --git a/block/bdev.c b/block/bdev.c index 1795c7d4b99e..e290020810dd 100644 --- a/block/bdev.c +++ b/block/bdev.c @@ -424,6 +424,7 @@ struct block_device *bdev_alloc(struct gendisk *disk, u8 partno) return NULL; } bdev->bd_disk = disk; + bdev->bd_filter = NULL; return bdev; } diff --git a/block/blk-core.c b/block/blk-core.c index 42926e6cb83c..179a1c9ecc90 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -591,10 +592,32 @@ static inline blk_status_t blk_check_zone_append(struct request_queue *q, return BLK_STS_OK; } +static bool submit_bio_filter(struct bio *bio) +{ + /* + * If this bio came from the filter driver, send it straight down to the + * actual device and clear the filtered flag, as the bio could be passed + * on to another device that might have a filter attached again. + */ + if (bio_flagged(bio, BIO_FILTERED)) { + bio_clear_flag(bio, BIO_FILTERED); + return false; + } + bio_set_flag(bio, BIO_FILTERED); + return bio->bi_bdev->bd_filter->ops->submit_bio(bio); +} + static void __submit_bio(struct bio *bio) { struct gendisk *disk = bio->bi_bdev->bd_disk; + /* + * If there is a filter driver attached, check if the BIO needs to go to + * the filter driver first, which can then pass on the bio or consume it. + */ + if (bio->bi_bdev->bd_filter && submit_bio_filter(bio)) + return; + if (unlikely(!blk_crypto_bio_prep(&bio))) return; @@ -682,6 +705,15 @@ static void __submit_bio_noacct_mq(struct bio *bio) current->bio_list = NULL; } +/** + * submit_bio_noacct_nocheck - re-submit a bio to the block device layer for I/O + * from block device filter. + * @bio: The bio describing the location in memory and on the device. + * + * This is a version of submit_bio() that shall only be used for I/O that is + * resubmitted to lower level by block device filters. All file systems and + * other upper level users of the block layer should use submit_bio() instead. + */ void submit_bio_noacct_nocheck(struct bio *bio) { blk_cgroup_bio_start(bio); @@ -702,13 +734,17 @@ void submit_bio_noacct_nocheck(struct bio *bio) * to collect a list of requests submited by a ->submit_bio method while * it is active, and then process them after it returned. */ - if (current->bio_list) + if (current->bio_list) { bio_list_add(¤t->bio_list[0], bio); - else if (!bio->bi_bdev->bd_disk->fops->submit_bio) + return; + } + + if (!bio->bi_bdev->bd_disk->fops->submit_bio) __submit_bio_noacct_mq(bio); else __submit_bio_noacct(bio); } +EXPORT_SYMBOL_GPL(submit_bio_noacct_nocheck); /** * submit_bio_noacct - re-submit a bio to the block device layer for I/O diff --git a/block/blk-filter.c b/block/blk-filter.c new file mode 100644 index 000000000000..5e9d884fad4d --- /dev/null +++ b/block/blk-filter.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#include +#include +#include + +#include "blk.h" + +static LIST_HEAD(blkfilters); +static DEFINE_SPINLOCK(blkfilters_lock); + +static inline struct blkfilter_operations *__blkfilter_find(const char *name) +{ + struct blkfilter_operations *ops; + + list_for_each_entry(ops, &blkfilters, link) + if (strncmp(ops->name, name, BLKFILTER_NAME_LENGTH) == 0) + return ops; + + return NULL; +} + +static inline struct blkfilter_operations *blkfilter_find_get(const char *name) +{ + struct blkfilter_operations *ops; + + spin_lock(&blkfilters_lock); + ops = __blkfilter_find(name); + if (ops && !try_module_get(ops->owner)) + ops = NULL; + spin_unlock(&blkfilters_lock); + + return ops; +} + +int blkfilter_ioctl_attach(struct block_device *bdev, + struct blkfilter_name __user *argp) +{ + struct blkfilter_name name; + struct blkfilter_operations *ops; + struct blkfilter *flt; + int ret; + + if (copy_from_user(&name, argp, sizeof(name))) + return -EFAULT; + + ops = blkfilter_find_get(name.name); + if (!ops) + return -ENOENT; + + ret = freeze_bdev(bdev); + if (ret) + goto out_put_module; + blk_mq_freeze_queue(bdev->bd_queue); + + if (bdev->bd_filter) { + if (bdev->bd_filter->ops == ops) + ret = -EALREADY; + else + ret = -EBUSY; + goto out_unfreeze; + } + + flt = ops->attach(bdev); + if (IS_ERR(flt)) { + ret = PTR_ERR(flt); + goto out_unfreeze; + } + + flt->ops = ops; + bdev->bd_filter = flt; + +out_unfreeze: + blk_mq_unfreeze_queue(bdev->bd_queue); + thaw_bdev(bdev); +out_put_module: + if (ret) + module_put(ops->owner); + return ret; +} + +static void __blkfilter_detach(struct block_device *bdev) +{ + struct blkfilter *flt = bdev->bd_filter; + const struct blkfilter_operations *ops = flt->ops; + + bdev->bd_filter = NULL; + ops->detach(flt); + module_put(ops->owner); +} + +void blkfilter_detach(struct block_device *bdev) +{ + if (bdev->bd_filter) { + blk_mq_freeze_queue(bdev->bd_queue); + __blkfilter_detach(bdev); + blk_mq_unfreeze_queue(bdev->bd_queue); + } +} + +int blkfilter_ioctl_detach(struct block_device *bdev, + struct blkfilter_name __user *argp) +{ + struct blkfilter_name name; + int error = 0; + + if (copy_from_user(&name, argp, sizeof(name))) + return -EFAULT; + + blk_mq_freeze_queue(bdev->bd_queue); + if (!bdev->bd_filter) { + error = -ENOENT; + goto out_unfreeze; + } + if (strncmp(bdev->bd_filter->ops->name, name.name, + BLKFILTER_NAME_LENGTH)) { + error = -EINVAL; + goto out_unfreeze; + } + + __blkfilter_detach(bdev); +out_unfreeze: + blk_mq_unfreeze_queue(bdev->bd_queue); + return error; +} + +int blkfilter_ioctl_ctl(struct block_device *bdev, + struct blkfilter_ctl __user *argp) +{ + struct blkfilter_ctl ctl; + struct blkfilter *flt; + int ret; + + if (copy_from_user(&ctl, argp, sizeof(ctl))) + return -EFAULT; + + ret = blk_queue_enter(bdev_get_queue(bdev), 0); + if (ret) + return ret; + + flt = bdev->bd_filter; + if (!flt || strncmp(flt->ops->name, ctl.name, BLKFILTER_NAME_LENGTH)) { + ret = -ENOENT; + goto out_queue_exit; + } + + if (!flt->ops->ctl) { + ret = -ENOTTY; + goto out_queue_exit; + } + + ret = flt->ops->ctl(flt, ctl.cmd, u64_to_user_ptr(ctl.opt), + &ctl.optlen); +out_queue_exit: + blk_queue_exit(bdev_get_queue(bdev)); + return ret; +} + +/** + * blkfilter_register() - Register block device filter operations + * @ops: The operations to register. + * + * Return: + * 0 if succeeded, + * -EBUSY if a block device filter with the same name is already + * registered. + */ +int blkfilter_register(struct blkfilter_operations *ops) +{ + struct blkfilter_operations *found; + int ret = 0; + + spin_lock(&blkfilters_lock); + found = __blkfilter_find(ops->name); + if (found) + ret = -EBUSY; + else + list_add_tail(&ops->link, &blkfilters); + spin_unlock(&blkfilters_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(blkfilter_register); + +/** + * blkfilter_unregister() - Unregister block device filter operations + * @ops: The operations to unregister. + * + * Important: before unloading, it is necessary to detach the filter from all + * block devices. + * + */ +void blkfilter_unregister(struct blkfilter_operations *ops) +{ + spin_lock(&blkfilters_lock); + list_del(&ops->link); + spin_unlock(&blkfilters_lock); +} +EXPORT_SYMBOL_GPL(blkfilter_unregister); diff --git a/block/blk.h b/block/blk.h index cc4e8873dfde..3500e46368e3 100644 --- a/block/blk.h +++ b/block/blk.h @@ -7,6 +7,8 @@ #include #include "blk-crypto-internal.h" +struct blkfilter_ctl; +struct blkfilter_name; struct elevator_type; /* Max future timer expiry for timeouts */ @@ -454,6 +456,14 @@ long compat_blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg); extern const struct address_space_operations def_blk_aops; +int blkfilter_ioctl_attach(struct block_device *bdev, + struct blkfilter_name __user *argp); +int blkfilter_ioctl_detach(struct block_device *bdev, + struct blkfilter_name __user *argp); +int blkfilter_ioctl_ctl(struct block_device *bdev, + struct blkfilter_ctl __user *argp); +void blkfilter_detach(struct block_device *bdev); + int disk_register_independent_access_ranges(struct gendisk *disk); void disk_unregister_independent_access_ranges(struct gendisk *disk); diff --git a/block/genhd.c b/block/genhd.c index 02d9cfb9e077..b23ceea895de 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "blk-throttle.h" #include "blk.h" @@ -625,6 +626,7 @@ void del_gendisk(struct gendisk *disk) fsync_bdev(disk->part0); __invalidate_device(disk->part0, true); + blkfilter_detach(disk->part0); /* * Fail any new I/O. diff --git a/block/ioctl.c b/block/ioctl.c index 9c5f637ff153..e840150e1aa8 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -545,6 +546,12 @@ static int blkdev_common_ioctl(struct block_device *bdev, fmode_t mode, return blkdev_pr_preempt(bdev, argp, true); case IOC_PR_CLEAR: return blkdev_pr_clear(bdev, argp); + case BLKFILTER_ATTACH: + return blkfilter_ioctl_attach(bdev, argp); + case BLKFILTER_DETACH: + return blkfilter_ioctl_detach(bdev, argp); + case BLKFILTER_CTL: + return blkfilter_ioctl_ctl(bdev, argp); default: return -ENOIOCTLCMD; } diff --git a/block/partitions/core.c b/block/partitions/core.c index 7b8ef6296abd..0b5e3a3f7c31 100644 --- a/block/partitions/core.c +++ b/block/partitions/core.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "check.h" static int (*check_part[])(struct parsed_partitions *) = { @@ -277,6 +278,7 @@ static void delete_partition(struct block_device *part) fsync_bdev(part); __invalidate_device(part, true); + blkfilter_detach(part); xa_erase(&part->bd_disk->part_tbl, part->bd_partno); kobject_put(part->bd_holder_dir); diff --git a/include/linux/blk-filter.h b/include/linux/blk-filter.h new file mode 100644 index 000000000000..0afdb40f3bab --- /dev/null +++ b/include/linux/blk-filter.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#ifndef _LINUX_BLK_FILTER_H +#define _LINUX_BLK_FILTER_H + +#include + +struct bio; +struct block_device; +struct blkfilter_operations; + +/** + * struct blkfilter - Block device filter. + * + * @ops: Block device filter operations. + * + * For each filtered block device, the filter creates a data structure + * associated with this device. The data in this structure is specific to the + * filter, but it must contain a pointer to the block device filter account. + */ +struct blkfilter { + const struct blkfilter_operations *ops; +}; + +/** + * struct blkfilter_operations - Block device filter operations. + * + * @link: Entry in the global list of filter drivers + * (must not be accessed by the driver). + * @owner: Module implementing the filter driver. + * @name: Name of the filter driver. + * @attach: Attach the filter driver to the block device. + * @detach: Detach the filter driver from the block device. + * @ctl: Send a control command to the filter driver. + * @submit_bio: Handle bio submissions to the filter driver. + */ +struct blkfilter_operations { + struct list_head link; + struct module *owner; + const char *name; + struct blkfilter *(*attach)(struct block_device *bdev); + void (*detach)(struct blkfilter *flt); + int (*ctl)(struct blkfilter *flt, const unsigned int cmd, + __u8 __user *buf, __u32 *plen); + bool (*submit_bio)(struct bio *bio); +}; + +int blkfilter_register(struct blkfilter_operations *ops); +void blkfilter_unregister(struct blkfilter_operations *ops); + +#endif /* _UAPI_LINUX_BLK_FILTER_H */ diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h index 99be590f952f..b140ddd9b7ab 100644 --- a/include/linux/blk_types.h +++ b/include/linux/blk_types.h @@ -68,6 +68,7 @@ struct block_device { #ifdef CONFIG_FAIL_MAKE_REQUEST bool bd_make_it_fail; #endif + struct blkfilter *bd_filter; } __randomize_layout; #define bdev_whole(_bdev) \ @@ -333,6 +334,7 @@ enum { BIO_QOS_MERGED, /* but went through rq_qos merge path */ BIO_REMAPPED, BIO_ZONE_WRITE_LOCKED, /* Owns a zoned device zone write lock */ + BIO_FILTERED, /* bio has already been filtered */ BIO_FLAG_LAST }; diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 941304f17492..25ebbf296f35 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -854,6 +854,7 @@ void blk_request_module(dev_t devt); extern int blk_register_queue(struct gendisk *disk); extern void blk_unregister_queue(struct gendisk *disk); +void submit_bio_noacct_nocheck(struct bio *bio); void submit_bio_noacct(struct bio *bio); struct bio *bio_split_to_limits(struct bio *bio); diff --git a/include/uapi/linux/blk-filter.h b/include/uapi/linux/blk-filter.h new file mode 100644 index 000000000000..18885dc1b717 --- /dev/null +++ b/include/uapi/linux/blk-filter.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#ifndef _UAPI_LINUX_BLK_FILTER_H +#define _UAPI_LINUX_BLK_FILTER_H + +#include + +#define BLKFILTER_NAME_LENGTH 32 + +/** + * struct blkfilter_name - parameter for BLKFILTER_ATTACH and BLKFILTER_DETACH + * ioctl. + * + * @name: Name of block device filter. + */ +struct blkfilter_name { + __u8 name[BLKFILTER_NAME_LENGTH]; +}; + +/** + * struct blkfilter_ctl - parameter for BLKFILTER_CTL ioctl + * + * @name: Name of block device filter. + * @cmd: The filter-specific operation code of the command. + * @optlen: Size of data at @opt. + * @opt: Userspace buffer with options. + */ +struct blkfilter_ctl { + __u8 name[BLKFILTER_NAME_LENGTH]; + __u32 cmd; + __u32 optlen; + __u64 opt; +}; + +#endif /* _UAPI_LINUX_BLK_FILTER_H */ diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index b7b56871029c..1848d62979a4 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -185,6 +185,11 @@ struct fsxattr { #define BLKROTATIONAL _IO(0x12,126) #define BLKZEROOUT _IO(0x12,127) #define BLKGETDISKSEQ _IOR(0x12,128,__u64) +/* 13* is defined in linux/blkzoned.h */ +#define BLKFILTER_ATTACH _IOWR(0x12, 140, struct blkfilter_name) +#define BLKFILTER_DETACH _IOWR(0x12, 141, struct blkfilter_name) +#define BLKFILTER_CTL _IOWR(0x12, 142, struct blkfilter_ctl) + /* * A jump here: 130-136 are reserved for zoned block devices * (see uapi/linux/blkzoned.h) From patchwork Tue Apr 4 14:08:27 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergei Shtepa X-Patchwork-Id: 79795 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:b0ea:0:b0:3b6:4342:cba0 with SMTP id b10csp442474vqo; Wed, 5 Apr 2023 09:37:40 -0700 (PDT) X-Google-Smtp-Source: AKy350bEqi6JRO75bq/TWNtu+GUfcjk1OltSZqOfdwBxiMUIyiNdNQbkGd5S6D1peQjs+ASYpO5K X-Received: by 2002:a05:6a20:8b87:b0:da:fa65:cd89 with SMTP id m7-20020a056a208b8700b000dafa65cd89mr5482014pzh.9.1680712660015; Wed, 05 Apr 2023 09:37:40 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1680712659; cv=none; d=google.com; s=arc-20160816; b=hIzGunuTBx4UcQIlIqMiCNFN30DmT3VvU0RVg2usgvkbfqa4CBzjHEyZdVFB9/Voi9 dgs8L0rYrZFaUXyToMdmI0LfQrYDx8nDT6cw5pCOQGbW5XmZTufVwbT9yC5ImGsG7pzj cLW9pvQqQEiAYBejXIusud3/N09VB0bRSQ/SRJfuUTAc932BzW3TLS4jXQkHHklmz78C mrmNR0OEXOupKv6YpZbG2U3x3ocrTOVU8GsP29vnTW466wMsAMMaapf0bBET8FxPX1qW 1anN07uUaZ/fCXWqnFUC7kThM5RoUADCBhB2lHR585Zvug1U91GCnYFHNK7Yjz4XXTS6 ANQg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=vI25glQHCKQl7a1zjzkrzoHXKxdZUuMwIgZtjSxTbJw=; b=sSBK+tvLljpcA1rkp8vIb4ruoafTfXeHMl7S4hKinPTcOuBsdLo2b/0CHdm9yu6AAG df/+R/VQRb2+JPBRM4MocXukc9e4P4dh80kxq45Zd9N3VrsdiuvHtkPp/oR5DQpnc2d/ XNIgu4gscpQVBl1N7trmrRqDIscmWl5gH7uFNcemXFTkaGa1ge1BEvQWa4hPzrs1b8E4 mQjSuwk5JzGOUKqgeJWuavJtW5CIkxpoPDG6Wi3GfLNwzRQHqhE+b2JYR+waU3QOJqPq H+SYEb0GcBZX75qDBhfeXPgVxmSUdSoybzuy8FTsIPCRSVMlkZ8RguBc7sLzl26/YA8z PMMg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b=nFlhpqtp; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id h187-20020a636cc4000000b0050fad5c0e52si12031848pgc.830.2023.04.05.09.37.26; Wed, 05 Apr 2023 09:37:39 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b=nFlhpqtp; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229484AbjDEQQU (ORCPT + 99 others); Wed, 5 Apr 2023 12:16:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44230 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230059AbjDEQP5 (ORCPT ); Wed, 5 Apr 2023 12:15:57 -0400 Received: from mx2.veeam.com (mx2.veeam.com [64.129.123.6]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 30A987ECC; Wed, 5 Apr 2023 09:15:13 -0700 (PDT) Received: from mx1.veeam.com (mx1.veeam.com [172.18.34.147]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx2.veeam.com (Postfix) with ESMTPS id 69A13411C6; Wed, 5 Apr 2023 12:13:42 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=veeam.com; s=mx2-2022; t=1680711222; bh=vI25glQHCKQl7a1zjzkrzoHXKxdZUuMwIgZtjSxTbJw=; h=From:To:CC:Subject:Date:In-Reply-To:References:From; b=nFlhpqtpYB21tDmChegSoSKngtgm1BJNb/Xk63WwbFXUitFDzNDcD/F0cB4r4A8oV qQnwY+2RfZWJMsdm8xXTJgbOerebZKE+DOp6Q5KMANO67VLGB4YrReOU6DP+FKhwhu 095IYv2+P4xCuOB1aawe7cybi93kGSn/NtXnW2RQrdWGFTvkEIb3Gcmt9CSbQebRKM 079JoZu2wofXuRFJh+JUROWmok6e72DBcoLCIThNFUSNVx6XmlTdXvoHp7AV15d+jv V2MANM0hRph5Uc24z7JRxnyxWeaWY2BcOd1xkCXcULSSmzRDNlGyysZBbW+pZlfTz6 ERmRCV3a/cCBA== Received: from mx4.veeam.com (mx4.amust.local [172.31.224.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.veeam.com (Postfix) with ESMTPS id 56437423F1; Wed, 5 Apr 2023 06:09:16 -0400 (EDT) Received: from mail.veeam.com (prgmbx01.amust.local [172.24.128.102]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx4.veeam.com (Postfix) with ESMTPS id 85C267D642; Tue, 4 Apr 2023 17:09:04 +0300 (MSK) Received: from ssh-deb10-ssd-vb.amust.local (172.24.10.107) by prgmbx01.amust.local (172.24.128.102) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.26; Tue, 4 Apr 2023 16:09:01 +0200 From: Sergei Shtepa To: , , , CC: , , , , , , , , , , , , Subject: [PATCH v3 03/11] documentation: Block Devices Snapshots Module Date: Tue, 4 Apr 2023 16:08:27 +0200 Message-ID: <20230404140835.25166-4-sergei.shtepa@veeam.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20230404140835.25166-1-sergei.shtepa@veeam.com> References: <20230404140835.25166-1-sergei.shtepa@veeam.com> MIME-Version: 1.0 X-Originating-IP: [172.24.10.107] X-ClientProxiedBy: prgmbx02.amust.local (172.24.128.103) To prgmbx01.amust.local (172.24.128.102) X-EsetResult: clean, is OK X-EsetId: 37303A2924031554657367 X-Veeam-MMEX: True X-Spam-Status: No, score=-0.2 required=5.0 tests=DKIMWL_WL_HIGH,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1762354957975316233?= X-GMAIL-MSGID: =?utf-8?q?1762354957975316233?= The document contains: * Describes the purpose of the mechanism * Description of features * Description of algorithms * Recommendations about using the module from the user-space side * Reference to module interface description Signed-off-by: Sergei Shtepa Tested-By: Donald Buczek --- Documentation/block/blksnap.rst | 345 ++++++++++++++++++++++++++++++++ Documentation/block/index.rst | 1 + MAINTAINERS | 6 + 3 files changed, 352 insertions(+) create mode 100644 Documentation/block/blksnap.rst diff --git a/Documentation/block/blksnap.rst b/Documentation/block/blksnap.rst new file mode 100644 index 000000000000..7752f33809bb --- /dev/null +++ b/Documentation/block/blksnap.rst @@ -0,0 +1,345 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================================== +Block Devices Snapshots Module (blksnap) +======================================== + +Introduction +============ + +At first glance, there is no novelty in the idea of creating snapshots for +block devices. The Linux kernel already has mechanisms for creating snapshots. +Device Mapper includes dm-snap, which allows to create snapshots of block +devices. BTRFS supports snapshots at the file system level. However, both +of these options have flaws that do not allow to use them as a universal +tool for creating backups. + +The main properties that a backup tool should have are: + +- Simplicity and versatility of use +- Reliability +- Minimal consumption of system resources during backup +- Minimal time required for recovery or replication of the entire system + +Therefore, the features of the blksnap module are: + +- Change tracker +- Snapshots at the block device level +- Dynamic allocation of space for storing differences +- Snapshot overflow resistance +- Coherent snapshot of multiple block devices + +Features +======== + +Change tracker +-------------- + +The change tracker allows to determine which blocks were changed during the +time between the last snapshot created and any of the previous snapshots. +Having a map of changes, it is enough to copy only the changed blocks, and +no need to reread the entire block device completely. The change tracker +allows to implement the logic of both incremental and differential backups. +Incremental backup is critical for large file repositories whose size can be +hundreds of terabytes and whose full backup time can take more than a day. +On such servers, the use of backup tools without a change tracker becomes +practically impossible. + +Snapshot at the block device level +---------------------------------- + +A snapshot at the block device level allows to simplify the backup algorithm +and reduce consumption of system resources. It also allows to perform linear +reading of disk space directly, which allows to achieve maximum reading speed +with minimal use of processor time. At the same time, the versatility of +creating snapshots for any block device is achieved, regardless of the file +system located on it. The exceptions are BTRFS, ZFS and cluster file systems. + +Dynamic allocation of storage space for differences +--------------------------------------------------- + +To store differences, the module does not require a pre-reserved block +device range. A range of sectors can be allocated on any block device +immediately before creating a snapshot in individual files on the file +system. In addition, the size of the difference storage can be increased +after the snapshot is created by adding new sector ranges on block devices. +Sector ranges can be allocated on any block devices of the system, including +those on which the snapshot was created. A shared difference storage for +all images of snapshot block devices allows to optimize the use of disk space. + +Snapshot overflow resistance +---------------------------- + +To create images of snapshots of block devices, the module stores blocks +of the original block device that have been changed since the snapshot +was taken. To do this, the module handles write requests and reads blocks +that need to be overwritten. This algorithm guarantees safety of the data +of the original block device in the event of an overflow of the snapshot, +and even in the case of unpredictable critical errors. If a problem occurs +during backup, the difference storage is released, the snapshot is closed, +no backup is created, but the server continues to work. + +Coherent snapshot of multiple block devices +------------------------------------------- + +A snapshot is created simultaneously for all block devices for which a backup +is being created, ensuring their coherent state. + + +Algorithms +========== + +Overview +-------- + +The blksnap module is a block-level filter. It handles all write I/O units. +The filter is attached to the block device when the snapshot is created +for the first time. The change tracker marks all overwritten blocks. +Information about the history of changes on the block device is available +while holding the snapshot. The module reads the blocks that need to be +overwritten and stores them in the difference storage. When reading from +a snapshot image, reading is performed either from the original device or +from the difference storage. + +Change tracking +--------------- + +A change tracker map is created for each block device. One byte +of this map corresponds to one block. The block size is set by the +``tracking_block_minimum_shift`` and ``tracking_block_maximum_count`` +module parameters. The ``tracking_block_minimum_shift`` parameter limits +the minimum block size for tracking, while ``tracking_block_maximum_count`` +defines the maximum allowed number of blocks. The size of the change tracker +block is determined depending on the size of the block device when adding +a tracking device, that is, when the snapshot is taken for the first time. +The block size must be a power of two. The ``tracking_block_maximum_shift`` +module parameter allows to limit the maximum block size for tracking. If the +block size reaches the allowable limit, the number of blocks will exceed the +``tracking_block_maximum_count`` parameter. + +The byte of the change map stores a number from 0 to 255. This is the +snapshot number, since the creation of which there have been changes in +the block. Each time a snapshot is created, the number of the current +snapshot is increased by one. This number is written to the cell of the +change map when writing to the block. Thus, knowing the number of one of +the previous snapshots and the number of the last snapshot, one can determine +from the change map which blocks have been changed. When the number of the +current change reaches the maximum allowed value for the map of 255, at the +time when the next snapshot is created, the map of changes is reset to zero, +and the number of the current snapshot is assigned the value 1. The change +tracker is reset, and a new UUID is generated - a unique identifier of the +snapshot generation. The snapshot generation identifier allows to identify +that a change tracking reset has been performed. + +The change map has two copies. One copy is active, it tracks the current +changes on the block device. The second copy is available for reading +while the snapshot is being held, and contains the history up to the moment +the snapshot is taken. Copies are synchronized at the moment of snapshot +creation. After the snapshot is released, a second copy of the map is not +needed, but it is not released, so as not to allocate memory for it again +the next time the snapshot is created. + +Copy on write +------------- + +Data is copied in blocks, or rather in chunks. The term "chunk" is used to +avoid confusion with change tracker blocks and I/O blocks. In addition, +the "chunk" in the blksnap module means about the same as the "chunk" in +the dm-snap module. + +The size of the chunk is determined by the ``chunk_minimum_shift`` and +``chunk_maximum_count`` module parameters. The ``chunk_minimum_shift`` +parameter limits the minimum size of the chunk, while ``chunk_maximum_count`` +defines the maximum allowed number of chunks. The size of the chunk is +determined depending on the size of the block device at the time of taking the +snapshot. The size of the chunk must be a power of two. The module parameter +``chunk_maximum_shift`` allows to limit the maximum chunk size. If the chunk +size reaches the allowable limit, the number of chunks will exceed the +``chunk_maximum_count`` parameter. + +One chunk is described by the ``struct chunk`` structure. An array of structures +is created for each block device. The structure contains all the necessary +information to copy the chunks data from the original block device to the +difference storage. This information allows to describe the snapshot image. +A semaphore is located in the structure, which allows synchronization of threads +accessing the chunk. + +The block level has a feature. If a read I/O unit was sent, and a write I/O +unit was sent after it, then a write can be performed first, and only then +a read. Therefore, the copy-on-write algorithm is executed synchronously. +If a write request is handled, the execution of this I/O unit will be +delayed until the overwritten chunks are copied to the difference storage. +But if, when handling a write I/O unit, it turns out that the recorded range +of sectors has already been copied to the difference storage, then the I/O +unit is simply passed. + +This algorithm allows to efficiently perform backups of systems that run +Round Robin Database. Such databases can be overwritten several times during +the system backup. Of course, the value of a backup of the RRD monitoring +system data can be questioned. However, it is often a task to make a backup +of the entire enterprise infrastructure in order to restore or replicate it +entirely in case of problems. + +There is also a flaw in the algorithm. When overwriting at least one sector, +an entire chunk is copied. Thus, a situation of rapid filling of the difference +storage when writing data to a block device in small portions in random order +is possible. This situation is possible in case of strong fragmentation of +data on the file system. But it must be borne in mind that with such data +fragmentation, performance of systems usually degrades greatly. So, this +problem does not occur on real servers, although it can easily be created +by artificial tests. + +Difference storage +------------------ + +The difference storage is a pool of disk space areas, and it is shared with +all block devices in the snapshot. Therefore, there is no need to divide +the difference storage area between block devices, and the difference storage +itself can be located on different block devices. + +There is no need to allocate a large disk space immediately before creating +a snapshot. Even while the snapshot is being held, the difference storage +can be expanded. It is enough to have free space on the file system. + +Areas of disk space can be allocated on the file system using fallocate(), +and the file location can be requested using Fiemap Ioctl or Fibmap Ioctl. +Unfortunately, not all file systems support these mechanisms, but the most +common XFS, EXT4 and BTRFS file systems support it. BTRFS requires additional +conversion of virtual offsets to physical ones. + +While holding the snapshot, the user process can poll the status of the module. +When free space in the difference storage is reduced to a threshold value, the +module generates an event about it. The user process can prepare a new area +and pass it to the module to expand the difference storage. The threshold +value is determined as half of the value of the ``diff_storage_minimum`` +module parameter. + +If free space in the difference storage runs out, an event is generated about +the overflow of the snapshot. Such a snapshot is considered corrupted, and +read I/O units to snapshot images will be terminated with an error code. +The difference storage stores outdated data required for snapshot images, +so when the snapshot is overflowed, the backup process is interrupted, +but the system maintains its operability without data loss. + +Performing I/O for a snapshot image +----------------------------------- + +To read snapshot data, when taking a snapshot, block devices of snapshot images +are created. The snapshot image block devices support the write operation. +This allows to perform additional data preparation on the file system before +creating a backup. + +To process the I/O unit, clones of the I/O unit are created, which redirect +the I/O unit either to the original block device or to the difference storage. +When processing of cloned I/O units is completed, the original I/O unit is +marked as completed too. + +An I/O unit can be partially processed without accessing to block devices if +the I/O unit refers to a chunk that is in the queue for storing to the +difference storage. In this case, the data is read or written in a buffer in +memory. + +If, when processing the write I/O unit, it turns out that the data of the +referred chunk has not yet been stored to the difference storage or has not +even been read from the original device, then an I/O unit to read data from the +original device is initiated beforehand. After the reading from original device +is performed, their data from the I/O unit is partially overwritten directly in +the buffer of the chunk in memory, and the chunk is scheduled to be saved to the +difference storage. + +How to use +========== + +Depending on the needs and the selected license, you can choose different +options for managing the module: + +- Using ioctl directly +- Using a static C++ library +- Using the blksnap console tool + +Using a BLKFILTER_CTL for block device +-------------------------------------- + +BLKFILTER_CTL allows to send a filter-specific command to the filter on block +device and get the result of its execution. The module provides the +``include/uapi/blksnap.h`` header file with a description of the commands and +their data structures. + +1. ``blkfilter_ctl_blksnap_cbtinfo`` allows to get information from the + change tracker. +2. ``blkfilter_ctl_blksnap_cbtmap`` reads the change tracker table. If a write + operation was performed for the snapshot, then the change tracker takes this + into account. Therefore, it is necessary to receive tracker data after write + operations have been completed. +3. ``blkfilter_ctl_blksnap_cbtdirty`` mark blocks as changed in the change + tracker table. This is necessary if post-processing is performed after the + backup is created, which changes the backup blocks. +4. ``blkfilter_ctl_blksnap_snapshotadd`` adds a block device to the snapshot. +5. ``blkfilter_ctl_blksnap_snapshotinfo`` allows to get the name of the snapshot + image block device and the presence of an error. + +Using ioctl +----------- + +Using a BLKFILTER_CTL ioctl does not allow to fully implement the management of +the blksnap module. A control file ``blksnap-control`` is created to manage +snapshots. The control commands are also described in the file +``include/uapi/blksnap.h``. + +1. ``blksnap_ioctl_version`` get the version number. +2. ``blk_snap_ioctl_snapshot_create`` initiates the snapshot creation process. +3. ``blk_snap_ioctl_snapshot_append_storage`` add the range of blocks to + difference storage. +4. ``blk_snap_ioctl_snapshot_take`` creates block devices of block device + snapshot images. +5. ``blk_snap_ioctl_snapshot_collect`` collect all created snapshots. +6. ``blk_snap_ioctl_snapshot_wait_event`` allows to track the status of + snapshots and receive events about the requirement to expand the difference + storage or about snapshot overflow. +7. ``blk_snap_ioctl_snapshot_destroy`` releases the snapshot. + +Static C++ library +------------------ + +The [#userspace_libs]_ library was created primarily to simplify creation of +tests in C++, and it is also a good example of using the module interface. +When creating applications, direct use of control calls is preferable. +However, the library can be used in an application with a GPL-2+ license, +or a library with an LGPL-2+ license can be created, with which even a +proprietary application can be dynamically linked. + +blksnap console tool +-------------------- + +The blksnap [#userspace_tools]_ console tool allows to control the module +from the command line. The tool contains detailed built-in help. To get +the list of commands, enter the ``blksnap --help`` command. The ``blksnap + --help`` command allows to get detailed information about the +parameters of each command call. This option may be convenient when creating +proprietary software, as it allows not to compile with the open source code. +At the same time, the blksnap tool can be used for creating backup scripts. +For example, rsync can be called to synchronize files on the file system of +the mounted snapshot image and files in the archive on a file system that +supports compression. + +Tests +----- + +A set of tests was created for regression testing [#userspace_tests]_. +Tests with simple algorithms that use the ``blksnap`` console tool to +control the module are written in Bash. More complex testing algorithms +are implemented in C++. + +References +========== + +.. [#userspace_libs] https://github.com/veeam/blksnap/tree/stable-v2.0/lib + +.. [#userspace_tools] https://github.com/veeam/blksnap/tree/stable-v2.0/tools + +.. [#userspace_tests] https://github.com/veeam/blksnap/tree/stable-v2.0/tests + +Module interface description +============================ + +.. kernel-doc:: include/uapi/linux/blksnap.h diff --git a/Documentation/block/index.rst b/Documentation/block/index.rst index e56d89db7b85..34937516c865 100644 --- a/Documentation/block/index.rst +++ b/Documentation/block/index.rst @@ -11,6 +11,7 @@ Block biovecs blk-mq blkfilter + blksnap cmdline-partition data-integrity deadline-iosched diff --git a/MAINTAINERS b/MAINTAINERS index fb6b7abe83e1..4bdb30369a74 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3580,6 +3580,12 @@ F: block/blk-filter.c F: include/linux/blk-filter.h F: include/uapi/linux/blk-filter.h +BLOCK DEVICE SNAPSHOTS MODULE +M: Sergei Shtepa +L: linux-block@vger.kernel.org +S: Supported +F: Documentation/block/blksnap.rst + BLOCK LAYER M: Jens Axboe L: linux-block@vger.kernel.org From patchwork Tue Apr 4 14:08:28 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergei Shtepa X-Patchwork-Id: 79793 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:b0ea:0:b0:3b6:4342:cba0 with SMTP id b10csp439057vqo; Wed, 5 Apr 2023 09:32:20 -0700 (PDT) X-Google-Smtp-Source: AKy350ZJoPF3LsWrojpvRpatjHdTNZ8n7gRQww/Sss6UWjVJXG2Elj4ib4dXN2KeSsvkVNPHDRug X-Received: by 2002:a17:907:75e3:b0:935:3028:ff58 with SMTP id jz3-20020a17090775e300b009353028ff58mr3490677ejc.55.1680712340538; Wed, 05 Apr 2023 09:32:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1680712340; cv=none; d=google.com; s=arc-20160816; b=spDES/fgl1sGBM7mpu0QHwCv3LIxzt8GijidelPxDQ6qnL91hZUZ/OpbEH6KPTzayL 0GVPyjojD5UK7iJLn69CmoJOX3mMfQqYusgmX/qXvqKZhd9j3UEwRT0hl/kjyuDtQbRZ 3MJVEA8I1c5atgfLVl7pHCok4mv/h6Ky61TBLCbH0yhP89JfSdRrc6TXSRGLXchCx01y 7T2bw4c233FLKRAyL7er3LEwKWfefYL4VPT40kfTfz24AuoTuukHCx+ca3W2uyQF2cwy pnHqeza+Twb1AgULGpRcjnVt8aMGQwfrGhULdH31D0iPdr8nXa0yYD72OqZ87bmaF/e4 GXZA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=znObU4UF+Olqt26EUzuZdy91yy1lR2Qb8Z/TdIa3Vb0=; b=LRlyuXIVsy/8vnUSgZAsbueCVKG0dkUe3S3XG26QS7pryp9lmuKweUXWiKOENrVBS3 imv74EZhVrwvsgoWp3F9GbBUQo5aVVYkXWDZoisDUHK1i9EApL2J6arz78LaCckfen1k 6N9wYH8idFCQeWcRTwMzObcNzAdmH+kqcjBYm51Web2qgeXRl0vlFuKqCYOhNvAJhYkY 6U55h8CmDheUMr5vdQYBk/adu0er6CFKK3Y5bSbNndLkc56UhORL6kD5TnGUP4XXEUYH Bd8Rj96o+waJxWx5qQmjWNsut48Vhu6+3pQRrej/CLejVAQBOW48SFewzkUoEbZ4uXyn X58A== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b=I8WpK3mq; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id i19-20020a05640200d300b005008879d10dsi3074287edu.579.2023.04.05.09.31.54; Wed, 05 Apr 2023 09:32:20 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b=I8WpK3mq; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230313AbjDEQUZ (ORCPT + 99 others); Wed, 5 Apr 2023 12:20:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52682 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233803AbjDEQUQ (ORCPT ); Wed, 5 Apr 2023 12:20:16 -0400 Received: from mx2.veeam.com (mx2.veeam.com [64.129.123.6]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8EE13173E; Wed, 5 Apr 2023 09:20:03 -0700 (PDT) Received: from mx1.veeam.com (mx1.veeam.com [172.18.34.147]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx2.veeam.com (Postfix) with ESMTPS id 0C93941450; Wed, 5 Apr 2023 12:13:40 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=veeam.com; s=mx2-2022; t=1680711220; bh=znObU4UF+Olqt26EUzuZdy91yy1lR2Qb8Z/TdIa3Vb0=; h=From:To:CC:Subject:Date:In-Reply-To:References:From; b=I8WpK3mqduIMKORLJt8maw4w80iFKVWHhXQ2O6o5OIgRzOa3N+NGyUxqZshyi3Fz6 s7g8C2wWze6hNPhTH9/4aX7P4yL6E4YOYUYIRuP2RTvxKYHKDxlhCtJsz02ionBdAX d1KOH3URD5BLXNrGHX38wCHbEBc768yjTIl9e5rfjh56Hvd6HRYXkaDgf5Gaiu5g9T BgaT57rpg/ipY5iFeK9Q+m+xyU26rqL+YqdpQuHijzMmFs69VS0LL6gw+QDPsk8+Pa uKuogGCxbNdI7PKsYKXCjRUWQ4q66KJW/CbbOZ0OS8drIZ8IdhlQagas43f3rpHip1 JfElmRNMxvzNA== Received: from mx4.veeam.com (mx4.amust.local [172.31.224.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.veeam.com (Postfix) with ESMTPS id 98B2741D08; Wed, 5 Apr 2023 06:09:15 -0400 (EDT) Received: from mail.veeam.com (prgmbx01.amust.local [172.24.128.102]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx4.veeam.com (Postfix) with ESMTPS id A6C7F7D663; Tue, 4 Apr 2023 17:09:05 +0300 (MSK) Received: from ssh-deb10-ssd-vb.amust.local (172.24.10.107) by prgmbx01.amust.local (172.24.128.102) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.26; Tue, 4 Apr 2023 16:09:03 +0200 From: Sergei Shtepa To: , , , CC: , , , , , , , , , , , , Subject: [PATCH v3 04/11] blksnap: header file of the module interface Date: Tue, 4 Apr 2023 16:08:28 +0200 Message-ID: <20230404140835.25166-5-sergei.shtepa@veeam.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20230404140835.25166-1-sergei.shtepa@veeam.com> References: <20230404140835.25166-1-sergei.shtepa@veeam.com> MIME-Version: 1.0 X-Originating-IP: [172.24.10.107] X-ClientProxiedBy: prgmbx02.amust.local (172.24.128.103) To prgmbx01.amust.local (172.24.128.102) X-EsetResult: clean, is OK X-EsetId: 37303A2924031554657367 X-Veeam-MMEX: True X-Spam-Status: No, score=-0.2 required=5.0 tests=DKIMWL_WL_HIGH,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1762354623634548309?= X-GMAIL-MSGID: =?utf-8?q?1762354623634548309?= The header file contains a set of declarations, structures and control requests (ioctl) that allows to manage the module from the user space. Co-developed-by: Christoph Hellwig Signed-off-by: Christoph Hellwig Signed-off-by: Sergei Shtepa --- MAINTAINERS | 1 + include/uapi/linux/blksnap.h | 421 +++++++++++++++++++++++++++++++++++ 2 files changed, 422 insertions(+) create mode 100644 include/uapi/linux/blksnap.h diff --git a/MAINTAINERS b/MAINTAINERS index 4bdb30369a74..d4a9b44521dd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3585,6 +3585,7 @@ M: Sergei Shtepa L: linux-block@vger.kernel.org S: Supported F: Documentation/block/blksnap.rst +F: include/uapi/linux/blksnap.h BLOCK LAYER M: Jens Axboe diff --git a/include/uapi/linux/blksnap.h b/include/uapi/linux/blksnap.h new file mode 100644 index 000000000000..2bfcf9031e95 --- /dev/null +++ b/include/uapi/linux/blksnap.h @@ -0,0 +1,421 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#ifndef _UAPI_LINUX_BLKSNAP_H +#define _UAPI_LINUX_BLKSNAP_H + +#include + +#define BLKSNAP_CTL "blksnap-control" +#define BLKSNAP_IMAGE_NAME "blksnap-image" +#define BLKSNAP 'V' + +/** + * DOC: Block device filter interface. + * + * Control commands that are transmitted through the block device filter + * interface. + */ + +/** + * enum blkfilter_ctl_blksnap - List of commands for BLKFILTER_CTL ioctl + * + * @blkfilter_ctl_blksnap_cbtinfo: + * Get CBT information. + * The result of executing the command is a &struct blksnap_cbtinfo. + * Return 0 if succeeded, negative errno otherwise. + * @blkfilter_ctl_blksnap_cbtmap: + * Read the CBT map. + * The option passes the &struct blksnap_cbtmap. + * The size of the table can be quite large. Thus, the table is read in + * a loop, in each cycle of which the next offset is set to + * &blksnap_tracker_read_cbt_bitmap.offset. + * Return a count of bytes read if succeeded, negative errno otherwise. + * @blkfilter_ctl_blksnap_cbtdirty: + * Set dirty blocks in the CBT map. + * The option passes the &struct blksnap_cbtdirty. + * There are cases when some blocks need to be marked as changed. + * This ioctl allows to do this. + * Return 0 if succeeded, negative errno otherwise. + * @blkfilter_ctl_blksnap_snapshotadd: + * Add device to snapshot. + * The option passes the &struct blksnap_snapshotadd. + * Return 0 if succeeded, negative errno otherwise. + * @blkfilter_ctl_blksnap_snapshotinfo: + * Get information about snapshot. + * The result of executing the command is a &struct blksnap_snapshotinfo. + * Return 0 if succeeded, negative errno otherwise. + */ +enum blkfilter_ctl_blksnap { + blkfilter_ctl_blksnap_cbtinfo, + blkfilter_ctl_blksnap_cbtmap, + blkfilter_ctl_blksnap_cbtdirty, + blkfilter_ctl_blksnap_snapshotadd, + blkfilter_ctl_blksnap_snapshotinfo, +}; + +#ifndef UUID_SIZE +#define UUID_SIZE 16 +#endif + +/** + * struct blksnap_uuid - Unique 16-byte identifier. + * + * @b: + * An array of 16 bytes. + */ +struct blksnap_uuid { + __u8 b[UUID_SIZE]; +}; + +/** + * struct blksnap_cbtinfo - Result for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_cbtinfo. + * + * @device_capacity: + * Device capacity in bytes. + * @block_size: + * Block size in bytes. + * @block_count: + * Number of blocks. + * @generation_id: + * Unique identifier of change tracking generation. + * @changes_number: + * Current changes number. + */ +struct blksnap_cbtinfo { + __u64 device_capacity; + __u32 block_size; + __u32 block_count; + struct blksnap_uuid generation_id; + __u8 changes_number; +}; + +/** + * struct blksnap_cbtmap - Option for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_cbtmap. + * + * @offset: + * Offset from the beginning of the CBT bitmap in bytes. + * @length: + * Size of @buff in bytes. + * @buffer: + * Pointer to the buffer for output. + */ +struct blksnap_cbtmap { + __u32 offset; + __u32 length; + __u8 *buffer; +}; + +/** + * struct blksnap_sectors - Description of the block device region. + * + * @offset: + * Offset from the beginning of the disk in sectors. + * @count: + * Count of sectors. + */ +struct blksnap_sectors { + __u64 offset; + __u64 count; +}; + +/** + * struct blksnap_cbtdirty - Option for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_cbtdirty. + * + * @count: + * Count of elements in the @dirty_sectors. + * @dirty_sectors: + * Pointer to the array of &struct blksnap_sectors. + */ +struct blksnap_cbtdirty { + __u32 count; + struct blksnap_sectors *dirty_sectors; +}; + +/** + * struct blksnap_snapshotadd - Option for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_snapshotadd. + * + * @id: + * ID of the snapshot to which the block device should be added. + */ +struct blksnap_snapshotadd { + struct blksnap_uuid id; +}; + +#define IMAGE_DISK_NAME_LEN 32 + +/** + * struct blksnap_snapshotinfo - Result for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_snapshotinfo. + * + * @error_code: + * Zero if there were no errors while holding the snapshot. + * The error code -ENOSPC means that while holding the snapshot, a snapshot + * overflow situation has occurred. Other error codes mean other reasons + * for failure. + * The error code is reset when the device is added to a new snapshot. + * @image: + * If the snapshot was taken, it stores the block device name of the + * image, or empty string otherwise. + */ +struct blksnap_snapshotinfo { + __s32 error_code; + __u8 image[IMAGE_DISK_NAME_LEN]; +}; + +/** + * DOC: Interface for managing snapshots + * + * Control commands that are transmitted through the blksnap module interface. + */ +enum blksnap_ioctl { + blksnap_ioctl_version, + blksnap_ioctl_snapshot_create, + blksnap_ioctl_snapshot_destroy, + blksnap_ioctl_snapshot_append_storage, + blksnap_ioctl_snapshot_take, + blksnap_ioctl_snapshot_collect, + blksnap_ioctl_snapshot_wait_event, +}; + +/** + * struct blksnap_version - Module version. + * + * @major: + * Version major part. + * @minor: + * Version minor part. + * @revision: + * Revision number. + * @build: + * Build number. Should be zero. + */ +struct blksnap_version { + __u16 major; + __u16 minor; + __u16 revision; + __u16 build; +}; + +/** + * define IOCTL_BLKSNAP_VERSION - Get module version. + * + * The version may increase when the API changes. But linking the user space + * behavior to the version code does not seem to be a good idea. + * To ensure backward compatibility, API changes should be made by adding new + * ioctl without changing the behavior of existing ones. The version should be + * used for logs. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_VERSION \ + _IOW(BLKSNAP, blksnap_ioctl_version, struct blksnap_version) + + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_CREATE - Create snapshot. + * + * Creates a snapshot structure in the memory and allocates an identifier for + * it. Further interaction with the snapshot is possible by this identifier. + * A snapshot is created for several block devices at once. + * Several snapshots can be created at the same time, but with the condition + * that one block device can only be included in one snapshot. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_CREATE \ + _IOW(BLKSNAP, blksnap_ioctl_snapshot_create, \ + struct blksnap_uuid) + + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_DESTROY - Release and destroy the snapshot. + * + * Destroys snapshot with &blksnap_snapshot_destroy.id. This leads to the + * deletion of all block device images of the snapshot. The difference storage + * is being released. But the change tracker keeps tracking. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_DESTROY \ + _IOR(BLKSNAP, blksnap_ioctl_snapshot_destroy, \ + struct blksnap_uuid) + +/** + * struct blksnap_snapshot_append_storage - Argument for the + * &IOCTL_BLKSNAP_SNAPSHOT_APPEND_STORAGE control. + * + * @id: + * Snapshot ID. + * @bdev_path: + * Device path string buffer. + * @bdev_path_size: + * Device path string buffer size. + * @count: + * Size of @ranges in the number of &struct blksnap_sectors. + * @ranges: + * Pointer to the array of &struct blksnap_sectors. + */ +struct blksnap_snapshot_append_storage { + struct blksnap_uuid id; + __u8 *bdev_path; + __u32 bdev_path_size; + __u32 count; + struct blksnap_sectors *ranges; +}; + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_APPEND_STORAGE - Append storage to the + * difference storage of the snapshot. + * + * The snapshot difference storage can be set either before or after creating + * the snapshot images. This allows to dynamically expand the difference + * storage while holding the snapshot. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_APPEND_STORAGE \ + _IOW(BLKSNAP, blksnap_ioctl_snapshot_append_storage, \ + struct blksnap_snapshot_append_storage) + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_TAKE - Take snapshot. + * + * Creates snapshot images of block devices and switches change trackers tables. + * The snapshot must be created before this call, and the areas of block + * devices should be added to the difference storage. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_TAKE \ + _IOR(BLKSNAP, blksnap_ioctl_snapshot_take, \ + struct blksnap_uuid) + +/** + * struct blksnap_snapshot_collect - Argument for the + * &IOCTL_BLKSNAP_SNAPSHOT_COLLECT control. + * + * @count: + * Size of &blksnap_snapshot_collect.ids in the number of 16-byte UUID. + * @ids: + * Pointer to the array with the snapshot ID for output. + */ +struct blksnap_snapshot_collect { + __u32 count; + struct blksnap_uuid *ids; +}; + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_COLLECT - Get collection of created snapshots. + * + * Multiple snapshots can be created at the same time. This allows for one + * system to create backups for different data with a independent schedules. + * + * If in &blksnap_snapshot_collect.count is less than required to store the + * &blksnap_snapshot_collect.ids, the array is not filled, and the ioctl + * returns the required count for &blksnap_snapshot_collect.ids. + * + * So, it is recommended to call the ioctl twice. The first call with an null + * pointer &blksnap_snapshot_collect.ids and a zero value in + * &blksnap_snapshot_collect.count. It will set the required array size in + * &blksnap_snapshot_collect.count. The second call with a pointer + * &blksnap_snapshot_collect.ids to an array of the required size will allow to + * get collection of active snapshots. + * + * Return: 0 if succeeded, -ENODATA if there is not enough space in the array + * to store collection of active snapshots, or negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_COLLECT \ + _IOW(BLKSNAP, blksnap_ioctl_snapshot_collect, \ + struct blksnap_snapshot_collect) + +/** + * enum blksnap_event_codes - Variants of event codes. + * + * @blksnap_event_code_low_free_space: + * Low free space in difference storage event. + * If the free space in the difference storage is reduced to the specified + * limit, the module generates this event. + * @blksnap_event_code_corrupted: + * Snapshot image is corrupted event. + * If a chunk could not be allocated when trying to save data to the + * difference storage, this event is generated. However, this does not mean + * that the backup process was interrupted with an error. If the snapshot + * image has been read to the end by this time, the backup process is + * considered successful. + */ +enum blksnap_event_codes { + blksnap_event_code_low_free_space, + blksnap_event_code_corrupted, +}; + +/** + * struct blksnap_snapshot_event - Argument for the + * &IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT control. + * + * @id: + * Snapshot ID. + * @timeout_ms: + * Timeout for waiting in milliseconds. + * @time_label: + * Timestamp of the received event. + * @code: + * Code of the received event &enum blksnap_event_codes. + * @data: + * The received event body. + */ +struct blksnap_snapshot_event { + struct blksnap_uuid id; + __u32 timeout_ms; + __u32 code; + __s64 time_label; + __u8 data[4096 - 32]; +}; + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT - Wait and get the event from the + * snapshot. + * + * While holding the snapshot, the kernel module can transmit information about + * changes in its state in the form of events to the user level. + * It is very important to receive these events as quickly as possible, so the + * user's thread is in the state of interruptable sleep. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT \ + _IOW(BLKSNAP, blksnap_ioctl_snapshot_wait_event, \ + struct blksnap_snapshot_event) + +/** + * struct blksnap_event_low_free_space - Data for the + * &blksnap_event_code_low_free_space event. + * + * @requested_nr_sect: + * The required number of sectors. + */ +struct blksnap_event_low_free_space { + __u64 requested_nr_sect; +}; + +/** + * struct blksnap_event_corrupted - Data for the + * &blksnap_event_code_corrupted event. + * + * @dev_id_mj: + * Major part of original device ID. + * @dev_id_mn: + * Minor part of original device ID. + * @err_code: + * Error code. + */ +struct blksnap_event_corrupted { + __u32 dev_id_mj; + __u32 dev_id_mn; + __s32 err_code; +}; + +#endif /* _UAPI_LINUX_BLKSNAP_H */ From patchwork Tue Apr 4 14:08:29 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergei Shtepa X-Patchwork-Id: 79791 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:b0ea:0:b0:3b6:4342:cba0 with SMTP id b10csp437769vqo; Wed, 5 Apr 2023 09:30:35 -0700 (PDT) X-Google-Smtp-Source: AKy350YqKjfLo/WkMgme2gx1aGMN7qJs2tF31F/Gb+qO44O6ls/Nu5vU46lzZKXPkcUwTpeVBQYo X-Received: by 2002:a50:ec92:0:b0:500:2c4f:3f5 with SMTP id e18-20020a50ec92000000b005002c4f03f5mr2288833edr.12.1680712235444; Wed, 05 Apr 2023 09:30:35 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1680712235; cv=none; d=google.com; s=arc-20160816; b=FqGGBcBigMLoRbwBYQUe+WcjYE2gJ9vPByTcPUsOC9mnXgA5pjSx5S99j33LhFxr48 X0mSCL0g4YQmSCQw1UD2KF6grLUwVCEYXwVSZDdotRS2Nofpk9qrGOstVvCQ2voNXSgu BLjZWEzShMCQ/NYjOVPrZ5As3X4ULFSYJ6AOFfh2OTW2lxEm4MczXtK8CB/YJJRCGgut 3N1id2Dm/q6DEc2cXYluhqKt4HAobhB2+/dvR2og02JJ87B86R3/s1AQAGlnfoS83yga JG6yV5qHjJfPQH5d5ZO0WjcXVtkxsyaAFxfDNcLMPC7wbPXGjZmOpBCZXW9nn2tl7zTJ KPbA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=Ze5QjE1yiwPMlIFa9p4GDtorLv/PPi0SS6nJAo7avL0=; b=MsNA7Hxlfkimuz1SvcGPAHpm1ZFOJxeeA1Crc3+kbm5Ef5sIspPGxcr98NGCD/Z5cE Jt937j343U47YqG+JfD+t5hvgxJAGkKBf+wZ4n+O3P5763AFmEpi67eqOSzKOnaJgv95 MSaTdWpTo3J4i7WmY6i+X0LbYW/QEiju8k9baSKN3eezLFfFDRauaQmuL0k1RV+pOIg4 VTFxUJJy0meUGABKwg92Uf8OHCQk3iSHCwJOydPy1VXsfV5m7xPHBLXbi5R+2nav1Kbn X47YsStqY+vKB+zil6h3ugKIFEqmyGal/ajnMokM0Ajmj5Wzh/pCKUdREZ1mk9K78RyT kSxg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b="kMifM/iD"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id p8-20020aa7d308000000b004fa18c459fcsi835305edq.263.2023.04.05.09.30.10; Wed, 05 Apr 2023 09:30:35 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b="kMifM/iD"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231166AbjDEQOh (ORCPT + 99 others); Wed, 5 Apr 2023 12:14:37 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43126 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229533AbjDEQOf (ORCPT ); Wed, 5 Apr 2023 12:14:35 -0400 X-Greylist: delayed 5343 seconds by postgrey-1.37 at lindbergh.monkeyblade.net; Wed, 05 Apr 2023 09:14:11 PDT Received: from mx2.veeam.com (mx2.veeam.com [64.129.123.6]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 86BF37293; Wed, 5 Apr 2023 09:14:11 -0700 (PDT) Received: from mx1.veeam.com (mx1.veeam.com [172.18.34.147]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx2.veeam.com (Postfix) with ESMTPS id 1B949418AA; Wed, 5 Apr 2023 12:13:40 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=veeam.com; s=mx2-2022; t=1680711220; bh=Ze5QjE1yiwPMlIFa9p4GDtorLv/PPi0SS6nJAo7avL0=; h=From:To:CC:Subject:Date:In-Reply-To:References:From; b=kMifM/iDzpTuM/LeLqWyMF8FbQZS1wTJqi6gvF6qg9JlvjvvBPgNL9QVg8hW7suZE cMtMPDRDpiwPXrqgYE+JY/apEqmyHOICatAJx9bm95ddRCbajZmBQsnptZJ3F1DNgo akonCBI0lPpM4AuLuGurDyznVLfWAKejqwpF6RK7j9T3+a0VCM92KYsDTh9nsHUJ1S q4eO6dhdEqRurQJz+Es+IEbYD9j1pQmBKP0qD4l+CHeGd8FpFp67pV/xdcCMhA5Rxn 0g8RRUyPOb1qFHUSOhuS9ODFCymxdT1CKFT54mxPeKytULiv3Xt1JFIn7qtgKahrMd uBh8CkeVbDxwg== Received: from mx4.veeam.com (mx4.amust.local [172.31.224.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.veeam.com (Postfix) with ESMTPS id 8DF6B41DC1; Wed, 5 Apr 2023 06:09:15 -0400 (EDT) Received: from mail.veeam.com (prgmbx01.amust.local [172.24.128.102]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx4.veeam.com (Postfix) with ESMTPS id BE44DA234B; Tue, 4 Apr 2023 17:09:06 +0300 (MSK) Received: from ssh-deb10-ssd-vb.amust.local (172.24.10.107) by prgmbx01.amust.local (172.24.128.102) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.26; Tue, 4 Apr 2023 16:09:04 +0200 From: Sergei Shtepa To: , , , CC: , , , , , , , , , , , , Subject: [PATCH v3 05/11] blksnap: module management interface functions Date: Tue, 4 Apr 2023 16:08:29 +0200 Message-ID: <20230404140835.25166-6-sergei.shtepa@veeam.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20230404140835.25166-1-sergei.shtepa@veeam.com> References: <20230404140835.25166-1-sergei.shtepa@veeam.com> MIME-Version: 1.0 X-Originating-IP: [172.24.10.107] X-ClientProxiedBy: prgmbx02.amust.local (172.24.128.103) To prgmbx01.amust.local (172.24.128.102) X-EsetResult: clean, is OK X-EsetId: 37303A2924031554657367 X-Veeam-MMEX: True X-Spam-Status: No, score=-0.2 required=5.0 tests=DKIMWL_WL_HIGH,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1762354512606658588?= X-GMAIL-MSGID: =?utf-8?q?1762354512606658588?= Contains callback functions for loading and unloading the module and implementation of module management interface functions. The module parameters and other mandatory declarations for the kernel module are also defined. Co-developed-by: Christoph Hellwig Signed-off-by: Christoph Hellwig Signed-off-by: Sergei Shtepa --- MAINTAINERS | 1 + drivers/block/blksnap/main.c | 428 +++++++++++++++++++++++++++++++++ drivers/block/blksnap/params.h | 16 ++ 3 files changed, 445 insertions(+) create mode 100644 drivers/block/blksnap/main.c create mode 100644 drivers/block/blksnap/params.h diff --git a/MAINTAINERS b/MAINTAINERS index d4a9b44521dd..570333ee3801 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3585,6 +3585,7 @@ M: Sergei Shtepa L: linux-block@vger.kernel.org S: Supported F: Documentation/block/blksnap.rst +F: drivers/block/blksnap/* F: include/uapi/linux/blksnap.h BLOCK LAYER diff --git a/drivers/block/blksnap/main.c b/drivers/block/blksnap/main.c new file mode 100644 index 000000000000..dd43e8877da1 --- /dev/null +++ b/drivers/block/blksnap/main.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include "snapimage.h" +#include "snapshot.h" +#include "tracker.h" +#include "chunk.h" +#include "params.h" + +/* + * The power of 2 for minimum tracking block size. + * If we make the tracking block size small, we will get detailed information + * about the changes, but the size of the change tracker table will be too + * large, which will lead to inefficient memory usage. + */ +int tracking_block_minimum_shift = 16; + +/* + * The maximum number of tracking blocks. + * A table is created to store information about the status of all tracking + * blocks in RAM. So, if the size of the tracking block is small, then the size + * of the table turns out to be large and memory is consumed inefficiently. + * As the size of the block device grows, the size of the tracking block + * size should also grow. For this purpose, the limit of the maximum + * number of block size is set. + */ +int tracking_block_maximum_count = 2097152; + +/* + * The power of 2 for maximum tracking block size. + * On very large capacity disks, the block size may be too large. To prevent + * this, the maximum block size is limited. + * If the limit on the maximum block size has been reached, then the number of + * blocks may exceed the tracking_block_maximum_count. + */ +int tracking_block_maximum_shift = 26; + +/* + * The power of 2 for minimum chunk size. + * The size of the chunk depends on how much data will be copied to the + * difference storage when at least one sector of the block device is changed. + * If the size is small, then small I/O units will be generated, which will + * reduce performance. Too large a chunk size will lead to inefficient use of + * the difference storage. + */ +int chunk_minimum_shift = 18; + +/* + * The maximum number of chunks. + * To store information about the state of all the chunks, a table is created + * in RAM. So, if the size of the chunk is small, then the size of the table + * turns out to be large and memory is consumed inefficiently. + * As the size of the block device grows, the size of the chunk should also + * grow. For this purpose, the maximum number of chunks is set. + */ +int chunk_maximum_count = 2097152; + +/* + * The power of 2 for maximum chunk size. + * On very large capacity disks, the block size may be too large. To prevent + * this, the maximum block size is limited. + * If the limit on the maximum block size has been reached, then the number of + * blocks may exceed the chunk_maximum_count. + */ +int chunk_maximum_shift = 26; +/* + * The maximum number of chunks in queue. + * The chunk is not immediately stored to the difference storage. The chunks + * are put in a store queue. The store queue allows to postpone the operation + * of storing a chunks data to the difference storage and perform it later in + * the worker thread. + */ +int chunk_maximum_in_queue = 16; + +/* + * The size of the pool of preallocated difference buffers. + * A buffer can be allocated for each chunk. After use, this buffer is not + * released immediately, but is sent to the pool of free buffers. + * However, if there are too many free buffers in the pool, then these free + * buffers will be released immediately. + */ +int free_diff_buffer_pool_size = 128; + +/* + * The minimum allowable size of the difference storage in sectors. + * The difference storage is a part of the disk space allocated for storing + * snapshot data. If there is less free space in the storage than the minimum, + * an event is generated about the lack of free space. + */ +int diff_storage_minimum = 2097152; + +#define VERSION_STR "2.0.0.0" +static const struct blksnap_version version = { + .major = 2, + .minor = 0, + .revision = 0, + .build = 0, +}; + +int get_tracking_block_minimum_shift(void) +{ + return tracking_block_minimum_shift; +} +int get_tracking_block_maximum_shift(void) +{ + return tracking_block_maximum_shift; +} +int get_tracking_block_maximum_count(void) +{ + return tracking_block_maximum_count; +} +int get_chunk_minimum_shift(void) +{ + return chunk_minimum_shift; +} +int get_chunk_maximum_shift(void) +{ + return chunk_maximum_shift; +} +int get_chunk_maximum_count(void) +{ + return chunk_maximum_count; +} +int get_chunk_maximum_in_queue(void) +{ + return chunk_maximum_in_queue; +} +int get_free_diff_buffer_pool_size(void) +{ + return free_diff_buffer_pool_size; +} +int get_diff_storage_minimum(void) +{ + return diff_storage_minimum; +} + +static int ioctl_version(unsigned long arg) +{ + struct blksnap_version __user *user_version = (void *)arg; + + if (copy_to_user(user_version, &version, sizeof(version))) { + pr_err("Unable to get version: invalid user buffer\n"); + return -ENODATA; + } + + return 0; +} + +static_assert(sizeof(uuid_t) == sizeof(struct blksnap_uuid), + "Invalid size of struct blksnap_uuid."); + +static int ioctl_snapshot_create(unsigned long arg) +{ + struct blksnap_uuid __user *user_id = (void *)arg; + uuid_t kernel_id; + int ret; + + ret = snapshot_create(&kernel_id); + if (ret) + return ret; + + if (copy_to_user(user_id->b, kernel_id.b, sizeof(uuid_t))) { + pr_err("Unable to create snapshot: invalid user buffer\n"); + return -ENODATA; + } + + return 0; +} + +static int ioctl_snapshot_destroy(unsigned long arg) +{ + struct blksnap_uuid __user *user_id = (void *)arg; + uuid_t kernel_id; + + if (copy_from_user(kernel_id.b, user_id->b, sizeof(uuid_t))) { + pr_err("Unable to destroy snapshot: invalid user buffer\n"); + return -ENODATA; + } + + return snapshot_destroy(&kernel_id); +} + +static int ioctl_snapshot_append_storage(unsigned long arg) +{ + int ret; + struct blksnap_snapshot_append_storage __user *uarg = (void *)arg; + struct blksnap_snapshot_append_storage karg; + char *bdev_path = NULL; + + pr_debug("Append difference storage\n"); + + if (copy_from_user(&karg, uarg, sizeof(karg))) { + pr_err("Unable to append difference storage: invalid user buffer\n"); + return -EINVAL; + } + + bdev_path = strndup_user(karg.bdev_path, karg.bdev_path_size); + if (IS_ERR(bdev_path)) { + pr_err("Unable to append difference storage: invalid block device name buffer\n"); + return PTR_ERR(bdev_path); + } + + ret = snapshot_append_storage((uuid_t *)karg.id.b, bdev_path, + karg.ranges, karg.count); + kfree(bdev_path); + return ret; +} + +static int ioctl_snapshot_take(unsigned long arg) +{ + struct blksnap_uuid __user *user_id = (void *)arg; + uuid_t kernel_id; + + if (copy_from_user(kernel_id.b, user_id->b, sizeof(uuid_t))) { + pr_err("Unable to take snapshot: invalid user buffer\n"); + return -ENODATA; + } + + return snapshot_take(&kernel_id); +} + +static int ioctl_snapshot_collect(unsigned long arg) +{ + int ret; + struct blksnap_snapshot_collect karg; + + if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { + pr_err("Unable to collect available snapshots: invalid user buffer\n"); + return -ENODATA; + } + + ret = snapshot_collect(&karg.count, karg.ids); + + if (copy_to_user((void *)arg, &karg, sizeof(karg))) { + pr_err("Unable to collect available snapshots: invalid user buffer\n"); + return -ENODATA; + } + + return ret; +} + +static_assert(sizeof(struct blksnap_snapshot_event) == 4096, + "The size struct blksnap_snapshot_event should be equal to the size of the page."); + +static int ioctl_snapshot_wait_event(unsigned long arg) +{ + int ret = 0; + struct blksnap_snapshot_event __user *uarg = (void *)arg; + struct blksnap_snapshot_event *karg; + struct event *ev; + + karg = kzalloc(sizeof(struct blksnap_snapshot_event), GFP_KERNEL); + if (!karg) + return -ENOMEM; + + /* Copy only snapshot ID and timeout*/ + if (copy_from_user(karg, uarg, sizeof(uuid_t) + sizeof(__u32))) { + pr_err("Unable to get snapshot event. Invalid user buffer\n"); + ret = -EINVAL; + goto out; + } + + ev = snapshot_wait_event((uuid_t *)karg->id.b, karg->timeout_ms); + if (IS_ERR(ev)) { + ret = PTR_ERR(ev); + goto out; + } + + pr_debug("Received event=%lld code=%d data_size=%d\n", ev->time, + ev->code, ev->data_size); + karg->code = ev->code; + karg->time_label = ev->time; + + if (ev->data_size > sizeof(karg->data)) { + pr_err("Event size %d is too big\n", ev->data_size); + ret = -ENOSPC; + /* If we can't copy all the data, we copy only part of it. */ + } + memcpy(karg->data, ev->data, ev->data_size); + event_free(ev); + + if (copy_to_user(uarg, karg, sizeof(struct blksnap_snapshot_event))) { + pr_err("Unable to get snapshot event. Invalid user buffer\n"); + ret = -EINVAL; + } +out: + kfree(karg); + + return ret; +} + +static int (*const blksnap_ioctl_table[])(unsigned long arg) = { + ioctl_version, + ioctl_snapshot_create, + ioctl_snapshot_destroy, + ioctl_snapshot_append_storage, + ioctl_snapshot_take, + ioctl_snapshot_collect, + ioctl_snapshot_wait_event, +}; + +static_assert( + sizeof(blksnap_ioctl_table) == + ((blksnap_ioctl_snapshot_wait_event + 1) * sizeof(void *)), + "The size of table blksnap_ioctl_table does not match the enum blksnap_ioctl."); + +static long ctrl_unlocked_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int nr = _IOC_NR(cmd); + + if (nr > (sizeof(blksnap_ioctl_table) / sizeof(void *))) + return -ENOTTY; + + if (!blksnap_ioctl_table[nr]) + return -ENOTTY; + + return blksnap_ioctl_table[nr](arg); +} + +static const struct file_operations blksnap_ctrl_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = ctrl_unlocked_ioctl, +}; + +static struct miscdevice blksnap_ctrl_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = BLKSNAP_CTL, + .fops = &blksnap_ctrl_fops, +}; + +static int __init blksnap_init(void) +{ + int ret; + + pr_debug("Loading\n"); + pr_debug("Version: %s\n", VERSION_STR); + pr_debug("tracking_block_minimum_shift: %d\n", + tracking_block_minimum_shift); + pr_debug("tracking_block_maximum_count: %d\n", + tracking_block_maximum_count); + pr_debug("chunk_minimum_shift: %d\n", chunk_minimum_shift); + pr_debug("chunk_maximum_count: %d\n", chunk_maximum_count); + pr_debug("chunk_maximum_in_queue: %d\n", chunk_maximum_in_queue); + pr_debug("free_diff_buffer_pool_size: %d\n", + free_diff_buffer_pool_size); + pr_debug("diff_storage_minimum: %d\n", diff_storage_minimum); + + ret = chunk_init(); + if (ret) + goto fail_chunk_init; + + ret = tracker_init(); + if (ret) + goto fail_tracker_init; + + ret = misc_register(&blksnap_ctrl_misc); + if (ret) + goto fail_misc_register; + + return 0; + +fail_misc_register: + tracker_done(); +fail_tracker_init: + chunk_done(); +fail_chunk_init: + + return ret; +} + +static void __exit blksnap_exit(void) +{ + pr_debug("Unloading module\n"); + + misc_deregister(&blksnap_ctrl_misc); + + chunk_done(); + snapshot_done(); + tracker_done(); + + pr_debug("Module was unloaded\n"); +} + +module_init(blksnap_init); +module_exit(blksnap_exit); + +module_param_named(tracking_block_minimum_shift, tracking_block_minimum_shift, + int, 0644); +MODULE_PARM_DESC(tracking_block_minimum_shift, + "The power of 2 for minimum tracking block size"); +module_param_named(tracking_block_maximum_count, tracking_block_maximum_count, + int, 0644); +MODULE_PARM_DESC(tracking_block_maximum_count, + "The maximum number of tracking blocks"); +module_param_named(tracking_block_maximum_shift, tracking_block_maximum_shift, + int, 0644); +MODULE_PARM_DESC(tracking_block_maximum_shift, + "The power of 2 for maximum trackings block size"); +module_param_named(chunk_minimum_shift, chunk_minimum_shift, int, 0644); +MODULE_PARM_DESC(chunk_minimum_shift, + "The power of 2 for minimum chunk size"); +module_param_named(chunk_maximum_count, chunk_maximum_count, int, 0644); +MODULE_PARM_DESC(chunk_maximum_count, + "The maximum number of chunks"); +module_param_named(chunk_maximum_shift, chunk_maximum_shift, int, 0644); +MODULE_PARM_DESC(chunk_maximum_shift, + "The power of 2 for maximum snapshots chunk size"); +module_param_named(chunk_maximum_in_queue, chunk_maximum_in_queue, int, 0644); +MODULE_PARM_DESC(chunk_maximum_in_queue, + "The maximum number of chunks in store queue"); +module_param_named(free_diff_buffer_pool_size, free_diff_buffer_pool_size, int, + 0644); +MODULE_PARM_DESC(free_diff_buffer_pool_size, + "The size of the pool of preallocated difference buffers"); +module_param_named(diff_storage_minimum, diff_storage_minimum, int, 0644); +MODULE_PARM_DESC(diff_storage_minimum, + "The minimum allowable size of the difference storage in sectors"); + +MODULE_DESCRIPTION("Block Device Snapshots Module"); +MODULE_VERSION(VERSION_STR); +MODULE_AUTHOR("Veeam Software Group GmbH"); +MODULE_LICENSE("GPL"); diff --git a/drivers/block/blksnap/params.h b/drivers/block/blksnap/params.h new file mode 100644 index 000000000000..36d4748a22c1 --- /dev/null +++ b/drivers/block/blksnap/params.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#ifndef __BLKSNAP_PARAMS_H +#define __BLKSNAP_PARAMS_H + +int get_tracking_block_minimum_shift(void); +int get_tracking_block_maximum_shift(void); +int get_tracking_block_maximum_count(void); +int get_chunk_minimum_shift(void); +int get_chunk_maximum_shift(void); +int get_chunk_maximum_count(void); +int get_chunk_maximum_in_queue(void); +int get_free_diff_buffer_pool_size(void); +int get_diff_storage_minimum(void); + +#endif /* __BLKSNAP_PARAMS_H */ From patchwork Tue Apr 4 14:08:30 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergei Shtepa X-Patchwork-Id: 79797 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:b0ea:0:b0:3b6:4342:cba0 with SMTP id b10csp443346vqo; Wed, 5 Apr 2023 09:39:05 -0700 (PDT) X-Google-Smtp-Source: AKy350Z9b//XH1eCq4u7nnu8Oob0Eo7fHwIynneoA3p0TklCbhq0DQCyeUW/z4rkCgQLPUePxtnL X-Received: by 2002:a17:90b:17cb:b0:237:a500:eca6 with SMTP id me11-20020a17090b17cb00b00237a500eca6mr3017311pjb.22.1680712744847; Wed, 05 Apr 2023 09:39:04 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1680712744; cv=none; d=google.com; s=arc-20160816; b=Un4GEYsRzTH+dHqd8ApSKq4OTgek8fbXdiB8O9R0ot4cOcWCeX7T9mXj+6Y1hhVdF7 PaltqSo5ROOMDuCUaDezqLjR6G4NA2JI3uCldRkWoJdmZi4BY1QvZd2iJCFhWl3YhxJI 53rmg65GodXS8aM+8fTgmeH92nREiO3+xnqlihKuoyChDFUl1zHs2GxpGI0C60wgSpLL JV+hg000DymlJBSQ6dOAmhtbCYhbzr+AUKX7ZksV7jbcucTiyME6iVoDckZPtqoJY12g 62tHes/3hZYG8OFrRUD6K1bqPu21ikCmn7CO8QZWHJ2zBkd1Rb3rYO3lvOwXCtWnnNTu zGxQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=LhDV5mScfcrjHxBu+auv0MbfrxnthkmjqU+cPTdkMCI=; b=y4xgqkmbl7K5ixKqlxENQkgocQLNvI2SNw33w9GhP7TZeR2WPGPn6eJ4HX0mXp9TxO 5ljPyoFqDKBBz0ku09LRRh9T4VN/ZuF6+NdWUf0XPhvfoizaRUKiLXUGI2bdzRy+Q7yN vZufQFn2cQQ86HN9xGdVAc/Zh7bWW87Dc+O/fuw+Wi7Vpbczf7swwMSr1IIMOp5Y00/k wjfS1+cX69S7tQ0Dc+n2W39DfbnKnRMggqGfmn0bL0WbXripfkeO+QKdNPScrB/+e+Qc FH68bGC2jBx+peJVXQdfTjACDK5IH2QzUEx++S521vGeG+A4Kll4d+SK1CKtr1zVGrwX Gxqw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b="Sqnx7/GR"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id e5-20020a17090ac20500b0022bb81526a9si1662131pjt.8.2023.04.05.09.38.52; Wed, 05 Apr 2023 09:39:04 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@veeam.com header.s=mx2-2022 header.b="Sqnx7/GR"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=veeam.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230296AbjDEQPI (ORCPT + 99 others); Wed, 5 Apr 2023 12:15:08 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43214 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229533AbjDEQOi (ORCPT ); Wed, 5 Apr 2023 12:14:38 -0400 Received: from mx2.veeam.com (mx2.veeam.com [64.129.123.6]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6E6FB72A6; Wed, 5 Apr 2023 09:14:13 -0700 (PDT) Received: from mx1.veeam.com (mx1.veeam.com [172.18.34.147]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx2.veeam.com (Postfix) with ESMTPS id 857F141A9C; Wed, 5 Apr 2023 12:13:40 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=veeam.com; s=mx2-2022; t=1680711220; bh=LhDV5mScfcrjHxBu+auv0MbfrxnthkmjqU+cPTdkMCI=; h=From:To:CC:Subject:Date:In-Reply-To:References:From; b=Sqnx7/GRBKbL2aL1s5y+t23/f1up1Ndgp5aSpJmUtFB5lYmx59SJ44Llvt8oYm9dd i961EK719Tvi34iWFuOEruLc8VTX6WvYbHm7YoVlUwpHSCqByHs6bP0LJm0aRv/nHG 5YMyvDBBCKDA/7yNSWAMbsNb9r0FaGfB98Gjmccl6gQ5+1fxAJ0X6+0hBdxV0seDcg 11AJuQ75LAP+eSmxOT9G4FKA4oQ575UI0uOpl8dk0JByqKHRdfOdUp3BQOGP2gBv2Z vt3ILGyy3bWFc0rvC+DkXQbPpn0fXrm90Q796DSkBTQn8fuI3zOElZ+EQ5NkWGgUWH ARzX2HJruNlkQ== Received: from mx4.veeam.com (mx4.amust.local [172.31.224.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.veeam.com (Postfix) with ESMTPS id CC6EC42401; Wed, 5 Apr 2023 06:09:15 -0400 (EDT) Received: from mail.veeam.com (prgmbx01.amust.local [172.24.128.102]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx4.veeam.com (Postfix) with ESMTPS id 56633CB7C0; Tue, 4 Apr 2023 17:09:08 +0300 (MSK) Received: from ssh-deb10-ssd-vb.amust.local (172.24.10.107) by prgmbx01.amust.local (172.24.128.102) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.26; Tue, 4 Apr 2023 16:09:06 +0200 From: Sergei Shtepa To: , , , CC: , , , , , , , , , , , , Subject: [PATCH v3 06/11] blksnap: handling and tracking I/O units Date: Tue, 4 Apr 2023 16:08:30 +0200 Message-ID: <20230404140835.25166-7-sergei.shtepa@veeam.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20230404140835.25166-1-sergei.shtepa@veeam.com> References: <20230404140835.25166-1-sergei.shtepa@veeam.com> MIME-Version: 1.0 X-Originating-IP: [172.24.10.107] X-ClientProxiedBy: prgmbx02.amust.local (172.24.128.103) To prgmbx01.amust.local (172.24.128.102) X-EsetResult: clean, is OK X-EsetId: 37303A2924031554657367 X-Veeam-MMEX: True X-Spam-Status: No, score=-0.2 required=5.0 tests=DKIMWL_WL_HIGH,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1762355047071007530?= X-GMAIL-MSGID: =?utf-8?q?1762355047071007530?= The struct tracker contains callback functions for handling a I/O units of a block device. When a write request is handled, the change block tracking (CBT) map functions are called and initiates the process of copying data from the original block device to the change store. Registering and unregistering the tracker is provided by the functions blkfilter_register() and blkfilter_unregister(). The struct cbt_map allows to store the history of block device changes. Co-developed-by: Christoph Hellwig Signed-off-by: Christoph Hellwig Signed-off-by: Sergei Shtepa --- drivers/block/blksnap/cbt_map.c | 228 +++++++++++++++++++++++ drivers/block/blksnap/cbt_map.h | 90 +++++++++ drivers/block/blksnap/tracker.c | 320 ++++++++++++++++++++++++++++++++ drivers/block/blksnap/tracker.h | 71 +++++++ 4 files changed, 709 insertions(+) create mode 100644 drivers/block/blksnap/cbt_map.c create mode 100644 drivers/block/blksnap/cbt_map.h create mode 100644 drivers/block/blksnap/tracker.c create mode 100644 drivers/block/blksnap/tracker.h diff --git a/drivers/block/blksnap/cbt_map.c b/drivers/block/blksnap/cbt_map.c new file mode 100644 index 000000000000..b233adc32f1a --- /dev/null +++ b/drivers/block/blksnap/cbt_map.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#define pr_fmt(fmt) KBUILD_MODNAME "-cbt_map: " fmt + +#include +#include +#include +#include "cbt_map.h" +#include "params.h" + +static inline unsigned long long count_by_shift(sector_t capacity, + unsigned long long shift) +{ + sector_t blk_size = 1ull << (shift - SECTOR_SHIFT); + + return round_up(capacity, blk_size) / blk_size; +} + +static void cbt_map_calculate_block_size(struct cbt_map *cbt_map) +{ + unsigned long long count; + unsigned long long shift = min(get_tracking_block_minimum_shift(), + get_tracking_block_maximum_shift()); + + pr_debug("Device capacity %llu sectors\n", cbt_map->device_capacity); + /* + * The size of the tracking block is calculated based on the size of the disk + * so that the CBT table does not exceed a reasonable size. + */ + count = count_by_shift(cbt_map->device_capacity, shift); + pr_debug("Blocks count %llu\n", count); + while (count > get_tracking_block_maximum_count()) { + if (shift >= get_tracking_block_maximum_shift()) { + pr_info("The maximum allowable CBT block size has been reached.\n"); + break; + } + shift = shift + 1ull; + count = count_by_shift(cbt_map->device_capacity, shift); + pr_debug("Blocks count %llu\n", count); + } + + cbt_map->blk_size_shift = shift; + cbt_map->blk_count = count; + pr_debug("The optimal CBT block size was calculated as %llu bytes\n", + (1ull << cbt_map->blk_size_shift)); +} + +static int cbt_map_allocate(struct cbt_map *cbt_map) +{ + unsigned char *read_map = NULL; + unsigned char *write_map = NULL; + size_t size = cbt_map->blk_count; + + pr_debug("Allocate CBT map of %zu blocks\n", size); + + if (cbt_map->read_map || cbt_map->write_map) + return -EINVAL; + + read_map = __vmalloc(size, GFP_NOIO | __GFP_ZERO); + if (!read_map) + return -ENOMEM; + + write_map = __vmalloc(size, GFP_NOIO | __GFP_ZERO); + if (!write_map) { + vfree(read_map); + return -ENOMEM; + } + + cbt_map->read_map = read_map; + cbt_map->write_map = write_map; + + cbt_map->snap_number_previous = 0; + cbt_map->snap_number_active = 1; + generate_random_uuid(cbt_map->generation_id.b); + cbt_map->is_corrupted = false; + + return 0; +} + +static void cbt_map_deallocate(struct cbt_map *cbt_map) +{ + cbt_map->is_corrupted = false; + + if (cbt_map->read_map) { + vfree(cbt_map->read_map); + cbt_map->read_map = NULL; + } + + if (cbt_map->write_map) { + vfree(cbt_map->write_map); + cbt_map->write_map = NULL; + } +} + +int cbt_map_reset(struct cbt_map *cbt_map, sector_t device_capacity) +{ + cbt_map_deallocate(cbt_map); + + cbt_map->device_capacity = device_capacity; + cbt_map_calculate_block_size(cbt_map); + + return cbt_map_allocate(cbt_map); +} + +void cbt_map_destroy(struct cbt_map *cbt_map) +{ + pr_debug("CBT map destroy\n"); + + cbt_map_deallocate(cbt_map); + kfree(cbt_map); +} + +struct cbt_map *cbt_map_create(struct block_device *bdev) +{ + struct cbt_map *cbt_map = NULL; + int ret; + + pr_debug("CBT map create\n"); + + cbt_map = kzalloc(sizeof(struct cbt_map), GFP_KERNEL); + if (cbt_map == NULL) + return NULL; + + cbt_map->device_capacity = bdev_nr_sectors(bdev); + cbt_map_calculate_block_size(cbt_map); + + ret = cbt_map_allocate(cbt_map); + if (ret) { + pr_err("Failed to create tracker. errno=%d\n", abs(ret)); + cbt_map_destroy(cbt_map); + return NULL; + } + + spin_lock_init(&cbt_map->locker); + cbt_map->is_corrupted = false; + + return cbt_map; +} + +void cbt_map_switch(struct cbt_map *cbt_map) +{ + pr_debug("CBT map switch\n"); + spin_lock(&cbt_map->locker); + + cbt_map->snap_number_previous = cbt_map->snap_number_active; + ++cbt_map->snap_number_active; + if (cbt_map->snap_number_active == 256) { + cbt_map->snap_number_active = 1; + + memset(cbt_map->write_map, 0, cbt_map->blk_count); + + generate_random_uuid(cbt_map->generation_id.b); + + pr_debug("CBT reset\n"); + } else + memcpy(cbt_map->read_map, cbt_map->write_map, cbt_map->blk_count); + spin_unlock(&cbt_map->locker); +} + +static inline int _cbt_map_set(struct cbt_map *cbt_map, sector_t sector_start, + sector_t sector_cnt, u8 snap_number, + unsigned char *map) +{ + int res = 0; + u8 num; + size_t inx; + size_t cbt_block_first = (size_t)( + sector_start >> (cbt_map->blk_size_shift - SECTOR_SHIFT)); + size_t cbt_block_last = (size_t)( + (sector_start + sector_cnt - 1) >> + (cbt_map->blk_size_shift - SECTOR_SHIFT)); + + for (inx = cbt_block_first; inx <= cbt_block_last; ++inx) { + if (unlikely(inx >= cbt_map->blk_count)) { + pr_err("Block index is too large\n"); + pr_err("Block #%zu was demanded, map size %zu blocks\n", + inx, cbt_map->blk_count); + res = -EINVAL; + break; + } + + num = map[inx]; + if (num < snap_number) + map[inx] = snap_number; + } + return res; +} + +int cbt_map_set(struct cbt_map *cbt_map, sector_t sector_start, + sector_t sector_cnt) +{ + int res; + + spin_lock(&cbt_map->locker); + if (unlikely(cbt_map->is_corrupted)) { + spin_unlock(&cbt_map->locker); + return -EINVAL; + } + res = _cbt_map_set(cbt_map, sector_start, sector_cnt, + (u8)cbt_map->snap_number_active, cbt_map->write_map); + if (unlikely(res)) + cbt_map->is_corrupted = true; + + spin_unlock(&cbt_map->locker); + + return res; +} + +int cbt_map_set_both(struct cbt_map *cbt_map, sector_t sector_start, + sector_t sector_cnt) +{ + int res; + + spin_lock(&cbt_map->locker); + if (unlikely(cbt_map->is_corrupted)) { + spin_unlock(&cbt_map->locker); + return -EINVAL; + } + res = _cbt_map_set(cbt_map, sector_start, sector_cnt, + (u8)cbt_map->snap_number_active, cbt_map->write_map); + if (!res) + res = _cbt_map_set(cbt_map, sector_start, sector_cnt, + (u8)cbt_map->snap_number_previous, + cbt_map->read_map); + spin_unlock(&cbt_map->locker); + + return res; +} diff --git a/drivers/block/blksnap/cbt_map.h b/drivers/block/blksnap/cbt_map.h new file mode 100644 index 000000000000..f87bffd5b3a7 --- /dev/null +++ b/drivers/block/blksnap/cbt_map.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#ifndef __BLKSNAP_CBT_MAP_H +#define __BLKSNAP_CBT_MAP_H + +#include +#include +#include +#include +#include + +struct blksnap_sectors; + +/** + * struct cbt_map - The table of changes for a block device. + * + * @locker: + * Locking for atomic modification of structure members. + * @blk_size_shift: + * The power of 2 used to specify the change tracking block size. + * @blk_count: + * The number of change tracking blocks. + * @device_capacity: + * The actual capacity of the device. + * @read_map: + * A table of changes available for reading. This is the table that can + * be read after taking a snapshot. + * @write_map: + * The current table for tracking changes. + * @snap_number_active: + * The current sequential number of changes. This is the number that is written to + * the current table when the block data changes. + * @snap_number_previous: + * The previous sequential number of changes. This number is used to identify the + * blocks that were changed between the penultimate snapshot and the last snapshot. + * @generation_id: + * UUID of the generation of changes. + * @is_corrupted: + * A flag that the change tracking data is no longer reliable. + * + * The change block tracking map is a byte table. Each byte stores the + * sequential number of changes for one block. To determine which blocks have changed + * since the previous snapshot with the change number 4, it is enough to + * find all bytes with the number more than 4. + * + * Since one byte is allocated to track changes in one block, the change + * table is created again at the 255th snapshot. At the same time, a new + * unique generation identifier is generated. Tracking changes is + * possible only for tables of the same generation. + * + * There are two tables on the change block tracking map. One is + * available for reading, and the other is available for writing. At the moment of taking + * a snapshot, the tables are synchronized. The user's process, when + * calling the corresponding ioctl, can read the readable table. + * At the same time, the change tracking mechanism continues to work with + * the writable table. + * + * To provide the ability to mount a snapshot image as writeable, it is + * possible to make changes to both of these tables simultaneously. + * + */ +struct cbt_map { + spinlock_t locker; + + size_t blk_size_shift; + size_t blk_count; + sector_t device_capacity; + + unsigned char *read_map; + unsigned char *write_map; + + unsigned long snap_number_active; + unsigned long snap_number_previous; + uuid_t generation_id; + + bool is_corrupted; +}; + +struct cbt_map *cbt_map_create(struct block_device *bdev); +int cbt_map_reset(struct cbt_map *cbt_map, sector_t device_capacity); + +void cbt_map_destroy(struct cbt_map *cbt_map); + +void cbt_map_switch(struct cbt_map *cbt_map); +int cbt_map_set(struct cbt_map *cbt_map, sector_t sector_start, + sector_t sector_cnt); +int cbt_map_set_both(struct cbt_map *cbt_map, sector_t sector_start, + sector_t sector_cnt); + +#endif /* __BLKSNAP_CBT_MAP_H */ diff --git a/drivers/block/blksnap/tracker.c b/drivers/block/blksnap/tracker.c new file mode 100644 index 000000000000..3f6586b86f24 --- /dev/null +++ b/drivers/block/blksnap/tracker.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#define pr_fmt(fmt) KBUILD_MODNAME "-tracker: " fmt + +#include +#include +#include +#include +#include +#include "tracker.h" +#include "cbt_map.h" +#include "diff_area.h" +#include "snapimage.h" +#include "snapshot.h" + +void tracker_free(struct kref *kref) +{ + struct tracker *tracker = container_of(kref, struct tracker, kref); + + might_sleep(); + + pr_debug("Free tracker for device [%u:%u]\n", MAJOR(tracker->dev_id), + MINOR(tracker->dev_id)); + + if (tracker->diff_area) + diff_area_free(tracker->diff_area); + if (tracker->cbt_map) + cbt_map_destroy(tracker->cbt_map); + + kfree(tracker); +} + +static bool tracker_submit_bio(struct bio *bio) +{ + struct blkfilter *flt = bio->bi_bdev->bd_filter; + struct tracker *tracker = container_of(flt, struct tracker, filter); + sector_t count = bio_sectors(bio); + struct bvec_iter copy_iter; + + if (!op_is_write(bio_op(bio)) || !count) + return false; + + copy_iter = bio->bi_iter; + if (bio_flagged(bio, BIO_REMAPPED)) + copy_iter.bi_sector -= bio->bi_bdev->bd_start_sect; + + if (cbt_map_set(tracker->cbt_map, copy_iter.bi_sector, count) || + !atomic_read(&tracker->snapshot_is_taken) || + diff_area_is_corrupted(tracker->diff_area)) + return false; + + return diff_area_cow(bio, tracker->diff_area, ©_iter); +} + +static struct blkfilter *tracker_attach(struct block_device *bdev) +{ + struct tracker *tracker = NULL; + struct cbt_map *cbt_map; + + pr_debug("Creating tracker for device [%u:%u]\n", + MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); + + cbt_map = cbt_map_create(bdev); + if (!cbt_map) { + pr_err("Failed to create CBT map for device [%u:%u]\n", + MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); + return ERR_PTR(-ENOMEM); + } + + tracker = kzalloc(sizeof(struct tracker), GFP_KERNEL); + if (tracker == NULL) { + cbt_map_destroy(cbt_map); + return ERR_PTR(-ENOMEM); + } + + INIT_LIST_HEAD(&tracker->link); + kref_init(&tracker->kref); + tracker->dev_id = bdev->bd_dev; + atomic_set(&tracker->snapshot_is_taken, false); + tracker->cbt_map = cbt_map; + tracker->diff_area = NULL; + + pr_debug("New tracker for device [%u:%u] was created\n", + MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); + + return &tracker->filter; +} + +static void tracker_detach(struct blkfilter *flt) +{ + struct tracker *tracker = container_of(flt, struct tracker, filter); + + pr_debug("Detach tracker from device [%u:%u]\n", + MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); + + tracker_put(tracker); +} + +static int ctl_cbtinfo(struct tracker *tracker, __u8 __user *buf, __u32 *plen) +{ + struct cbt_map *cbt_map = tracker->cbt_map; + struct blksnap_cbtinfo arg; + + if (!cbt_map) + return -ESRCH; + + if (*plen < sizeof(arg)) + return -EINVAL; + + arg.device_capacity = (__u64)(cbt_map->device_capacity << SECTOR_SHIFT); + arg.block_size = (__u32)(1 << cbt_map->blk_size_shift); + arg.block_count = (__u32)cbt_map->blk_count; + export_uuid(arg.generation_id.b, &cbt_map->generation_id); + arg.changes_number = (__u8)cbt_map->snap_number_previous; + + if (copy_to_user(buf, &arg, sizeof(arg))) + return -ENODATA; + + *plen = sizeof(arg); + return 0; +} + +static int ctl_cbtmap(struct tracker *tracker, __u8 __user *buf, __u32 *plen) +{ + struct cbt_map *cbt_map = tracker->cbt_map; + struct blksnap_cbtmap arg; + + if (!cbt_map) + return -ESRCH; + + if (unlikely(cbt_map->is_corrupted)) { + pr_err("CBT table was corrupted\n"); + return -EFAULT; + } + + if (*plen < sizeof(arg)) + return -EINVAL; + + if (copy_from_user(&arg, buf, sizeof(arg))) + return -ENODATA; + + if (copy_to_user(arg.buffer, cbt_map->read_map + arg.offset, + min_t(unsigned int, cbt_map->blk_count - arg.offset, arg.length))) + return -EINVAL; + + *plen = 0; + return 0; +} +static int ctl_cbtdirty(struct tracker *tracker, __u8 __user *buf, __u32 *plen) +{ + struct cbt_map *cbt_map = tracker->cbt_map; + struct blksnap_cbtdirty arg; + unsigned int inx; + + if (!cbt_map) + return -ESRCH; + + if (*plen < sizeof(arg)) + return -EINVAL; + + if (copy_from_user(&arg, buf, sizeof(arg))) + return -ENODATA; + + for (inx = 0; inx < arg.count; inx++) { + struct blksnap_sectors range; + int ret; + + if (copy_from_user(&range, arg.dirty_sectors, sizeof(range))) + return -ENODATA; + + ret = cbt_map_set_both(cbt_map, range.offset, range.count); + if (ret) + return ret; + } + *plen = 0; + return 0; +} +static int ctl_snapshotadd(struct tracker *tracker, + __u8 __user *buf, __u32 *plen) +{ + struct blksnap_snapshotadd arg; + + if (*plen < sizeof(arg)) + return -EINVAL; + + if (copy_from_user(&arg, buf, sizeof(arg))) + return -ENODATA; + + *plen = 0; + return snapshot_add_device((uuid_t *)&arg.id, tracker); +} +static int ctl_snapshotinfo(struct tracker *tracker, + __u8 __user *buf, __u32 *plen) +{ + struct blksnap_snapshotinfo arg = {0}; + + if (*plen < sizeof(arg)) + return -EINVAL; + + if (copy_from_user(&arg, buf, sizeof(arg))) + return -ENODATA; + + + if (tracker->diff_area && diff_area_is_corrupted(tracker->diff_area)) + arg.error_code = tracker->diff_area->error_code; + else + arg.error_code = 0; + + if (tracker->snap_disk) + strncpy(arg.image, tracker->snap_disk->disk_name, IMAGE_DISK_NAME_LEN); + + if (copy_to_user(buf, &arg, sizeof(arg))) + return -ENODATA; + + *plen = sizeof(arg); + return 0; +} + +static int (*const ctl_table[])(struct tracker *tracker, + __u8 __user *buf, __u32 *plen) = { + ctl_cbtinfo, + ctl_cbtmap, + ctl_cbtdirty, + ctl_snapshotadd, + ctl_snapshotinfo, +}; + +static int tracker_ctl(struct blkfilter *flt, const unsigned int cmd, + __u8 __user *buf, __u32 *plen) +{ + struct tracker *tracker = container_of(flt, struct tracker, filter); + + if (cmd > ARRAY_SIZE(ctl_table)) + return -ENOTTY; + + return ctl_table[cmd](tracker, buf, plen); +} + +static struct blkfilter_operations tracker_ops = { + .owner = THIS_MODULE, + .name = "blksnap", + .attach = tracker_attach, + .detach = tracker_detach, + .ctl = tracker_ctl, + .submit_bio = tracker_submit_bio, +}; + +int tracker_take_snapshot(struct tracker *tracker) +{ + int ret = 0; + bool cbt_reset_needed = false; + struct block_device *orig_bdev = tracker->diff_area->orig_bdev; + sector_t capacity; + unsigned int current_flag; + + blk_mq_freeze_queue(orig_bdev->bd_queue); + current_flag = memalloc_noio_save(); + + if (tracker->cbt_map->is_corrupted) { + cbt_reset_needed = true; + pr_warn("Corrupted CBT table detected. CBT fault\n"); + } + + capacity = bdev_nr_sectors(orig_bdev); + if (tracker->cbt_map->device_capacity != capacity) { + cbt_reset_needed = true; + pr_warn("Device resize detected. CBT fault\n"); + } + + if (cbt_reset_needed) { + ret = cbt_map_reset(tracker->cbt_map, capacity); + if (ret) { + pr_err("Failed to create tracker. errno=%d\n", + abs(ret)); + return ret; + } + } + + cbt_map_switch(tracker->cbt_map); + atomic_set(&tracker->snapshot_is_taken, true); + + memalloc_noio_restore(current_flag); + blk_mq_unfreeze_queue(orig_bdev->bd_queue); + + return 0; +} + +void tracker_release_snapshot(struct tracker *tracker) +{ + if (tracker->diff_area) { + blk_mq_freeze_queue(tracker->diff_area->orig_bdev->bd_queue); + + pr_debug("Tracker for device [%u:%u] release snapshot\n", + MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); + + atomic_set(&tracker->snapshot_is_taken, false); + + blk_mq_unfreeze_queue(tracker->diff_area->orig_bdev->bd_queue); + } + snapimage_free(tracker); + + if (tracker->diff_area) { + diff_area_free(tracker->diff_area); + tracker->diff_area = NULL; + } +} + +int __init tracker_init(void) +{ + pr_debug("Register filter '%s'", tracker_ops.name); + + return blkfilter_register(&tracker_ops); +} + +void tracker_done(void) +{ + pr_debug("Unregister filter '%s'", tracker_ops.name); + + blkfilter_unregister(&tracker_ops); +} diff --git a/drivers/block/blksnap/tracker.h b/drivers/block/blksnap/tracker.h new file mode 100644 index 000000000000..d0972994d528 --- /dev/null +++ b/drivers/block/blksnap/tracker.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023 Veeam Software Group GmbH */ +#ifndef __BLKSNAP_TRACKER_H +#define __BLKSNAP_TRACKER_H + +#include +#include +#include +#include +#include +#include +#include + +struct cbt_map; +struct diff_area; + +/** + * struct tracker - Tracker for a block device. + * + * @filter: + * The block device filter structure. + * @link: + * List header. Allows to combine trackers into a list in a snapshot. + * @kref: + * The link counter allows to control the lifetime of the tracker. + * @dev_id: + * Original block device ID. + * @snapshot_is_taken: + * Indicates that a snapshot was taken for the device whose I/O unit are + * handled by this tracker. + * @cbt_map: + * Pointer to a change block tracker map. + * @diff_area: + * Pointer to a difference area. + * @snap_disk: + * Snapshot image disk. + * + * The goal of the tracker is to handle I/O unit. The tracker detectes + * the range of sectors that will change and transmits them to the CBT map + * and to the difference area. + */ +struct tracker { + struct blkfilter filter; + struct list_head link; + struct kref kref; + dev_t dev_id; + + atomic_t snapshot_is_taken; + + struct cbt_map *cbt_map; + struct diff_area *diff_area; + struct gendisk *snap_disk; +}; + +int __init tracker_init(void); +void tracker_done(void); + +void tracker_free(struct kref *kref); +static inline void tracker_put(struct tracker *tracker) +{ + if (likely(tracker)) + kref_put(&tracker->kref, tracker_free); +}; +static inline void tracker_get(struct tracker *tracker) +{ + kref_get(&tracker->kref); +}; +int tracker_take_snapshot(struct tracker *tracker); +void tracker_release_snapshot(struct tracker *tracker); + +#endif /* __BLKSNAP_TRACKER_H */