Blame SOURCES/lvm2-2_03_12-thin-improve-16g-support-for-thin-pool-metadata.patch

abb29f
 conf/example.conf.in             |  7 +++
abb29f
 device_mapper/all.h              | 16 +++++--
abb29f
 device_mapper/libdm-deptree.c    | 39 ++++++++++++-----
abb29f
 lib/activate/dev_manager.c       |  8 ++--
abb29f
 lib/config/config_settings.h     |  5 +++
abb29f
 lib/config/defaults.h            |  2 +
abb29f
 lib/format_text/flags.c          |  1 +
abb29f
 lib/metadata/lv_manip.c          | 31 ++++++++++++++
abb29f
 lib/metadata/merge.c             |  2 +
abb29f
 lib/metadata/metadata-exported.h | 11 +++++
abb29f
 lib/metadata/metadata.h          | 13 ++++++
abb29f
 lib/metadata/pool_manip.c        | 46 ++++++++++++++++++++
abb29f
 lib/metadata/thin_manip.c        | 92 ++++++++++++++++++++++++++--------------
abb29f
 lib/thin/thin.c                  | 22 +++++++---
abb29f
 man/lvmthin.7_main               | 10 ++++-
abb29f
 tools/lvconvert.c                |  4 ++
abb29f
 tools/lvcreate.c                 |  2 +
abb29f
 17 files changed, 256 insertions(+), 55 deletions(-)
abb29f
abb29f
diff --git a/conf/example.conf.in b/conf/example.conf.in
abb29f
index d149ed9..107a071 100644
abb29f
--- a/conf/example.conf.in
abb29f
+++ b/conf/example.conf.in
abb29f
@@ -494,6 +494,13 @@ allocation {
abb29f
 	# This configuration option has an automatic default value.
abb29f
 	# thin_pool_metadata_require_separate_pvs = 0
abb29f
 
abb29f
+	# Configuration option allocation/thin_pool_crop_metadata.
abb29f
+	# Older version of lvm2 cropped pool's metadata size to 15.81 GiB.
abb29f
+	# This is slightly less then the actual maximum 15.88 GiB.
abb29f
+	# For compatibility with older version and use of cropped size set to 1.
abb29f
+	# This configuration option has an automatic default value.
abb29f
+	# thin_pool_crop_metadata = 0
abb29f
+
abb29f
 	# Configuration option allocation/thin_pool_zero.
abb29f
 	# Thin pool data chunks are zeroed before they are first used.
abb29f
 	# Zeroing with a larger thin pool chunk size reduces performance.
abb29f
diff --git a/device_mapper/all.h b/device_mapper/all.h
abb29f
index 1080d25..489ca1c 100644
abb29f
--- a/device_mapper/all.h
abb29f
+++ b/device_mapper/all.h
abb29f
@@ -1072,10 +1072,10 @@ int dm_tree_node_add_replicator_dev_target(struct dm_tree_node *node,
abb29f
 #define DM_THIN_MIN_DATA_BLOCK_SIZE (UINT32_C(128))
abb29f
 #define DM_THIN_MAX_DATA_BLOCK_SIZE (UINT32_C(2097152))
abb29f
 /*
abb29f
- * Max supported size for thin pool  metadata device (17112760320 bytes)
abb29f
- * Limitation is hardcoded into the kernel and bigger device size
abb29f
- * is not accepted.
abb29f
+ * Max supported size for thin pool metadata device (17045913600 bytes)
abb29f
  * drivers/md/dm-thin-metadata.h THIN_METADATA_MAX_SECTORS
abb29f
+ * But here DM_THIN_MAX_METADATA_SIZE got defined incorrectly
abb29f
+ * Correct size is (UINT64_C(255) * ((1 << 14) - 64) * (4096 / (1 << 9)))
abb29f
  */
abb29f
 #define DM_THIN_MAX_METADATA_SIZE   (UINT64_C(255) * (1 << 14) * (4096 / (1 << 9)) - 256 * 1024)
abb29f
 
abb29f
@@ -1088,6 +1088,16 @@ int dm_tree_node_add_thin_pool_target(struct dm_tree_node *node,
abb29f
 				      uint64_t low_water_mark,
abb29f
 				      unsigned skip_block_zeroing);
abb29f
 
abb29f
+int dm_tree_node_add_thin_pool_target_v1(struct dm_tree_node *node,
abb29f
+					 uint64_t size,
abb29f
+					 uint64_t transaction_id,
abb29f
+					 const char *metadata_uuid,
abb29f
+					 const char *pool_uuid,
abb29f
+					 uint32_t data_block_size,
abb29f
+					 uint64_t low_water_mark,
abb29f
+					 unsigned skip_block_zeroing,
abb29f
+					 unsigned crop_metadata);
abb29f
+
abb29f
 /* Supported messages for thin provision target */
abb29f
 typedef enum {
abb29f
 	DM_THIN_MESSAGE_CREATE_SNAP,		/* device_id, origin_id */
abb29f
diff --git a/device_mapper/libdm-deptree.c b/device_mapper/libdm-deptree.c
abb29f
index 6ce956f..5b60dc9 100644
abb29f
--- a/device_mapper/libdm-deptree.c
abb29f
+++ b/device_mapper/libdm-deptree.c
abb29f
@@ -3979,6 +3979,24 @@ int dm_tree_node_add_thin_pool_target(struct dm_tree_node *node,
abb29f
 				      uint64_t low_water_mark,
abb29f
 				      unsigned skip_block_zeroing)
abb29f
 {
abb29f
+	return dm_tree_node_add_thin_pool_target_v1(node, size, transaction_id,
abb29f
+						    metadata_uuid, pool_uuid,
abb29f
+						    data_block_size,
abb29f
+						    low_water_mark,
abb29f
+						    skip_block_zeroing,
abb29f
+						    1);
abb29f
+}
abb29f
+
abb29f
+int dm_tree_node_add_thin_pool_target_v1(struct dm_tree_node *node,
abb29f
+					 uint64_t size,
abb29f
+					 uint64_t transaction_id,
abb29f
+					 const char *metadata_uuid,
abb29f
+					 const char *pool_uuid,
abb29f
+					 uint32_t data_block_size,
abb29f
+					 uint64_t low_water_mark,
abb29f
+					 unsigned skip_block_zeroing,
abb29f
+					 unsigned crop_metadata)
abb29f
+{
abb29f
 	struct load_segment *seg, *mseg;
abb29f
 	uint64_t devsize = 0;
abb29f
 
abb29f
@@ -4005,17 +4023,18 @@ int dm_tree_node_add_thin_pool_target(struct dm_tree_node *node,
abb29f
 	if (!_link_tree_nodes(node, seg->metadata))
abb29f
 		return_0;
abb29f
 
abb29f
-	/* FIXME: more complex target may need more tweaks */
abb29f
-	dm_list_iterate_items(mseg, &seg->metadata->props.segs) {
abb29f
-		devsize += mseg->size;
abb29f
-		if (devsize > DM_THIN_MAX_METADATA_SIZE) {
abb29f
-			log_debug_activation("Ignoring %" PRIu64 " of device.",
abb29f
-					     devsize - DM_THIN_MAX_METADATA_SIZE);
abb29f
-			mseg->size -= (devsize - DM_THIN_MAX_METADATA_SIZE);
abb29f
-			devsize = DM_THIN_MAX_METADATA_SIZE;
abb29f
-			/* FIXME: drop remaining segs */
abb29f
+	if (crop_metadata)
abb29f
+		/* FIXME: more complex target may need more tweaks */
abb29f
+		dm_list_iterate_items(mseg, &seg->metadata->props.segs) {
abb29f
+			devsize += mseg->size;
abb29f
+			if (devsize > DM_THIN_MAX_METADATA_SIZE) {
abb29f
+				log_debug_activation("Ignoring %" PRIu64 " of device.",
abb29f
+						     devsize - DM_THIN_MAX_METADATA_SIZE);
abb29f
+				mseg->size -= (devsize - DM_THIN_MAX_METADATA_SIZE);
abb29f
+				devsize = DM_THIN_MAX_METADATA_SIZE;
abb29f
+				/* FIXME: drop remaining segs */
abb29f
+			}
abb29f
 		}
abb29f
-	}
abb29f
 
abb29f
 	if (!(seg->pool = dm_tree_find_node_by_uuid(node->dtree, pool_uuid))) {
abb29f
 		log_error("Missing pool uuid %s.", pool_uuid);
abb29f
diff --git a/lib/activate/dev_manager.c b/lib/activate/dev_manager.c
abb29f
index 8d27bd3..9a25482 100644
abb29f
--- a/lib/activate/dev_manager.c
abb29f
+++ b/lib/activate/dev_manager.c
abb29f
@@ -261,7 +261,7 @@ static int _info_run(const char *dlid, struct dm_info *dminfo,
abb29f
 	int dmtask;
abb29f
 	int with_flush; /* TODO: arg for _info_run */
abb29f
 	void *target = NULL;
abb29f
-	uint64_t target_start, target_length, start, length;
abb29f
+	uint64_t target_start, target_length, start, length, length_crop = 0;
abb29f
 	char *target_name, *target_params;
abb29f
 	const char *devname;
abb29f
 
abb29f
@@ -297,7 +297,7 @@ static int _info_run(const char *dlid, struct dm_info *dminfo,
abb29f
 		/* Uses max DM_THIN_MAX_METADATA_SIZE sectors for metadata device */
abb29f
 		if (lv_is_thin_pool_metadata(seg_status->seg->lv) &&
abb29f
 		    (length > DM_THIN_MAX_METADATA_SIZE))
abb29f
-			length = DM_THIN_MAX_METADATA_SIZE;
abb29f
+			length_crop = DM_THIN_MAX_METADATA_SIZE;
abb29f
 
abb29f
 		/* Uses virtual size with headers for VDO pool device */
abb29f
 		if (lv_is_vdo_pool(seg_status->seg->lv))
abb29f
@@ -310,7 +310,9 @@ static int _info_run(const char *dlid, struct dm_info *dminfo,
abb29f
 			target = dm_get_next_target(dmt, target, &target_start,
abb29f
 						    &target_length, &target_name, &target_params);
abb29f
 
abb29f
-			if ((start == target_start) && (length == target_length))
abb29f
+			if ((start == target_start) &&
abb29f
+			    ((length == target_length) ||
abb29f
+			     (length_crop && (length_crop == target_length))))
abb29f
 				break; /* Keep target_params when matching segment is found */
abb29f
 
abb29f
 			target_params = NULL; /* Marking this target_params unusable */
abb29f
diff --git a/lib/config/config_settings.h b/lib/config/config_settings.h
abb29f
index 3c4032e..cb4e23a 100644
abb29f
--- a/lib/config/config_settings.h
abb29f
+++ b/lib/config/config_settings.h
abb29f
@@ -628,6 +628,11 @@ cfg(allocation_cache_pool_max_chunks_CFG, "cache_pool_max_chunks", allocation_CF
abb29f
 cfg(allocation_thin_pool_metadata_require_separate_pvs_CFG, "thin_pool_metadata_require_separate_pvs", allocation_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_BOOL, DEFAULT_THIN_POOL_METADATA_REQUIRE_SEPARATE_PVS, vsn(2, 2, 89), NULL, 0, NULL,
abb29f
 	"Thin pool metadata and data will always use different PVs.\n")
abb29f
 
abb29f
+cfg(allocation_thin_pool_crop_metadata_CFG, "thin_pool_crop_metadata", allocation_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_BOOL, DEFAULT_THIN_POOL_CROP_METADATA, vsn(2, 3, 12), NULL, 0, NULL,
abb29f
+	"Older version of lvm2 cropped pool's metadata size to 15.81 GiB.\n"
abb29f
+	"This is slightly less then the actual maximum 15.88 GiB.\n"
abb29f
+	"For compatibility with older version and use of cropped size set to 1.\n")
abb29f
+
abb29f
 cfg(allocation_thin_pool_zero_CFG, "thin_pool_zero", allocation_CFG_SECTION, CFG_PROFILABLE | CFG_PROFILABLE_METADATA | CFG_DEFAULT_COMMENTED, CFG_TYPE_BOOL, DEFAULT_THIN_POOL_ZERO, vsn(2, 2, 99), NULL, 0, NULL,
abb29f
 	"Thin pool data chunks are zeroed before they are first used.\n"
abb29f
 	"Zeroing with a larger thin pool chunk size reduces performance.\n")
abb29f
diff --git a/lib/config/defaults.h b/lib/config/defaults.h
abb29f
index 708a575..bcc20cc 100644
abb29f
--- a/lib/config/defaults.h
abb29f
+++ b/lib/config/defaults.h
abb29f
@@ -118,6 +118,8 @@
abb29f
 #define DEFAULT_THIN_REPAIR_OPTION1 ""
abb29f
 #define DEFAULT_THIN_REPAIR_OPTIONS_CONFIG "#S" DEFAULT_THIN_REPAIR_OPTION1
abb29f
 #define DEFAULT_THIN_POOL_METADATA_REQUIRE_SEPARATE_PVS 0
abb29f
+#define DEFAULT_THIN_POOL_CROP_METADATA 0
abb29f
+#define DEFAULT_THIN_POOL_MAX_METADATA_SIZE_V1_KB (UINT64_C(255) * ((1 << 14) - 64) * 4)  /* KB */ /* 0x3f8040 blocks */
abb29f
 #define DEFAULT_THIN_POOL_MAX_METADATA_SIZE (DM_THIN_MAX_METADATA_SIZE / 2)  /* KB */
abb29f
 #define DEFAULT_THIN_POOL_MIN_METADATA_SIZE 2048  /* KB */
abb29f
 #define DEFAULT_THIN_POOL_OPTIMAL_METADATA_SIZE (128 * 1024) /* KB */
abb29f
diff --git a/lib/format_text/flags.c b/lib/format_text/flags.c
abb29f
index bc93a5d..4cee14a 100644
abb29f
--- a/lib/format_text/flags.c
abb29f
+++ b/lib/format_text/flags.c
abb29f
@@ -72,6 +72,7 @@ static const struct flag _lv_flags[] = {
abb29f
 	{LV_ACTIVATION_SKIP, "ACTIVATION_SKIP", COMPATIBLE_FLAG},
abb29f
 	{LV_ERROR_WHEN_FULL, "ERROR_WHEN_FULL", COMPATIBLE_FLAG},
abb29f
 	{LV_METADATA_FORMAT, "METADATA_FORMAT", SEGTYPE_FLAG},
abb29f
+	{LV_CROP_METADATA, "CROP_METADATA", SEGTYPE_FLAG},
abb29f
 	{LV_CACHE_VOL, "CACHE_VOL", COMPATIBLE_FLAG},
abb29f
 	{LV_CACHE_USES_CACHEVOL, "CACHE_USES_CACHEVOL", SEGTYPE_FLAG},
abb29f
 	{LV_NOSCAN, NULL, 0},
abb29f
diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c
abb29f
index 443d32c..445c4ad 100644
abb29f
--- a/lib/metadata/lv_manip.c
abb29f
+++ b/lib/metadata/lv_manip.c
abb29f
@@ -5384,6 +5384,8 @@ static int _lvresize_adjust_extents(struct logical_volume *lv,
abb29f
 	uint32_t existing_extents;
abb29f
 	uint32_t seg_size = 0;
abb29f
 	uint32_t new_extents;
abb29f
+	uint64_t max_metadata_size;
abb29f
+	thin_crop_metadata_t crop;
abb29f
 	int reducing = 0;
abb29f
 
abb29f
 	seg_last = last_seg(lv);
abb29f
@@ -5544,6 +5546,33 @@ static int _lvresize_adjust_extents(struct logical_volume *lv,
abb29f
 					return 1;
abb29f
 				}
abb29f
 			}
abb29f
+		} else if (lv_is_thin_pool_metadata(lv)) {
abb29f
+			if (!(seg = get_only_segment_using_this_lv(lv)))
abb29f
+				return_0;
abb29f
+
abb29f
+			max_metadata_size = get_thin_pool_max_metadata_size(cmd, vg->profile, &crop;;
abb29f
+
abb29f
+			if (((uint64_t)lp->extents * vg->extent_size) > max_metadata_size) {
abb29f
+				lp->extents = (max_metadata_size + vg->extent_size - 1) / vg->extent_size;
abb29f
+				log_print_unless_silent("Reached maximum pool metadata size %s (%" PRIu32 " extents).",
abb29f
+							display_size(vg->cmd, max_metadata_size), lp->extents);
abb29f
+			}
abb29f
+
abb29f
+			if (existing_logical_extents >= lp->extents)
abb29f
+				lp->extents = existing_logical_extents;
abb29f
+
abb29f
+			crop = get_thin_pool_crop_metadata(cmd, crop, (uint64_t)lp->extents * vg->extent_size);
abb29f
+
abb29f
+			if (seg->crop_metadata != crop) {
abb29f
+				seg->crop_metadata = crop;
abb29f
+				seg->lv->status |= LV_CROP_METADATA;
abb29f
+				/* Crop change require reload even if there no size change */
abb29f
+				lp->size_changed = 1;
abb29f
+				log_print_unless_silent("Thin pool will use metadata without cropping.");
abb29f
+			}
abb29f
+
abb29f
+			if (!(seg_size = lp->extents - existing_logical_extents))
abb29f
+				return 1;  /* No change in metadata size */
abb29f
 		}
abb29f
 	} else {  /* If reducing, find stripes, stripesize & size of last segment */
abb29f
 		if (lp->stripes || lp->stripe_size || lp->mirrors)
abb29f
@@ -8388,6 +8417,8 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg,
abb29f
 		first_seg(lv)->chunk_size = lp->chunk_size;
abb29f
 		first_seg(lv)->zero_new_blocks = lp->zero_new_blocks;
abb29f
 		first_seg(lv)->discards = lp->discards;
abb29f
+		if ((first_seg(lv)->crop_metadata = lp->crop_metadata) == THIN_CROP_METADATA_NO)
abb29f
+			lv->status |= LV_CROP_METADATA;
abb29f
 		if (!recalculate_pool_chunk_size_with_dev_hints(lv, lp->thin_chunk_size_calc_policy)) {
abb29f
 			stack;
abb29f
 			goto revert_new_lv;
abb29f
diff --git a/lib/metadata/merge.c b/lib/metadata/merge.c
abb29f
index 0aa2293..eff59ae 100644
abb29f
--- a/lib/metadata/merge.c
abb29f
+++ b/lib/metadata/merge.c
abb29f
@@ -495,6 +495,8 @@ static void _check_lv_segment(struct logical_volume *lv, struct lv_segment *seg,
abb29f
 			seg_error("sets discards");
abb29f
 		if (!dm_list_empty(&seg->thin_messages))
abb29f
 			seg_error("sets thin_messages list");
abb29f
+		if (seg->lv->status & LV_CROP_METADATA)
abb29f
+			seg_error("sets CROP_METADATA flag");
abb29f
 	}
abb29f
 
abb29f
 	if (seg_is_thin_volume(seg)) {
abb29f
diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h
abb29f
index 54dc29f..0e57722 100644
abb29f
--- a/lib/metadata/metadata-exported.h
abb29f
+++ b/lib/metadata/metadata-exported.h
abb29f
@@ -143,6 +143,7 @@
abb29f
 
abb29f
 #define LV_REMOVE_AFTER_RESHAPE	UINT64_C(0x0400000000000000)	/* LV needs to be removed after a shrinking reshape */
abb29f
 #define LV_METADATA_FORMAT	UINT64_C(0x0800000000000000)    /* LV has segments with metadata format */
abb29f
+#define LV_CROP_METADATA	UINT64_C(0x0000000000000400)	/* LV - also VG CLUSTERED */
abb29f
 
abb29f
 #define LV_RESHAPE		UINT64_C(0x1000000000000000)    /* Ongoing reshape (number of stripes, stripesize or raid algorithm change):
abb29f
 								   used as SEGTYPE_FLAG to prevent activation on old runtime */
abb29f
@@ -326,6 +327,12 @@ typedef enum {
abb29f
 } thin_discards_t;
abb29f
 
abb29f
 typedef enum {
abb29f
+	THIN_CROP_METADATA_UNSELECTED = 0,  /* 'auto' selects */
abb29f
+	THIN_CROP_METADATA_NO,
abb29f
+	THIN_CROP_METADATA_YES,
abb29f
+} thin_crop_metadata_t;
abb29f
+
abb29f
+typedef enum {
abb29f
 	CACHE_MODE_UNSELECTED = 0,
abb29f
 	CACHE_MODE_WRITETHROUGH,
abb29f
 	CACHE_MODE_WRITEBACK,
abb29f
@@ -502,6 +509,7 @@ struct lv_segment {
abb29f
 	uint64_t transaction_id;		/* For thin_pool, thin */
abb29f
 	thin_zero_t zero_new_blocks;		/* For thin_pool */
abb29f
 	thin_discards_t discards;		/* For thin_pool */
abb29f
+	thin_crop_metadata_t crop_metadata;	/* For thin_pool */
abb29f
 	struct dm_list thin_messages;		/* For thin_pool */
abb29f
 	struct logical_volume *external_lv;	/* For thin */
abb29f
 	struct logical_volume *pool_lv;		/* For thin, cache */
abb29f
@@ -885,6 +893,8 @@ int update_thin_pool_params(struct cmd_context *cmd,
abb29f
 			    unsigned attr,
abb29f
 			    uint32_t pool_data_extents,
abb29f
 			    uint32_t *pool_metadata_extents,
abb29f
+			    struct logical_volume *metadata_lv,
abb29f
+			    unsigned *crop_metadata,
abb29f
 			    int *chunk_size_calc_method, uint32_t *chunk_size,
abb29f
 			    thin_discards_t *discards, thin_zero_t *zero_new_blocks);
abb29f
 
abb29f
@@ -1011,6 +1021,7 @@ struct lvcreate_params {
abb29f
 
abb29f
 	uint64_t permission; /* all */
abb29f
 	unsigned error_when_full; /* when segment supports it */
abb29f
+	thin_crop_metadata_t crop_metadata;
abb29f
 	uint32_t read_ahead; /* all */
abb29f
 	int approx_alloc;     /* all */
abb29f
 	alloc_policy_t alloc; /* all */
abb29f
diff --git a/lib/metadata/metadata.h b/lib/metadata/metadata.h
abb29f
index 2c22450..0f230e4 100644
abb29f
--- a/lib/metadata/metadata.h
abb29f
+++ b/lib/metadata/metadata.h
abb29f
@@ -512,8 +512,21 @@ int pool_below_threshold(const struct lv_segment *pool_seg);
abb29f
 int pool_check_overprovisioning(const struct logical_volume *lv);
abb29f
 int create_pool(struct logical_volume *pool_lv, const struct segment_type *segtype,
abb29f
 		struct alloc_handle *ah, uint32_t stripes, uint32_t stripe_size);
abb29f
+uint64_t get_thin_pool_max_metadata_size(struct cmd_context *cmd, struct profile *profile,
abb29f
+					 thin_crop_metadata_t *crop);
abb29f
+thin_crop_metadata_t get_thin_pool_crop_metadata(struct cmd_context *cmd,
abb29f
+						  thin_crop_metadata_t crop,
abb29f
+						  uint64_t metadata_size);
abb29f
 uint64_t estimate_thin_pool_metadata_size(uint32_t data_extents, uint32_t extent_size, uint32_t chunk_size);
abb29f
 
abb29f
+int update_pool_metadata_min_max(struct cmd_context *cmd,
abb29f
+				 uint32_t extent_size,
abb29f
+				 uint64_t min_metadata_size,		/* required min */
abb29f
+				 uint64_t max_metadata_size,		/* writable max */
abb29f
+				 uint64_t *metadata_size,		/* current calculated */
abb29f
+				 struct logical_volume *metadata_lv,	/* name of converted LV or NULL */
abb29f
+				 uint32_t *metadata_extents);		/* resulting extent count */
abb29f
+
abb29f
 /*
abb29f
  * Begin skeleton for external LVM library
abb29f
  */
abb29f
diff --git a/lib/metadata/pool_manip.c b/lib/metadata/pool_manip.c
abb29f
index a9dc611..b67882e 100644
abb29f
--- a/lib/metadata/pool_manip.c
abb29f
+++ b/lib/metadata/pool_manip.c
abb29f
@@ -742,6 +742,52 @@ int handle_pool_metadata_spare(struct volume_group *vg, uint32_t extents,
abb29f
 	return 1;
abb29f
 }
abb29f
 
abb29f
+int update_pool_metadata_min_max(struct cmd_context *cmd,
abb29f
+				 uint32_t extent_size,
abb29f
+				 uint64_t min_metadata_size,		/* required min */
abb29f
+				 uint64_t max_metadata_size,		/* writable max */
abb29f
+				 uint64_t *metadata_size,		/* current calculated */
abb29f
+				 struct logical_volume *metadata_lv,	/* name of converted LV or NULL */
abb29f
+				 uint32_t *metadata_extents)		/* resulting extent count */
abb29f
+{
abb29f
+	max_metadata_size = dm_round_up(max_metadata_size, extent_size);
abb29f
+	min_metadata_size = dm_round_up(min_metadata_size, extent_size);
abb29f
+
abb29f
+	if (*metadata_size > max_metadata_size) {
abb29f
+		if (metadata_lv) {
abb29f
+			log_print_unless_silent("Size %s of pool metadata volume %s is bigger then maximum usable size %s.",
abb29f
+						display_size(cmd, *metadata_size),
abb29f
+						display_lvname(metadata_lv),
abb29f
+						display_size(cmd, max_metadata_size));
abb29f
+		} else {
abb29f
+			if (*metadata_extents)
abb29f
+				log_print_unless_silent("Reducing pool metadata size %s to maximum usable size %s.",
abb29f
+							display_size(cmd, *metadata_size),
abb29f
+							display_size(cmd, max_metadata_size));
abb29f
+			*metadata_size = max_metadata_size;
abb29f
+		}
abb29f
+	} else if (*metadata_size < min_metadata_size) {
abb29f
+		if (metadata_lv) {
abb29f
+			log_error("Can't use volume %s with size %s as pool metadata. Minimal required size is %s.",
abb29f
+				  display_lvname(metadata_lv),
abb29f
+				  display_size(cmd, *metadata_size),
abb29f
+				  display_size(cmd, min_metadata_size));
abb29f
+			return 0;
abb29f
+		} else {
abb29f
+			if (*metadata_extents)
abb29f
+				log_print_unless_silent("Extending pool metadata size %s to required minimal size %s.",
abb29f
+							display_size(cmd, *metadata_size),
abb29f
+							display_size(cmd, min_metadata_size));
abb29f
+			*metadata_size = min_metadata_size;
abb29f
+		}
abb29f
+	}
abb29f
+
abb29f
+	if (!(*metadata_extents = extents_from_size(cmd, *metadata_size, extent_size)))
abb29f
+		return_0;
abb29f
+
abb29f
+	return 1;
abb29f
+}
abb29f
+
abb29f
 int vg_set_pool_metadata_spare(struct logical_volume *lv)
abb29f
 {
abb29f
 	char new_name[NAME_LEN];
abb29f
diff --git a/lib/metadata/thin_manip.c b/lib/metadata/thin_manip.c
abb29f
index 4591dd7..451c382 100644
abb29f
--- a/lib/metadata/thin_manip.c
abb29f
+++ b/lib/metadata/thin_manip.c
abb29f
@@ -610,9 +610,9 @@ static uint64_t _estimate_metadata_size(uint32_t data_extents, uint32_t extent_s
abb29f
 }
abb29f
 
abb29f
 /* Estimate maximal supportable thin pool data size for given chunk_size */
abb29f
-static uint64_t _estimate_max_data_size(uint32_t chunk_size)
abb29f
+static uint64_t _estimate_max_data_size(uint64_t max_metadata_size, uint32_t chunk_size)
abb29f
 {
abb29f
-	return  chunk_size * (DEFAULT_THIN_POOL_MAX_METADATA_SIZE * 2) * SECTOR_SIZE / UINT64_C(64);
abb29f
+	return  max_metadata_size * chunk_size * SECTOR_SIZE / UINT64_C(64);
abb29f
 }
abb29f
 
abb29f
 /* Estimate thin pool chunk size from data and metadata size (in sector units) */
abb29f
@@ -662,6 +662,38 @@ int get_default_allocation_thin_pool_chunk_size(struct cmd_context *cmd, struct
abb29f
 	return 1;
abb29f
 }
abb29f
 
abb29f
+/* Return max supported metadata size with selected cropping */
abb29f
+uint64_t get_thin_pool_max_metadata_size(struct cmd_context *cmd, struct profile *profile,
abb29f
+					 thin_crop_metadata_t *crop)
abb29f
+{
abb29f
+	*crop = find_config_tree_bool(cmd, allocation_thin_pool_crop_metadata_CFG, profile) ?
abb29f
+		THIN_CROP_METADATA_YES : THIN_CROP_METADATA_NO;
abb29f
+
abb29f
+	return (*crop == THIN_CROP_METADATA_NO) ?
abb29f
+		(2 * DEFAULT_THIN_POOL_MAX_METADATA_SIZE_V1_KB) : (2 * DEFAULT_THIN_POOL_MAX_METADATA_SIZE);
abb29f
+}
abb29f
+
abb29f
+/*
abb29f
+ * With existing crop method, check if the metadata_size would need cropping.
abb29f
+ * If not, set UNSELECTED, otherwise print some verbose info about selected cropping
abb29f
+ */
abb29f
+thin_crop_metadata_t get_thin_pool_crop_metadata(struct cmd_context *cmd,
abb29f
+						  thin_crop_metadata_t crop,
abb29f
+						  uint64_t metadata_size)
abb29f
+{
abb29f
+	const uint64_t crop_size = (2 * DEFAULT_THIN_POOL_MAX_METADATA_SIZE);
abb29f
+
abb29f
+	if (metadata_size > crop_size) {
abb29f
+		if (crop == THIN_CROP_METADATA_NO)
abb29f
+			log_verbose("Using metadata size without cropping.");
abb29f
+		else
abb29f
+			log_verbose("Cropping metadata size to %s.", display_size(cmd, crop_size));
abb29f
+	} else
abb29f
+		crop = THIN_CROP_METADATA_UNSELECTED;
abb29f
+
abb29f
+	return crop;
abb29f
+}
abb29f
+
abb29f
 int update_thin_pool_params(struct cmd_context *cmd,
abb29f
 			    struct profile *profile,
abb29f
 			    uint32_t extent_size,
abb29f
@@ -669,10 +701,13 @@ int update_thin_pool_params(struct cmd_context *cmd,
abb29f
 			    unsigned attr,
abb29f
 			    uint32_t pool_data_extents,
abb29f
 			    uint32_t *pool_metadata_extents,
abb29f
+			    struct logical_volume *metadata_lv,
abb29f
+			    thin_crop_metadata_t *crop_metadata,
abb29f
 			    int *chunk_size_calc_method, uint32_t *chunk_size,
abb29f
 			    thin_discards_t *discards, thin_zero_t *zero_new_blocks)
abb29f
 {
abb29f
-	uint64_t pool_metadata_size = (uint64_t) *pool_metadata_extents * extent_size;
abb29f
+	uint64_t pool_metadata_size;
abb29f
+	uint64_t max_metadata_size;
abb29f
 	uint32_t estimate_chunk_size;
abb29f
 	uint64_t max_pool_data_size;
abb29f
 	const char *str;
abb29f
@@ -702,7 +737,9 @@ int update_thin_pool_params(struct cmd_context *cmd,
abb29f
 		*zero_new_blocks = find_config_tree_bool(cmd, allocation_thin_pool_zero_CFG, profile)
abb29f
 			? THIN_ZERO_YES : THIN_ZERO_NO;
abb29f
 
abb29f
-	if (!pool_metadata_size) {
abb29f
+	max_metadata_size = get_thin_pool_max_metadata_size(cmd, profile, crop_metadata);
abb29f
+
abb29f
+	if (!*pool_metadata_extents) {
abb29f
 		if (!*chunk_size) {
abb29f
 			if (!get_default_allocation_thin_pool_chunk_size(cmd, profile,
abb29f
 									 chunk_size,
abb29f
@@ -723,20 +760,20 @@ int update_thin_pool_params(struct cmd_context *cmd,
abb29f
 		} else {
abb29f
 			pool_metadata_size = _estimate_metadata_size(pool_data_extents, extent_size, *chunk_size);
abb29f
 
abb29f
-			if (pool_metadata_size > (DEFAULT_THIN_POOL_MAX_METADATA_SIZE * 2)) {
abb29f
+			if (pool_metadata_size > max_metadata_size) {
abb29f
 				/* Suggest bigger chunk size */
abb29f
 				estimate_chunk_size =
abb29f
 					_estimate_chunk_size(pool_data_extents, extent_size,
abb29f
-							     (DEFAULT_THIN_POOL_MAX_METADATA_SIZE * 2), attr);
abb29f
+							     max_metadata_size, attr);
abb29f
 				log_warn("WARNING: Chunk size is too small for pool, suggested minimum is %s.",
abb29f
 					 display_size(cmd, estimate_chunk_size));
abb29f
 			}
abb29f
 		}
abb29f
 
abb29f
 		/* Round up to extent size silently */
abb29f
-		if (pool_metadata_size % extent_size)
abb29f
-			pool_metadata_size += extent_size - pool_metadata_size % extent_size;
abb29f
+		pool_metadata_size = dm_round_up(pool_metadata_size, extent_size);
abb29f
 	} else {
abb29f
+		pool_metadata_size = (uint64_t) *pool_metadata_extents * extent_size;
abb29f
 		estimate_chunk_size = _estimate_chunk_size(pool_data_extents, extent_size,
abb29f
 							   pool_metadata_size, attr);
abb29f
 
abb29f
@@ -751,7 +788,19 @@ int update_thin_pool_params(struct cmd_context *cmd,
abb29f
 		}
abb29f
 	}
abb29f
 
abb29f
-	max_pool_data_size = _estimate_max_data_size(*chunk_size);
abb29f
+	/* Use not rounded max for data size */
abb29f
+	max_pool_data_size = _estimate_max_data_size(max_metadata_size, *chunk_size);
abb29f
+
abb29f
+	if (!update_pool_metadata_min_max(cmd, extent_size,
abb29f
+					  2 * DEFAULT_THIN_POOL_MIN_METADATA_SIZE,
abb29f
+					  max_metadata_size,
abb29f
+					  &pool_metadata_size,
abb29f
+					  metadata_lv,
abb29f
+					  pool_metadata_extents))
abb29f
+		return_0;
abb29f
+
abb29f
+	*crop_metadata = get_thin_pool_crop_metadata(cmd, *crop_metadata, pool_metadata_size);
abb29f
+
abb29f
 	if ((max_pool_data_size / extent_size) < pool_data_extents) {
abb29f
 		log_error("Selected chunk size %s cannot address more then %s of thin pool data space.",
abb29f
 			  display_size(cmd, *chunk_size), display_size(cmd, max_pool_data_size));
abb29f
@@ -764,22 +813,6 @@ int update_thin_pool_params(struct cmd_context *cmd,
abb29f
 	if (!validate_thin_pool_chunk_size(cmd, *chunk_size))
abb29f
 		return_0;
abb29f
 
abb29f
-	if (pool_metadata_size > (2 * DEFAULT_THIN_POOL_MAX_METADATA_SIZE)) {
abb29f
-		pool_metadata_size = 2 * DEFAULT_THIN_POOL_MAX_METADATA_SIZE;
abb29f
-		if (*pool_metadata_extents)
abb29f
-			log_warn("WARNING: Maximum supported pool metadata size is %s.",
abb29f
-				 display_size(cmd, pool_metadata_size));
abb29f
-	} else if (pool_metadata_size < (2 * DEFAULT_THIN_POOL_MIN_METADATA_SIZE)) {
abb29f
-		pool_metadata_size = 2 * DEFAULT_THIN_POOL_MIN_METADATA_SIZE;
abb29f
-		if (*pool_metadata_extents)
abb29f
-			log_warn("WARNING: Minimum supported pool metadata size is %s.",
abb29f
-				 display_size(cmd, pool_metadata_size));
abb29f
-	}
abb29f
-
abb29f
-	if (!(*pool_metadata_extents =
abb29f
-	      extents_from_size(cmd, pool_metadata_size, extent_size)))
abb29f
-		return_0;
abb29f
-
abb29f
 	if ((uint64_t) *chunk_size > (uint64_t) pool_data_extents * extent_size) {
abb29f
 		log_error("Size of %s data volume cannot be smaller than chunk size %s.",
abb29f
 			  segtype->name, display_size(cmd, *chunk_size));
abb29f
@@ -958,12 +991,5 @@ int validate_thin_pool_chunk_size(struct cmd_context *cmd, uint32_t chunk_size)
abb29f
 
abb29f
 uint64_t estimate_thin_pool_metadata_size(uint32_t data_extents, uint32_t extent_size, uint32_t chunk_size)
abb29f
 {
abb29f
-	uint64_t sz = _estimate_metadata_size(data_extents, extent_size, chunk_size);
abb29f
-
abb29f
-	if (sz > (2 * DEFAULT_THIN_POOL_MAX_METADATA_SIZE))
abb29f
-		sz = 2 * DEFAULT_THIN_POOL_MAX_METADATA_SIZE;
abb29f
-	else if (sz < (2 * DEFAULT_THIN_POOL_MIN_METADATA_SIZE))
abb29f
-		sz = 2 * DEFAULT_THIN_POOL_MIN_METADATA_SIZE;
abb29f
-
abb29f
-	return sz;
abb29f
+	return _estimate_metadata_size(data_extents, extent_size, chunk_size);
abb29f
 }
abb29f
diff --git a/lib/thin/thin.c b/lib/thin/thin.c
abb29f
index ba0da71..51bc269 100644
abb29f
--- a/lib/thin/thin.c
abb29f
+++ b/lib/thin/thin.c
abb29f
@@ -86,6 +86,7 @@ static int _thin_pool_text_import(struct lv_segment *seg,
abb29f
 	struct logical_volume *pool_data_lv, *pool_metadata_lv;
abb29f
 	const char *discards_str = NULL;
abb29f
 	uint32_t zero = 0;
abb29f
+	uint32_t crop = 0;
abb29f
 
abb29f
 	if (!dm_config_get_str(sn, "metadata", &lv_name))
abb29f
 		return SEG_LOG_ERROR("Metadata must be a string in");
abb29f
@@ -131,6 +132,13 @@ static int _thin_pool_text_import(struct lv_segment *seg,
abb29f
 
abb29f
 	seg->zero_new_blocks = (zero) ? THIN_ZERO_YES : THIN_ZERO_NO;
abb29f
 
abb29f
+	if (dm_config_has_node(sn, "crop_metadata")) {
abb29f
+		if (!dm_config_get_uint32(sn, "crop_metadata", &crop))
abb29f
+			return SEG_LOG_ERROR("Could not read crop_metadata for");
abb29f
+		seg->crop_metadata = (crop) ? THIN_CROP_METADATA_YES : THIN_CROP_METADATA_NO;
abb29f
+		seg->lv->status |= LV_CROP_METADATA;
abb29f
+	}
abb29f
+
abb29f
 	/* Read messages */
abb29f
 	for (; sn; sn = sn->sib)
abb29f
 		if (!(sn->v) && !_thin_pool_add_message(seg, sn->key, sn->child))
abb29f
@@ -177,6 +185,9 @@ static int _thin_pool_text_export(const struct lv_segment *seg, struct formatter
abb29f
 		return 0;
abb29f
 	}
abb29f
 
abb29f
+	if (seg->crop_metadata != THIN_CROP_METADATA_UNSELECTED)
abb29f
+		outf(f, "crop_metadata = %u", (seg->crop_metadata == THIN_CROP_METADATA_YES) ? 1 : 0);
abb29f
+
abb29f
 	dm_list_iterate_items(tmsg, &seg->thin_messages) {
abb29f
 		/* Extra validation */
abb29f
 		switch (tmsg->type) {
abb29f
@@ -307,11 +318,12 @@ static int _thin_pool_add_target_line(struct dev_manager *dm,
abb29f
 	else
abb29f
 		low_water_mark = 0;
abb29f
 
abb29f
-	if (!dm_tree_node_add_thin_pool_target(node, len,
abb29f
-					       seg->transaction_id,
abb29f
-					       metadata_dlid, pool_dlid,
abb29f
-					       seg->chunk_size, low_water_mark,
abb29f
-					       (seg->zero_new_blocks == THIN_ZERO_YES) ? 0 : 1))
abb29f
+	if (!dm_tree_node_add_thin_pool_target_v1(node, len,
abb29f
+						  seg->transaction_id,
abb29f
+						  metadata_dlid, pool_dlid,
abb29f
+						  seg->chunk_size, low_water_mark,
abb29f
+						  (seg->zero_new_blocks == THIN_ZERO_YES) ? 0 : 1,
abb29f
+						  (seg->crop_metadata == THIN_CROP_METADATA_YES) ? 1 : 0))
abb29f
 		return_0;
abb29f
 
abb29f
 	if (attr & THIN_FEATURE_DISCARDS) {
abb29f
diff --git a/man/lvmthin.7_main b/man/lvmthin.7_main
abb29f
index e6f1d63..3ce34a5 100644
abb29f
--- a/man/lvmthin.7_main
abb29f
+++ b/man/lvmthin.7_main
abb29f
@@ -1104,7 +1104,7 @@ The default value is shown by:
abb29f
 The amount of thin metadata depends on how many blocks are shared between
abb29f
 thin LVs (i.e. through snapshots).  A thin pool with many snapshots may
abb29f
 need a larger metadata LV.  Thin pool metadata LV sizes can be from 2MiB
abb29f
-to 16GiB.
abb29f
+to approximately 16GiB.
abb29f
 
abb29f
 When using lvcreate to create what will become a thin metadata LV, the
abb29f
 size is specified with the -L|--size option.
abb29f
@@ -1119,6 +1119,14 @@ needed, so it is recommended to start with a size of 1GiB which should be
abb29f
 enough for all practical purposes.  A thin pool metadata LV can later be
abb29f
 manually or automatically extended if needed.
abb29f
 
abb29f
+Configurable setting
abb29f
+.BR lvm.conf (5)
abb29f
+.BR allocation / thin_pool_crop_metadata
abb29f
+gives control over cropping to 15.81GiB to stay backward compatible with older
abb29f
+versions of lvm2. With enabled cropping there can be observed some problems when
abb29f
+using volumes above this size with thin tools (i.e. thin_repair).
abb29f
+Cropping should be enabled only when compatibility is required.
abb29f
+
abb29f
 
abb29f
 .SS Create a thin snapshot of an external, read only LV
abb29f
 
abb29f
diff --git a/tools/lvconvert.c b/tools/lvconvert.c
abb29f
index 7b74afb..ce90279 100644
abb29f
--- a/tools/lvconvert.c
abb29f
+++ b/tools/lvconvert.c
abb29f
@@ -3032,6 +3032,7 @@ static int _lvconvert_to_pool(struct cmd_context *cmd,
abb29f
 	const char *policy_name;
abb29f
 	struct dm_config_tree *policy_settings = NULL;
abb29f
 	int pool_metadata_spare;
abb29f
+	thin_crop_metadata_t crop_metadata;
abb29f
 	thin_discards_t discards;
abb29f
 	thin_zero_t zero_new_blocks;
abb29f
 	int r = 0;
abb29f
@@ -3196,6 +3197,8 @@ static int _lvconvert_to_pool(struct cmd_context *cmd,
abb29f
 					     pool_segtype, target_attr,
abb29f
 					     lv->le_count,
abb29f
 					     &meta_extents,
abb29f
+					     metadata_lv,
abb29f
+					     &crop_metadata,
abb29f
 					     &chunk_calc,
abb29f
 					     &chunk_size,
abb29f
 					     &discards, &zero_new_blocks))
abb29f
@@ -3401,6 +3404,7 @@ static int _lvconvert_to_pool(struct cmd_context *cmd,
abb29f
 			goto_bad;
abb29f
 	} else {
abb29f
 		seg->transaction_id = 0;
abb29f
+		seg->crop_metadata = crop_metadata;
abb29f
 		seg->chunk_size = chunk_size;
abb29f
 		seg->discards = discards;
abb29f
 		seg->zero_new_blocks = zero_new_blocks;
abb29f
diff --git a/tools/lvcreate.c b/tools/lvcreate.c
abb29f
index e384291..1ee9e14 100644
abb29f
--- a/tools/lvcreate.c
abb29f
+++ b/tools/lvcreate.c
abb29f
@@ -391,6 +391,8 @@ static int _update_extents_params(struct volume_group *vg,
abb29f
 						     lp->segtype, lp->target_attr,
abb29f
 						     lp->extents,
abb29f
 						     &lp->pool_metadata_extents,
abb29f
+						     NULL,
abb29f
+						     &lp->crop_metadata,
abb29f
 						     &lp->thin_chunk_size_calc_policy,
abb29f
 						     &lp->chunk_size,
abb29f
 						     &lp->discards,