Blob Blame History Raw
 WHATS_NEW                             |   2 +
 lib/metadata/raid_manip.c             | 277 +++++++++++++++++++++++++++++++---
 test/shell/lvconvert-raid-takeover.sh |  53 ++++++-
 tools/lvconvert.c                     |  24 +--
 4 files changed, 314 insertions(+), 42 deletions(-)

diff --git a/WHATS_NEW b/WHATS_NEW
index 5cbf4ec..6a0c311 100644
--- a/WHATS_NEW
+++ b/WHATS_NEW
@@ -1,5 +1,7 @@
 Version 2.02.167 - 
 ======================================
+  Add direct striped -> raid4 conversion
+  Fix raid4 parity image pair position on conversions from striped/raid0*
   Disable lvconvert of thin pool to raid while active.
 
 Version 2.02.166 - 26th September 2016
diff --git a/lib/metadata/raid_manip.c b/lib/metadata/raid_manip.c
index 5fc520e..e5fd195 100644
--- a/lib/metadata/raid_manip.c
+++ b/lib/metadata/raid_manip.c
@@ -2459,7 +2459,7 @@ static struct lv_segment *_convert_striped_to_raid0(struct logical_volume *lv,
 					   0 /* chunk_size */,
 					   0 /* seg->region_size */, 0u /* extents_copied */ ,
 					   NULL /* pvmove_source_seg */))) {
-		log_error("Failed to allocate new raid0 segement for LV %s.", display_lvname(lv));
+		log_error("Failed to allocate new raid0 segment for LV %s.", display_lvname(lv));
 		return NULL;
 	}
 
@@ -2519,42 +2519,51 @@ static struct possible_takeover_reshape_type _possible_takeover_reshape_types[]
 	{ .current_types  = SEG_STRIPED_TARGET, /* linear, i.e. seg->area_count = 1 */
 	  .possible_types = SEG_RAID1,
 	  .current_areas = 1,
-	  .options = ALLOW_NONE },
+	  .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */
 	{ .current_types  = SEG_STRIPED_TARGET, /* linear, i.e. seg->area_count = 1 */
 	  .possible_types = SEG_RAID0|SEG_RAID0_META,
 	  .current_areas = 1,
 	  .options = ALLOW_STRIPE_SIZE },
-	{ .current_types  = SEG_STRIPED_TARGET, /* striped, i.e. seg->area_count > 1 */
+	{ .current_types  = SEG_STRIPED_TARGET, /* striped -> raid0*, i.e. seg->area_count > 1 */
 	  .possible_types = SEG_RAID0|SEG_RAID0_META,
 	  .current_areas = ~0U,
 	  .options = ALLOW_NONE },
+	{ .current_types  = SEG_STRIPED_TARGET, /* striped -> raid4 , i.e. seg->area_count > 1 */
+	  .possible_types = SEG_RAID4,
+	  .current_areas = ~0U,
+	  .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */
 	/* raid0* -> */
 	{ .current_types  = SEG_RAID0|SEG_RAID0_META, /* seg->area_count = 1 */
 	  .possible_types = SEG_RAID1,
 	  .current_areas = 1,
+	  .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */
+	{ .current_types  = SEG_RAID0|SEG_RAID0_META, /* raid0* -> striped, i.e. seg->area_count > 1 */
+	  .possible_types = SEG_STRIPED_TARGET,
+	  .current_areas = ~0U,
 	  .options = ALLOW_NONE },
-	{ .current_types  = SEG_RAID0|SEG_RAID0_META, /* seg->area_count > 1 */
-	  .possible_types = SEG_RAID4,
+	{ .current_types  = SEG_RAID0|SEG_RAID0_META, /* raid0* -> raid0*, i.e. seg->area_count > 1 */
+	  .possible_types = SEG_RAID0_META|SEG_RAID0,
 	  .current_areas = ~0U,
 	  .options = ALLOW_NONE },
-	{ .current_types  = SEG_RAID0|SEG_RAID0_META, /* raid0 striped, i.e. seg->area_count > 0 */
+	{ .current_types  = SEG_RAID0|SEG_RAID0_META, /* raid0* -> raid4, i.e. seg->area_count > 1 */
+	  .possible_types = SEG_RAID4,
+	  .current_areas = ~0U,
+	  .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */
+	/* raid4 -> -> */
+	{ .current_types  = SEG_RAID4, /* raid4 ->striped/raid0*, i.e. seg->area_count > 1 */
 	  .possible_types = SEG_STRIPED_TARGET|SEG_RAID0|SEG_RAID0_META,
 	  .current_areas = ~0U,
 	  .options = ALLOW_NONE },
-	/* raid1 -> */
+	/* raid1 -> mirror */
 	{ .current_types  = SEG_RAID1,
-	  .possible_types = SEG_RAID1|SEG_MIRROR,
+	  .possible_types = SEG_MIRROR,
 	  .current_areas = ~0U,
-	  .options = ALLOW_NONE },
+	  .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */
 	/* mirror -> raid1 with arbitrary number of legs */
 	{ .current_types  = SEG_MIRROR,
-	  .possible_types = SEG_MIRROR|SEG_RAID1,
-	  .current_areas = ~0U,
-	  .options = ALLOW_NONE },
-	{ .current_types  = SEG_RAID4,
-	  .possible_types = SEG_STRIPED_TARGET|SEG_RAID0|SEG_RAID0_META,
+	  .possible_types = SEG_RAID1,
 	  .current_areas = ~0U,
-	  .options = ALLOW_NONE },
+	  .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */
 
 	/* END */
 	{ .current_types  = 0 }
@@ -2861,9 +2870,176 @@ static int _raid1_to_mirrored_wrapper(TAKEOVER_FN_ARGS)
 					allocate_pvs, 1, &removal_lvs);
 }
 
+/*
+ * HM Helper: (raid0_meta -> raid4)
+ *
+ * To convert raid0_meta to raid4, which involves shifting the
+ * parity device to lv segment area 0 and thus changing MD
+ * array roles, detach the MetaLVs and reload as raid0 in
+ * order to wipe them then reattach and set back to raid0_meta.
+ */
+static int _clear_meta_lvs(struct logical_volume *lv)
+{
+	uint32_t s;
+	struct lv_segment *seg = first_seg(lv);
+	struct lv_segment_area *tmp_areas;
+	const struct segment_type *tmp_segtype;
+	struct dm_list meta_lvs;
+	struct lv_list *lvl_array, *lvl;
+
+	/* Reject non-raid0_meta segment types cautiously */
+	if (!seg_is_raid0_meta(seg) ||
+	    !seg->meta_areas)
+		return_0;
+
+	if (!(lvl_array = dm_pool_alloc(lv->vg->vgmem, seg->area_count * sizeof(*lvl_array))))
+		return_0;
+
+	dm_list_init(&meta_lvs);
+	tmp_areas = seg->meta_areas;
+
+	/* Extract all MetaLVs listing them on @meta_lvs */
+	log_debug_metadata("Extracting all MetaLVs of %s to activate as raid0",
+			   display_lvname(lv));
+	if (!_extract_image_component_sublist(seg, RAID_META, 0, seg->area_count, &meta_lvs, 0))
+		return_0;
+
+	/* Memorize meta areas and segtype to set again after initializing. */
+	seg->meta_areas = NULL;
+	tmp_segtype = seg->segtype;
+
+	if (!(seg->segtype = get_segtype_from_flag(lv->vg->cmd, SEG_RAID0)) ||
+	    !lv_update_and_reload(lv))
+		return_0;
+
+	/*
+	 * Now deactivate the MetaLVs before clearing, so
+	 * that _clear_lvs() will activate them visible.
+	 */
+	log_debug_metadata("Deactivating pulled out MetaLVs of %s before initializing.",
+			   display_lvname(lv));
+	dm_list_iterate_items(lvl, &meta_lvs)
+		if (!deactivate_lv(lv->vg->cmd, lvl->lv))
+			return_0;
+
+	log_debug_metadata("Clearing allocated raid0_meta metadata LVs for conversion to raid4");
+	if (!_clear_lvs(&meta_lvs)) {
+		log_error("Failed to initialize metadata LVs");
+		return 0;
+	}
+
+	/* Set memorized meta areas and raid0_meta segtype */
+	seg->meta_areas = tmp_areas;
+	seg->segtype = tmp_segtype;
+
+	log_debug_metadata("Adding metadata LVs back into %s", display_lvname(lv));
+	s = 0;
+	dm_list_iterate_items(lvl, &meta_lvs) {
+		lv_set_hidden(lvl->lv);
+		if (!set_lv_segment_area_lv(seg, s++, lvl->lv, 0, RAID_META))
+			return 0;
+	}
+
+	return 1;
+}
+
+/*
+ * HM Helper: (raid0* <-> raid4)
+ *
+ * Rename SubLVs (pairs) allowing to shift names w/o collisions with active ones.
+ */
+#define	SLV_COUNT	2
+static int _rename_area_lvs(struct logical_volume *lv, const char *suffix)
+{
+	uint32_t s;
+	size_t sz = strlen("rimage") + (suffix ? strlen(suffix) : 0) + 1;
+	char *sfx[SLV_COUNT] = { NULL, NULL };
+	struct lv_segment *seg = first_seg(lv);
+
+	/* Create _generate_raid_name() suffixes w/ or w/o passed in @suffix */
+	for (s = 0; s < SLV_COUNT; s++)
+		if (!(sfx[s] = dm_pool_alloc(lv->vg->cmd->mem, sz)) ||
+		    dm_snprintf(sfx[s], sz, suffix ? "%s%s" : "%s", s ? "rmeta" : "rimage", suffix) < 0)
+			return_0;
+
+	/* Change names (temporarily) to be able to shift numerical name suffixes */
+	for (s = 0; s < seg->area_count; s++) {
+		if (!(seg_lv(seg, s)->name = _generate_raid_name(lv, sfx[0], s)))
+			return_0;
+		if (seg->meta_areas &&
+		    !(seg_metalv(seg, s)->name = _generate_raid_name(lv, sfx[1], s)))
+			return_0;
+	}
+
+	for (s = 0; s < SLV_COUNT; s++)
+		dm_pool_free(lv->vg->cmd->mem, sfx[s]);
+
+	return 1;
+}
+
+/*
+ * HM Helper: (raid0* <-> raid4)
+ *
+ * Switch area LVs in lv segment @seg indexed by @s1 and @s2
+ */
+static void _switch_area_lvs(struct lv_segment *seg, uint32_t s1, uint32_t s2)
+{
+	struct logical_volume *lvt;
+
+	lvt = seg_lv(seg, s1);
+	seg_lv(seg, s1) = seg_lv(seg, s2);
+	seg_lv(seg, s2) = lvt;
+
+	/* Be cautious */
+	if (seg->meta_areas) {
+		lvt = seg_metalv(seg, s1);
+		seg_metalv(seg, s1) = seg_metalv(seg, s2);
+		seg_metalv(seg, s2) = lvt;
+	}
+}
+
+/*
+ * HM Helper:
+ *
+ * shift range of area LVs in @seg in range [ @s1, @s2 ] up if @s1 < @s2,
+ * else down  bubbling the parity SubLVs up/down whilst shifting.
+ */
+static void _shift_area_lvs(struct lv_segment *seg, uint32_t s1, uint32_t s2)
+{
+	uint32_t s;
+
+	if (s1 < s2)
+		/* Forward shift n+1 -> n */
+		for (s = s1; s < s2; s++)
+			_switch_area_lvs(seg, s, s + 1);
+	else
+		/* Reverse shift n-1 -> n */
+		for (s = s1; s > s2; s--)
+			_switch_area_lvs(seg, s, s - 1);
+}
+
+/*
+ * Switch position of first and last area lv within
+ * @lv to move parity SubLVs from end to end.
+ *
+ * Direction depends on segment type raid4 / raid0_meta.
+ */
+static int _shift_parity_dev(struct lv_segment *seg)
+{
+	if (seg_is_raid0_meta(seg))
+		_shift_area_lvs(seg, seg->area_count - 1, 0);
+	else if (seg_is_raid4(seg))
+		_shift_area_lvs(seg, 0, seg->area_count - 1);
+	else
+		return 0;
+
+	return 1;
+}
+
 /* raid45 -> raid0* / striped */
 static int _raid456_to_raid0_or_striped_wrapper(TAKEOVER_FN_ARGS)
 {
+	int rename_sublvs = 0;
 	struct lv_segment *seg = first_seg(lv);
 	struct dm_list removal_lvs;
 
@@ -2879,10 +3055,39 @@ static int _raid456_to_raid0_or_striped_wrapper(TAKEOVER_FN_ARGS)
 	if (!_raid_in_sync(lv))
 		return 0;
 
+	if (!yes && yes_no_prompt("Are you sure you want to convert \"%s\" LV %s to \"%s\" "
+				  "type using all resilience? [y/n]: ",
+				  lvseg_name(seg), display_lvname(lv), new_segtype->name) == 'n') {
+		log_error("Logical volume %s NOT converted to \"%s\"",
+			  display_lvname(lv), new_segtype->name);
+		return 0;
+	}
+	if (sigint_caught())
+		return_0;
+
 	/* Archive metadata */
 	if (!archive(lv->vg))
 		return_0;
 
+	/*
+	 * raid4 (which actually gets mapped to raid5/dedicated first parity disk)
+	 * needs shifting of SubLVs to move the parity SubLV pair in the first area
+	 * to the last one before conversion to raid0[_meta]/striped to allow for
+	 * SubLV removal from the end of the areas arrays.
+	 */
+	if (seg_is_raid4(seg)) {
+		/* Shift parity SubLV pair "PDD..." -> "DD...P" to be able to remove it off the end */
+		if (!_shift_parity_dev(seg))
+			return 0;
+
+		if (segtype_is_any_raid0(new_segtype) &&
+		    !(rename_sublvs = _rename_area_lvs(lv, "_"))) {
+			log_error("Failed to rename %s LV %s MetaLVs", lvseg_name(seg), display_lvname(lv));
+			return 0;
+		}
+
+	}
+
 	/* Remove meta and data LVs requested */
 	if (!_lv_raid_change_image_count(lv, new_image_count, allocate_pvs, &removal_lvs, 0, 0))
 		return 0;
@@ -2902,7 +3107,19 @@ static int _raid456_to_raid0_or_striped_wrapper(TAKEOVER_FN_ARGS)
 
 	seg->region_size = 0;
 
-	return _lv_update_reload_fns_reset_eliminate_lvs(lv, &removal_lvs);
+	if (!_lv_update_reload_fns_reset_eliminate_lvs(lv, &removal_lvs))
+		return_0;
+
+	if (rename_sublvs) {
+		if (!_rename_area_lvs(lv, NULL)) {
+			log_error("Failed to rename %s LV %s MetaLVs", lvseg_name(seg), display_lvname(lv));
+			return 0;
+		}
+		if (!lv_update_and_reload(lv))
+			return_0;
+	}
+
+	return 1;
 }
 
 static int _striped_to_raid0_wrapper(struct logical_volume *lv,
@@ -2930,6 +3147,9 @@ static int _striped_to_raid0_wrapper(struct logical_volume *lv,
 static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS)
 {
 	struct lv_segment *seg = first_seg(lv);
+	struct dm_list removal_lvs;
+
+	dm_list_init(&removal_lvs);
 
 	if (seg_is_raid10(seg))
 		return _takeover_unsupported_yet(lv, new_stripes, new_segtype);
@@ -2944,6 +3164,13 @@ static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS)
 		return 0;
 	}
 
+	/* FIXME: restricted to raid4 for the time being... */
+	if (!segtype_is_raid4(new_segtype)) {
+		/* Can't convert striped/raid0* to e.g. raid10_offset */
+		log_error("Can't convert %s to %s", display_lvname(lv), new_segtype->name);
+		return 0;
+	}
+
 	/* Archive metadata */
 	if (!archive(lv->vg))
 		return_0;
@@ -2961,7 +3188,10 @@ static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS)
 		log_debug_metadata("Adding metadata LVs to %s", display_lvname(lv));
 		if (!_raid0_add_or_remove_metadata_lvs(lv, 1 /* update_and_reload */, allocate_pvs, NULL))
 			return 0;
-	}
+	/* raid0_meta -> raid4 needs clearing of MetaLVs in order to avoid raid disk role cahnge issues in the kernel */
+	} else if (segtype_is_raid4(new_segtype) &&
+		   !_clear_meta_lvs(lv))
+		return 0;
 
 	/* Add the additional component LV pairs */
 	log_debug_metadata("Adding %" PRIu32 " component LV pair(s) to %s", new_image_count - lv_raid_image_count(lv),
@@ -2969,8 +3199,9 @@ static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS)
 	if (!_lv_raid_change_image_count(lv, new_image_count, allocate_pvs, NULL, 0, 1))
 		return 0;
 
-	if (!segtype_is_raid4(new_segtype)) {
-		/* Can't convert striped/raid0* to e.g. raid10_offset */
+	if (segtype_is_raid4(new_segtype) &&
+	    (!_shift_parity_dev(seg) ||
+	     !_rename_area_lvs(lv, "_"))) {
 		log_error("Can't convert %s to %s", display_lvname(lv), new_segtype->name);
 		return 0;
 	}
@@ -2987,6 +3218,14 @@ static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS)
 	if (!_lv_update_reload_fns_reset_eliminate_lvs(lv, NULL))
 		return_0;
 
+	if (segtype_is_raid4(new_segtype)) {
+		/* We had to rename SubLVs because of collision free sgifting, rename back... */
+		if (!_rename_area_lvs(lv, NULL))
+			return 0;
+		if (!lv_update_and_reload(lv))
+			return_0;
+	}
+
 	return 1;
 }
 
diff --git a/test/shell/lvconvert-raid-takeover.sh b/test/shell/lvconvert-raid-takeover.sh
index 19a65d3..0140e22 100644
--- a/test/shell/lvconvert-raid-takeover.sh
+++ b/test/shell/lvconvert-raid-takeover.sh
@@ -78,22 +78,58 @@ aux wait_for_sync $vg $lv1
 # Clean up
 lvremove --yes $vg/$lv1
 
-# Create 3-way raid0
-lvcreate -y -aey --type raid0 -i 3 -L 64M -n $lv1 $vg
-check lv_field $vg/$lv1 segtype "raid0"
+# Create 3-way striped
+lvcreate -y -aey --type striped -i 3 -L 64M -n $lv1 $vg
+check lv_field $vg/$lv1 segtype "striped"
 check lv_field $vg/$lv1 stripes 3
 echo y | mkfs -t ext4 /dev/mapper/$vg-$lv1
 fsck -fn  /dev/mapper/$vg-$lv1
 
-# Convert raid0 -> raid4
+# Create 3-way raid0
+lvcreate -y -aey --type raid0 -i 3 -L 64M -n $lv2 $vg
+check lv_field $vg/$lv2 segtype "raid0"
+check lv_field $vg/$lv2 stripes 3
+echo y | mkfs -t ext4 /dev/mapper/$vg-$lv2
+fsck -fn  /dev/mapper/$vg-$lv2
+
+# Create 3-way raid0_meta
+lvcreate -y -aey --type raid0_meta -i 3 -L 64M -n $lv3 $vg
+check lv_field $vg/$lv3 segtype "raid0_meta"
+check lv_field $vg/$lv3 stripes 3
+echo y | mkfs -t ext4 /dev/mapper/$vg-$lv3
+fsck -fn  /dev/mapper/$vg-$lv3
+
+# Create 3-way raid4
+lvcreate -y -aey --type raid4 -i 3 -L 64M -n $lv4 $vg
+check lv_field $vg/$lv4 segtype "raid4"
+check lv_field $vg/$lv4 stripes 4
+echo y | mkfs -t ext4 /dev/mapper/$vg-$lv4
+fsck -fn  /dev/mapper/$vg-$lv4
+aux wait_for_sync $vg $lv4
+fsck -fn  /dev/mapper/$vg-$lv4
+
+# Convert raid4 -> striped (correct raid4 mapping test!)
+lvconvert -y --ty striped $vg/$lv4
+check lv_field $vg/$lv4 segtype "striped"
+check lv_field $vg/$lv4 stripes 3
+fsck -fn  /dev/mapper/$vg-$lv4
+
+# Convert striped -> raid4
 lvconvert -y --ty raid4 $vg/$lv1
-lvchange --refresh $vg/$lv1
 check lv_field $vg/$lv1 segtype "raid4"
 check lv_field $vg/$lv1 stripes 4
 fsck -fn  /dev/mapper/$vg-$lv1
 aux wait_for_sync $vg $lv1
 fsck -fn  /dev/mapper/$vg-$lv1
 
+# Convert raid0 -> raid4
+lvconvert -y --ty raid4 $vg/$lv2
+check lv_field $vg/$lv2 segtype "raid4"
+check lv_field $vg/$lv2 stripes 4
+fsck -fn  /dev/mapper/$vg-$lv2
+aux wait_for_sync $vg $lv2
+fsck -fn  /dev/mapper/$vg-$lv2
+
 # Convert raid4 -> raid0_meta
 lvconvert -y --ty raid0_meta $vg/$lv1
 check lv_field $vg/$lv1 segtype "raid0_meta"
@@ -116,11 +152,16 @@ fsck -fn  /dev/mapper/$vg-$lv1
 
 # Convert raid0 -> raid4
 lvconvert -y --ty raid4 $vg/$lv1
-lvchange --refresh $vg/$lv1
 check lv_field $vg/$lv1 segtype "raid4"
 check lv_field $vg/$lv1 stripes 4
 fsck -fn  /dev/mapper/$vg-$lv1
 aux wait_for_sync $vg $lv1
 fsck -fn  /dev/mapper/$vg-$lv1
 
+# Convert raid4 -> striped
+lvconvert -y --ty striped $vg/$lv1
+check lv_field $vg/$lv1 segtype "striped"
+check lv_field $vg/$lv1 stripes 3
+fsck -fn  /dev/mapper/$vg-$lv1
+
 vgremove -ff $vg
diff --git a/tools/lvconvert.c b/tools/lvconvert.c
index 0d2a4d1..541df72 100644
--- a/tools/lvconvert.c
+++ b/tools/lvconvert.c
@@ -1931,7 +1931,7 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l
 			return 1;
 		}
 		goto try_new_takeover_or_reshape;
-	} else if (!lp->repair && !lp->replace && (!*lp->type_str || seg->segtype == lp->segtype)) {
+	} else if (!lp->repair && !lp->replace && !*lp->type_str) {
 		log_error("Conversion operation not yet supported.");
 		return 0;
 	}
@@ -2017,28 +2017,18 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l
 		return 1;
 	}
 
-
 try_new_takeover_or_reshape:
-
 	/* FIXME This needs changing globally. */
 	if (!arg_is_set(cmd, stripes_long_ARG))
 		lp->stripes = 0;
 
-	/* Only let raid4 through for now. */
-	if (lp->type_str && lp->type_str[0] && lp->segtype != seg->segtype &&
-	    ((seg_is_raid4(seg) && seg_is_striped(lp) && lp->stripes > 1) ||
-	     (seg_is_striped(seg) && seg->area_count > 1 && seg_is_raid4(lp)))) {
-		if (!lv_raid_convert(lv, lp->segtype, lp->yes, lp->force, lp->stripes, lp->stripe_size_supplied, lp->stripe_size,
-				     lp->region_size, lp->pvh))
-			return_0;
-
-		log_print_unless_silent("Logical volume %s successfully converted.",
-					display_lvname(lv));
-		return 1;
-	}
+	if (!lv_raid_convert(lv, lp->segtype, lp->yes, lp->force, lp->stripes, lp->stripe_size_supplied, lp->stripe_size,
+			     lp->region_size, lp->pvh))
+		return_0;
 
-	log_error("Conversion operation not yet supported.");
-	return 0;
+	log_print_unless_silent("Logical volume %s successfully converted.",
+				display_lvname(lv));
+	return 1;
 }
 
 static int _lvconvert_splitsnapshot(struct cmd_context *cmd, struct logical_volume *cow,