From 41a5f705f110fc51695a8f5e593cec1a474790cf Mon Sep 17 00:00:00 2001 From: Marian Csontos Date: Thu, 2 Jul 2026 15:57:33 +0200 Subject: [PATCH] Additional patches for 10.3 lvm2 Resolves: RHEL-188186 RHEL-164001 --- ...EAR-EAAR-reserve-race-when-multiple-.patch | 47 +++ ...-invalid-vgchange-setpersist-setlock.patch | 34 ++ ...e-udev-between-register-and-preempt-.patch | 46 +++ ...ockd-improve-setlockargs-description.patch | 49 +++ ...pre-format-pool-geometry-computation.patch | 374 ++++++++++++++++++ ...ort-for-VDO-kernel-direct-formatting.patch | 239 +++++++++++ ...ormatting-path-with-LV_VDOFORMAT-met.patch | 355 +++++++++++++++++ 0013-make-generate.patch | 44 +++ 0014-WHATS_NEW-update.patch | 38 ++ lvm2.spec | 16 + 10 files changed, 1242 insertions(+) create mode 100644 0006-fix-lvmpersist-WEAR-EAAR-reserve-race-when-multiple-.patch create mode 100644 0007-man-lvmlockd-fix-invalid-vgchange-setpersist-setlock.patch create mode 100644 0008-lvmpersist-settle-udev-between-register-and-preempt-.patch create mode 100644 0009-man-lvmlockd-improve-setlockargs-description.patch create mode 100644 0010-vdo-add-pre-format-pool-geometry-computation.patch create mode 100644 0011-vdo-add-support-for-VDO-kernel-direct-formatting.patch create mode 100644 0012-vdo-add-kernel-formatting-path-with-LV_VDOFORMAT-met.patch create mode 100644 0013-make-generate.patch create mode 100644 0014-WHATS_NEW-update.patch diff --git a/0006-fix-lvmpersist-WEAR-EAAR-reserve-race-when-multiple-.patch b/0006-fix-lvmpersist-WEAR-EAAR-reserve-race-when-multiple-.patch new file mode 100644 index 0000000..48ee433 --- /dev/null +++ b/0006-fix-lvmpersist-WEAR-EAAR-reserve-race-when-multiple-.patch @@ -0,0 +1,47 @@ +From b185eefe423ad4292edac96456c45fe3da3402a1 Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Thu, 4 Jun 2026 15:36:29 -0500 +Subject: [PATCH 06/16] fix lvmpersist WEAR/EAAR reserve race when multiple + nodes start simultaneously + +When multiple nodes run "vgchange --persist start" in parallel, the +check-then-reserve sequence for WEAR/EAAR reservations can race: a node +checks that no reservation exists, but by the time it calls resv-acquire, +another node has already created the reservation. The acquire fails and +lvmpersist treats it as fatal, undoing its registration and failing the +start. + +After a failed acquire, re-check whether the expected reservation type +now exists. If another registrant completed the reservation, the node +is already protected under the all-registrants scope, so treat it as +success. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit a01615872e5ba97fecb16c3c67d14186c1ef91ca) +--- + scripts/lvmpersist.sh | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/scripts/lvmpersist.sh b/scripts/lvmpersist.sh +index 4cc0ba300..02ab245f0 100755 +--- a/scripts/lvmpersist.sh ++++ b/scripts/lvmpersist.sh +@@ -766,6 +766,15 @@ do_start() { + fi + + if [[ "$?" -ne 0 ]]; then ++ # For WEAR/EAAR, another host may have acquired the ++ # reservation between our check and our acquire attempt. ++ # Re-check: if the reservation now exists, that's fine. ++ if [[ "$type_str" == "WEAR" || "$type_str" == "EAAR" ]]; then ++ get_dev_reservation "$dev" ++ if [[ "$DEV_PRDESC" == "$type_str" ]]; then ++ continue ++ fi ++ fi + logmsg "start $GROUP failed to reserve $dev." + undo_register + exit 1 +-- +2.54.0 + diff --git a/0007-man-lvmlockd-fix-invalid-vgchange-setpersist-setlock.patch b/0007-man-lvmlockd-fix-invalid-vgchange-setpersist-setlock.patch new file mode 100644 index 0000000..2ad2340 --- /dev/null +++ b/0007-man-lvmlockd-fix-invalid-vgchange-setpersist-setlock.patch @@ -0,0 +1,34 @@ +From a186787a056bf0f4f424cfa1ce9dec4ee22492f2 Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Wed, 24 Jun 2026 13:17:07 -0500 +Subject: [PATCH 07/16] man lvmlockd: fix invalid vgchange --setpersist + --setlockargs + +(cherry picked from commit c19a1561e031e3633ffc83eeb9daf10a9d08e3aa) +--- + man/lvmlockd.8_main | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/man/lvmlockd.8_main b/man/lvmlockd.8_main +index dc8865a4c..a7a17c4df 100644 +--- a/man/lvmlockd.8_main ++++ b/man/lvmlockd.8_main +@@ -382,9 +382,13 @@ For more information on using PR with LVM in general, see + .P + The vgcreate options to enable PR-based lock recovery in a new VG are shown above. + .P +-To enable PR-based lock recovery in an existing VG: ++To enable PR-based lock recovery in an existing VG, first enable PR (if ++not already), then change the lock args: + .br +-.B $ vgchange --setpersist y --setlockargs persist,notimeout ++.B $ vgchange --setpersist y ++.I VG ++.br ++.B $ vgchange --setlockargs persist,notimeout + .I VG + .P + Changing the PR or lockargs settings requires the VG to be stopped on all +-- +2.54.0 + diff --git a/0008-lvmpersist-settle-udev-between-register-and-preempt-.patch b/0008-lvmpersist-settle-udev-between-register-and-preempt-.patch new file mode 100644 index 0000000..87df9f0 --- /dev/null +++ b/0008-lvmpersist-settle-udev-between-register-and-preempt-.patch @@ -0,0 +1,46 @@ +From 559cb88b10214befbb786ba1a51f892a9a4c47bd Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Tue, 23 Jun 2026 14:01:48 -0500 +Subject: [PATCH 08/16] lvmpersist: settle udev between register and + preempt-abort + +sg_persist REGISTER triggers udev to re-probe the device (blkid, +scsi_id), issuing ~50 reads over ~14ms. do_takeover() follows +REGISTER with PREEMPT AND ABORT only ~3ms later, while probing +reads are still in-flight on the same iSCSI connection. + +On the LIO target, core_tmr_drain_state_list() finds those reads +as in-flight commands and blocks, forming a deadlock with the TX +thread's iscsit_close_connection(). + +Add udevadm settle between the register and preempt-abort loops +so udev probing completes before the preempt-abort is issued. + +Co-Authored-By: Claude Opus 4.6 +(cherry picked from commit 0080e310bf82f91d737f4ec4e4be8454bd7361af) +--- + scripts/lvmpersist.sh | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/scripts/lvmpersist.sh b/scripts/lvmpersist.sh +index 02ab245f0..b91f456f3 100755 +--- a/scripts/lvmpersist.sh ++++ b/scripts/lvmpersist.sh +@@ -682,6 +682,14 @@ do_takeover() { + exit 1 + fi + ++ # The register above triggers udev to re-probe the device (blkid, ++ # scsi_id). Those probing reads share the iSCSI connection with ++ # the preempt-abort below. If probing is still in-flight when the ++ # preempt-abort arrives at the LIO target, the target deadlocks in ++ # core_tmr_drain_state_list (waiting for in-flight reads) vs ++ # iscsit_close_connection (waiting for RX thread exit). ++ udevadm settle ++ + # Reserve the device + + for dev in "${DEVICES[@]}"; do +-- +2.54.0 + diff --git a/0009-man-lvmlockd-improve-setlockargs-description.patch b/0009-man-lvmlockd-improve-setlockargs-description.patch new file mode 100644 index 0000000..0951967 --- /dev/null +++ b/0009-man-lvmlockd-improve-setlockargs-description.patch @@ -0,0 +1,49 @@ +From a8871780844b22491314ab49b60cc10d802d9aa2 Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Thu, 25 Jun 2026 11:50:38 -0500 +Subject: [PATCH 09/16] man lvmlockd: improve setlockargs description + +(cherry picked from commit 41cbab67e9e21ac736cfe4f25ecf8d72db508358) +--- + man/lvmlockd.8_main | 19 +++++++++++++++---- + 1 file changed, 15 insertions(+), 4 deletions(-) + +diff --git a/man/lvmlockd.8_main b/man/lvmlockd.8_main +index a7a17c4df..c86a0a130 100644 +--- a/man/lvmlockd.8_main ++++ b/man/lvmlockd.8_main +@@ -383,16 +383,27 @@ For more information on using PR with LVM in general, see + The vgcreate options to enable PR-based lock recovery in a new VG are shown above. + .P + To enable PR-based lock recovery in an existing VG, first enable PR (if +-not already), then change the lock args: ++not already), then change the lock args. Changing lock args can involve ++stopping/starting PR and locking at different stages. Changing the PR or ++lock args settings requires the VG to be stopped on all other nodes. ++.P ++.I Example ++.br ++.B $ vgcreate --shared ++.I VG ++.I devices + .br + .B $ vgchange --setpersist y + .I VG + .br ++.B $ vgchange --persist start ++.I VG ++.br + .B $ vgchange --setlockargs persist,notimeout + .I VG +-.P +-Changing the PR or lockargs settings requires the VG to be stopped on all +-other nodes. ++.br ++.B $ vgchange --persist start --lockstart ++.I VG + .P + Display the VG attributes configured by setpersist and setlockargs: + .br +-- +2.54.0 + diff --git a/0010-vdo-add-pre-format-pool-geometry-computation.patch b/0010-vdo-add-pre-format-pool-geometry-computation.patch new file mode 100644 index 0000000..38dd126 --- /dev/null +++ b/0010-vdo-add-pre-format-pool-geometry-computation.patch @@ -0,0 +1,374 @@ +From fc77a2a50faa1d1e6b10310f84384d2ad18dec47 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 26 Apr 2026 11:35:09 +0200 +Subject: [PATCH 10/16] vdo: add pre-format pool geometry computation + +Add vdo_format.c with vdo_pool_info() that replicates the vdoformat +pool layout calculation: given a pool's physical size and VDO +parameters, compute UDS index size, slab count, per-slab data blocks, +and usable data blocks (accounting for block map forest overhead). + +Return results in struct vdo_pool_info so callers can both set +the correct virtual size before kernel formatting and report pool +geometry (slab count, addressable capacity, growth limits). + +(cherry picked from commit d5298389476a869a7e650f0188d6830202986ac6) +--- + lib/Makefile.in | 1 + + lib/metadata/metadata-exported.h | 10 + + lib/metadata/vdo_format.c | 310 +++++++++++++++++++++++++++++++ + 3 files changed, 321 insertions(+) + create mode 100644 lib/metadata/vdo_format.c + +diff --git a/lib/Makefile.in b/lib/Makefile.in +index bffe5dddc..667534967 100644 +--- a/lib/Makefile.in ++++ b/lib/Makefile.in +@@ -94,6 +94,7 @@ SOURCES =\ + metadata/segtype.c \ + metadata/snapshot_manip.c \ + metadata/thin_manip.c \ ++ metadata/vdo_format.c \ + metadata/vdo_manip.c \ + metadata/vg.c \ + mirror/mirrored.c \ +diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h +index 4318477f4..1f78c7aac 100644 +--- a/lib/metadata/metadata-exported.h ++++ b/lib/metadata/metadata-exported.h +@@ -1388,6 +1388,16 @@ const char *get_vdo_index_state_name(enum dm_vdo_index_state state); + const char *get_vdo_operating_mode_name(enum dm_vdo_operating_mode mode); + const char *get_vdo_write_policy_name(enum dm_vdo_write_policy policy); + uint64_t get_vdo_pool_virtual_size(const struct lv_segment *vdo_pool_seg); ++struct vdo_pool_info { ++ uint64_t data_blocks; ++ uint64_t uds_blocks; ++ unsigned slab_count; ++ unsigned data_per_slab; ++}; ++ ++int vdo_pool_info(uint64_t pool_size_sectors, ++ const struct dm_vdo_target_params *vtp, ++ struct vdo_pool_info *info); + int update_vdo_pool_virtual_size(struct lv_segment *vdo_pool_seg); + uint32_t get_vdo_pool_max_extents(const struct dm_vdo_target_params *vtp, + uint32_t extent_size); +diff --git a/lib/metadata/vdo_format.c b/lib/metadata/vdo_format.c +new file mode 100644 +index 000000000..48b0e6a2c +--- /dev/null ++++ b/lib/metadata/vdo_format.c +@@ -0,0 +1,310 @@ ++/* ++ * Copyright (C) 2026 Red Hat, Inc. All rights reserved. ++ * ++ * This file is part of LVM2. ++ * ++ * This copyrighted material is made available to anyone wishing to use, ++ * modify, copy, or redistribute it subject to the terms and conditions ++ * of the GNU Lesser General Public License v.2.1. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include "lib/misc/lib.h" ++#include "lib/metadata/metadata.h" ++ ++#include ++ ++/* ++ * Compute VDO data blocks available for storing compressed/deduplicated content. ++ * ++ * Replicates the vdoformat calculation: given a pool's physical size and ++ * VDO parameters, compute the usable data blocks at 1:1 mapping ++ * (accounting for UDS index, slab overhead, and block map forest). ++ */ ++ ++/* UDS index geometry constants */ ++#define UDS_BLOCK_SIZE 4096U ++#define UDS_BYTES_PER_RECORD 32U ++#define UDS_BYTES_PER_PAGE 32768U ++#define UDS_RECORDS_PER_PAGE (UDS_BYTES_PER_PAGE / UDS_BYTES_PER_RECORD) /* 1024 */ ++#define UDS_DEFAULT_CHAPTERS 1024U ++#define UDS_SMALL_RPC 64U ++#define UDS_DEFAULT_RPC 256U ++#define UDS_DELTA_LIST_SIZE 256U ++#define UDS_MAX_ZONES 16U ++#define UDS_MAX_SAVES 2U ++#define UDS_HEADER_SIZE 19U /* immutable delta index header */ ++#define UDS_CHAPTER_MEAN_DELTA_BITS 16U /* chapter mean_delta = 1<<16 */ ++#define UDS_VOLUME_INDEX_MEAN_DELTA 4096U ++#define UDS_SPARSE_SAMPLE_RATE 32U ++ ++/* UDS on-disk structure sizes */ ++#define UDS_SIZEOF_DI_HEADER 40U ++#define UDS_SIZEOF_DL_SAVE_INFO 8U ++#define UDS_SIZEOF_DP_HEADER 20U ++#define UDS_SIZEOF_SUB_INDEX_DATA 40U ++#define UDS_SIZEOF_VI_DATA 12U /* magic[8] + u32 sparse_sample_rate */ ++#define UDS_PAGE_MAP_MAGIC_LEN 8U ++#define UDS_OPEN_CHAPTER_MAGIC_LEN (sizeof("ALBOC") - 1) ++#define UDS_OPEN_CHAPTER_VER_LEN (sizeof("02.00") - 1) ++ ++/* VDO layout constants */ ++#define VDO_BLOCK_MAP_TREE_HEIGHT 5U ++#define VDO_BLOCK_MAP_ROOT_COUNT 60U ++#define VDO_BLOCK_MAP_ENTRIES_PER_PAGE 812U ++#define VDO_SLAB_JOURNAL_SIZE 224U /* blocks per slab for journal */ ++#define VDO_SLAB_SUMMARY_BLOCKS 64U ++#define VDO_RECOVERY_JOURNAL_SIZE 32768U ++#define VDO_COUNTS_PER_BLOCK 4032U /* reference counts per block */ ++ ++static unsigned _bits_per(unsigned n) ++{ ++ if (!n) ++ return 0; ++ ++ return 32 - clz(n); ++} ++ ++static unsigned _delta_min_bits(unsigned mean_delta) ++{ ++ unsigned incr_keys = (unsigned)round(log(2.0) * mean_delta); ++ ++ return _bits_per(incr_keys + 1); ++} ++ ++/* Total bits for one chapter's delta index */ ++static uint64_t _delta_index_size(uint64_t entry_count, unsigned mean_delta, ++ unsigned payload_bits) ++{ ++ unsigned min_bits = _delta_min_bits(mean_delta); ++ ++ return entry_count * (payload_bits + min_bits + 1) + entry_count / 2; ++} ++ ++/* Number of delta index pages needed for one chapter */ ++static unsigned _delta_index_pages(uint64_t entry_count, unsigned list_count, ++ unsigned mean_delta, unsigned payload_bits) ++{ ++ uint64_t total_bits = _delta_index_size(entry_count, mean_delta, payload_bits); ++ unsigned bits_per_list = (unsigned)(total_bits / list_count); ++ unsigned bits_per_page; ++ ++ total_bits += (uint64_t)list_count * UDS_HEADER_SIZE; ++ bits_per_page = (UDS_BYTES_PER_PAGE - UDS_SIZEOF_DP_HEADER) * 8 - ++ UDS_HEADER_SIZE - bits_per_list; ++ ++ return (unsigned)dm_div_up(total_bits, bits_per_page); ++} ++ ++/* Save bytes for one volume sub-index */ ++static uint64_t _sub_index_save_bytes(unsigned records_per_chapter, ++ unsigned chapters, unsigned vi_mean) ++{ ++ uint64_t delta_list_records = (uint64_t)records_per_chapter * chapters; ++ unsigned list_count = (unsigned)(delta_list_records / UDS_DELTA_LIST_SIZE); ++ unsigned addr_bits, chapter_bits, invalid_chapters; ++ unsigned chapters_in_vi; ++ uint64_t entries_in_vi, addr_span; ++ unsigned mean_delta; ++ uint64_t chapter_size_bits, index_size_bytes, memory_size; ++ uint64_t zone_memory, di_save; ++ ++ if (list_count < UDS_MAX_ZONES * UDS_MAX_ZONES) ++ list_count = UDS_MAX_ZONES * UDS_MAX_ZONES; ++ ++ addr_bits = _bits_per(vi_mean * UDS_DELTA_LIST_SIZE - 1); ++ chapter_bits = _bits_per(chapters - 1); ++ invalid_chapters = chapters / 256; ++ if (invalid_chapters < 2) ++ invalid_chapters = 2; ++ chapters_in_vi = chapters + invalid_chapters; ++ entries_in_vi = (uint64_t)records_per_chapter * chapters_in_vi; ++ addr_span = (uint64_t)list_count << addr_bits; ++ mean_delta = (unsigned)(addr_span / entries_in_vi); ++ ++ chapter_size_bits = _delta_index_size(records_per_chapter, mean_delta, ++ chapter_bits); ++ index_size_bytes = (chapter_size_bits * chapters_in_vi) / 8; ++ /* add 6% headroom to reduce delta list rebalancing when index is full */ ++ memory_size = index_size_bytes * 106 / 100; ++ ++ /* zone_mem(1, memory_size): round up to 64K */ ++ zone_memory = dm_div_up(memory_size, 65536U) * 65536U; ++ ++ /* delta_index save = header + list_count*(save_info+1) + zone_memory */ ++ di_save = UDS_SIZEOF_DI_HEADER + zone_memory + ++ (uint64_t)list_count * (UDS_SIZEOF_DL_SAVE_INFO + 1); ++ ++ /* sub_index save = sub_index_data + list_count*8 + di_save */ ++ return UDS_SIZEOF_SUB_INDEX_DATA + (uint64_t)list_count * 8 + di_save; ++} ++ ++/* ++ * Compute total UDS index size in 4K blocks. ++ * ++ * index_memory_mb: UDS memory config (256, 512, 768, or N*1024) ++ * use_sparse: nonzero for sparse index ++ */ ++static uint64_t _uds_index_blocks(unsigned index_memory_mb, int use_sparse) ++{ ++ unsigned base_chapters, rpc, chapters, sparse_chapters = 0; ++ unsigned records_per_chapter, chapter_payload_bits; ++ unsigned chapter_dl_bits, delta_lists_per_chapter; ++ unsigned ippc, pages_per_chapter; ++ uint64_t pages_per_volume, bytes_per_volume, volume_blocks; ++ uint64_t vi_save_bytes, vi_blocks; ++ unsigned page_map_blocks, open_chapter_blocks; ++ unsigned save_blocks; ++ ++ /* Map memory config to chapters and records-per-page */ ++ if (index_memory_mb <= 256) { ++ base_chapters = UDS_DEFAULT_CHAPTERS; ++ rpc = UDS_SMALL_RPC; ++ } else if (index_memory_mb <= 512) { ++ base_chapters = UDS_DEFAULT_CHAPTERS; ++ rpc = 2 * UDS_SMALL_RPC; ++ } else if (index_memory_mb <= 768) { ++ base_chapters = UDS_DEFAULT_CHAPTERS; ++ rpc = 3 * UDS_SMALL_RPC; ++ } else { ++ base_chapters = (index_memory_mb / 1024) * UDS_DEFAULT_CHAPTERS; ++ rpc = UDS_DEFAULT_RPC; ++ } ++ ++ /* sparse index: 10x total chapters, 95% sparse (19/2 sparse per base chapter) */ ++ if (use_sparse) { ++ sparse_chapters = (19 * base_chapters) / 2; ++ chapters = base_chapters * 10; ++ } else { ++ chapters = base_chapters; ++ } ++ ++ /* Geometry */ ++ records_per_chapter = UDS_RECORDS_PER_PAGE * rpc; ++ chapter_payload_bits = _bits_per(rpc - 1); ++ chapter_dl_bits = _bits_per((records_per_chapter - 1) | 077) - 6; ++ delta_lists_per_chapter = 1U << chapter_dl_bits; ++ ippc = _delta_index_pages(records_per_chapter, delta_lists_per_chapter, ++ 1U << UDS_CHAPTER_MEAN_DELTA_BITS, ++ chapter_payload_bits); ++ pages_per_chapter = ippc + rpc; ++ pages_per_volume = (uint64_t)pages_per_chapter * chapters; ++ bytes_per_volume = UDS_BYTES_PER_PAGE * (pages_per_volume + 1); ++ volume_blocks = bytes_per_volume / UDS_BLOCK_SIZE; ++ ++ /* Volume index save blocks */ ++ if (!use_sparse) { ++ vi_save_bytes = _sub_index_save_bytes(records_per_chapter, ++ chapters, ++ UDS_VOLUME_INDEX_MEAN_DELTA); ++ } else { ++ unsigned hook_rec = records_per_chapter / UDS_SPARSE_SAMPLE_RATE; ++ unsigned non_hook_rec = records_per_chapter - hook_rec; ++ unsigned dense_chapters = chapters - sparse_chapters; ++ uint64_t hook_bytes, non_hook_bytes; ++ ++ hook_bytes = _sub_index_save_bytes(hook_rec, chapters, ++ UDS_VOLUME_INDEX_MEAN_DELTA); ++ non_hook_bytes = _sub_index_save_bytes(non_hook_rec, ++ dense_chapters, ++ UDS_VOLUME_INDEX_MEAN_DELTA); ++ vi_save_bytes = UDS_SIZEOF_VI_DATA + hook_bytes + non_hook_bytes; ++ } ++ vi_blocks = dm_div_up(vi_save_bytes + UDS_SIZEOF_DL_SAVE_INFO, ++ UDS_BLOCK_SIZE) + UDS_MAX_ZONES; ++ ++ /* Page map and open chapter save blocks */ ++ page_map_blocks = ++ (unsigned)dm_div_up(UDS_PAGE_MAP_MAGIC_LEN + 8 + (uint64_t)chapters * (ippc - 1) * 2, ++ UDS_BLOCK_SIZE); ++ open_chapter_blocks = ++ (unsigned)dm_div_up(UDS_OPEN_CHAPTER_MAGIC_LEN + UDS_OPEN_CHAPTER_VER_LEN + 4 + ++ (uint64_t)records_per_chapter * UDS_BYTES_PER_RECORD, ++ UDS_BLOCK_SIZE); ++ ++ save_blocks = 1 + vi_blocks + page_map_blocks + open_chapter_blocks; ++ ++ /* total = header(3) + volume + MAX_SAVES * save */ ++ return 3 + volume_blocks + UDS_MAX_SAVES * save_blocks; ++} ++ ++/* ++ * Compute block map forest overhead: the number of blocks consumed ++ * by the 5-level block map tree when mapping data_blocks entries. ++ * Tree roots (level 3) and super roots (level 4) are not allocated ++ * from slabs, so they are excluded. ++ */ ++static uint64_t _forest_overhead(uint64_t data_blocks) ++{ ++ uint64_t leaf_pages, level_size, non_leaves, leaves; ++ unsigned levels[VDO_BLOCK_MAP_TREE_HEIGHT]; ++ unsigned h; ++ ++ leaf_pages = dm_div_up(data_blocks, VDO_BLOCK_MAP_ENTRIES_PER_PAGE); ++ level_size = dm_div_up(leaf_pages, VDO_BLOCK_MAP_ROOT_COUNT); ++ ++ for (h = 0; h < VDO_BLOCK_MAP_TREE_HEIGHT; h++) { ++ level_size = dm_div_up(level_size, VDO_BLOCK_MAP_ENTRIES_PER_PAGE); ++ levels[h] = (unsigned)level_size; ++ } ++ ++ non_leaves = 0; ++ for (h = 0; h < VDO_BLOCK_MAP_TREE_HEIGHT; h++) ++ non_leaves += (uint64_t)levels[h] * VDO_BLOCK_MAP_ROOT_COUNT; ++ ++ /* Exclude tree roots and super roots (not from slabs) */ ++ non_leaves -= (levels[VDO_BLOCK_MAP_TREE_HEIGHT - 2] + ++ levels[VDO_BLOCK_MAP_TREE_HEIGHT - 1]) * ++ VDO_BLOCK_MAP_ROOT_COUNT; ++ ++ leaves = dm_div_up(data_blocks - non_leaves, ++ VDO_BLOCK_MAP_ENTRIES_PER_PAGE); ++ ++ return non_leaves + leaves; ++} ++ ++int vdo_pool_info(uint64_t pool_size_sectors, ++ const struct dm_vdo_target_params *vtp, ++ struct vdo_pool_info *info) ++{ ++ uint64_t pool_4k = pool_size_sectors / DM_VDO_BLOCK_SIZE; ++ unsigned slab_4k = (unsigned)((uint64_t)vtp->slab_size_mb * (1048576 / UDS_BLOCK_SIZE)); ++ uint64_t offset, depot_blocks; ++ unsigned ref_blocks; ++ uint64_t total_data, forest; ++ ++ memset(info, 0, sizeof(*info)); ++ ++ info->uds_blocks = _uds_index_blocks(vtp->index_memory_size_mb, ++ vtp->use_sparse_index); ++ ++ /* Layout: geometry(1) + UDS + super(1) + roots + slab_summary + recovery_journal */ ++ offset = info->uds_blocks + 2; ++ if (pool_4k <= offset + VDO_BLOCK_MAP_ROOT_COUNT + ++ VDO_SLAB_SUMMARY_BLOCKS + VDO_RECOVERY_JOURNAL_SIZE) ++ return 0; ++ ++ depot_blocks = pool_4k - offset - VDO_BLOCK_MAP_ROOT_COUNT - ++ VDO_SLAB_SUMMARY_BLOCKS - VDO_RECOVERY_JOURNAL_SIZE; ++ ++ info->slab_count = (unsigned)(depot_blocks / slab_4k); ++ if (!info->slab_count) ++ return 0; ++ ++ /* Slab layout: journal(224) + ref_blocks + data_blocks = slab_4k */ ++ ref_blocks = (unsigned)dm_div_up(slab_4k - VDO_SLAB_JOURNAL_SIZE, ++ VDO_COUNTS_PER_BLOCK + 1); ++ info->data_per_slab = slab_4k - VDO_SLAB_JOURNAL_SIZE - ref_blocks; ++ total_data = (uint64_t)info->data_per_slab * info->slab_count; ++ ++ forest = _forest_overhead(total_data); ++ if (forest >= total_data) ++ return 0; ++ ++ info->data_blocks = total_data - forest; ++ ++ return 1; ++} +-- +2.54.0 + diff --git a/0011-vdo-add-support-for-VDO-kernel-direct-formatting.patch b/0011-vdo-add-support-for-VDO-kernel-direct-formatting.patch new file mode 100644 index 0000000..2e79e57 --- /dev/null +++ b/0011-vdo-add-support-for-VDO-kernel-direct-formatting.patch @@ -0,0 +1,239 @@ +From efdcd4b2f8c2f25e9db8e3b217de85d8cba54aab Mon Sep 17 00:00:00 2001 +From: Bruce Johnston +Date: Wed, 17 Dec 2025 12:40:44 -0500 +Subject: [PATCH 11/16] vdo: add support for VDO kernel direct formatting + +VDO has added a feature (dm-vdo 9.2.0+) to format devices directly +in its kernel code vs using the vdoformat userspace tool. Add support +for this via a new config parameter vdo_use_kernel_format. + +When vdo_use_kernel_format=1, LVM zeroes the first 4KiB of the pool +data LV and passes slabSize, indexMemory, and indexSparse parameters +on the device-mapper table line so the kernel formats on activation. + +The use_kernel_format flag is persisted in VDO pool metadata so it +survives reactivation. Old metadata without the flag defaults to 0 +(userspace vdoformat), ensuring backward compatibility. + +Signed-off-by: Bruce Johnston +Made-with: Cursor +(cherry picked from commit 7642c5b4ab0c87ed6dc31bcb56e2de40a45045ce) +--- + lib/config/config_settings.h | 10 ++++++++-- + lib/config/defaults.h | 1 + + lib/metadata/segtype.h | 1 + + lib/metadata/vdo_manip.c | 32 +++++++++++++++++++++++++++++--- + lib/vdo/vdo.c | 6 ++++++ + libdm/libdevmapper.h | 2 ++ + libdm/libdm-deptree.c | 20 ++++++++++++++++++++ + tools/toollib.c | 1 + + 8 files changed, 68 insertions(+), 5 deletions(-) + +diff --git a/lib/config/config_settings.h b/lib/config/config_settings.h +index 8a1872b62..92d5f9e40 100644 +--- a/lib/config/config_settings.h ++++ b/lib/config/config_settings.h +@@ -782,6 +782,12 @@ cfg(allocation_vdo_check_point_frequency_CFG, "vdo_check_point_frequency", alloc + cfg(allocation_vdo_use_sparse_index_CFG, "vdo_use_sparse_index", allocation_CFG_SECTION, CFG_PROFILABLE | CFG_PROFILABLE_METADATA | CFG_DEFAULT_COMMENTED, CFG_TYPE_BOOL, DEFAULT_VDO_USE_SPARSE_INDEX, VDO_1ST_VSN, NULL, 0, NULL, + "Enables sparse indexing for VDO volume.\n") + ++/* vdo format */ ++cfg(allocation_vdo_use_kernel_format_CFG, "vdo_use_kernel_format", allocation_CFG_SECTION, CFG_PROFILABLE | CFG_PROFILABLE_METADATA | CFG_DEFAULT_COMMENTED, CFG_TYPE_BOOL, DEFAULT_VDO_USE_KERNEL_FORMAT, vsn(2, 3, 41), NULL, 0, NULL, ++ "Use kernel direct formatting for VDO volume.\n" ++ "When enabled (1), uses kernel direct formatting (requires dm-vdo 9.2.0+).\n" ++ "When disabled (0), uses traditional userspace vdoformat tool.\n") ++ + /* vdo format */ + cfg(allocation_vdo_index_memory_size_mb_CFG, "vdo_index_memory_size_mb", allocation_CFG_SECTION, CFG_PROFILABLE | CFG_PROFILABLE_METADATA | CFG_DEFAULT_COMMENTED, CFG_TYPE_INT, DEFAULT_VDO_INDEX_MEMORY_SIZE_MB, VDO_1ST_VSN, NULL, 0, NULL, + "Specifies the amount of index memory in MiB for VDO volume.\n" +@@ -1336,10 +1342,10 @@ cfg_array(global_vdo_format_options_CFG, "vdo_format_options", global_CFG_SECTIO + cfg_array(global_vdo_disabled_features_CFG, "vdo_disabled_features", global_CFG_SECTION, CFG_ALLOW_EMPTY | CFG_DEFAULT_UNDEFINED, CFG_TYPE_STRING, NULL, vsn(2, 3, 11), NULL, 0, NULL, + "Features to not use in the vdo driver.\n" + "This can be helpful for testing, or to avoid using a feature that is\n" +- "causing problems. Features include: online_rename, version4\n" ++ "causing problems. Features include: online_rename, version4, kernel_format\n" + "#\n" + "Example\n" +- "vdo_disabled_features = [ \"online_rename\", \"version4\" ]\n" ++ "vdo_disabled_features = [ \"online_rename\", \"version4\", \"kernel_format\" ]\n" + "#\n") + + cfg(global_fsadm_executable_CFG, "fsadm_executable", global_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_STRING, DEFAULT_FSADM_PATH, vsn(2, 2, 170), "@FSADM_PATH@", 0, NULL, +diff --git a/lib/config/defaults.h b/lib/config/defaults.h +index c9c19dcef..f0cbddf72 100644 +--- a/lib/config/defaults.h ++++ b/lib/config/defaults.h +@@ -169,6 +169,7 @@ + #define DEFAULT_VDO_BLOCK_MAP_CACHE_SIZE_MB (DM_VDO_BLOCK_MAP_CACHE_SIZE_MINIMUM_MB) + #define DEFAULT_VDO_BLOCK_MAP_ERA_LENGTH (DM_VDO_BLOCK_MAP_ERA_LENGTH_MAXIMUM) + #define DEFAULT_VDO_USE_SPARSE_INDEX 0 ++#define DEFAULT_VDO_USE_KERNEL_FORMAT 1 + #define DEFAULT_VDO_CHECK_POINT_FREQUENCY (0) + #define DEFAULT_VDO_INDEX_MEMORY_SIZE_MB (DM_VDO_INDEX_MEMORY_SIZE_MINIMUM_MB) + #define DEFAULT_VDO_SLAB_SIZE_MB (2 * 1024) /* 2GiB ... 19 slabbits */ +diff --git a/lib/metadata/segtype.h b/lib/metadata/segtype.h +index b53859d9d..64650579b 100644 +--- a/lib/metadata/segtype.h ++++ b/lib/metadata/segtype.h +@@ -356,6 +356,7 @@ int init_vdo_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); + + #define VDO_FEATURE_ONLINE_RENAME (1U << 0) /* version 6.2.3 */ + #define VDO_FEATURE_VERSION4 (1U << 1) /* version 8.2.0 */ ++#define VDO_FEATURE_KERNEL_FORMAT (1U << 2) /* version 9.2.0 */ + + int init_writecache_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); + +diff --git a/lib/metadata/vdo_manip.c b/lib/metadata/vdo_manip.c +index 9d1c7305e..0b4f2d3fe 100644 +--- a/lib/metadata/vdo_manip.c ++++ b/lib/metadata/vdo_manip.c +@@ -390,6 +390,8 @@ int convert_vdo_pool_lv(struct logical_volume *data_lv, + struct lv_segment *vdo_pool_seg; + uint64_t vdo_logical_size = 0; + uint64_t adjust; ++ unsigned attrs = 0; ++ int use_kernel = format && vtp->use_kernel_format; + + if (!(vdo_pool_segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_VDO_POOL))) + return_0; +@@ -412,9 +414,30 @@ int convert_vdo_pool_lv(struct logical_volume *data_lv, + if (format) { + if (test_mode()) { + log_verbose("Test mode: Skipping formatting of VDO pool volume."); +- } else if (!_format_vdo_pool_data_lv(data_lv, vtp, &vdo_logical_size)) { +- log_error("Cannot format VDO pool volume %s.", display_lvname(data_lv)); +- return 0; ++ } else if (use_kernel) { ++ if (use_kernel && ++ (!vdo_pool_segtype->ops->target_present || ++ !vdo_pool_segtype->ops->target_present(cmd, NULL, &attrs) || ++ !(attrs & VDO_FEATURE_KERNEL_FORMAT))) { ++ log_error("Kernel direct formatting (vdo_use_kernel_format=1) requires VDO target version 9.2.0 or newer."); ++ return 0; ++ } ++ ++ /* Kernel decides to format if the first 4k are zeroes */ ++ if (!activate_and_wipe_lv(data_lv, WIPE_MODE_DO_ZERO, 0, 0)) { ++ log_error("Aborting. Failed to wipe first 4 KiB of VDO pool volume %s.", ++ display_lvname(data_lv)); ++ return 0; ++ } ++ ++ if (!*virtual_extents) ++ vdo_logical_size = data_lv->size; ++ } else { ++ /* Traditional userspace vdoformat */ ++ if (!_format_vdo_pool_data_lv(data_lv, vtp, &vdo_logical_size)) { ++ log_error("Cannot format VDO pool volume %s.", display_lvname(data_lv)); ++ return 0; ++ } + } + } else { + log_verbose("Skipping VDO formatting %s.", display_lvname(data_lv)); +@@ -455,6 +478,7 @@ int convert_vdo_pool_lv(struct logical_volume *data_lv, + vdo_pool_seg = first_seg(vdo_pool_lv); + vdo_pool_seg->segtype = vdo_pool_segtype; + vdo_pool_seg->vdo_params = *vtp; ++ vdo_pool_seg->vdo_params.use_kernel_format = use_kernel; + vdo_pool_seg->vdo_pool_header_size = vdo_pool_header_size; + vdo_pool_seg->vdo_pool_virtual_extents = *virtual_extents; + +@@ -621,6 +645,8 @@ int fill_vdo_target_params(struct cmd_context *cmd, + find_config_tree_int(cmd, allocation_vdo_block_map_era_length_CFG, profile); + vtp->use_sparse_index = + find_config_tree_bool(cmd, allocation_vdo_use_sparse_index_CFG, profile); ++ vtp->use_kernel_format = ++ find_config_tree_bool(cmd, allocation_vdo_use_kernel_format_CFG, profile); + vtp->index_memory_size_mb = + find_config_tree_int64(cmd, allocation_vdo_index_memory_size_mb_CFG, profile); + vtp->slab_size_mb = +diff --git a/lib/vdo/vdo.c b/lib/vdo/vdo.c +index c3cb2b2a5..1e9d377b5 100644 +--- a/lib/vdo/vdo.c ++++ b/lib/vdo/vdo.c +@@ -254,6 +254,9 @@ static int _vdo_pool_text_import(struct lv_segment *seg, + if (!_import_bool(n, "use_sparse_index", &vtp->use_sparse_index)) + return_0; + ++ if (!_import_bool(n, "use_kernel_format", &vtp->use_kernel_format)) ++ return_0; ++ + if (!dm_config_get_uint32(n, "index_memory_size_mb", &vtp->index_memory_size_mb)) + return _bad_field("index_memory_size_mb"); + +@@ -327,6 +330,8 @@ static int _vdo_pool_text_export(const struct lv_segment *seg, struct formatter + + if (vtp->use_sparse_index) + outf(f, "use_sparse_index = 1"); ++ if (vtp->use_kernel_format) ++ outf(f, "use_kernel_format = 1"); + /* TODO - conditionally */ + outsize(f, vtp->index_memory_size_mb * UINT64_C(2 * 1024), + "index_memory_size_mb = %u", vtp->index_memory_size_mb); +@@ -430,6 +435,7 @@ static int _vdo_target_present(struct cmd_context *cmd, + } _features[] = { + { 6, 2, 3, VDO_FEATURE_ONLINE_RENAME, "online_rename" }, + { 8, 2, 0, VDO_FEATURE_VERSION4, "version4" }, ++ { 9, 2, 0, VDO_FEATURE_KERNEL_FORMAT, "kernel_format" }, + }; + static const char _lvmconf[] = "global/vdo_disabled_features"; + static int _vdo_checked = 0; +diff --git a/libdm/libdevmapper.h b/libdm/libdevmapper.h +index c393c217e..d5066fbda 100644 +--- a/libdm/libdevmapper.h ++++ b/libdm/libdevmapper.h +@@ -2167,6 +2167,8 @@ struct dm_vdo_target_params { + + /* write policy */ + enum dm_vdo_write_policy write_policy; ++ ++ int use_kernel_format; /* kernel direct format (added last for ABI compat) */ + }; + + int dm_vdo_validate_target_params(const struct dm_vdo_target_params *vtp, +diff --git a/libdm/libdm-deptree.c b/libdm/libdm-deptree.c +index 0a246f272..02b730bb8 100644 +--- a/libdm/libdm-deptree.c ++++ b/libdm/libdm-deptree.c +@@ -3026,6 +3026,26 @@ static int _vdo_emit_segment_line(struct dm_task *dmt, uint32_t major, uint32_t + seg->vdo_params.logical_threads, + seg->vdo_params.physical_threads); + ++ /* Add format parameters for kernel direct formatting */ ++ if (seg->vdo_params.use_kernel_format) { ++ /* Convert slab size from MB to 4KiB blocks */ ++ EMIT_PARAMS(pos, " slabSize " FMTu64, ++ seg->vdo_params.slab_size_mb * UINT64_C(256)); // 1MiB -> 256 * 4KiB blocks ++ ++ /* Index memory size: format as GiB if >= 1024MB, otherwise as fraction */ ++ if (seg->vdo_params.index_memory_size_mb >= 1024) ++ EMIT_PARAMS(pos, " indexMemory %u", seg->vdo_params.index_memory_size_mb / 1024); ++ else if (seg->vdo_params.index_memory_size_mb >= 768) ++ EMIT_PARAMS(pos, " indexMemory 0.75"); ++ else if (seg->vdo_params.index_memory_size_mb >= 512) ++ EMIT_PARAMS(pos, " indexMemory 0.50"); ++ else ++ EMIT_PARAMS(pos, " indexMemory 0.25"); ++ ++ /* Sparse index setting */ ++ EMIT_PARAMS(pos, " indexSparse %s", seg->vdo_params.use_sparse_index ? "on" : "off"); ++ } ++ + return 1; + } + +diff --git a/tools/toollib.c b/tools/toollib.c +index 0e6ce5d3a..f91fae701 100644 +--- a/tools/toollib.c ++++ b/tools/toollib.c +@@ -1369,6 +1369,7 @@ int get_vdo_settings(struct cmd_context *cmd, + DO_OFFLINE(slab_size_mb); + DO_OFFLINE(use_metadata_hints); + DO_OFFLINE(use_sparse_index); ++ DO_OFFLINE(use_kernel_format); + + option = "write_policy"; + if (_compare_vdo_option(cn->key, option)) { +-- +2.54.0 + diff --git a/0012-vdo-add-kernel-formatting-path-with-LV_VDOFORMAT-met.patch b/0012-vdo-add-kernel-formatting-path-with-LV_VDOFORMAT-met.patch new file mode 100644 index 0000000..9b33098 --- /dev/null +++ b/0012-vdo-add-kernel-formatting-path-with-LV_VDOFORMAT-met.patch @@ -0,0 +1,355 @@ +From cc3b0438480b7bdc140416010e077943543a305a Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Thu, 25 Jun 2026 17:17:32 +0200 +Subject: [PATCH 12/16] vdo: add kernel formatting path with LV_VDOFORMAT + metadata flag + +Introduce LV_VDOFORMAT status flag persisted in VG metadata so the +VDO pool activation can pass use_kernel_format=1 to the dm-vdo target. +The flag is set before activation and cleared after formatting completes. + +Add _format_vdo_pool_kernel() which activates the VDO pool with the +flag set, waits for the VDO index to come online, deactivates, clears +LV_VDOFORMAT and commits metadata. + +In convert_vdo_pool_lv(), gracefully fall back to userspace vdoformat +when the VDO target is older than 9.2.0 instead of failing with an +error. For the kernel path, compute pool geometry via vdo_pool_info() +to determine virtual size and report slab layout after formatting. +On failure, revert status flags and remove the data layer. + +Correct versioning for dm_tree_node_add_vdo_target as DM_1_02_216 after +adding use_kernel_format to dm_vdo_target_params. +Add ABI compatibility wrapper for 1.02.213 callers whose struct lacks +the new field -- the wrapper zero-initialises use_kernel_format so old +binaries default to userspace vdoformat. + +Use a local copy of vdo_params in _vdo_pool_add_target_line() to +avoid mutating in-memory metadata when setting use_kernel_format + +(cherry picked from commit 50355f35e5eaad7d44f82c7d2a4c3d39f23527e1) +--- + lib/format_text/flags.c | 1 + + lib/metadata/metadata-exported.h | 1 + + lib/metadata/vdo_manip.c | 138 ++++++++++++++++++++++++---- + lib/vdo/vdo.c | 7 +- + libdm/.exported_symbols.DM_1_02_216 | 1 + + libdm/libdm-deptree.c | 53 +++++++++-- + 6 files changed, 176 insertions(+), 25 deletions(-) + create mode 100644 libdm/.exported_symbols.DM_1_02_216 + +diff --git a/lib/format_text/flags.c b/lib/format_text/flags.c +index 1f81d063a..dde5aabc8 100644 +--- a/lib/format_text/flags.c ++++ b/lib/format_text/flags.c +@@ -77,6 +77,7 @@ static const struct flag _lv_flags[] = { + {LV_CROP_METADATA, "CROP_METADATA", SEGTYPE_FLAG}, + {LV_CACHE_VOL, "CACHE_VOL", COMPATIBLE_FLAG}, + {LV_CACHE_USES_CACHEVOL, "CACHE_USES_CACHEVOL", SEGTYPE_FLAG}, ++ {LV_VDOFORMAT, "VDOFORMAT", SEGTYPE_FLAG}, + {LV_NOSCAN, NULL, 0}, + {LV_TEMPORARY, NULL, 0}, + {POOL_METADATA_SPARE, NULL, 0}, +diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h +index 1f78c7aac..70a00d0bd 100644 +--- a/lib/metadata/metadata-exported.h ++++ b/lib/metadata/metadata-exported.h +@@ -156,6 +156,7 @@ + #define LV_VDO UINT64_C(0x0000000020000000) /* LV - Internal use only */ + #define LV_VDO_POOL UINT64_C(0x0000000040000000) /* LV - Internal use only */ + #define LV_VDO_POOL_DATA UINT64_C(0x8000000000000000) /* LV - Internal use only */ ++#define LV_VDOFORMAT UINT64_C(0x0000000000000800) /* LV - VDO pool passes format args */ + + #define LV_CACHE_VOL UINT64_C(0x0010000000000000) /* LV - also a PV flag */ + #define LV_CACHE_USES_CACHEVOL UINT64_C(0x4000000000000000) /* LV - also a PV flag */ +diff --git a/lib/metadata/vdo_manip.c b/lib/metadata/vdo_manip.c +index 0b4f2d3fe..ca1c34331 100644 +--- a/lib/metadata/vdo_manip.c ++++ b/lib/metadata/vdo_manip.c +@@ -16,6 +16,7 @@ + #include "lib/metadata/metadata.h" + #include "lib/locking/locking.h" + #include "lib/misc/lvm-string.h" ++#include "lib/misc/lvm-signal.h" + #include "lib/commands/toolcontext.h" + #include "lib/display/display.h" + #include "lib/metadata/segtype.h" +@@ -367,6 +368,73 @@ static int _format_vdo_pool_data_lv(struct logical_volume *data_lv, + return 1; + } + ++static int _format_vdo_pool_kernel(struct logical_volume *vdo_pool_lv) ++{ ++ struct cmd_context *cmd = vdo_pool_lv->vg->cmd; ++ struct lv_status_vdo *vdo_status; ++ enum dm_vdo_index_state index_state; ++ int r = 0; ++ ++ if (!vg_write(vdo_pool_lv->vg) || !vg_commit(vdo_pool_lv->vg)) ++ return_0; ++ ++ if (!activate_lv(cmd, vdo_pool_lv)) { ++ log_error("Failed to activate VDO pool %s for kernel formatting.", ++ display_lvname(vdo_pool_lv)); ++ goto out; ++ } ++ ++ sync_local_dev_names(cmd); ++ ++ /* Wait for the UDS index to finish initializing */ ++ for (;;) { ++ if (!lv_vdo_pool_status(vdo_pool_lv, 0, &vdo_status)) { ++ stack; ++ break; ++ } ++ ++ index_state = vdo_status->vdo->index_state; ++ dm_pool_destroy(vdo_status->mem); ++ ++ if (index_state == DM_VDO_INDEX_ERROR) { ++ log_error("Failed to format VDO pool %s, index error.", ++ display_lvname(vdo_pool_lv)); ++ break; ++ } ++ ++ log_verbose("VDO pool %s index state: %s.", ++ display_lvname(vdo_pool_lv), ++ get_vdo_index_state_name(index_state)); ++ ++ if (index_state != DM_VDO_INDEX_OPENING) { ++ r = 1; /* Initialized */ ++ break; ++ } ++ ++ /* 50ms: formatting is typically fast, avoid unnecessary delay */ ++ if (!sigint_usleep(50000)) { ++ log_warn("WARNING: Interrupted waiting for VDO index on %s.", ++ display_lvname(vdo_pool_lv)); ++ break; ++ } ++ } ++ ++ if (!deactivate_lv(cmd, vdo_pool_lv)) { ++ log_error("Failed to deactivate VDO pool %s after kernel formatting.", ++ display_lvname(vdo_pool_lv)); ++ r = 0; ++ } ++out: ++ vdo_pool_lv->status &= ~LV_VDOFORMAT; ++ ++ if (!vg_write(vdo_pool_lv->vg) || !vg_commit(vdo_pool_lv->vg)) ++ return_0; ++ ++ sync_local_dev_names(cmd); ++ ++ return r; ++} ++ + /* + * convert_vdo_pool_lv + * @data_lv +@@ -388,6 +456,7 @@ int convert_vdo_pool_lv(struct logical_volume *data_lv, + struct logical_volume *vdo_pool_lv = data_lv; + const struct segment_type *vdo_pool_segtype; + struct lv_segment *vdo_pool_seg; ++ struct vdo_pool_info pinfo = { 0 }; + uint64_t vdo_logical_size = 0; + uint64_t adjust; + unsigned attrs = 0; +@@ -414,29 +483,37 @@ int convert_vdo_pool_lv(struct logical_volume *data_lv, + if (format) { + if (test_mode()) { + log_verbose("Test mode: Skipping formatting of VDO pool volume."); +- } else if (use_kernel) { ++ } else { + if (use_kernel && + (!vdo_pool_segtype->ops->target_present || + !vdo_pool_segtype->ops->target_present(cmd, NULL, &attrs) || + !(attrs & VDO_FEATURE_KERNEL_FORMAT))) { +- log_error("Kernel direct formatting (vdo_use_kernel_format=1) requires VDO target version 9.2.0 or newer."); +- return 0; ++ log_verbose("Kernel formatting requires VDO target version 9.2.0 or newer, using vdoformat."); ++ use_kernel = 0; + } + +- /* Kernel decides to format if the first 4k are zeroes */ +- if (!activate_and_wipe_lv(data_lv, WIPE_MODE_DO_ZERO, 0, 0)) { +- log_error("Aborting. Failed to wipe first 4 KiB of VDO pool volume %s.", +- display_lvname(data_lv)); +- return 0; +- } +- +- if (!*virtual_extents) +- vdo_logical_size = data_lv->size; +- } else { +- /* Traditional userspace vdoformat */ +- if (!_format_vdo_pool_data_lv(data_lv, vtp, &vdo_logical_size)) { +- log_error("Cannot format VDO pool volume %s.", display_lvname(data_lv)); +- return 0; ++ if (use_kernel) { ++ /* Kernel decides to format if the first 4k are zeroes */ ++ if (!activate_and_wipe_lv(data_lv, WIPE_MODE_DO_ZERO, 0, 0)) { ++ log_error("Aborting. Failed to wipe first 4 KiB of VDO pool volume %s.", ++ display_lvname(data_lv)); ++ return 0; ++ } ++ ++ if (!vdo_pool_info(data_lv->size, vtp, &pinfo)) { ++ log_error("Pool %s is too small for the given VDO configuration.", ++ display_lvname(data_lv)); ++ return 0; ++ } ++ ++ if (!*virtual_extents) ++ vdo_logical_size = pinfo.data_blocks * DM_VDO_BLOCK_SIZE; ++ } else { ++ /* Traditional userspace vdoformat */ ++ if (!_format_vdo_pool_data_lv(data_lv, vtp, &vdo_logical_size)) { ++ log_error("Cannot format VDO pool volume %s.", display_lvname(data_lv)); ++ return 0; ++ } + } + } + } else { +@@ -485,6 +562,33 @@ int convert_vdo_pool_lv(struct logical_volume *data_lv, + vdo_pool_lv->status |= LV_VDO_POOL; + data_lv->status |= LV_VDO_POOL_DATA; + ++ if (use_kernel) { ++ vdo_pool_lv->status |= LV_VDOFORMAT; ++ ++ if (!_format_vdo_pool_kernel(vdo_pool_lv)) { ++ log_error("Failed to kernel format VDO pool %s, reverting.", ++ display_lvname(vdo_pool_lv)); ++ vdo_pool_lv->status &= ~LV_VDO_POOL; ++ data_lv->status &= ~LV_VDO_POOL_DATA; ++ if (!remove_layer_from_lv(vdo_pool_lv, data_lv)) ++ return_0; ++ if (!lv_remove(data_lv)) ++ return_0; ++ if (!vg_write(vdo_pool_lv->vg) || !vg_commit(vdo_pool_lv->vg)) ++ return_0; ++ return 0; ++ } ++ ++ log_print_unless_silent(" The VDO volume can address %s in %u data slabs, each %s.", ++ display_size(cmd, (uint64_t)pinfo.data_per_slab * pinfo.slab_count * DM_VDO_BLOCK_SIZE), ++ pinfo.slab_count, ++ display_mb_size(cmd, vtp->slab_size_mb)); ++ log_print_unless_silent(" It can grow to address at most %s of physical storage in %u slabs.", ++ display_mb_size(cmd, (uint64_t)DM_VDO_SLABS_MAXIMUM * vtp->slab_size_mb), ++ (unsigned)DM_VDO_SLABS_MAXIMUM); ++ log_print_unless_silent(" If a larger maximum size might be needed, use bigger slabs."); ++ } ++ + return 1; + } + +diff --git a/lib/vdo/vdo.c b/lib/vdo/vdo.c +index 1e9d377b5..e0a0c3079 100644 +--- a/lib/vdo/vdo.c ++++ b/lib/vdo/vdo.c +@@ -391,6 +391,7 @@ static int _vdo_pool_add_target_line(struct dev_manager *dm, + struct dm_tree_node *node, uint64_t len, + uint32_t *pvmove_mirror_count __attribute__((unused))) + { ++ struct dm_vdo_target_params vdo_params; + char *vdo_pool_name, *data_uuid; + unsigned attrs = 0; + +@@ -411,11 +412,15 @@ static int _vdo_pool_add_target_line(struct dev_manager *dm, + if (!(data_uuid = build_dm_uuid(mem, seg_lv(seg, 0), lv_layer(seg_lv(seg, 0))))) + return_0; + ++ /* Use local copy to avoid mutating in-memory metadata */ ++ vdo_params = seg->vdo_params; ++ vdo_params.use_kernel_format = (seg->lv->status & LV_VDOFORMAT) ? 1 : 0; ++ + /* VDO uses virtual size instead of its physical size */ + if (!dm_tree_node_add_vdo_target(node, get_vdo_pool_virtual_size(seg), + !(attrs & VDO_FEATURE_VERSION4) ? 2 : 4, + vdo_pool_name, data_uuid, seg_lv(seg, 0)->size, +- &seg->vdo_params)) ++ &vdo_params)) + return_0; + + return 1; +diff --git a/libdm/.exported_symbols.DM_1_02_216 b/libdm/.exported_symbols.DM_1_02_216 +new file mode 100644 +index 000000000..f88fb03a7 +--- /dev/null ++++ b/libdm/.exported_symbols.DM_1_02_216 +@@ -0,0 +1 @@ ++dm_tree_node_add_vdo_target +diff --git a/libdm/libdm-deptree.c b/libdm/libdm-deptree.c +index 02b730bb8..4c420eeba 100644 +--- a/libdm/libdm-deptree.c ++++ b/libdm/libdm-deptree.c +@@ -4609,13 +4609,14 @@ void dm_tree_node_set_callback(struct dm_tree_node *dnode, + dnode->callback_data = cb_data; + } + +-int dm_tree_node_add_vdo_target(struct dm_tree_node *node, +- uint64_t size, +- uint32_t vdo_version, +- const char *vdo_pool_name, +- const char *data_uuid, +- uint64_t data_size, +- const struct dm_vdo_target_params *vtp) ++DM_EXPORT_NEW_SYMBOL(int, dm_tree_node_add_vdo_target, 1_02_216) ++ (struct dm_tree_node *node, ++ uint64_t size, ++ uint32_t vdo_version, ++ const char *vdo_pool_name, ++ const char *data_uuid, ++ uint64_t data_size, ++ const struct dm_vdo_target_params *vtp) + { + struct load_segment *seg; + +@@ -4699,4 +4700,42 @@ int dm_tree_node_add_cache_target_base(struct dm_tree_node *node, + metadata_uuid, data_uuid, origin_uuid, + policy_name, policy_settings, data_block_size); + } ++ ++/* ++ * Retain ABI compatibility after adding use_kernel_format field ++ * to dm_vdo_target_params in version 1.02.216. ++ * ++ * Old binaries compiled against 1.02.213 have struct dm_vdo_target_params ++ * without the use_kernel_format field at the end. The wrapper copies only ++ * the old fields and leaves use_kernel_format=0 (old apps use userspace ++ * vdoformat). ++ * ++ * dm_vdo_validate_target_params does not need versioning as it never ++ * accesses the use_kernel_format field. ++ */ ++ ++DM_EXPORT_SYMBOL(dm_tree_node_add_vdo_target, 1_02_213) ++int dm_tree_node_add_vdo_target_v1_02_213(struct dm_tree_node *node, ++ uint64_t size, ++ uint32_t vdo_version, ++ const char *vdo_pool_name, ++ const char *data_uuid, ++ uint64_t data_size, ++ const struct dm_vdo_target_params *vtp); ++int dm_tree_node_add_vdo_target_v1_02_213(struct dm_tree_node *node, ++ uint64_t size, ++ uint32_t vdo_version, ++ const char *vdo_pool_name, ++ const char *data_uuid, ++ uint64_t data_size, ++ const struct dm_vdo_target_params *vtp) ++{ ++ struct dm_vdo_target_params vtp_compat = { 0 }; ++ ++ /* Old callers pass struct without use_kernel_format at the end */ ++ memcpy(&vtp_compat, vtp, offsetof(struct dm_vdo_target_params, use_kernel_format)); ++ ++ return dm_tree_node_add_vdo_target(node, size, vdo_version, vdo_pool_name, ++ data_uuid, data_size, &vtp_compat); ++} + #endif +-- +2.54.0 + diff --git a/0013-make-generate.patch b/0013-make-generate.patch new file mode 100644 index 0000000..c8dfcd6 --- /dev/null +++ b/0013-make-generate.patch @@ -0,0 +1,44 @@ +From 60d971e910d8e1ffac7c4e5dafa9933ab0e3a767 Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 26 Apr 2026 16:29:42 +0200 +Subject: [PATCH 13/16] make: generate + +(cherry picked from commit 9ec3d17d61a0a7bb5063129bbbf4b3144234ccc4) +--- + conf/example.conf.in | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/conf/example.conf.in b/conf/example.conf.in +index 9a74feda5..ef8ca256f 100644 +--- a/conf/example.conf.in ++++ b/conf/example.conf.in +@@ -703,6 +703,13 @@ allocation { + # This configuration option has an automatic default value. + # vdo_use_sparse_index = 0 + ++ # Configuration option allocation/vdo_use_kernel_format. ++ # Use kernel direct formatting for VDO volume. ++ # When enabled (1), uses kernel direct formatting (requires dm-vdo 9.2.0+). ++ # When disabled (0), uses traditional userspace vdoformat tool. ++ # This configuration option has an automatic default value. ++ # vdo_use_kernel_format = 1 ++ + # Configuration option allocation/vdo_index_memory_size_mb. + # Specifies the amount of index memory in MiB for VDO volume. + # The value must be at least 256MiB and at most 1TiB. +@@ -1383,10 +1390,10 @@ global { + # Configuration option global/vdo_disabled_features. + # Features to not use in the vdo driver. + # This can be helpful for testing, or to avoid using a feature that is +- # causing problems. Features include: online_rename, version4 ++ # causing problems. Features include: online_rename, version4, kernel_format + # + # Example +- # vdo_disabled_features = [ "online_rename", "version4" ] ++ # vdo_disabled_features = [ "online_rename", "version4", "kernel_format" ] + # + # This configuration option does not have a default value defined. + +-- +2.54.0 + diff --git a/0014-WHATS_NEW-update.patch b/0014-WHATS_NEW-update.patch new file mode 100644 index 0000000..e50e42d --- /dev/null +++ b/0014-WHATS_NEW-update.patch @@ -0,0 +1,38 @@ +From d3bc31dc85f8890ccb14101703b8da4484040a5b Mon Sep 17 00:00:00 2001 +From: Zdenek Kabelac +Date: Sun, 26 Apr 2026 11:36:45 +0200 +Subject: [PATCH 14/16] WHATS_NEW: update + +(cherry picked from commit 4785084a64bbe6426dbc4adc43a4e181280713ab) +--- + WHATS_NEW | 2 ++ + WHATS_NEW_DM | 4 ++++ + 2 files changed, 6 insertions(+) + +diff --git a/WHATS_NEW b/WHATS_NEW +index e53280e10..56c565d9c 100644 +--- a/WHATS_NEW ++++ b/WHATS_NEW +@@ -1,5 +1,7 @@ + Version 2.03.42 - + ================== ++ Support formatting vdo volumes with kernel vdo target (version >= 9.2). ++ Add vdo_format.c with pre-format logical size computation for kernel format. + Fix autoactivation on top of MD and loop devices if udev db records incomplete. + Make MD device ready state check in udev to be in sync with MD udev rules. + +diff --git a/WHATS_NEW_DM b/WHATS_NEW_DM +index b567591db..e58e4f86e 100644 +--- a/WHATS_NEW_DM ++++ b/WHATS_NEW_DM +@@ -1,3 +1,7 @@ ++Version 1.02.216 - ++=================== ++ Add use_kernel_format to dm_vdo_target_params. ++ + Version 1.02.215 - 15th May 2026 + ================================ + Fix ttree insert/lookup depth mismatch in libdm. +-- +2.54.0 + diff --git a/lvm2.spec b/lvm2.spec index 09a519d..0c661c8 100644 --- a/lvm2.spec +++ b/lvm2.spec @@ -49,6 +49,18 @@ Patch2: 0002-lvmlockd-fix-adopt-failure-when-gl-is-not-found.patch Patch3: 0003-udev-check-MD-array_state-value-instead-of-just-file.patch Patch4: 0004-udev-MD-and-loop-fallback-handling-for-synthetic-ADD.patch Patch5: 0005-WHATS_NEW-update.patch +# RHEL-188186: +Patch6: 0006-fix-lvmpersist-WEAR-EAAR-reserve-race-when-multiple-.patch +# Minor fixes +Patch7: 0007-man-lvmlockd-fix-invalid-vgchange-setpersist-setlock.patch +Patch8: 0008-lvmpersist-settle-udev-between-register-and-preempt-.patch +Patch9: 0009-man-lvmlockd-improve-setlockargs-description.patch +# RHEL-164001: +Patch10: 0010-vdo-add-pre-format-pool-geometry-computation.patch +Patch11: 0011-vdo-add-support-for-VDO-kernel-direct-formatting.patch +Patch12: 0012-vdo-add-kernel-formatting-path-with-LV_VDOFORMAT-met.patch +Patch13: 0013-make-generate.patch +Patch14: 0014-WHATS_NEW-update.patch BuildRequires: make BuildRequires: gcc @@ -660,6 +672,10 @@ An extensive functional testsuite for LVM2. %endif %changelog +* Thu Jul 02 2026 Marian Csontos - 2.03.41-3 +- Fix concurrent PR start. +- Use kernel vdoformat if present. + * Tue Jun 02 2026 Marian Csontos - 2.03.41-2 - Fix MD handling in udev rules.