summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Teigland <teigland@redhat.com>2019-11-20 16:07:27 -0600
committerDavid Teigland <teigland@redhat.com>2020-02-17 11:37:18 -0600
commit6e51db786d92ffdf3dd59c2d93edc0b6defdf833 (patch)
tree184b6ad035ad58a5e96b9ede3b4fe63684b0b5ec
parent8a795e5eb04ead884deb97438e148af73fc32d09 (diff)
downloadlvm2-dev-dct-integrity22.tar.gz
dm-integrity supportdev-dct-integrity22
dm-integrity stores checksums of the data written to an LV, and returns an error if data read from the LV does not match the previously saved checksum. When used on raid images, dm-raid will correct the error by reading the block from another image, and the device user sees no error. Create a linear LV with an integrity layer above it: lvcreate --type integrity --integrity String [options] Create a raid LV with an integrity layer over each raid image (for raid levels 1,4,5,6): lvcreate --type raidN --integrity y [options] Add an integrity layer to an existing linear LV, or to each image of an existing raid LV: lvconvert --integrity y LV Remove the integrity layer from a linear LV, or from the images of a raid LV: lvconvert --integrity n LV Integrity metadata: The --integrity String specifies if the dm-integrity metadata (checksums) should be interleaved with data blocks, or written to a separate external LV. --integrity external (default) Use integrity with metadata on a separate LV. Allows removing integrity from the LV later. --integrity y Same as integrity external. --integrity n Remove integrity (external metadata only.) --integrity internal Use integrity with metadata interleaved with data on the same LV. Only allowed with new linear LVs. Internal integrity cannot be removed from an LV. Around 1% of the LV size is used for integrity metadata. Command variations: lvcreate --type integrity -n Name -L Size VG [Uses integrity external, the default.] lvcreate --integrity y|external -n Name -L Size VG [Uses type integrity, which is implied.] lvcreate --integrity internal -n Name -L Size VG [Uses type integrity, which is implied.] lvcreate --type raidN --integrity y -m Num -n Name -L Size VG [Uses type integrity for each raid image.] lvconvert --type integrity LV [Converts linear LV to type integrity.] lvconvert --integrity y|external LV [Converts linear LV to type integrity, or each image to type integrity in a raid LV.] Options: --integritymetadata LV|PV Use a specified LV for external metadata, or allocate a integrity metadata LV(s) from the named PV. With raid, only a PV can be specified (because multiple metadata LVs are required.) When this option is not used, the command allocates integrity metadata LVs using any PVs in the VG. --integritysettings String set dm-integrity parameters, e.g. to use a journal instead of bitmap, --integritysettings "mode=J". Initialization: When a new integrity LV is created, or integrity is added to an LV, the kernel needs to initialize the integrity metadata/checksums for all blocks in the LV. Integrity checks (detecting corruption) is not performed on blocks in the LV until they have been intialized. The progress of integrity initialization is reported by the "copypercent" LV reporting field (and under the Cpy%Sync lvs column.) $ lvconvert --integrity y vg/lv $ lvs vg/lv LV VG Attr LSize Origin Cpy%Sync lv vg gwi-a----- 1.00g [lv_iorig] 85.16 Examples: $ lvcreate --integrity external -n lvex -L1G vg $ lvs -a vg LV VG Attr LSize Origin lvex vg gwi-a----- 1.00g [lvex_iorig] [lvex_imeta] vg ewi-ao---- 12.00m [lvex_iorig] vg -wi-ao---- 1.00g $ lvcreate --integrity internal -n lvin -L1G vg $ lvs -a vg LV VG Attr LSize Origin lvin vg gwi-a----- 1.00g [lvin_iorig] [lvin_iorig] vg -wi-ao---- 1.00g $ lvcreate --type raid1 --integrity y -m 1 -n lver -L1G vg $ lvs -a vg LV VG Attr LSize Origin lver vg rwi-a-r--- 1.00g [lver_rimage_0] vg gwi-aor--- 1.00g [lver_rimage_0_iorig] [lver_rimage_0_imeta] vg ewi-ao---- 12.00m [lver_rimage_0_iorig] vg -wi-ao---- 1.00g [lver_rimage_1] vg gwi-aor--- 1.00g [lver_rimage_1_iorig] [lver_rimage_1_imeta] vg ewi-ao---- 12.00m [lver_rimage_1_iorig] vg -wi-ao---- 1.00g [lver_rmeta_0] vg ewi-aor--- 4.00m [lver_rmeta_1] vg ewi-aor--- 4.00m integrity: allow adding and removing with active LV integrity: lvextend integrity: change some log message levels integrity: remove integrity meta lv on error path tests: integrity check sync_percent tests: check integrity sync_percent on raid images not on the raid lv itself integrity: include raid10 integrity: handle adding new raid images integrity: allocate imeta on same PV as rimage and rimage
-rw-r--r--configure.ac18
-rw-r--r--device_mapper/all.h39
-rw-r--r--device_mapper/libdm-deptree.c130
-rw-r--r--device_mapper/libdm-targets.c27
-rw-r--r--include/configure.h.in3
-rw-r--r--lib/Makefile.in2
-rw-r--r--lib/activate/activate.h4
-rw-r--r--lib/activate/dev_manager.c18
-rw-r--r--lib/commands/toolcontext.c5
-rw-r--r--lib/format_text/flags.c2
-rw-r--r--lib/integrity/integrity.c346
-rw-r--r--lib/metadata/integrity_manip.c1255
-rw-r--r--lib/metadata/lv.c18
-rw-r--r--lib/metadata/lv_manip.c202
-rw-r--r--lib/metadata/merge.c2
-rw-r--r--lib/metadata/metadata-exported.h39
-rw-r--r--lib/metadata/pool_manip.c2
-rw-r--r--lib/metadata/raid_manip.c2
-rw-r--r--lib/metadata/segtype.h6
-rw-r--r--lib/misc/lvm-string.c4
-rw-r--r--lib/report/report.c2
-rw-r--r--test/shell/integrity.sh376
-rw-r--r--tools/args.h13
-rw-r--r--tools/command-lines.in39
-rw-r--r--tools/command.c1
-rw-r--r--tools/lv_types.h1
-rw-r--r--tools/lvconvert.c139
-rw-r--r--tools/lvcreate.c20
-rw-r--r--tools/lvmcmdline.c13
-rw-r--r--tools/lvresize.c2
-rw-r--r--tools/toollib.c198
-rw-r--r--tools/tools.h6
-rw-r--r--tools/vals.h1
33 files changed, 2913 insertions, 22 deletions
diff --git a/configure.ac b/configure.ac
index 74ca20191..9a0e41a81 100644
--- a/configure.ac
+++ b/configure.ac
@@ -668,6 +668,24 @@ case "$WRITECACHE" in
esac
################################################################################
+dnl -- integrity inclusion type
+AC_MSG_CHECKING(whether to include integrity)
+AC_ARG_WITH(integrity,
+ AC_HELP_STRING([--with-integrity=TYPE],
+ [integrity support: internal/none [none]]),
+ INTEGRITY=$withval, INTEGRITY="none")
+
+AC_MSG_RESULT($INTEGRITY)
+
+case "$INTEGRITY" in
+ none) ;;
+ internal)
+ AC_DEFINE([INTEGRITY_INTERNAL], 1, [Define to 1 to include built-in support for integrity.])
+ ;;
+ *) AC_MSG_ERROR([--with-integrity parameter invalid]) ;;
+esac
+
+################################################################################
dnl -- Disable readline
AC_ARG_ENABLE([readline],
AC_HELP_STRING([--disable-readline], [disable readline support]),
diff --git a/device_mapper/all.h b/device_mapper/all.h
index b23485f00..50f0508cd 100644
--- a/device_mapper/all.h
+++ b/device_mapper/all.h
@@ -392,6 +392,15 @@ struct dm_status_writecache {
int dm_get_status_writecache(struct dm_pool *mem, const char *params,
struct dm_status_writecache **status);
+struct dm_status_integrity {
+ uint64_t number_of_mismatches;
+ uint64_t provided_data_sectors;
+ uint64_t recalc_sector;
+};
+
+int dm_get_status_integrity(struct dm_pool *mem, const char *params,
+ struct dm_status_integrity **status);
+
/*
* Parse params from STATUS call for snapshot target
*
@@ -970,6 +979,36 @@ int dm_tree_node_add_writecache_target(struct dm_tree_node *node,
uint32_t writecache_block_size,
struct writecache_settings *settings);
+struct integrity_settings {
+ char mode[8];
+ uint32_t tag_size;
+ const char *internal_hash;
+
+ uint32_t journal_sectors;
+ uint32_t interleave_sectors;
+ uint32_t buffer_sectors;
+ uint32_t journal_watermark;
+ uint32_t commit_time;
+ uint32_t block_size;
+ uint32_t bitmap_flush_interval;
+ uint64_t sectors_per_bit;
+
+ unsigned journal_sectors_set:1;
+ unsigned interleave_sectors_set:1;
+ unsigned buffer_sectors_set:1;
+ unsigned journal_watermark_set:1;
+ unsigned commit_time_set:1;
+ unsigned block_size_set:1;
+ unsigned bitmap_flush_interval_set:1;
+ unsigned sectors_per_bit_set:1;
+};
+
+int dm_tree_node_add_integrity_target(struct dm_tree_node *node,
+ uint64_t size,
+ const char *origin_uuid,
+ const char *meta_uuid,
+ struct integrity_settings *settings,
+ int recalculate);
/*
* VDO target
diff --git a/device_mapper/libdm-deptree.c b/device_mapper/libdm-deptree.c
index 7fac6ab20..8cde5ff58 100644
--- a/device_mapper/libdm-deptree.c
+++ b/device_mapper/libdm-deptree.c
@@ -38,6 +38,7 @@ enum {
SEG_STRIPED,
SEG_ZERO,
SEG_WRITECACHE,
+ SEG_INTEGRITY,
SEG_THIN_POOL,
SEG_THIN,
SEG_VDO,
@@ -78,6 +79,7 @@ static const struct {
{ SEG_STRIPED, "striped" },
{ SEG_ZERO, "zero"},
{ SEG_WRITECACHE, "writecache"},
+ { SEG_INTEGRITY, "integrity"},
{ SEG_THIN_POOL, "thin-pool"},
{ SEG_THIN, "thin"},
{ SEG_VDO, "vdo" },
@@ -221,6 +223,11 @@ struct load_segment {
int writecache_pmem; /* writecache, 1 if pmem, 0 if ssd */
uint32_t writecache_block_size; /* writecache, in bytes */
struct writecache_settings writecache_settings; /* writecache */
+
+ uint64_t integrity_data_sectors; /* integrity (provided_data_sectors) */
+ struct dm_tree_node *integrity_meta_node; /* integrity */
+ struct integrity_settings integrity_settings; /* integrity */
+ int integrity_recalculate; /* integrity */
};
/* Per-device properties */
@@ -2705,6 +2712,88 @@ static int _writecache_emit_segment_line(struct dm_task *dmt,
return 1;
}
+static int _integrity_emit_segment_line(struct dm_task *dmt,
+ struct load_segment *seg,
+ char *params, size_t paramsize)
+{
+ struct integrity_settings *set = &seg->integrity_settings;
+ int pos = 0;
+ int count;
+ char origin_dev[DM_FORMAT_DEV_BUFSIZE];
+ char meta_dev[DM_FORMAT_DEV_BUFSIZE];
+
+ if (!_build_dev_string(origin_dev, sizeof(origin_dev), seg->origin))
+ return_0;
+
+ if (seg->integrity_meta_node &&
+ !_build_dev_string(meta_dev, sizeof(meta_dev), seg->integrity_meta_node))
+ return_0;
+
+ count = 1; /* for internal_hash which we always pass in */
+
+ if (seg->integrity_meta_node)
+ count++;
+
+ if (seg->integrity_recalculate)
+ count++;
+
+ if (set->journal_sectors_set)
+ count++;
+ if (set->interleave_sectors_set)
+ count++;
+ if (set->buffer_sectors_set)
+ count++;
+ if (set->journal_watermark_set)
+ count++;
+ if (set->commit_time_set)
+ count++;
+ if (set->block_size_set)
+ count++;
+ if (set->bitmap_flush_interval_set)
+ count++;
+ if (set->sectors_per_bit_set)
+ count++;
+
+ EMIT_PARAMS(pos, "%s 0 %u %s %d internal_hash:%s",
+ origin_dev,
+ set->tag_size,
+ set->mode,
+ count,
+ set->internal_hash);
+
+ if (seg->integrity_meta_node)
+ EMIT_PARAMS(pos, " meta_device:%s", meta_dev);
+
+ if (seg->integrity_recalculate)
+ EMIT_PARAMS(pos, " recalculate");
+
+ if (set->journal_sectors_set)
+ EMIT_PARAMS(pos, " journal_sectors:%u", set->journal_sectors);
+
+ if (set->interleave_sectors_set)
+ EMIT_PARAMS(pos, " ineterleave_sectors:%u", set->interleave_sectors);
+
+ if (set->buffer_sectors_set)
+ EMIT_PARAMS(pos, " buffer_sectors:%u", set->buffer_sectors);
+
+ if (set->journal_watermark_set)
+ EMIT_PARAMS(pos, " journal_watermark:%u", set->journal_watermark);
+
+ if (set->commit_time_set)
+ EMIT_PARAMS(pos, " commit_time:%u", set->commit_time);
+
+ if (set->block_size_set)
+ EMIT_PARAMS(pos, " block_size:%u", set->block_size);
+
+ if (set->bitmap_flush_interval_set)
+ EMIT_PARAMS(pos, " bitmap_flush_interval:%u", set->bitmap_flush_interval);
+
+ if (set->sectors_per_bit_set)
+ EMIT_PARAMS(pos, " sectors_per_bit:%llu", (unsigned long long)set->sectors_per_bit);
+
+ return 1;
+}
+
static int _thin_pool_emit_segment_line(struct dm_task *dmt,
struct load_segment *seg,
char *params, size_t paramsize)
@@ -2889,6 +2978,10 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major,
if (!_writecache_emit_segment_line(dmt, seg, params, paramsize))
return_0;
break;
+ case SEG_INTEGRITY:
+ if (!_integrity_emit_segment_line(dmt, seg, params, paramsize))
+ return_0;
+ break;
}
switch(seg->type) {
@@ -2901,6 +2994,7 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major,
case SEG_THIN:
case SEG_CACHE:
case SEG_WRITECACHE:
+ case SEG_INTEGRITY:
break;
case SEG_CRYPT:
case SEG_LINEAR:
@@ -3738,6 +3832,42 @@ int dm_tree_node_add_writecache_target(struct dm_tree_node *node,
return 1;
}
+int dm_tree_node_add_integrity_target(struct dm_tree_node *node,
+ uint64_t size,
+ const char *origin_uuid,
+ const char *meta_uuid,
+ struct integrity_settings *settings,
+ int recalculate)
+{
+ struct load_segment *seg;
+
+ if (!(seg = _add_segment(node, SEG_INTEGRITY, size)))
+ return_0;
+
+ if (meta_uuid) {
+ if (!(seg->integrity_meta_node = dm_tree_find_node_by_uuid(node->dtree, meta_uuid))) {
+ log_error("Missing integrity's meta uuid %s.", meta_uuid);
+ return 0;
+ }
+
+ if (!_link_tree_nodes(node, seg->integrity_meta_node))
+ return_0;
+ }
+
+ if (!(seg->origin = dm_tree_find_node_by_uuid(node->dtree, origin_uuid))) {
+ log_error("Missing integrity's origin uuid %s.", origin_uuid);
+ return 0;
+ }
+ if (!_link_tree_nodes(node, seg->origin))
+ return_0;
+
+ memcpy(&seg->integrity_settings, settings, sizeof(struct integrity_settings));
+
+ seg->integrity_recalculate = recalculate;
+
+ return 1;
+}
+
int dm_tree_node_add_replicator_target(struct dm_tree_node *node,
uint64_t size,
const char *rlog_uuid,
diff --git a/device_mapper/libdm-targets.c b/device_mapper/libdm-targets.c
index 86cb84713..587a5f022 100644
--- a/device_mapper/libdm-targets.c
+++ b/device_mapper/libdm-targets.c
@@ -380,6 +380,33 @@ int dm_get_status_writecache(struct dm_pool *mem, const char *params,
return 1;
}
+int dm_get_status_integrity(struct dm_pool *mem, const char *params,
+ struct dm_status_integrity **status)
+{
+ struct dm_status_integrity *s;
+ char recalc_str[8] = "\0";
+
+ if (!(s = dm_pool_zalloc(mem, sizeof(*s))))
+ return_0;
+
+ if (sscanf(params, "%llu %llu %s",
+ (unsigned long long *)&s->number_of_mismatches,
+ (unsigned long long *)&s->provided_data_sectors,
+ recalc_str) != 3) {
+ log_error("Failed to parse integrity params: %s.", params);
+ dm_pool_free(mem, s);
+ return 0;
+ }
+
+ if (recalc_str[0] == '-')
+ s->recalc_sector = 0;
+ else
+ s->recalc_sector = strtoull(recalc_str, NULL, 0);
+
+ *status = s;
+ return 1;
+}
+
int parse_thin_pool_status(const char *params, struct dm_status_thin_pool *s)
{
int pos;
diff --git a/include/configure.h.in b/include/configure.h.in
index 91a3a7ddb..57736cc3b 100644
--- a/include/configure.h.in
+++ b/include/configure.h.in
@@ -678,6 +678,9 @@
/* Define to 1 to include built-in support for writecache. */
#undef WRITECACHE_INTERNAL
+/* Define to 1 to include built-in support for integrity. */
+#undef INTEGRITY_INTERNAL
+
/* Define to get access to GNU/Linux extension */
#undef _GNU_SOURCE
diff --git a/lib/Makefile.in b/lib/Makefile.in
index 2a064f381..8e50ec45c 100644
--- a/lib/Makefile.in
+++ b/lib/Makefile.in
@@ -20,6 +20,7 @@ SOURCES =\
activate/activate.c \
cache/lvmcache.c \
writecache/writecache.c \
+ integrity/integrity.c \
cache_segtype/cache.c \
commands/toolcontext.c \
config/config.c \
@@ -67,6 +68,7 @@ SOURCES =\
log/log.c \
metadata/cache_manip.c \
metadata/writecache_manip.c \
+ metadata/integrity_manip.c \
metadata/lv.c \
metadata/lv_manip.c \
metadata/merge.c \
diff --git a/lib/activate/activate.h b/lib/activate/activate.h
index a5ee438ad..e3c1bb35e 100644
--- a/lib/activate/activate.h
+++ b/lib/activate/activate.h
@@ -39,6 +39,7 @@ typedef enum {
SEG_STATUS_THIN_POOL,
SEG_STATUS_VDO_POOL,
SEG_STATUS_WRITECACHE,
+ SEG_STATUS_INTEGRITY,
SEG_STATUS_UNKNOWN
} lv_seg_status_type_t;
@@ -53,6 +54,7 @@ struct lv_seg_status {
struct dm_status_thin *thin;
struct dm_status_thin_pool *thin_pool;
struct dm_status_writecache *writecache;
+ struct dm_status_integrity *integrity;
struct lv_status_vdo vdo_pool;
};
};
@@ -260,6 +262,7 @@ void fs_unlock(void);
#define TARGET_NAME_CACHE "cache"
#define TARGET_NAME_WRITECACHE "writecache"
+#define TARGET_NAME_INTEGRITY "integrity"
#define TARGET_NAME_ERROR "error"
#define TARGET_NAME_ERROR_OLD "erro" /* Truncated in older kernels */
#define TARGET_NAME_LINEAR "linear"
@@ -277,6 +280,7 @@ void fs_unlock(void);
#define MODULE_NAME_CLUSTERED_MIRROR "clog"
#define MODULE_NAME_CACHE TARGET_NAME_CACHE
#define MODULE_NAME_WRITECACHE TARGET_NAME_WRITECACHE
+#define MODULE_NAME_INTEGRITY TARGET_NAME_INTEGRITY
#define MODULE_NAME_ERROR TARGET_NAME_ERROR
#define MODULE_NAME_LOG_CLUSTERED "log-clustered"
#define MODULE_NAME_LOG_USERSPACE "log-userspace"
diff --git a/lib/activate/dev_manager.c b/lib/activate/dev_manager.c
index c8a22fb56..607552c69 100644
--- a/lib/activate/dev_manager.c
+++ b/lib/activate/dev_manager.c
@@ -46,7 +46,7 @@ typedef enum {
} action_t;
/* This list must match lib/misc/lvm-string.c:build_dm_uuid(). */
-const char *uuid_suffix_list[] = { "pool", "cdata", "cmeta", "cvol", "tdata", "tmeta", "vdata", "vpool", NULL};
+const char *uuid_suffix_list[] = { "pool", "cdata", "cmeta", "cvol", "tdata", "tmeta", "vdata", "vpool", "imeta", NULL};
struct dlid_list {
struct dm_list list;
@@ -222,6 +222,10 @@ static int _get_segment_status_from_target_params(const char *target_name,
if (!dm_get_status_writecache(seg_status->mem, params, &(seg_status->writecache)))
return_0;
seg_status->type = SEG_STATUS_WRITECACHE;
+ } else if (segtype_is_integrity(segtype)) {
+ if (!dm_get_status_integrity(seg_status->mem, params, &(seg_status->integrity)))
+ return_0;
+ seg_status->type = SEG_STATUS_INTEGRITY;
} else
/*
* TODO: Add support for other segment types too!
@@ -299,6 +303,9 @@ static int _info_run(const char *dlid, struct dm_info *dminfo,
if (lv_is_vdo_pool(seg_status->seg->lv))
length = get_vdo_pool_virtual_size(seg_status->seg);
+ if (lv_is_integrity(seg_status->seg->lv))
+ length = seg_status->seg->integrity_data_sectors;
+
do {
target = dm_get_next_target(dmt, target, &target_start,
&target_length, &target_name, &target_params);
@@ -2620,6 +2627,10 @@ static int _add_lv_to_dtree(struct dev_manager *dm, struct dm_tree *dtree,
if (!_add_lv_to_dtree(dm, dtree, seg->writecache, dm->activation ? origin_only : 1))
return_0;
}
+ if (seg->integrity_meta_dev && seg_is_integrity(seg)) {
+ if (!_add_lv_to_dtree(dm, dtree, seg->integrity_meta_dev, dm->activation ? origin_only : 1))
+ return_0;
+ }
if (seg->pool_lv &&
(lv_is_cache_pool(seg->pool_lv) || lv_is_cache_vol(seg->pool_lv) || dm->track_external_lv_deps) &&
/* When activating and not origin_only detect linear 'overlay' over pool */
@@ -3076,6 +3087,11 @@ static int _add_segment_to_dtree(struct dev_manager *dm,
lv_layer(seg->writecache)))
return_0;
+ if (seg->integrity_meta_dev && !laopts->origin_only &&
+ !_add_new_lv_to_dtree(dm, dtree, seg->integrity_meta_dev, laopts,
+ lv_layer(seg->integrity_meta_dev)))
+ return_0;
+
/* Add any LVs used by this segment */
for (s = 0; s < seg->area_count; ++s) {
if ((seg_type(seg, s) == AREA_LV) &&
diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c
index 479d4991c..88d5b3eb0 100644
--- a/lib/commands/toolcontext.c
+++ b/lib/commands/toolcontext.c
@@ -1362,6 +1362,11 @@ static int _init_segtypes(struct cmd_context *cmd)
return 0;
#endif
+#ifdef INTEGRITY_INTERNAL
+ if (!init_integrity_segtypes(cmd, &seglib))
+ return 0;
+#endif
+
return 1;
}
diff --git a/lib/format_text/flags.c b/lib/format_text/flags.c
index 2873ba632..bc93a5dcf 100644
--- a/lib/format_text/flags.c
+++ b/lib/format_text/flags.c
@@ -104,6 +104,8 @@ static const struct flag _lv_flags[] = {
{LV_VDO_POOL, NULL, 0},
{LV_VDO_POOL_DATA, NULL, 0},
{WRITECACHE, NULL, 0},
+ {INTEGRITY, NULL, 0},
+ {INTEGRITY_METADATA, NULL, 0},
{LV_PENDING_DELETE, NULL, 0}, /* FIXME Display like COMPATIBLE_FLAG */
{LV_REMOVED, NULL, 0},
{0, NULL, 0}
diff --git a/lib/integrity/integrity.c b/lib/integrity/integrity.c
new file mode 100644
index 000000000..ad44067f6
--- /dev/null
+++ b/lib/integrity/integrity.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2013-2016 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 "base/memory/zalloc.h"
+#include "lib/misc/lib.h"
+#include "lib/commands/toolcontext.h"
+#include "lib/metadata/segtype.h"
+#include "lib/display/display.h"
+#include "lib/format_text/text_export.h"
+#include "lib/config/config.h"
+#include "lib/datastruct/str_list.h"
+#include "lib/misc/lvm-string.h"
+#include "lib/activate/activate.h"
+#include "lib/metadata/metadata.h"
+#include "lib/metadata/lv_alloc.h"
+#include "lib/config/defaults.h"
+
+#define SEG_LOG_ERROR(t, p...) \
+ log_error(t " segment %s of logical volume %s.", ## p, \
+ dm_config_parent_name(sn), seg->lv->name), 0;
+
+static void _integrity_display(const struct lv_segment *seg)
+{
+ /* TODO: lvdisplay segments */
+}
+
+static int _integrity_text_import(struct lv_segment *seg,
+ const struct dm_config_node *sn,
+ struct dm_hash_table *pv_hash __attribute__((unused)))
+{
+ struct integrity_settings *set;
+ struct logical_volume *origin_lv = NULL;
+ struct logical_volume *meta_lv = NULL;
+ const char *origin_name = NULL;
+ const char *meta_dev = NULL;
+ const char *mode = NULL;
+ const char *hash = NULL;
+
+ memset(&seg->integrity_settings, 0, sizeof(struct integrity_settings));
+ set = &seg->integrity_settings;
+
+ /* origin always set */
+
+ if (!dm_config_has_node(sn, "origin"))
+ return SEG_LOG_ERROR("origin not specified in");
+
+ if (!dm_config_get_str(sn, "origin", &origin_name))
+ return SEG_LOG_ERROR("origin must be a string in");
+
+ if (!(origin_lv = find_lv(seg->lv->vg, origin_name)))
+ return SEG_LOG_ERROR("Unknown LV specified for integrity origin %s in", origin_name);
+
+ if (!set_lv_segment_area_lv(seg, 0, origin_lv, 0, 0))
+ return_0;
+
+ /* data_sectors always set */
+
+ if (!dm_config_get_uint64(sn, "data_sectors", &seg->integrity_data_sectors))
+ return SEG_LOG_ERROR("integrity data_sectors must be set in");
+
+ /* mode always set */
+
+ if (!dm_config_get_str(sn, "mode", &mode))
+ return SEG_LOG_ERROR("integrity mode must be set in");
+
+ if (strlen(mode) > 7)
+ return SEG_LOG_ERROR("integrity mode invalid in");
+
+ strncpy(set->mode, mode, 7);
+
+ /* tag_size always set */
+
+ if (!dm_config_get_uint32(sn, "tag_size", &set->tag_size))
+ return SEG_LOG_ERROR("integrity tag_size must be set in");
+
+ /* internal_hash always set */
+
+ if (!dm_config_get_str(sn, "internal_hash", &hash))
+ return SEG_LOG_ERROR("integrity internal_hash must be set in");
+
+ if (!(set->internal_hash = dm_pool_strdup(seg->lv->vg->vgmem, hash)))
+ return SEG_LOG_ERROR("integrity internal_hash failed to be set in");
+
+ /* meta_dev optional */
+
+ if (dm_config_has_node(sn, "meta_dev")) {
+ if (!dm_config_get_str(sn, "meta_dev", &meta_dev))
+ return SEG_LOG_ERROR("meta_dev must be a string in");
+
+ if (!(meta_lv = find_lv(seg->lv->vg, meta_dev)))
+ return SEG_LOG_ERROR("Unknown logical volume %s specified for integrity in", meta_dev);
+ }
+
+ if (dm_config_has_node(sn, "recalculate")) {
+ if (!dm_config_get_uint32(sn, "recalculate", &seg->integrity_recalculate))
+ return SEG_LOG_ERROR("integrity recalculate error in");
+ }
+
+ /* the rest are optional */
+
+ if (dm_config_has_node(sn, "journal_sectors")) {
+ if (!dm_config_get_uint32(sn, "journal_sectors", &set->journal_sectors))
+ return SEG_LOG_ERROR("Unknown integrity_setting in");
+ set->journal_sectors_set = 1;
+ }
+
+ if (dm_config_has_node(sn, "interleave_sectors")) {
+ if (!dm_config_get_uint32(sn, "interleave_sectors", &set->interleave_sectors))
+ return SEG_LOG_ERROR("Unknown integrity_setting in");
+ set->interleave_sectors_set = 1;
+ }
+
+ if (dm_config_has_node(sn, "buffer_sectors")) {
+ if (!dm_config_get_uint32(sn, "buffer_sectors", &set->buffer_sectors))
+ return SEG_LOG_ERROR("Unknown integrity_setting in");
+ set->buffer_sectors_set = 1;
+ }
+
+ if (dm_config_has_node(sn, "journal_watermark")) {
+ if (!dm_config_get_uint32(sn, "journal_watermark", &set->journal_watermark))
+ return SEG_LOG_ERROR("Unknown integrity_setting in");
+ set->journal_watermark_set = 1;
+ }
+
+ if (dm_config_has_node(sn, "commit_time")) {
+ if (!dm_config_get_uint32(sn, "commit_time", &set->commit_time))
+ return SEG_LOG_ERROR("Unknown integrity_setting in");
+ set->commit_time_set = 1;
+ }
+
+ if (dm_config_has_node(sn, "block_size")) {
+ if (!dm_config_get_uint32(sn, "block_size", &set->block_size))
+ return SEG_LOG_ERROR("Unknown integrity_setting in");
+ set->block_size_set = 1;
+ }
+
+ if (dm_config_has_node(sn, "bitmap_flush_interval")) {
+ if (!dm_config_get_uint32(sn, "bitmap_flush_interval", &set->bitmap_flush_interval))
+ return SEG_LOG_ERROR("Unknown integrity_setting in");
+ set->bitmap_flush_interval_set = 1;
+ }
+
+ if (dm_config_has_node(sn, "sectors_per_bit")) {
+ if (!dm_config_get_uint64(sn, "sectors_per_bit", &set->sectors_per_bit))
+ return SEG_LOG_ERROR("Unknown integrity_setting in");
+ set->sectors_per_bit_set = 1;
+ }
+
+ seg->origin = origin_lv;
+ seg->integrity_meta_dev = meta_lv;
+ seg->lv->status |= INTEGRITY;
+
+ if (meta_lv)
+ meta_lv->status |= INTEGRITY_METADATA;
+
+ if (meta_lv && !add_seg_to_segs_using_this_lv(meta_lv, seg))
+ return_0;
+
+ return 1;
+}
+
+static int _integrity_text_import_area_count(const struct dm_config_node *sn,
+ uint32_t *area_count)
+{
+ *area_count = 1;
+
+ return 1;
+}
+
+static int _integrity_text_export(const struct lv_segment *seg,
+ struct formatter *f)
+{
+ const struct integrity_settings *set = &seg->integrity_settings;
+
+ outf(f, "origin = \"%s\"", seg_lv(seg, 0)->name);
+ outf(f, "data_sectors = %llu", (unsigned long long)seg->integrity_data_sectors);
+
+ outf(f, "mode = \"%s\"", set->mode);
+ outf(f, "tag_size = %u", set->tag_size);
+ outf(f, "internal_hash = \"%s\"", set->internal_hash);
+
+ if (seg->integrity_meta_dev)
+ outf(f, "meta_dev = \"%s\"", seg->integrity_meta_dev->name);
+
+ if (seg->integrity_recalculate)
+ outf(f, "recalculate = %u", seg->integrity_recalculate);
+
+ if (set->journal_sectors_set)
+ outf(f, "journal_sectors = %u", set->journal_sectors);
+
+ if (set->interleave_sectors_set)
+ outf(f, "interleave_sectors = %u", set->interleave_sectors);
+
+ if (set->buffer_sectors_set)
+ outf(f, "buffer_sectors = %u", set->buffer_sectors);
+
+ if (set->journal_watermark_set)
+ outf(f, "journal_watermark = %u", set->journal_watermark);
+
+ if (set->commit_time_set)
+ outf(f, "commit_time = %u", set->commit_time);
+
+ if (set->block_size_set)
+ outf(f, "block_size = %u", set->block_size);
+
+ if (set->bitmap_flush_interval)
+ outf(f, "bitmap_flush_interval = %u", set->bitmap_flush_interval);
+
+ if (set->sectors_per_bit)
+ outf(f, "sectors_per_bit = %llu", (unsigned long long)set->sectors_per_bit);
+
+ return 1;
+}
+
+static void _destroy(struct segment_type *segtype)
+{
+ free((void *) segtype);
+}
+
+#ifdef DEVMAPPER_SUPPORT
+
+static int _target_present(struct cmd_context *cmd,
+ const struct lv_segment *seg __attribute__((unused)),
+ unsigned *attributes __attribute__((unused)))
+{
+ static int _integrity_checked = 0;
+ static int _integrity_present = 0;
+ uint32_t maj, min, patchlevel;
+
+ if (!activation())
+ return 0;
+
+ if (!_integrity_checked) {
+ _integrity_checked = 1;
+ _integrity_present = target_present(cmd, TARGET_NAME_INTEGRITY, 1);
+
+ if (!target_version(TARGET_NAME_INTEGRITY, &maj, &min, &patchlevel))
+ return 0;
+
+ if (maj < 1 || min < 3) {
+ log_error("Integrity target version older than minimum 1.3.0");
+ return 0;
+ }
+ }
+
+ return _integrity_present;
+}
+
+static int _modules_needed(struct dm_pool *mem,
+ const struct lv_segment *seg __attribute__((unused)),
+ struct dm_list *modules)
+{
+ if (!str_list_add(mem, modules, MODULE_NAME_INTEGRITY)) {
+ log_error("String list allocation failed for integrity module.");
+ return 0;
+ }
+
+ return 1;
+}
+#endif /* DEVMAPPER_SUPPORT */
+
+#ifdef DEVMAPPER_SUPPORT
+static int _integrity_add_target_line(struct dev_manager *dm,
+ struct dm_pool *mem,
+ struct cmd_context *cmd __attribute__((unused)),
+ void **target_state __attribute__((unused)),
+ struct lv_segment *seg,
+ const struct lv_activate_opts *laopts,
+ struct dm_tree_node *node, uint64_t len,
+ uint32_t *pvmove_mirror_count __attribute__((unused)))
+{
+ char *origin_uuid;
+ char *meta_uuid = NULL;
+
+ if (!seg_is_integrity(seg)) {
+ log_error(INTERNAL_ERROR "Passed segment is not integrity.");
+ return 0;
+ }
+
+ if (!(origin_uuid = build_dm_uuid(mem, seg_lv(seg, 0), NULL)))
+ return_0;
+
+ if (seg->integrity_meta_dev) {
+ if (!(meta_uuid = build_dm_uuid(mem, seg->integrity_meta_dev, NULL)))
+ return_0;
+ }
+
+ if (!seg->integrity_data_sectors) {
+ log_error("_integrity_add_target_line zero size");
+ return_0;
+ }
+
+ if (!dm_tree_node_add_integrity_target(node, seg->integrity_data_sectors,
+ origin_uuid, meta_uuid,
+ &seg->integrity_settings,
+ seg->integrity_recalculate))
+ return_0;
+
+ return 1;
+}
+#endif /* DEVMAPPER_SUPPORT */
+
+static struct segtype_handler _integrity_ops = {
+ .display = _integrity_display,
+ .text_import = _integrity_text_import,
+ .text_import_area_count = _integrity_text_import_area_count,
+ .text_export = _integrity_text_export,
+#ifdef DEVMAPPER_SUPPORT
+ .add_target_line = _integrity_add_target_line,
+ .target_present = _target_present,
+ .modules_needed = _modules_needed,
+#endif
+ .destroy = _destroy,
+};
+
+int init_integrity_segtypes(struct cmd_context *cmd,
+ struct segtype_library *seglib)
+{
+ struct segment_type *segtype = zalloc(sizeof(*segtype));
+
+ if (!segtype) {
+ log_error("Failed to allocate memory for integrity segtype");
+ return 0;
+ }
+
+ segtype->name = SEG_TYPE_NAME_INTEGRITY;
+ segtype->flags = SEG_INTEGRITY;
+ segtype->ops = &_integrity_ops;
+
+ if (!lvm_register_segtype(seglib, segtype))
+ return_0;
+ log_very_verbose("Initialised segtype: %s", segtype->name);
+
+ return 1;
+}
diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c
new file mode 100644
index 000000000..d904a26cd
--- /dev/null
+++ b/lib/metadata/integrity_manip.c
@@ -0,0 +1,1255 @@
+/*
+ * Copyright (C) 2014-2015 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 "lib/locking/locking.h"
+#include "lib/misc/lvm-string.h"
+#include "lib/commands/toolcontext.h"
+#include "lib/display/display.h"
+#include "lib/metadata/segtype.h"
+#include "lib/activate/activate.h"
+#include "lib/config/defaults.h"
+#include "lib/activate/dev_manager.h"
+
+#define DEFAULT_TAG_SIZE 4 /* bytes */
+#define DEFAULT_MODE 'B'
+#define DEFAULT_INTERNAL_HASH "crc32c"
+
+#define ONE_MB_IN_BYTES 1048576
+
+int lv_is_integrity_origin(const struct logical_volume *lv)
+{
+ struct seg_list *sl;
+
+ dm_list_iterate_items(sl, &lv->segs_using_this_lv) {
+ if (!sl->seg || !sl->seg->lv || !sl->seg->origin)
+ continue;
+ if (lv_is_integrity(sl->seg->lv) && (sl->seg->origin == lv))
+ return 1;
+ }
+ return 0;
+}
+
+static struct logical_volume *_lv_integrity_from_origin(const struct logical_volume *lv)
+{
+ struct seg_list *sl;
+
+ dm_list_iterate_items(sl, &lv->segs_using_this_lv) {
+ if (!sl->seg || !sl->seg->lv || !sl->seg->origin)
+ continue;
+ if (lv_is_integrity(sl->seg->lv) && (sl->seg->origin == lv))
+ return sl->seg->lv;
+ }
+ return NULL;
+}
+
+/*
+ * Every 500M of data needs 4M of metadata.
+ * (From trial and error testing.)
+ */
+static uint64_t _lv_size_bytes_to_integrity_meta_bytes(uint64_t lv_size_bytes)
+{
+ return ((lv_size_bytes / (500 * ONE_MB_IN_BYTES)) + 1) * (4 * ONE_MB_IN_BYTES);
+}
+
+/*
+ * The user wants external metadata, but did not specify an existing
+ * LV to hold metadata, so create an LV for metadata.
+ */
+static int _lv_create_integrity_metadata(struct cmd_context *cmd,
+ struct volume_group *vg,
+ struct lvcreate_params *lp,
+ struct logical_volume **meta_lv)
+{
+ char metaname[NAME_LEN];
+ uint64_t lv_size_bytes, meta_bytes, meta_sectors;
+ struct logical_volume *lv;
+ struct lvcreate_params lp_meta = {
+ .activate = CHANGE_AN,
+ .alloc = ALLOC_INHERIT,
+ .major = -1,
+ .minor = -1,
+ .permission = LVM_READ | LVM_WRITE,
+ .pvh = &vg->pvs,
+ .read_ahead = DM_READ_AHEAD_NONE,
+ .stripes = 1,
+ .vg_name = vg->name,
+ .zero = 0,
+ .wipe_signatures = 0,
+ .suppress_zero_warn = 1,
+ };
+
+ if (lp->lv_name &&
+ dm_snprintf(metaname, NAME_LEN, "%s_imeta", lp->lv_name) < 0) {
+ log_error("Failed to create metadata LV name.");
+ return 0;
+ }
+
+ lp_meta.lv_name = metaname;
+ lp_meta.pvh = lp->pvh;
+
+ lv_size_bytes = (uint64_t)lp->extents * (uint64_t)vg->extent_size * 512;
+ meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv_size_bytes);
+ meta_sectors = meta_bytes / 512;
+ lp_meta.extents = meta_sectors / vg->extent_size;
+
+ log_print_unless_silent("Creating integrity metadata LV %s with size %s.",
+ metaname, display_size(cmd, meta_sectors));
+
+ dm_list_init(&lp_meta.tags);
+
+ if (!(lp_meta.segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED)))
+ return_0;
+
+ if (!(lv = lv_create_single(vg, &lp_meta))) {
+ log_error("Failed to create integrity metadata LV");
+ return 0;
+ }
+
+ *meta_lv = lv;
+ return 1;
+}
+
+static int _get_provided_data_sectors(struct logical_volume *lv, uint64_t *provided_data_sectors)
+{
+ struct lv_with_info_and_seg_status status;
+ uint64_t data_sectors, extra_sectors;
+
+ memset(&status, 0, sizeof(status));
+ status.seg_status.type = SEG_STATUS_NONE;
+
+ status.seg_status.seg = first_seg(lv);
+
+ /* FIXME: why reporter_pool? */
+ if (!(status.seg_status.mem = dm_pool_create("reporter_pool", 1024))) {
+ log_error("Failed to get mem for LV status.");
+ return 0;
+ }
+
+ if (!lv_info_with_seg_status(lv->vg->cmd, first_seg(lv), &status, 1, 1)) {
+ log_error("Failed to get device mapper status for %s", display_lvname(lv));
+ goto fail;
+ }
+
+ if (!status.info.exists) {
+ log_error("No device mapper info exists for %s", display_lvname(lv));
+ goto fail;
+ }
+
+ if (status.seg_status.type != SEG_STATUS_INTEGRITY) {
+ log_error("Invalid device mapper status type (%d) for %s",
+ (uint32_t)status.seg_status.type, display_lvname(lv));
+ goto fail;
+ }
+
+ data_sectors = status.seg_status.integrity->provided_data_sectors;
+
+ if ((extra_sectors = (data_sectors % lv->vg->extent_size))) {
+ data_sectors -= extra_sectors;
+ log_debug("Reduce provided_data_sectors by %llu to %llu for extent alignment",
+ (unsigned long long)extra_sectors, (unsigned long long)data_sectors);
+ }
+
+ *provided_data_sectors = data_sectors;
+
+ dm_pool_destroy(status.seg_status.mem);
+ return 1;
+
+fail:
+ dm_pool_destroy(status.seg_status.mem);
+ return 0;
+}
+
+/*
+ * lv_iorig has been extended, now extend the integrity layer
+ * above it.
+ */
+int lv_extend_integrity_for_origin(struct logical_volume *lv_iorig,
+ struct dm_list *pvh, const char *meta_name)
+{
+ struct cmd_context *cmd = lv_iorig->vg->cmd;
+ struct volume_group *vg = lv_iorig->vg;
+ const struct segment_type *segtype;
+ struct logical_volume *lv;
+ struct logical_volume *lv_imeta;
+ struct lv_segment *seg;
+ struct device *meta_dev;
+ struct dm_list *use_pvh = pvh;
+ uint64_t lv_size_bytes, meta_bytes, meta_sectors, prev_meta_sectors;
+ uint32_t meta_extents, prev_meta_extents;
+
+ if (meta_name) {
+ if (!(meta_dev = dev_cache_get(cmd, meta_name, NULL))) {
+ log_error("integritymetadata device not found %s.", meta_name);
+ return 0;
+ }
+ if (!(use_pvh = create_pv_list(cmd->mem, vg, 1, (char **)&meta_name, 1))) {
+ log_error("integritymetadata not found in VG %s.", meta_name);
+ return 0;
+ }
+ }
+
+ if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_STRIPED)))
+ return_0;
+
+ if (!(lv = _lv_integrity_from_origin(lv_iorig)))
+ return_0;
+
+ seg = first_seg(lv);
+
+ lv_imeta = seg->integrity_meta_dev;
+
+ /*
+ * Shouldn't happen because we prevent extending an LV with
+ * internal integrity. To allow that, we'd need to find out
+ * the new provided_data_sectors to set in the metadata.
+ * That would require doing a temporary activation of the
+ * extended LV with provided_data_sectors=1 and then check
+ * what the kernel reports.
+ */
+ if (!lv_imeta) {
+ log_error("Cannot extend with internal integrity.");
+ return_0;
+ }
+
+ /* new size in sectors */
+ lv->size = lv_iorig->size;
+ seg->integrity_data_sectors = lv_iorig->size;
+ /* new size in extents */
+ lv->le_count = lv_iorig->le_count;
+ seg->len = lv_iorig->le_count;
+ seg->area_len = lv_iorig->le_count;
+
+ lv_size_bytes = lv_iorig->size * 512;
+ meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv_size_bytes);
+ meta_sectors = meta_bytes / 512;
+ meta_extents = meta_sectors / vg->extent_size;
+
+ prev_meta_sectors = lv_imeta->size;
+ prev_meta_extents = prev_meta_sectors / vg->extent_size;
+
+ if (meta_extents <= prev_meta_extents) {
+ log_debug("extend not needed for imeta LV %s", lv_imeta->name);
+ return 1;
+ }
+
+ if (!lv_extend(lv_imeta, segtype, 1, 0, 0, 0,
+ meta_extents - prev_meta_extents,
+ use_pvh, lv_imeta->alloc, 0, NULL)) {
+ log_error("Failed to extend integrity metadata LV %s", lv_imeta->name);
+ return 0;
+ }
+
+ return 1;
+}
+
+int lv_extend_integrity_in_raid(struct logical_volume *lv,
+ struct dm_list *pvh, const char *meta_name)
+{
+ struct cmd_context *cmd = lv->vg->cmd;
+ struct volume_group *vg = lv->vg;
+ const struct segment_type *segtype;
+ struct lv_segment *seg_top, *seg_image;
+ struct logical_volume *lv_image;
+ struct logical_volume *lv_iorig;
+ struct logical_volume *lv_imeta;
+ struct device *meta_dev;
+ struct dm_list *use_pvh = pvh;
+ uint64_t lv_size_bytes, meta_bytes, meta_sectors, prev_meta_sectors;
+ uint32_t meta_extents, prev_meta_extents;
+ uint32_t area_count, s;
+
+ if (meta_name) {
+ if (!(meta_dev = dev_cache_get(cmd, meta_name, NULL))) {
+ log_error("integritymetadata device not found %s.", meta_name);
+ return 0;
+ }
+ if (!(use_pvh = create_pv_list(cmd->mem, vg, 1, (char **)&meta_name, 1))) {
+ log_error("integritymetadata not found in VG %s.", meta_name);
+ return 0;
+ }
+ }
+
+ seg_top = first_seg(lv);
+
+ if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_STRIPED)))
+ return_0;
+
+ area_count = seg_top->area_count;
+
+ for (s = 0; s < area_count; s++) {
+ lv_image = seg_lv(seg_top, s);
+ seg_image = first_seg(lv_image);
+
+ if (!(lv_imeta = seg_image->integrity_meta_dev)) {
+ log_error("LV %s segment has no integrity metadata device.", display_lvname(lv));
+ return 0;
+ }
+
+ if (!(lv_iorig = seg_lv(seg_image, 0))) {
+ log_error("LV %s integrity segment has no origin", display_lvname(lv));
+ return 0;
+ }
+
+ lv_size_bytes = lv_iorig->size * 512;
+ meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv_size_bytes);
+ meta_sectors = meta_bytes / 512;
+ meta_extents = meta_sectors / vg->extent_size;
+
+ prev_meta_sectors = lv_imeta->size;
+ prev_meta_extents = prev_meta_sectors / vg->extent_size;
+
+ if (meta_extents <= prev_meta_extents) {
+ log_debug("extend not needed for imeta LV %s", lv_imeta->name);
+ continue;
+ }
+
+ if (!lv_extend(lv_imeta, segtype, 1, 0, 0, 0,
+ meta_extents - prev_meta_extents,
+ use_pvh, lv_imeta->alloc, 0, NULL)) {
+ log_error("Failed to extend raid image integrity metadata LV %s", lv_imeta->name);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int lv_remove_integrity_from_raid(struct logical_volume *lv)
+{
+ struct logical_volume *iorig_lvs[DEFAULT_RAID_MAX_IMAGES];
+ struct logical_volume *imeta_lvs[DEFAULT_RAID_MAX_IMAGES];
+ struct cmd_context *cmd = lv->vg->cmd;
+ struct volume_group *vg = lv->vg;
+ struct lv_segment *seg_top, *seg_image;
+ struct logical_volume *lv_image;
+ struct logical_volume *lv_iorig;
+ struct logical_volume *lv_imeta;
+ uint32_t area_count, s;
+ int is_active = lv_is_active(lv);
+
+ seg_top = first_seg(lv);
+
+ if (!seg_is_raid1(seg_top) && !seg_is_raid4(seg_top) &&
+ !seg_is_any_raid5(seg_top) && !seg_is_any_raid6(seg_top) &&
+ !seg_is_any_raid10(seg_top)) {
+ log_error("LV %s segment is unsupported raid for integrity.", display_lvname(lv));
+ return 0;
+ }
+
+ area_count = seg_top->area_count;
+
+ for (s = 0; s < area_count; s++) {
+ lv_image = seg_lv(seg_top, s);
+ seg_image = first_seg(lv_image);
+
+ if (!(lv_imeta = seg_image->integrity_meta_dev)) {
+ log_error("LV %s segment has no integrity metadata device.", display_lvname(lv));
+ return 0;
+ }
+
+ if (!(lv_iorig = seg_lv(seg_image, 0))) {
+ log_error("LV %s integrity segment has no origin", display_lvname(lv));
+ return 0;
+ }
+
+ if (!remove_seg_from_segs_using_this_lv(seg_image->integrity_meta_dev, seg_image))
+ return_0;
+
+ iorig_lvs[s] = lv_iorig;
+ imeta_lvs[s] = lv_imeta;
+
+ lv_image->status &= ~INTEGRITY;
+ seg_image->integrity_meta_dev = NULL;
+ seg_image->integrity_data_sectors = 0;
+ memset(&seg_image->integrity_settings, 0, sizeof(seg_image->integrity_settings));
+
+ if (!remove_layer_from_lv(lv_image, lv_iorig))
+ return_0;
+ }
+
+ if (is_active) {
+ /* vg_write(), suspend_lv(), vg_commit(), resume_lv() */
+ if (!lv_update_and_reload(lv)) {
+ log_error("Failed to update and reload LV after integrity remove.");
+ return 0;
+ }
+ }
+
+ for (s = 0; s < area_count; s++) {
+ lv_iorig = iorig_lvs[s];
+ lv_imeta = imeta_lvs[s];
+
+ if (is_active) {
+ if (!deactivate_lv(cmd, lv_iorig))
+ log_error("Failed to deactivate unused iorig LV %s.", lv_iorig->name);
+
+ if (!deactivate_lv(cmd, lv_imeta))
+ log_error("Failed to deactivate unused imeta LV %s.", lv_imeta->name);
+ }
+
+ lv_imeta->status &= ~INTEGRITY_METADATA;
+ lv_set_visible(lv_imeta);
+
+ if (!lv_remove(lv_iorig))
+ log_error("Failed to remove unused iorig LV %s.", lv_iorig->name);
+
+ if (!lv_remove(lv_imeta))
+ log_error("Failed to remove unused imeta LV %s.", lv_imeta->name);
+ }
+
+ if (!vg_write(vg) || !vg_commit(vg))
+ return_0;
+
+ return 1;
+}
+
+int lv_remove_integrity(struct logical_volume *lv)
+{
+ struct cmd_context *cmd = lv->vg->cmd;
+ struct volume_group *vg = lv->vg;
+ struct lv_segment *seg = first_seg(lv);
+ struct logical_volume *lv_iorig;
+ struct logical_volume *lv_imeta;
+ int is_active = lv_is_active(lv);
+
+ if (!seg_is_integrity(seg)) {
+ log_error("LV %s segment is not integrity.", display_lvname(lv));
+ return 0;
+ }
+
+ if (!seg->integrity_meta_dev) {
+ log_error("Internal integrity cannot be removed.");
+ return 0;
+ }
+
+ if (!(lv_imeta = seg->integrity_meta_dev)) {
+ log_error("LV %s segment has no integrity metadata device.", display_lvname(lv));
+ return 0;
+ }
+
+ if (!(lv_iorig = seg_lv(seg, 0))) {
+ log_error("LV %s integrity segment has no origin", display_lvname(lv));
+ return 0;
+ }
+
+ if (!remove_seg_from_segs_using_this_lv(seg->integrity_meta_dev, seg))
+ return_0;
+
+ lv->status &= ~INTEGRITY;
+ seg->integrity_meta_dev = NULL;
+
+ if (!remove_layer_from_lv(lv, lv_iorig))
+ return_0;
+
+ if (is_active) {
+ /* vg_write(), suspend_lv(), vg_commit(), resume_lv() */
+ if (!lv_update_and_reload(lv)) {
+ log_error("Failed to update and reload LV after integrity remove.");
+ return_0;
+ }
+
+ if (!deactivate_lv(cmd, lv_iorig))
+ log_error("Failed to deactivate unused iorig LV %s.", lv_iorig->name);
+
+ if (!deactivate_lv(cmd, lv_imeta))
+ log_error("Failed to deactivate unused imeta LV %s.", lv_iorig->name);
+ }
+
+ lv_imeta->status &= ~INTEGRITY_METADATA;
+ lv_set_visible(lv_imeta);
+
+ if (!lv_remove(lv_iorig))
+ log_error("Failed to remove unused iorig LV %s.", lv_iorig->name);
+
+ if (!lv_remove(lv_imeta))
+ log_error("Failed to remove unused imeta LV %s.", lv_imeta->name);
+
+ if (!vg_write(vg) || !vg_commit(vg))
+ return_0;
+
+ return 1;
+}
+
+/*
+ * Add integrity to each raid image.
+ *
+ * for each rimage_N:
+ * . create and allocate a new linear LV rimage_N_imeta
+ * . move the segments from rimage_N to a new rimage_N_iorig
+ * . add an integrity segment to rimage_N with
+ * origin=rimage_N_iorig, meta_dev=rimage_N_imeta
+ *
+ * Before:
+ * rimage_0
+ * segment1: striped: pv0:A
+ * rimage_1
+ * segment1: striped: pv1:B
+ *
+ * After:
+ * rimage_0
+ * segment1: integrity: rimage_0_iorig, rimage_0_imeta
+ * rimage_1
+ * segment1: integrity: rimage_1_iorig, rimage_1_imeta
+ * rimage_0_iorig
+ * segment1: striped: pv0:A
+ * rimage_1_iorig
+ * segment1: striped: pv1:B
+ * rimage_0_imeta
+ * segment1: striped: pv2:A
+ * rimage_1_imeta
+ * segment1: striped: pv2:B
+ *
+ */
+
+int lv_add_integrity_to_raid(struct logical_volume *lv, const char *arg,
+ const char *meta_name,
+ struct integrity_settings *settings,
+ struct dm_list *pvh)
+{
+ struct lvcreate_params lp;
+ struct dm_list allocatable_pvs;
+ struct logical_volume *imeta_lvs[DEFAULT_RAID_MAX_IMAGES];
+ struct cmd_context *cmd = lv->vg->cmd;
+ struct volume_group *vg = lv->vg;
+ struct logical_volume *lv_image, *lv_imeta, *lv_iorig;
+ struct lv_segment *seg_top, *seg_image;
+ const struct segment_type *segtype;
+ struct integrity_settings *set;
+ struct device *meta_dev = NULL;
+ struct dm_list *use_pvh = NULL;
+ uint64_t status_data_sectors = 0;
+ uint32_t area_count, s;
+ uint32_t revert_meta_lvs = 0;
+ int is_active;
+ int external = 0, internal = 0;
+ int ret;
+
+ memset(imeta_lvs, 0, sizeof(imeta_lvs));
+
+ is_active = lv_is_active(lv);
+
+ if (!arg || !strcmp(arg, "y") || !strcmp(arg, "external"))
+ external = 1;
+ else if (!strcmp(arg, "internal"))
+ internal = 1;
+ else {
+ log_error("Invalid --integrity arg for lvcreate.");
+ return 0;
+ }
+
+ if (meta_name) {
+ if (!(meta_dev = dev_cache_get(cmd, meta_name, NULL))) {
+ log_error("integritymetadata device not found %s.", meta_name);
+ return 0;
+ }
+ if (!(use_pvh = create_pv_list(cmd->mem, vg, 1, (char **)&meta_name, 1))) {
+ log_error("integritymetadata not found in VG %s.", meta_name);
+ return 0;
+ }
+ meta_name = NULL;
+ }
+
+ if (dm_list_size(&lv->segments) != 1)
+ return_0;
+
+ seg_top = first_seg(lv);
+ area_count = seg_top->area_count;
+
+ if (!seg_is_raid1(seg_top) && !seg_is_raid4(seg_top) &&
+ !seg_is_any_raid5(seg_top) && !seg_is_any_raid6(seg_top) &&
+ !seg_is_any_raid10(seg_top)) {
+ log_error("Integrity can only be added to raid1,4,5,6,10.");
+ return 0;
+ }
+
+ if (internal) {
+ /*
+ * FIXME: raid on internal integrity might not be used widely
+ * enough to enable, given the additional complexity/support.
+ * i.e. nearly everyone may just use external metadata.
+ *
+ * FIXME: to use internal with raid:
+ * _info_run() needs code to adjust the length, like
+ * is done for if (lv_is_integrity()) length = ...
+ * _raid_add_target_line() the len needs to be adjusted
+ * to integrity_data_sectors.
+ */
+ /* goto skip_imeta; */
+ log_error("Internal integrity metadata is not supported with raid.");
+ return 0;
+ }
+
+ /*
+ * For each rimage, create an _imeta LV for integrity metadata.
+ * Each needs to be zeroed.
+ */
+ for (s = 0; s < area_count; s++) {
+ struct logical_volume *meta_lv;
+ struct wipe_params wipe = { .do_zero = 1, .zero_sectors = 8 };
+
+ if (s >= DEFAULT_RAID_MAX_IMAGES)
+ goto_bad;
+
+ lv_image = seg_lv(seg_top, s);
+
+ /*
+ * This function is used to add integrity to new images added
+ * to the raid, in which case old images will already be
+ * integrity.
+ */
+ if (seg_is_integrity(first_seg(lv_image)))
+ continue;
+
+ if (!seg_is_striped(first_seg(lv_image))) {
+ log_error("raid image must be linear to add integrity");
+ goto_bad;
+ }
+
+ dm_list_init(&allocatable_pvs);
+
+ if (!get_pv_list_for_lv(cmd->mem, lv_image, &allocatable_pvs)) {
+ log_error("Failed to build list of PVs for %s.", display_lvname(lv_image));
+ goto_bad;
+ }
+
+ if (!use_pvh)
+ use_pvh = &allocatable_pvs;
+
+ /*
+ * allocate a new linear LV NAME_rimage_N_imeta
+ */
+ memset(&lp, 0, sizeof(lp));
+ lp.lv_name = lv_image->name;
+ lp.pvh = use_pvh;
+ lp.extents = lv_image->size / vg->extent_size;
+
+ if (!_lv_create_integrity_metadata(cmd, vg, &lp, &meta_lv))
+ goto_bad;
+
+ revert_meta_lvs++;
+
+ /* Used below to set up the new integrity segment. */
+ imeta_lvs[s] = meta_lv;
+
+ /*
+ * dm-integrity requires the metadata LV header to be zeroed.
+ */
+
+ if (!activate_lv(cmd, meta_lv)) {
+ log_error("Failed to activate LV %s to zero", display_lvname(meta_lv));
+ goto_bad;
+ }
+
+ if (!wipe_lv(meta_lv, wipe)) {
+ log_error("Failed to zero LV for integrity metadata %s", display_lvname(meta_lv));
+ if (deactivate_lv(cmd, meta_lv))
+ log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv));
+ goto_bad;
+ }
+
+ if (!deactivate_lv(cmd, meta_lv)) {
+ log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv));
+ goto_bad;
+ }
+ }
+
+/* skip_imeta: */
+
+ /*
+ * For each rimage, move its segments to a new rimage_iorig and give
+ * the rimage a new integrity segment.
+ */
+ for (s = 0; s < area_count; s++) {
+ lv_image = seg_lv(seg_top, s);
+
+ /* Not adding integrity to this image. */
+ if (!imeta_lvs[s])
+ continue;
+
+ if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_INTEGRITY)))
+ goto_bad;
+
+ log_debug("Adding integrity to raid image %s", lv_image->name);
+
+ /*
+ * "lv_iorig" is a new LV with new id, but with the segments
+ * from "lv_image". "lv_image" keeps the existing name and id,
+ * but gets a new integrity segment, in place of the segments
+ * that were moved to lv_iorig.
+ */
+ if (!(lv_iorig = insert_layer_for_lv(cmd, lv_image, INTEGRITY, "_iorig")))
+ goto_bad;
+
+ lv_image->status |= INTEGRITY;
+
+ /*
+ * Set up the new first segment of lv_image as integrity.
+ */
+ seg_image = first_seg(lv_image);
+ seg_image->segtype = segtype;
+
+ if (external) {
+ lv_imeta = imeta_lvs[s]; /* external metadata lv created above */
+ lv_imeta->status |= INTEGRITY_METADATA;
+ lv_set_hidden(lv_imeta);
+ seg_image->integrity_data_sectors = lv_image->size;
+ seg_image->integrity_meta_dev = lv_imeta;
+ seg_image->integrity_recalculate = 1;
+ }
+
+ memcpy(&seg_image->integrity_settings, settings, sizeof(struct integrity_settings));
+ set = &seg_image->integrity_settings;
+
+ if (!set->mode[0])
+ set->mode[0] = DEFAULT_MODE;
+
+ if (!set->tag_size)
+ set->tag_size = DEFAULT_TAG_SIZE;
+
+ if (!set->internal_hash)
+ set->internal_hash = DEFAULT_INTERNAL_HASH;
+ }
+
+ /*
+ * When using internal metadata, we have to temporarily activate the
+ * integrity image with size 1 to get provided_data_sectors from the
+ * dm-integrity module.
+ */
+ if (internal) {
+ /* Get size from the first image, others will be the same. */
+ lv_image = seg_lv(seg_top, 0);
+
+ lv_image->status |= LV_TEMPORARY;
+ lv_image->status |= LV_NOSCAN;
+ seg_image = first_seg(lv_image);
+ seg_image->integrity_data_sectors = 1;
+
+ /* write-commit allows activating the LV to get data_sectors */
+ if (!vg_write(vg) || !vg_commit(vg)) {
+ log_error("Preliminary internal integrity write commit error");
+ goto_bad;
+ }
+
+ log_debug("Activating temporary integrity LV to get data sectors.");
+
+ if (!activate_lv(cmd, lv_image)) {
+ log_error("Failed to activate temporary integrity.");
+ goto_bad;
+ }
+
+ if (!_get_provided_data_sectors(lv_image, &status_data_sectors)) {
+ log_error("Failed to get data sectors from dm-integrity");
+ ret = 0;
+ } else {
+ log_debug("Found integrity provided_data_sectors %llu", (unsigned long long)status_data_sectors);
+ ret = 1;
+ }
+
+ if (!status_data_sectors) {
+ log_error("LV size too small to include metadata.");
+ ret = 0;
+ }
+
+ if (!deactivate_lv(cmd, lv_image)) {
+ log_error("Failed to deactivate temporary integrity.");
+ ret = 0;
+ }
+
+ if (!ret)
+ goto_bad;
+
+ lv_image->status &= ~LV_NOSCAN;
+ lv_image->status &= ~LV_TEMPORARY;
+
+ /* The main point, setting integrity_data_sectors. */
+ for (s = 0; s < area_count; s++) {
+ lv_image = seg_lv(seg_top, s);
+ seg_image = first_seg(lv_image);
+ seg_image->integrity_data_sectors = status_data_sectors;
+ seg_image->integrity_recalculate = 1;
+ }
+ }
+
+ if (is_active) {
+ log_debug("Writing VG and updating LV with new integrity LV %s", lv->name);
+
+ /* vg_write(), suspend_lv(), vg_commit(), resume_lv() */
+ if (!lv_update_and_reload(lv)) {
+ log_error("LV update and reload failed");
+ goto_bad;
+ }
+ revert_meta_lvs = 0;
+
+ } else {
+ log_debug("Writing VG with new integrity LV %s", lv->name);
+
+ if (!vg_write(vg) || !vg_commit(vg))
+ goto_bad;
+
+ revert_meta_lvs = 0;
+
+ /*
+ * This first activation includes "recalculate" which starts the
+ * kernel's recalculating (initialization) process.
+ */
+
+ log_debug("Activating to start integrity initialization for LV %s", lv->name);
+
+ if (!activate_lv(cmd, lv)) {
+ log_error("Failed to activate integrity LV to initialize.");
+ goto_bad;
+ }
+ }
+
+ /*
+ * Now that the device is being initialized, update the VG to clear
+ * integrity_recalculate so that subsequent activations will not
+ * include "recalculate" and restart initialization.
+ */
+
+ log_debug("Writing VG with initialized integrity LV %s", lv->name);
+
+ for (s = 0; s < area_count; s++) {
+ lv_image = seg_lv(seg_top, s);
+ seg_image = first_seg(lv_image);
+ seg_image->integrity_recalculate = 0;
+ }
+
+ if (!vg_write(vg) || !vg_commit(vg))
+ goto_bad;
+
+ return 1;
+
+bad:
+ log_error("Failed to add integrity.");
+
+ for (s = 0; s < revert_meta_lvs; s++) {
+ if (!lv_remove(imeta_lvs[s]))
+ log_error("New integrity metadata LV may require manual removal.");
+ }
+
+ if (!vg_write(vg) || !vg_commit(vg))
+ log_error("New integrity metadata LV may require manual removal.");
+
+ return 0;
+}
+
+int lv_add_integrity(struct logical_volume *lv, const char *arg,
+ const char *meta_name,
+ struct integrity_settings *settings,
+ struct dm_list *pvh)
+{
+ char imeta_name[NAME_LEN];
+ struct cmd_context *cmd = lv->vg->cmd;
+ struct volume_group *vg = lv->vg;
+ struct integrity_settings *segset;
+ struct logical_volume *lv_orig;
+ struct logical_volume *meta_lv = NULL;
+ const struct segment_type *segtype;
+ struct device *meta_dev = NULL;
+ struct dm_list *use_pvh = pvh;
+ struct lv_segment *seg;
+ uint64_t lv_size_sectors;
+ int revert_meta_lv = 0;
+ int is_active;
+ int external = 0, internal = 0;
+ int ret;
+
+ lv_size_sectors = lv->size;
+
+ is_active = lv_is_active(lv);
+
+ /*
+ * --integrity <arg> is y|external|internal
+ */
+
+ if (!arg || !strcmp(arg, "y") || !strcmp(arg, "external"))
+ external = 1;
+ else if (!strcmp(arg, "internal"))
+ internal = 1;
+ else {
+ log_error("Invalid --integrity arg for lvcreate.");
+ return 0;
+ }
+
+ if (internal && meta_name) {
+ log_error("Internal integrity cannot be used with integritymetadata option.");
+ return 0;
+ }
+
+ if (meta_name) {
+ if (!(meta_lv = find_lv(vg, meta_name))) {
+ if (!(meta_dev = dev_cache_get(cmd, meta_name, NULL))) {
+ log_error("integritymetadata device not found %s.", meta_name);
+ return 0;
+ }
+ if (!(use_pvh = create_pv_list(cmd->mem, vg, 1, (char **)&meta_name, 1))) {
+ log_error("integritymetadata not found in VG %s.", meta_name);
+ return 0;
+ }
+ meta_name = NULL;
+ }
+ }
+
+ if (external && !meta_lv) {
+ struct lvcreate_params lp = { 0 };
+ lp.lv_name = lv->name;
+ lp.pvh = use_pvh;
+ lp.extents = lv->size / vg->extent_size;
+
+ if (!_lv_create_integrity_metadata(cmd, vg, &lp, &meta_lv))
+ goto_bad;
+
+ revert_meta_lv = 1;
+
+ } else if (external && meta_lv) {
+ uint64_t meta_bytes, meta_sectors;
+
+ meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv_size_sectors * 512);
+ meta_sectors = meta_bytes / 512;
+
+ if (meta_lv->size < meta_sectors) {
+ log_error("Integrity metadata needs %s, metadata LV is only %s.",
+ display_size(cmd, meta_sectors), display_size(cmd, meta_lv->size));
+ goto_bad;
+ }
+ }
+
+ if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_INTEGRITY)))
+ goto_bad;
+
+ /*
+ * "lv_orig" is a new LV with new id, but with the segments from "lv".
+ * "lv" keeps the existing name and id, but gets a new integrity segment,
+ * in place of the segments that were moved to lv_orig.
+ */
+
+ if (!(lv_orig = insert_layer_for_lv(cmd, lv, INTEGRITY, "_iorig")))
+ goto_bad;
+
+ seg = first_seg(lv);
+ seg->segtype = segtype;
+
+ lv->status |= INTEGRITY;
+
+ memcpy(&seg->integrity_settings, settings, sizeof(struct integrity_settings));
+ segset = &seg->integrity_settings;
+
+ if (!segset->mode[0])
+ segset->mode[0] = DEFAULT_MODE;
+
+ if (!segset->tag_size)
+ segset->tag_size = DEFAULT_TAG_SIZE;
+
+ if (!segset->internal_hash)
+ segset->internal_hash = DEFAULT_INTERNAL_HASH;
+
+ /*
+ * When not using a metadata LV, dm-integrity needs to tell us what the
+ * usable size of the LV is, which is the dev size minus the integrity
+ * overhead. To find that, we need to do a special, temporary activation
+ * of the new LV, specifying a dm dev size of 1, then check the dm dev
+ * status field provided_data_sectors, which is the actual size of the
+ * LV. We need to include provided_data_sectors in the metadata for the
+ * new LV, and use this as the dm dev size for normal LV activation.
+ *
+ * When using a metadata LV, the dm dev size is the size of the data
+ * device. The necessary size of the metadata LV for the given data
+ * device needs to be estimated.
+ */
+
+ if (meta_lv) {
+ struct wipe_params wipe = { .do_zero = 1, .zero_sectors = 8 };
+
+ if (!sync_local_dev_names(cmd)) {
+ log_error("Failed to sync local devices.");
+ goto_bad;
+ }
+
+ log_verbose("Zeroing LV for integrity metadata");
+
+ if (!lv_is_active(meta_lv)) {
+ if (!activate_lv(cmd, meta_lv)) {
+ log_error("Failed to activate LV %s to zero", display_lvname(meta_lv));
+ goto_bad;
+ }
+ }
+
+ if (!wipe_lv(meta_lv, wipe)) {
+ log_error("Failed to zero LV for integrity metadata %s", display_lvname(meta_lv));
+ if (!deactivate_lv(cmd, meta_lv))
+ log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv));
+ goto_bad;
+ }
+
+ if (!deactivate_lv(cmd, meta_lv)) {
+ log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv));
+ goto_bad;
+ }
+
+ if (meta_name) {
+ /* An existing LV was specified to use for metaddata. */
+ /* LVM tradition to add a suffix to an existing LV when using it. */
+ if (dm_snprintf(imeta_name, sizeof(imeta_name), "%s_imeta", meta_lv->name) < 0) {
+ log_error("Can't prepare new imeta name for %s", display_lvname(meta_lv));
+ goto_bad;
+ }
+ if (!lv_rename_update(cmd, meta_lv, imeta_name, 0))
+ goto_bad;
+ }
+
+ meta_lv->status |= INTEGRITY_METADATA;
+ seg->integrity_data_sectors = lv_size_sectors;
+ seg->integrity_recalculate = 1;
+ seg->integrity_meta_dev = meta_lv;
+ lv_set_hidden(meta_lv);
+ } else {
+ /* dm-integrity wants temp/fake size of 1 to report usable size */
+ seg->integrity_data_sectors = 1;
+ seg->integrity_recalculate = 1;
+
+ /* write-commit allows activating the LV to get data_sectors */
+ if (!vg_write(vg) || !vg_commit(vg)) {
+ log_error("Preliminary internal integrity write commit error");
+ goto_bad;
+ }
+
+ lv->status |= LV_TEMPORARY;
+ lv->status |= LV_NOSCAN;
+
+ log_debug("Activating temporary integrity LV to get data sectors.");
+
+ if (!activate_lv(cmd, lv)) {
+ log_error("Failed to activate temporary integrity.");
+ goto_bad;
+ }
+
+ if (!_get_provided_data_sectors(lv, &seg->integrity_data_sectors)) {
+ log_error("Failed to get data sectors from dm-integrity");
+ ret = 0;
+ } else {
+ log_debug("Found integrity provided_data_sectors %llu", (unsigned long long)seg->integrity_data_sectors);
+ ret = 1;
+ }
+
+ if (!seg->integrity_data_sectors) {
+ log_error("LV size too small to include metadata.");
+ ret = 0;
+ }
+
+ if (!deactivate_lv(cmd, lv)) {
+ log_error("Failed to deactivate temporary integrity.");
+ ret = 0;
+ }
+
+ lv->status &= ~LV_NOSCAN;
+ lv->status &= ~LV_TEMPORARY;
+
+ if (!ret)
+ goto_bad;
+ }
+
+ if (is_active) {
+ log_debug("Writing VG and updating LV with new integrity LV %s", lv->name);
+
+ /* vg_write(), suspend_lv(), vg_commit(), resume_lv() */
+ if (!lv_update_and_reload(lv)) {
+ log_error("LV update and reload failed");
+ goto_bad;
+ }
+ revert_meta_lv = 0;
+
+ } else {
+ log_debug("Writing VG with new integrity LV %s", lv->name);
+
+ if (!vg_write(vg) || !vg_commit(vg))
+ goto_bad;
+
+ revert_meta_lv = 0;
+
+ /*
+ * This first activation includes "recalculate" which starts the
+ * kernel's recalculating (initialization) process.
+ */
+
+ log_debug("Activating to start integrity initialization for LV %s", lv->name);
+
+ if (!activate_lv(cmd, lv)) {
+ log_error("Failed to activate integrity LV to initialize.");
+ goto_bad;
+ }
+ }
+
+ /*
+ * Now that the device is being initialized, update the VG to clear
+ * integrity_recalculate so that subsequent activations will not
+ * include "recalculate" and restart initialization.
+ *
+ * If command fails here, before clearing integrity_recalculate from
+ * the metadata, then the next activation will clear this
+ * (see lv_clear_integrity_recalculate_metadata).
+ */
+
+ log_debug("Writing VG with initialized integrity LV %s", lv->name);
+
+ seg->integrity_recalculate = 0;
+
+ if (!vg_write(vg) || !vg_commit(vg))
+ goto_bad;
+
+ return 1;
+
+ bad:
+ log_error("Failed to add integrity.");
+
+ if (revert_meta_lv) {
+ log_debug("Removing meta LV %s", meta_lv->name);
+ if (!lv_remove(meta_lv) || !vg_write(vg) || !vg_commit(vg))
+ log_error("New integrity metadata LV may require manual removal: %s", display_lvname(meta_lv));
+ }
+
+ return 0;
+}
+
+/*
+ * This should rarely if ever be used. A command that adds integrity
+ * to an LV will activate and then clear the flag. If it fails before
+ * clearing the flag, then this function will be used by a subsequent
+ * activation to clear the flag.
+ */
+void lv_clear_integrity_recalculate_metadata(struct logical_volume *lv)
+{
+ struct volume_group *vg = lv->vg;
+ struct logical_volume *lv_image;
+ struct lv_segment *seg, *seg_image;
+ uint32_t s;
+
+ seg = first_seg(lv);
+
+ if (seg_is_raid(seg)) {
+ for (s = 0; s < seg->area_count; s++) {
+ lv_image = seg_lv(seg, s);
+ seg_image = first_seg(lv_image);
+ seg_image->integrity_recalculate = 0;
+ }
+ } else if (seg_is_integrity(seg)) {
+ seg->integrity_recalculate = 0;
+ } else {
+ log_error("Invalid LV type for clearing integrity");
+ return;
+ }
+
+ if (!vg_write(vg) || !vg_commit(vg)) {
+ log_warn("WARNING: failed to clear integrity recalculate flag for %s",
+ display_lvname(lv));
+ }
+}
+
+int lv_has_integrity_recalculate_metadata(struct logical_volume *lv)
+{
+ struct logical_volume *lv_image;
+ struct lv_segment *seg, *seg_image;
+ uint32_t s;
+ int ret = 0;
+
+ seg = first_seg(lv);
+
+ if (seg_is_raid(seg)) {
+ for (s = 0; s < seg->area_count; s++) {
+ lv_image = seg_lv(seg, s);
+ seg_image = first_seg(lv_image);
+
+ if (!seg_is_integrity(seg_image))
+ continue;
+ if (seg_image->integrity_recalculate)
+ ret = 1;
+ }
+ } else if (seg_is_integrity(seg)) {
+ ret = seg->integrity_recalculate;
+ }
+
+ return ret;
+}
+
+int lv_has_integrity_internal(struct logical_volume *lv)
+{
+ struct logical_volume *lv_image;
+ struct lv_segment *seg, *seg_image;
+ uint32_t s;
+
+ seg = first_seg(lv);
+
+ if (seg_is_raid(seg)) {
+ for (s = 0; s < seg->area_count; s++) {
+ lv_image = seg_lv(seg, s);
+ seg_image = first_seg(lv_image);
+
+ if (!seg_is_integrity(seg_image))
+ continue;
+ if (!seg_image->integrity_meta_dev)
+ return 1;
+ }
+ } else if (seg_is_integrity(seg)) {
+ if (!seg->integrity_meta_dev)
+ return 1;
+ }
+ return 0;
+}
+
+int lv_raid_has_integrity(struct logical_volume *lv)
+{
+ struct logical_volume *lv_image;
+ struct lv_segment *seg, *seg_image;
+ uint32_t s;
+
+ seg = first_seg(lv);
+
+ if (seg_is_raid(seg)) {
+ for (s = 0; s < seg->area_count; s++) {
+ lv_image = seg_lv(seg, s);
+ seg_image = first_seg(lv_image);
+
+ if (seg_is_integrity(seg_image))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int lv_get_raid_integrity_settings(struct logical_volume *lv, struct integrity_settings **isettings)
+{
+ struct logical_volume *lv_image;
+ struct lv_segment *seg, *seg_image;
+ uint32_t s;
+
+ seg = first_seg(lv);
+
+ if (seg_is_raid(seg)) {
+ for (s = 0; s < seg->area_count; s++) {
+ lv_image = seg_lv(seg, s);
+ seg_image = first_seg(lv_image);
+
+ if (seg_is_integrity(seg_image)) {
+ *isettings = &seg_image->integrity_settings;
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c
index ab26b8dae..7694cb5de 100644
--- a/lib/metadata/lv.c
+++ b/lib/metadata/lv.c
@@ -385,6 +385,17 @@ dm_percent_t lvseg_percent_with_info_and_seg_status(const struct lv_with_info_an
* Esentially rework _target_percent API for segtype.
*/
switch (s->type) {
+ case SEG_STATUS_INTEGRITY:
+ if (type != PERCENT_GET_DIRTY)
+ p = DM_PERCENT_INVALID;
+ else if (!s->integrity->recalc_sector)
+ p = DM_PERCENT_INVALID;
+ else if (s->integrity->recalc_sector == s->integrity->provided_data_sectors)
+ p = DM_PERCENT_100;
+ else
+ p = dm_make_percent(s->integrity->recalc_sector,
+ s->integrity->provided_data_sectors);
+ break;
case SEG_STATUS_CACHE:
if (s->cache->fail || s->cache->error)
p = DM_PERCENT_INVALID;
@@ -593,6 +604,8 @@ struct logical_volume *lv_origin_lv(const struct logical_volume *lv)
origin = first_seg(lv)->external_lv;
else if (lv_is_writecache(lv) && first_seg(lv)->origin)
origin = first_seg(lv)->origin;
+ else if (lv_is_integrity(lv) && first_seg(lv)->origin)
+ origin = first_seg(lv)->origin;
return origin;
}
@@ -1208,10 +1221,13 @@ char *lv_attr_dup_with_info_and_seg_status(struct dm_pool *mem, const struct lv_
repstr[0] = (lv_is_merging_origin(lv)) ? 'O' : 'o';
else if (lv_is_pool_metadata(lv) ||
lv_is_pool_metadata_spare(lv) ||
- lv_is_raid_metadata(lv))
+ lv_is_raid_metadata(lv) ||
+ lv_is_integrity_metadata(lv))
repstr[0] = 'e';
else if (lv_is_cache_type(lv) || lv_is_writecache(lv))
repstr[0] = 'C';
+ else if (lv_is_integrity(lv))
+ repstr[0] = 'g';
else if (lv_is_raid(lv))
repstr[0] = (lv_is_not_synced(lv)) ? 'R' : 'r';
else if (lv_is_mirror(lv))
diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c
index 3604a6347..8b1931902 100644
--- a/lib/metadata/lv_manip.c
+++ b/lib/metadata/lv_manip.c
@@ -134,7 +134,9 @@ enum {
LV_TYPE_SANLOCK,
LV_TYPE_CACHEVOL,
LV_TYPE_WRITECACHE,
- LV_TYPE_WRITECACHEORIGIN
+ LV_TYPE_WRITECACHEORIGIN,
+ LV_TYPE_INTEGRITY,
+ LV_TYPE_INTEGRITYORIGIN
};
static const char *_lv_type_names[] = {
@@ -190,6 +192,8 @@ static const char *_lv_type_names[] = {
[LV_TYPE_CACHEVOL] = "cachevol",
[LV_TYPE_WRITECACHE] = "writecache",
[LV_TYPE_WRITECACHEORIGIN] = "writecacheorigin",
+ [LV_TYPE_INTEGRITY] = "integrity",
+ [LV_TYPE_INTEGRITYORIGIN] = "integrityorigin",
};
static int _lv_layout_and_role_mirror(struct dm_pool *mem,
@@ -461,6 +465,43 @@ bad:
return 0;
}
+static int _lv_layout_and_role_integrity(struct dm_pool *mem,
+ const struct logical_volume *lv,
+ struct dm_list *layout,
+ struct dm_list *role,
+ int *public_lv)
+{
+ int top_level = 0;
+
+ /* non-top-level LVs */
+ if (lv_is_integrity_metadata(lv)) {
+ if (!str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_INTEGRITY]) ||
+ !str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_METADATA]))
+ goto_bad;
+ } else if (lv_is_integrity_origin(lv)) {
+ if (!str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_INTEGRITY]) ||
+ !str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_ORIGIN]) ||
+ !str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_INTEGRITYORIGIN]))
+ goto_bad;
+ } else
+ top_level = 1;
+
+ if (!top_level) {
+ *public_lv = 0;
+ return 1;
+ }
+
+ /* top-level LVs */
+ if (lv_is_integrity(lv)) {
+ if (!str_list_add_no_dup_check(mem, layout, _lv_type_names[LV_TYPE_INTEGRITY]))
+ goto_bad;
+ }
+
+ return 1;
+bad:
+ return 0;
+}
+
static int _lv_layout_and_role_thick_origin_snapshot(struct dm_pool *mem,
const struct logical_volume *lv,
struct dm_list *layout,
@@ -577,6 +618,11 @@ int lv_layout_and_role(struct dm_pool *mem, const struct logical_volume *lv,
!_lv_layout_and_role_cache(mem, lv, *layout, *role, &public_lv))
goto_bad;
+ /* Integrity related */
+ if ((lv_is_integrity(lv) || lv_is_integrity_origin(lv) || lv_is_integrity_metadata(lv)) &&
+ !_lv_layout_and_role_integrity(mem, lv, *layout, *role, &public_lv))
+ goto_bad;
+
/* VDO and related */
if (lv_is_vdo_type(lv) &&
!_lv_layout_and_role_vdo(mem, lv, *layout, *role, &public_lv))
@@ -1457,6 +1503,15 @@ static int _lv_reduce(struct logical_volume *lv, uint32_t extents, int delete)
return_0;
}
+ if (delete && seg_is_integrity(seg)) {
+ /* Remove integrity origin in addition to integrity layer. */
+ if (!lv_remove(seg_lv(seg, 0)))
+ return_0;
+ /* Remove integrity metadata. */
+ if (seg->integrity_meta_dev && !lv_remove(seg->integrity_meta_dev))
+ return_0;
+ }
+
if ((pool_lv = seg->pool_lv)) {
if (!detach_pool_lv(seg))
return_0;
@@ -4111,11 +4166,14 @@ static int _lv_extend_layered_lv(struct alloc_handle *ah,
uint32_t extents, uint32_t first_area,
uint32_t mirrors, uint32_t stripes, uint32_t stripe_size)
{
+ struct logical_volume *sub_lvs[DEFAULT_RAID_MAX_IMAGES];
const struct segment_type *segtype;
- struct logical_volume *sub_lv, *meta_lv;
+ struct logical_volume *meta_lv, *sub_lv;
struct lv_segment *seg = first_seg(lv);
+ struct lv_segment *sub_lv_seg;
uint32_t fa, s;
int clear_metadata = 0;
+ int integrity_sub_lvs = 0;
uint32_t area_multiple = 1;
if (!(segtype = get_segtype_from_string(lv->vg->cmd, SEG_TYPE_NAME_STRIPED)))
@@ -4133,16 +4191,28 @@ static int _lv_extend_layered_lv(struct alloc_handle *ah,
area_multiple = seg->area_count;
}
+ for (s = 0; s < seg->area_count; s++) {
+ sub_lv = seg_lv(seg, s);
+ sub_lv_seg = sub_lv ? first_seg(sub_lv) : NULL;
+
+ if (sub_lv_seg && seg_is_integrity(sub_lv_seg)) {
+ sub_lvs[s] = seg_lv(sub_lv_seg, 0);
+ integrity_sub_lvs = 1;
+ } else
+ sub_lvs[s] = sub_lv;
+ }
+
for (fa = first_area, s = 0; s < seg->area_count; s++) {
- if (is_temporary_mirror_layer(seg_lv(seg, s))) {
- if (!_lv_extend_layered_lv(ah, seg_lv(seg, s), extents / area_multiple,
+ sub_lv = sub_lvs[s];
+
+ if (is_temporary_mirror_layer(sub_lv)) {
+ if (!_lv_extend_layered_lv(ah, sub_lv, extents / area_multiple,
fa, mirrors, stripes, stripe_size))
return_0;
- fa += lv_mirror_count(seg_lv(seg, s));
+ fa += lv_mirror_count(sub_lv);
continue;
}
- sub_lv = seg_lv(seg, s);
if (!lv_add_segment(ah, fa, stripes, sub_lv, segtype,
stripe_size, sub_lv->status, 0)) {
log_error("Aborting. Failed to extend %s in %s.",
@@ -4184,6 +4254,41 @@ static int _lv_extend_layered_lv(struct alloc_handle *ah,
fa += stripes;
}
+ /*
+ * In raid+integrity, the lv_iorig raid images have been extended above.
+ * Now propagate the new lv_iorig sizes up to the integrity LV layers
+ * that are referencing the lv_iorig.
+ */
+ if (integrity_sub_lvs) {
+ for (s = 0; s < seg->area_count; s++) {
+ struct logical_volume *lv_image;
+ struct logical_volume *lv_iorig;
+ struct logical_volume *lv_imeta;
+ struct lv_segment *seg_image;
+
+ lv_image = seg_lv(seg, s);
+ seg_image = first_seg(lv_image);
+
+ if (!(lv_imeta = seg_image->integrity_meta_dev)) {
+ log_error("1");
+ return_0;
+ }
+
+ if (!(lv_iorig = seg_lv(seg_image, 0))) {
+ log_error("2");
+ return_0;
+ }
+
+ /* new size in sectors */
+ lv_image->size = lv_iorig->size;
+ seg_image->integrity_data_sectors = lv_iorig->size;
+ /* new size in extents */
+ lv_image->le_count = lv_iorig->le_count;
+ seg_image->len = lv_iorig->le_count;
+ seg_image->area_len = lv_iorig->le_count;
+ }
+ }
+
seg->len += extents;
if (seg_is_raid(seg))
seg->area_len = seg->len;
@@ -4258,7 +4363,7 @@ int lv_extend(struct logical_volume *lv,
uint32_t mirrors, uint32_t region_size,
uint32_t extents,
struct dm_list *allocatable_pvs, alloc_policy_t alloc,
- int approx_alloc)
+ int approx_alloc, const char *integritymetadata)
{
int r = 1;
int log_count = 0;
@@ -4345,6 +4450,9 @@ int lv_extend(struct logical_volume *lv,
mirrors, stripes, stripe_size)))
goto_out;
+ if (lv_raid_has_integrity(lv))
+ lv_extend_integrity_in_raid(lv, allocatable_pvs, integritymetadata);
+
/*
* If we are expanding an existing mirror, we can skip the
* resync of the extension if the LV is currently in-sync
@@ -4390,6 +4498,11 @@ int lv_extend(struct logical_volume *lv,
}
out:
+ if (r && lv_is_integrity_origin(lv)) {
+ if (!lv_extend_integrity_for_origin(lv, allocatable_pvs, integritymetadata))
+ r = 0;
+ }
+
alloc_destroy(ah);
return r;
}
@@ -5064,6 +5177,20 @@ static int _lvresize_check(struct logical_volume *lv,
return 0;
}
+ if (lv_is_integrity(lv) || lv_raid_has_integrity(lv)) {
+ if (lv_has_integrity_internal(lv)) {
+ log_error("LV with internal integrity cannot be extended.");
+ return 0;
+ }
+ if (lp->resize == LV_REDUCE) {
+ log_error("Cannot reduce LV with integrity.");
+ return 0;
+ }
+ if (lv_is_active(lv)) {
+ log_error("Resize not allowed while LV with integrity is active.");
+ return 0;
+ }
+ }
return 1;
}
@@ -5578,7 +5705,7 @@ static int _lvresize_volume(struct logical_volume *lv,
lp->stripes, lp->stripe_size,
lp->mirrors, first_seg(lv)->region_size,
lp->extents - lv->le_count,
- pvh, alloc, lp->approx_alloc))
+ pvh, alloc, lp->approx_alloc, lp->integritymetadata))
return_0;
/* Check for over provisioning only when lv_extend() passed,
* ATM this check does not fail */
@@ -5613,6 +5740,9 @@ static int _lvresize_prepare(struct logical_volume **lv,
if (lv_is_thin_pool(*lv) || lv_is_vdo_pool(*lv))
*lv = seg_lv(first_seg(*lv), 0); /* switch to data LV */
+ if (lv_is_integrity(*lv))
+ *lv = seg_lv(first_seg(*lv), 0);
+
/* Resolve extents from size */
if (lp->size && !_lvresize_adjust_size(vg, lp->size, lp->sign, &lp->extents))
return_0;
@@ -7627,7 +7757,7 @@ static struct logical_volume *_create_virtual_origin(struct cmd_context *cmd,
return_NULL;
if (!lv_extend(lv, segtype, 1, 0, 1, 0, voriginextents,
- NULL, ALLOC_INHERIT, 0))
+ NULL, ALLOC_INHERIT, 0, NULL))
return_NULL;
return lv;
@@ -7948,6 +8078,11 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg,
/* FIXME Eventually support raid/mirrors with -m */
if (!(create_segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED)))
return_0;
+
+ } else if (seg_is_integrity(lp)) {
+ if (!(create_segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED)))
+ return_0;
+
} else if (seg_is_mirrored(lp) || (seg_is_raid(lp) && !seg_is_any_raid0(lp))) {
if (!(lp->region_size = adjusted_mirror_region_size(vg->cmd,
vg->extent_size,
@@ -8090,7 +8225,7 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg,
segtype_is_pool(create_segtype) ? lp->pool_metadata_extents : lp->region_size,
(segtype_is_thin_volume(create_segtype) ||
segtype_is_vdo(create_segtype)) ? lp->virtual_extents : lp->extents,
- lp->pvh, lp->alloc, lp->approx_alloc)) {
+ lp->pvh, lp->alloc, lp->approx_alloc, NULL)) {
unlink_lv_from_vg(lv); /* Keep VG consistent and remove LV without any segment */
return_NULL;
}
@@ -8198,6 +8333,53 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg,
goto out;
}
+ if (seg_is_integrity(lp) || (seg_is_raid(lp) && lp->integrity_arg)) {
+ log_debug("Adding integrity to new LV");
+
+ /*
+ * internal integrity requires wiping the origin, before
+ * integrity is added, so the integrity superblock is zero.
+ * This is independent of wiping the final LV below.
+ */
+ if (seg_is_integrity(lp) && lp->integrity_arg && !strcmp(lp->integrity_arg, "internal")) {
+ struct wipe_params wp = { .do_zero = 1, .zero_sectors = 8 };
+
+ log_debug("Activating to zero integrity superblock of origin %s", lv->name);
+ lv->status |= LV_TEMPORARY;
+ lv->status |= LV_NOSCAN;
+
+ if (!activate_lv(cmd, lv)) {
+ log_error("Failed to activate LV for zeroing integrity superblock.");
+ goto revert_new_lv;
+ }
+
+ lv->status &= ~LV_TEMPORARY;
+ lv->status &= ~LV_NOSCAN;
+
+ if (!wipe_lv(lv, wp)) {
+ log_error("Failed to zero integrity superblock of new origin.");
+ goto revert_new_lv;
+ }
+
+ if (!deactivate_lv(cmd, lv)) {
+ log_error("Failed to deactivate LV from zeroing integrity superblock.");
+ goto revert_new_lv;
+ }
+ }
+
+ if (seg_is_raid(lp)) {
+ if (!lv_add_integrity_to_raid(lv, lp->integrity_arg, lp->integrity_meta_name,
+ &lp->integrity_settings, lp->pvh))
+ goto revert_new_lv;
+ } else {
+ if (!lv_add_integrity(lv, lp->integrity_arg, lp->integrity_meta_name,
+ &lp->integrity_settings, lp->pvh))
+ goto revert_new_lv;
+ }
+
+ backup(vg);
+ }
+
/* Do not scan this LV until properly zeroed/wiped. */
if (_should_wipe_lv(lp, lv, 0))
lv->status |= LV_NOSCAN;
diff --git a/lib/metadata/merge.c b/lib/metadata/merge.c
index 11b26b469..ecd55efdd 100644
--- a/lib/metadata/merge.c
+++ b/lib/metadata/merge.c
@@ -742,6 +742,8 @@ int check_lv_segments(struct logical_volume *lv, int complete_vg)
seg_found++;
if (seg->metadata_lv == lv || seg->pool_lv == lv || seg->writecache == lv)
seg_found++;
+ if (seg->integrity_meta_dev == lv)
+ seg_found++;
if (seg_is_thin_volume(seg) && (seg->origin == lv || seg->external_lv == lv))
seg_found++;
diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h
index 35c12318f..9f2c7f306 100644
--- a/lib/metadata/metadata-exported.h
+++ b/lib/metadata/metadata-exported.h
@@ -84,12 +84,14 @@
#define CONVERTING UINT64_C(0x0000000000400000) /* LV */
#define MISSING_PV UINT64_C(0x0000000000800000) /* PV */
+#define INTEGRITY UINT64_C(0x0000000000800000) /* LV - Internal use only */
#define PV_MOVED_VG UINT64_C(0x4000000000000000) /* PV - Moved to a new VG */
#define PARTIAL_LV UINT64_C(0x0000000001000000) /* LV - derived flag, not
written out in metadata*/
//#define POSTORDER_FLAG UINT64_C(0x0000000002000000) /* Not real flags, reserved for
//#define POSTORDER_OPEN_FLAG UINT64_C(0x0000000004000000) temporary use inside vg_read_internal. */
+#define INTEGRITY_METADATA UINT64_C(0x0000000004000000) /* LV - Internal use only */
#define VIRTUAL_ORIGIN UINT64_C(0x0000000008000000) /* LV - internal use only */
#define MERGING UINT64_C(0x0000000010000000) /* LV SEG */
@@ -261,6 +263,8 @@
#define lv_is_pool_metadata_spare(lv) (((lv)->status & POOL_METADATA_SPARE) ? 1 : 0)
#define lv_is_lockd_sanlock_lv(lv) (((lv)->status & LOCKD_SANLOCK_LV) ? 1 : 0)
#define lv_is_writecache(lv) (((lv)->status & WRITECACHE) ? 1 : 0)
+#define lv_is_integrity(lv) (((lv)->status & INTEGRITY) ? 1 : 0)
+#define lv_is_integrity_metadata(lv) (((lv)->status & INTEGRITY_METADATA) ? 1 : 0)
#define lv_is_vdo(lv) (((lv)->status & LV_VDO) ? 1 : 0)
#define lv_is_vdo_pool(lv) (((lv)->status & LV_VDO_POOL) ? 1 : 0)
@@ -272,9 +276,11 @@
/* Recognize component LV (matching lib/misc/lvm-string.c _lvname_has_reserved_component_string()) */
#define lv_is_component(lv) (lv_is_cache_origin(lv) || \
lv_is_writecache_origin(lv) || \
+ lv_is_integrity_origin(lv) || \
((lv)->status & (\
CACHE_POOL_DATA |\
CACHE_POOL_METADATA |\
+ INTEGRITY_METADATA |\
LV_CACHE_VOL |\
LV_VDO_POOL_DATA |\
MIRROR_IMAGE |\
@@ -519,6 +525,11 @@ struct lv_segment {
uint32_t writecache_block_size; /* For writecache */
struct writecache_settings writecache_settings; /* For writecache */
+ uint64_t integrity_data_sectors;
+ struct logical_volume *integrity_meta_dev;
+ struct integrity_settings integrity_settings;
+ uint32_t integrity_recalculate;
+
struct dm_vdo_target_params vdo_params; /* For VDO-pool */
uint32_t vdo_pool_header_size; /* For VDO-pool */
uint32_t vdo_pool_virtual_extents; /* For VDO-pool */
@@ -682,6 +693,8 @@ struct lvresize_params {
const char *lockopt;
char *lockd_lv_refresh_path; /* set during resize to use for refresh at the end */
char *lockd_lv_refresh_uuid; /* set during resize to use for refresh at the end */
+
+ const char *integritymetadata;
};
void pvcreate_params_set_defaults(struct pvcreate_params *pp);
@@ -827,7 +840,7 @@ int lv_extend(struct logical_volume *lv,
uint32_t mirrors, uint32_t region_size,
uint32_t extents,
struct dm_list *allocatable_pvs, alloc_policy_t alloc,
- int approx_alloc);
+ int approx_alloc, const char *integritymetadata);
/* lv must be part of lv->vg->lvs */
int lv_remove(struct logical_volume *lv);
@@ -992,6 +1005,10 @@ struct lvcreate_params {
alloc_policy_t alloc; /* all */
struct dm_vdo_target_params vdo_params; /* vdo */
+ const char *integrity_arg;
+ const char *integrity_meta_name; /* external LV is user-specified */
+ struct integrity_settings integrity_settings;
+
struct dm_list tags; /* all */
int yes;
@@ -1086,6 +1103,8 @@ int lv_is_cache_origin(const struct logical_volume *lv);
int lv_is_writecache_origin(const struct logical_volume *lv);
int lv_is_writecache_cachevol(const struct logical_volume *lv);
+int lv_is_integrity_origin(const struct logical_volume *lv);
+
int lv_is_merging_cow(const struct logical_volume *cow);
uint32_t cow_max_extents(const struct logical_volume *origin, uint32_t chunk_size);
int cow_has_min_chunks(const struct volume_group *vg, uint32_t cow_extents, uint32_t chunk_size);
@@ -1389,4 +1408,22 @@ struct dm_list *create_pv_list(struct dm_pool *mem, struct volume_group *vg, int
char **argv, int allocatable_only);
struct dm_list *clone_pv_list(struct dm_pool *mem, struct dm_list *pvsl);
+int lv_add_integrity(struct logical_volume *lv, const char *arg,
+ const char *meta_name, struct integrity_settings *settings,
+ struct dm_list *pvh);
+int lv_add_integrity_to_raid(struct logical_volume *lv, const char *arg,
+ const char *meta_name, struct integrity_settings *settings,
+ struct dm_list *pvh);
+int lv_remove_integrity(struct logical_volume *lv);
+int lv_remove_integrity_from_raid(struct logical_volume *lv);
+void lv_clear_integrity_recalculate_metadata(struct logical_volume *lv);
+int lv_has_integrity_recalculate_metadata(struct logical_volume *lv);
+int lv_has_integrity_internal(struct logical_volume *lv);
+int lv_raid_has_integrity(struct logical_volume *lv);
+int lv_extend_integrity_in_raid(struct logical_volume *lv,
+ struct dm_list *pvh, const char *meta_name);
+int lv_extend_integrity_for_origin(struct logical_volume *lv_iorig,
+ struct dm_list *pvh, const char *meta_name);
+int lv_get_raid_integrity_settings(struct logical_volume *lv, struct integrity_settings **isettings);
+
#endif
diff --git a/lib/metadata/pool_manip.c b/lib/metadata/pool_manip.c
index bed51f12a..a87130691 100644
--- a/lib/metadata/pool_manip.c
+++ b/lib/metadata/pool_manip.c
@@ -733,7 +733,7 @@ int handle_pool_metadata_spare(struct volume_group *vg, uint32_t extents,
seg_mirrors,
seg->region_size,
extents - lv->le_count,
- pvh, lv->alloc, 0))
+ pvh, lv->alloc, 0, NULL))
return_0;
return 1;
diff --git a/lib/metadata/raid_manip.c b/lib/metadata/raid_manip.c
index fa1b91a7e..bfbf642df 100644
--- a/lib/metadata/raid_manip.c
+++ b/lib/metadata/raid_manip.c
@@ -1575,7 +1575,7 @@ static int _lv_alloc_reshape_space(struct logical_volume *lv,
if (!lv_extend(lv, segtype, data_rimages, stripe_size,
mirrors, /* seg_is_any_raid10(seg) ? seg->data_copies : mirrors, */
seg->region_size, reshape_len /* # of reshape LEs to add */,
- allocate_pvs, lv->alloc, 0)) {
+ allocate_pvs, lv->alloc, 0, NULL)) {
log_error("Failed to allocate out-of-place reshape space for %s.",
display_lvname(lv));
if (!_lv_alloc_reshape_post_extend(lv, segtype_sav, stripe_size_sav, lv_size_cur))
diff --git a/lib/metadata/segtype.h b/lib/metadata/segtype.h
index 22a511eac..08ddc3565 100644
--- a/lib/metadata/segtype.h
+++ b/lib/metadata/segtype.h
@@ -67,6 +67,7 @@ struct dev_manager;
#define SEG_RAID6_N_6 (1ULL << 35)
#define SEG_RAID6 SEG_RAID6_ZR
#define SEG_WRITECACHE (1ULL << 36)
+#define SEG_INTEGRITY (1ULL << 37)
#define SEG_STRIPED_TARGET (1ULL << 39)
#define SEG_LINEAR_TARGET (1ULL << 40)
@@ -84,6 +85,7 @@ struct dev_manager;
#define SEG_TYPE_NAME_CACHE "cache"
#define SEG_TYPE_NAME_CACHE_POOL "cache-pool"
#define SEG_TYPE_NAME_WRITECACHE "writecache"
+#define SEG_TYPE_NAME_INTEGRITY "integrity"
#define SEG_TYPE_NAME_ERROR "error"
#define SEG_TYPE_NAME_FREE "free"
#define SEG_TYPE_NAME_ZERO "zero"
@@ -117,6 +119,7 @@ struct dev_manager;
#define segtype_is_cache(segtype) ((segtype)->flags & SEG_CACHE ? 1 : 0)
#define segtype_is_cache_pool(segtype) ((segtype)->flags & SEG_CACHE_POOL ? 1 : 0)
#define segtype_is_writecache(segtype) ((segtype)->flags & SEG_WRITECACHE ? 1 : 0)
+#define segtype_is_integrity(segtype) ((segtype)->flags & SEG_INTEGRITY ? 1 : 0)
#define segtype_is_mirrored(segtype) ((segtype)->flags & SEG_AREAS_MIRRORED ? 1 : 0)
#define segtype_is_mirror(segtype) ((segtype)->flags & SEG_MIRROR ? 1 : 0)
#define segtype_is_pool(segtype) ((segtype)->flags & (SEG_CACHE_POOL | SEG_THIN_POOL) ? 1 : 0)
@@ -179,6 +182,7 @@ struct dev_manager;
#define seg_is_cache(seg) segtype_is_cache((seg)->segtype)
#define seg_is_cache_pool(seg) segtype_is_cache_pool((seg)->segtype)
#define seg_is_writecache(seg) segtype_is_writecache((seg)->segtype)
+#define seg_is_integrity(seg) segtype_is_integrity((seg)->segtype)
#define seg_is_used_cache_pool(seg) (seg_is_cache_pool(seg) && (!dm_list_empty(&(seg->lv)->segs_using_this_lv)))
#define seg_is_linear(seg) (seg_is_striped(seg) && ((seg)->area_count == 1))
#define seg_is_mirror(seg) segtype_is_mirror((seg)->segtype)
@@ -347,6 +351,8 @@ int init_vdo_segtypes(struct cmd_context *cmd, struct segtype_library *seglib);
int init_writecache_segtypes(struct cmd_context *cmd, struct segtype_library *seglib);
+int init_integrity_segtypes(struct cmd_context *cmd, struct segtype_library *seglib);
+
#define CACHE_FEATURE_POLICY_MQ (1U << 0)
#define CACHE_FEATURE_POLICY_SMQ (1U << 1)
#define CACHE_FEATURE_METADATA2 (1U << 2)
diff --git a/lib/misc/lvm-string.c b/lib/misc/lvm-string.c
index 0ee3403d5..959a6a16e 100644
--- a/lib/misc/lvm-string.c
+++ b/lib/misc/lvm-string.c
@@ -166,7 +166,9 @@ static const char *_lvname_has_reserved_component_string(const char *lvname)
"_rmeta",
"_tdata",
"_tmeta",
- "_vdata"
+ "_vdata",
+ "_imeta",
+ "_iorig"
};
unsigned i;
diff --git a/lib/report/report.c b/lib/report/report.c
index d379e2a27..170df6995 100644
--- a/lib/report/report.c
+++ b/lib/report/report.c
@@ -3173,7 +3173,7 @@ static int _copypercent_disp(struct dm_report *rh,
dm_percent_t percent = DM_PERCENT_INVALID;
/* TODO: just cache passes through lvseg_percent... */
- if (lv_is_cache(lv) || lv_is_used_cache_pool(lv) ||
+ if (lv_is_integrity(lv) || lv_is_cache(lv) || lv_is_used_cache_pool(lv) ||
(!lv_is_merging_origin(lv) && lv_is_raid(lv) && !seg_is_any_raid0(first_seg(lv))))
percent = lvseg_percent_with_info_and_seg_status(lvdm, PERCENT_GET_DIRTY);
else if (lv_is_raid(lv) && !seg_is_any_raid0(first_seg(lv)))
diff --git a/test/shell/integrity.sh b/test/shell/integrity.sh
new file mode 100644
index 000000000..9e6414d2d
--- /dev/null
+++ b/test/shell/integrity.sh
@@ -0,0 +1,376 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2018 Red Hat, Inc. All rights reserved.
+#
+# 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 General Public License v.2.
+#
+# You should have received a copy of the GNU 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
+
+# Test writecache usage
+
+SKIP_WITH_LVMPOLLD=1
+
+. lib/inittest
+
+# aux have_integrity 1 0 0 || skip
+which mkfs.xfs || skip
+
+mnt="mnt"
+mkdir -p $mnt
+aux prepare_devs 5 128
+
+for i in `seq 1 16384`; do echo -n "A" >> fileA; done
+for i in `seq 1 16384`; do echo -n "B" >> fileB; done
+for i in `seq 1 16384`; do echo -n "C" >> fileC; done
+
+# generate random data
+dd if=/dev/urandom of=randA bs=512K count=2
+dd if=/dev/urandom of=randB bs=512K count=3
+dd if=/dev/urandom of=randC bs=512K count=4
+
+_prepare_vg() {
+ # zero devs so we are sure to find the correct file data
+ # on the underlying devs when corrupting it
+ dd if=/dev/zero of="$dev1" || true
+ dd if=/dev/zero of="$dev2" || true
+ dd if=/dev/zero of="$dev3" || true
+ dd if=/dev/zero of="$dev4" || true
+ dd if=/dev/zero of="$dev5" || true
+ vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" "$dev5"
+}
+
+_test_fs_with_error() {
+ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1"
+
+ mount "$DM_DEV_DIR/$vg/$lv1" $mnt
+
+ # add original data
+ cp fileA $mnt
+ cp fileB $mnt
+ cp fileC $mnt
+
+ umount $mnt
+ lvchange -an $vg/$lv1
+
+ # corrupt the original data on the underying dev
+ # flip one bit in fileB, changing a 0x42 to 0x43
+ # the bit is changed in the last 4096 byte block
+ # of the file, so when reading back the file we
+ # will get the first three 4096 byte blocks, for
+ # a total of 12288 bytes before getting an error
+ # on the last 4096 byte block.
+ xxd "$dev1" > dev1.txt
+ tac dev1.txt > dev1.rev
+ sed -e '0,/4242 4242 4242 4242 4242 4242 4242 4242/ s/4242 4242 4242 4242 4242 4242 4242 4242/4242 4242 4242 4242 4242 4242 4242 4243/' dev1.rev > dev1.rev.bad
+ tac dev1.rev.bad > dev1.bad
+ xxd -r dev1.bad > "$dev1"
+ rm dev1.txt dev1.rev dev1.rev.bad dev1.bad
+
+ lvchange -ay $vg/$lv1
+ mount "$DM_DEV_DIR/$vg/$lv1" $mnt
+
+ # read complete fileA which was not corrupted
+ dd if=$mnt/fileA of=tmp bs=1k
+ ls -l tmp
+ stat -c %s tmp
+ diff fileA tmp
+ rm tmp
+
+ # read partial fileB which was corrupted
+ not dd if=$mnt/fileB of=tmp bs=1k
+ ls -l tmp
+ stat -c %s tmp | grep 12288
+ not diff fileB tmp
+ rm tmp
+
+ umount $mnt
+}
+
+_test_fs_with_raid() {
+ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1"
+
+ mount "$DM_DEV_DIR/$vg/$lv1" $mnt
+
+ # add original data
+ cp fileA $mnt
+ cp fileB $mnt
+ cp fileC $mnt
+
+ umount $mnt
+ lvchange -an $vg/$lv1
+
+ xxd "$dev1" > dev1.txt
+ tac dev1.txt > dev1.rev
+ sed -e '0,/4242 4242 4242 4242 4242 4242 4242 4242/ s/4242 4242 4242 4242 4242 4242 4242 4242/4242 4242 4242 4242 4242 4242 4242 4243/' dev1.rev > dev1.rev.bad
+ tac dev1.rev.bad > dev1.bad
+ xxd -r dev1.bad > "$dev1"
+ rm dev1.txt dev1.rev dev1.rev.bad dev1.bad
+
+ lvchange -ay $vg/$lv1
+ mount "$DM_DEV_DIR/$vg/$lv1" $mnt
+
+ # read complete fileA which was not corrupted
+ dd if=$mnt/fileA of=tmp bs=1k
+ ls -l tmp
+ stat -c %s tmp | grep 16384
+ diff fileA tmp
+ rm tmp
+
+ # read complete fileB, corruption is corrected by raid
+ dd if=$mnt/fileB of=tmp bs=1k
+ ls -l tmp
+ stat -c %s tmp | grep 16384
+ diff fileB tmp
+ rm tmp
+
+ umount $mnt
+}
+
+_add_data_to_lv() {
+ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1"
+
+ mount "$DM_DEV_DIR/$vg/$lv1" $mnt
+
+ # add original data
+ cp randA $mnt
+ cp randB $mnt
+ cp randC $mnt
+ mkdir $mnt/1
+ cp fileA $mnt/1
+ cp fileB $mnt/1
+ cp fileC $mnt/1
+ mkdir $mnt/2
+ cp fileA $mnt/2
+ cp fileB $mnt/2
+ cp fileC $mnt/2
+
+ umount $mnt
+}
+
+_verify_data_on_lv() {
+ mount "$DM_DEV_DIR/$vg/$lv1" $mnt
+
+ diff randA $mnt/randA
+ diff randB $mnt/randB
+ diff randC $mnt/randC
+ diff fileA $mnt/1/fileA
+ diff fileB $mnt/1/fileB
+ diff fileC $mnt/1/fileC
+ diff fileA $mnt/2/fileA
+ diff fileB $mnt/2/fileB
+ diff fileC $mnt/2/fileC
+
+ umount $mnt
+}
+
+_sync_percent() {
+ local checklv=$1
+ get lv_field "$checklv" sync_percent | cut -d. -f1
+}
+
+_wait_recalc() {
+ local checklv=$1
+
+ for i in $(seq 1 10) ; do
+ sync=$(_sync_percent "$checklv")
+ echo "sync_percent is $sync"
+
+ if test "$sync" = "100"; then
+ return
+ fi
+
+ sleep 1
+ done
+
+ echo "timeout waiting for recalc"
+ return 1
+}
+
+_prepare_vg
+lvcreate --integrity y -n $lv1 -l 8 $vg "$dev1"
+_test_fs_with_error
+lvchange -an $vg/$lv1
+lvconvert --integrity n $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate -y --integrity internal -n $lv1 -l 8 $vg "$dev1"
+_test_fs_with_error
+lvchange -an $vg/$lv1
+not lvconvert --integrity n $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate -an -n meta -L4M $vg "$dev2"
+lvcreate --integrity y --integritymetadata meta -n $lv1 -l 8 $vg "$dev1"
+_test_fs_with_error
+lvchange -an $vg/$lv1
+lvconvert --integrity n $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate --type raid1 -m1 --integrity y -n $lv1 -l 8 $vg
+_test_fs_with_raid
+lvchange -an $vg/$lv1
+lvconvert --integrity n $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate --type raid1 -m2 --integrity y -n $lv1 -l 8 $vg
+_test_fs_with_raid
+lvchange -an $vg/$lv1
+lvconvert --integrity n $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate --type raid4 --integrity y -n $lv1 -l 8 $vg
+_test_fs_with_raid
+lvchange -an $vg/$lv1
+lvconvert --integrity n $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate --type raid5 --integrity y -n $lv1 -l 8 $vg
+_test_fs_with_raid
+lvchange -an $vg/$lv1
+lvconvert --integrity n $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate --type raid6 --integrity y -n $lv1 -l 8 $vg
+_test_fs_with_raid
+lvchange -an $vg/$lv1
+lvconvert --integrity n $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+# Test removing integrity from an active LV
+
+_prepare_vg
+lvcreate --integrity y -n $lv1 -l 8 $vg
+_wait_recalc $vg/$lv1
+lvchange -an $vg/$lv1
+lvchange -ay $vg/$lv1
+_add_data_to_lv
+lvconvert --integrity n $vg/$lv1
+_verify_data_on_lv
+lvchange -an $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate --type raid1 -m1 --integrity y -n $lv1 -l 8 $vg
+_wait_recalc $vg/${lv1}_rimage_0
+_wait_recalc $vg/${lv1}_rimage_1
+lvchange -an $vg/$lv1
+lvchange -ay $vg/$lv1
+_add_data_to_lv
+lvconvert --integrity n $vg/$lv1
+_verify_data_on_lv
+lvchange -an $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+# Test adding integrity to an active LV
+
+_prepare_vg
+lvcreate -n $lv1 -l 8 $vg
+lvchange -an $vg/$lv1
+lvchange -ay $vg/$lv1
+_add_data_to_lv
+lvconvert --integrity y $vg/$lv1
+_wait_recalc $vg/$lv1
+_verify_data_on_lv
+lvchange -an $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg
+lvchange -an $vg/$lv1
+lvchange -ay $vg/$lv1
+_add_data_to_lv
+lvconvert --integrity y $vg/$lv1
+_wait_recalc $vg/${lv1}_rimage_0
+_wait_recalc $vg/${lv1}_rimage_1
+_verify_data_on_lv
+lvchange -an $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+# Test lvextend
+
+_prepare_vg
+lvcreate -n $lv1 -l 8 $vg
+lvchange -an $vg/$lv1
+lvchange -ay $vg/$lv1
+_add_data_to_lv
+lvconvert --integrity y $vg/$lv1
+_wait_recalc $vg/$lv1
+lvs -a $vg
+_verify_data_on_lv
+lvchange -an $vg/$lv1
+lvextend -l 16 $vg/$lv1
+lvchange -ay $vg/$lv1
+_verify_data_on_lv
+_wait_recalc $vg/$lv1
+lvs -a $vg
+lvchange -an $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+_prepare_vg
+lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg
+lvchange -an $vg/$lv1
+lvchange -ay $vg/$lv1
+_add_data_to_lv
+lvconvert --integrity y $vg/$lv1
+_wait_recalc $vg/${lv1}_rimage_0
+_wait_recalc $vg/${lv1}_rimage_1
+lvs -a $vg
+_verify_data_on_lv
+lvchange -an $vg/$lv1
+lvextend -l 16 $vg/$lv1
+lvchange -ay $vg/$lv1
+_verify_data_on_lv
+_wait_recalc $vg/${lv1}_rimage_0
+_wait_recalc $vg/${lv1}_rimage_1
+lvs -a $vg
+lvchange -an $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
+# lvextend to 512MB is needed for the imeta LV to
+# be extended from 4MB to 8MB.
+
+_prepare_vg
+lvcreate -n $lv1 -l 8 $vg
+lvchange -an $vg/$lv1
+lvchange -ay $vg/$lv1
+_add_data_to_lv
+lvconvert --integrity y $vg/$lv1
+_wait_recalc $vg/$lv1
+lvs -a $vg
+_verify_data_on_lv
+lvchange -an $vg/$lv1
+lvextend -L 512M $vg/$lv1
+lvchange -ay $vg/$lv1
+_verify_data_on_lv
+_wait_recalc $vg/$lv1
+lvs -a $vg
+check lv_field $vg/${lv1}_imeta size "8.00m"
+lvchange -an $vg/$lv1
+lvremove $vg/$lv1
+vgremove -ff $vg
+
diff --git a/tools/args.h b/tools/args.h
index 999d891f7..89c8a84b9 100644
--- a/tools/args.h
+++ b/tools/args.h
@@ -274,6 +274,19 @@ arg(ignoreunsupported_ARG, '\0', "ignoreunsupported", 0, 0, 0,
"and \\fBdiff\\fP types include unsupported settings in their output by default,\n"
"all the other types ignore unsupported settings.\n")
+arg(integrity_ARG, '\0', "integrity", integrity_VAL, 0, 0,
+ "Enable or disable integrity metadata for an LV.\n"
+ "\\fBy|external\\fP adds integrity with metadata stored on a separate, external LV.\n"
+ "\\fBinternal\\fP adds integrity with metadata interleaved with data\n"
+ "(cannot be removed.) Use \\fBn\\fP to remove integrity.\n")
+
+arg(integritymetadata_ARG, '\0', "integritymetadata", string_VAL, 0, 0,
+ "The name of an LV to hold integrity metadata, or the name of a PV\n"
+ "to allocate internal integrity metadata LVs from.\n")
+
+arg(integritysettings_ARG, '\0', "integritysettings", string_VAL, ARG_GROUPABLE, 0,
+ "Set dm-integrity parameters.\n")
+
arg(labelsector_ARG, '\0', "labelsector", number_VAL, 0, 0,
"By default the PV is labelled with an LVM2 identifier in its second\n"
"sector (sector 1). This lets you use a different sector near the\n"
diff --git a/tools/command-lines.in b/tools/command-lines.in
index 37a01cb55..dffa50c65 100644
--- a/tools/command-lines.in
+++ b/tools/command-lines.in
@@ -757,6 +757,20 @@ FLAGS: SECONDARY_SYNTAX
---
+lvconvert --type integrity LV_linear_striped
+OO: OO_LVCONVERT, --integrity IntegrityType, --integritymetadata String, --integritysettings String
+OP: PV ...
+ID: lvconvert_integrity
+DESC: Convert LV to type integrity.
+
+lvconvert --integrity IntegrityType LV_linear_striped_raid_integrity
+OO: OO_LVCONVERT, --integritymetadata String, --integritysettings String
+OP: PV ...
+ID: lvconvert_integrity
+DESC: Add or remove integrity to LV, or to an LV's raid images.
+
+---
+
# --extents is not specified; it's an automatic alternative for --size
OO_LVCREATE: --addtag Tag, --alloc Alloc, --autobackup Bool, --activate Active,
@@ -870,7 +884,8 @@ DESC: Create a raid1 or mirror LV (infers --type raid1|mirror).
# R9,R10,R11,R12 (--type raid with any use of --stripes/--mirrors)
lvcreate --type raid --size SizeMB VG
OO: --mirrors PNumber, --stripes Number, --stripesize SizeKB,
---regionsize RegionSize, --minrecoveryrate SizeKB, --maxrecoveryrate SizeKB, OO_LVCREATE
+--regionsize RegionSize, --minrecoveryrate SizeKB, --maxrecoveryrate SizeKB,
+--integrity IntegrityType, --integritymetadata String, --integritysettings String, OO_LVCREATE
OP: PV ...
ID: lvcreate_raid_any
DESC: Create a raid LV (a specific raid level must be used, e.g. raid1).
@@ -1269,6 +1284,20 @@ FLAGS: SECONDARY_SYNTAX
---
+lvcreate --type integrity --size SizeMB VG
+OO: --integrity IntegrityType, --integritymetadata String, --integritysettings String, OO_LVCREATE
+OP: PV ...
+ID: lvcreate_integrity
+DESC: Create a LV with integrity.
+
+lvcreate --integrity IntegrityType --size SizeMB VG
+OO: --type integrity, --integritymetadata String, --integritysettings String, OO_LVCREATE
+OP: PV ...
+ID: lvcreate_integrity
+DESC: Create a LV with integrity (infers --type integrity).
+
+---
+
lvdisplay
OO: --aligned, --all, --binary, --colon, --columns,
--configreport ConfigReport, --foreign, --history, --ignorelockingfailure,
@@ -1292,7 +1321,7 @@ lvextend --size PSizeMB LV
OO: --alloc Alloc, --autobackup Bool, --force, --mirrors Number,
--nofsck, --nosync, --noudevsync, --reportformat ReportFmt, --resizefs,
--stripes Number, --stripesize SizeKB, --poolmetadatasize PSizeMB,
---type SegType
+--type SegType, --integritymetadata String
OP: PV ...
ID: lvextend_by_size
DESC: Extend an LV by a specified size.
@@ -1301,7 +1330,7 @@ lvextend LV PV ...
OO: --alloc Alloc, --autobackup Bool, --force, --mirrors Number,
--nofsck, --nosync, --noudevsync,
--reportformat ReportFmt, --resizefs, --stripes Number, --stripesize SizeKB,
---type SegType
+--type SegType, --integritymetadata String
ID: lvextend_by_pv
DESC: Extend an LV by specified PV extents.
@@ -1364,7 +1393,7 @@ lvresize --size SSizeMB LV
OO: --alloc Alloc, --autobackup Bool, --force,
--nofsck, --nosync, --noudevsync, --reportformat ReportFmt, --resizefs,
--stripes Number, --stripesize SizeKB, --poolmetadatasize PSizeMB,
---type SegType
+--type SegType, --integritymetadata String
OP: PV ...
ID: lvresize_by_size
DESC: Resize an LV by a specified size.
@@ -1373,7 +1402,7 @@ lvresize LV PV ...
OO: --alloc Alloc, --autobackup Bool, --force,
--nofsck, --nosync, --noudevsync,
--reportformat ReportFmt, --resizefs, --stripes Number, --stripesize SizeKB,
---type SegType
+--type SegType, --integritymetadata String
ID: lvresize_by_pv
DESC: Resize an LV by specified PV extents.
diff --git a/tools/command.c b/tools/command.c
index 50791b169..4381555b6 100644
--- a/tools/command.c
+++ b/tools/command.c
@@ -124,6 +124,7 @@ static inline int configreport_arg(struct cmd_context *cmd __attribute__((unused
static inline int configtype_arg(struct cmd_context *cmd __attribute__((unused)), struct arg_values *av) { return 0; }
static inline int repairtype_arg(struct cmd_context *cmd __attribute__((unused)), struct arg_values *av) { return 0; }
static inline int dumptype_arg(struct cmd_context *cmd __attribute__((unused)), struct arg_values *av) { return 0; }
+static inline int integritytype_arg(struct cmd_context *cmd __attribute__((unused)), struct arg_values *av) { return 0; }
/* needed to include commands.h when building man page generator */
#define CACHE_VGMETADATA 0x00000001
diff --git a/tools/lv_types.h b/tools/lv_types.h
index 778cd541d..d1c94ccd8 100644
--- a/tools/lv_types.h
+++ b/tools/lv_types.h
@@ -34,5 +34,6 @@ lvt(raid10_LVT, "raid10", NULL)
lvt(error_LVT, "error", NULL)
lvt(zero_LVT, "zero", NULL)
lvt(writecache_LVT, "writecache", NULL)
+lvt(integrity_LVT, "integrity", NULL)
lvt(LVT_COUNT, "", NULL)
diff --git a/tools/lvconvert.c b/tools/lvconvert.c
index 757b32335..2efabeb19 100644
--- a/tools/lvconvert.c
+++ b/tools/lvconvert.c
@@ -1396,6 +1396,14 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l
lp->region_size : seg->region_size , lp->pvh))
return_0;
+ if (lv_raid_has_integrity(lv)) {
+ struct integrity_settings *isettings = NULL;
+ if (!lv_get_raid_integrity_settings(lv, &isettings))
+ return_0;
+ if (!lv_add_integrity_to_raid(lv, "external", NULL, isettings, lp->pvh))
+ return_0;
+ }
+
log_print_unless_silent("Logical volume %s successfully converted.",
display_lvname(lv));
@@ -1438,6 +1446,14 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l
lp->region_size, lp->pvh))
return_0;
+ if (lv_raid_has_integrity(lv)) {
+ struct integrity_settings *isettings = NULL;
+ if (!lv_get_raid_integrity_settings(lv, &isettings))
+ return_0;
+ if (!lv_add_integrity_to_raid(lv, "external", NULL, isettings, lp->pvh))
+ return_0;
+ }
+
log_print_unless_silent("Logical volume %s successfully converted.",
display_lvname(lv));
return 1;
@@ -1459,6 +1475,14 @@ try_new_takeover_or_reshape:
lp->region_size : seg->region_size , lp->pvh))
return_0;
+ if (lv_raid_has_integrity(lv)) {
+ struct integrity_settings *isettings = NULL;
+ if (!lv_get_raid_integrity_settings(lv, &isettings))
+ return_0;
+ if (!lv_add_integrity_to_raid(lv, "external", NULL, isettings, lp->pvh))
+ return_0;
+ }
+
log_print_unless_silent("Logical volume %s successfully converted.",
display_lvname(lv));
return 1;
@@ -5750,6 +5774,121 @@ int lvconvert_to_cache_with_cachevol_cmd(struct cmd_context *cmd, int argc, char
return ret;
}
+static int _lvconvert_integrity_remove(struct cmd_context *cmd,
+ struct logical_volume *lv)
+{
+ struct volume_group *vg = lv->vg;
+ int ret;
+
+ if (!lv_is_integrity(lv) && !lv_is_raid(lv)) {
+ log_error("LV does not have integrity.");
+ return 0;
+ }
+
+ /* ensure it's not active elsewhere. */
+ if (!lockd_lv(cmd, lv, "ex", 0))
+ return_0;
+
+ if (!archive(vg))
+ return_0;
+
+ if (lv_is_raid(lv))
+ ret = lv_remove_integrity_from_raid(lv);
+ else
+ ret = lv_remove_integrity(lv);
+ if (!ret)
+ return 0;
+
+ backup(vg);
+
+ log_print_unless_silent("Logical volume %s has removed integrity.", display_lvname(lv));
+ return 1;
+}
+
+static int _lvconvert_integrity_add(struct cmd_context *cmd,
+ struct logical_volume *lv,
+ const char *meta_name,
+ struct integrity_settings *set)
+{
+ struct volume_group *vg = lv->vg;
+ struct dm_list *use_pvh;
+ int ret;
+
+ /* ensure it's not active elsewhere. */
+ if (!lockd_lv(cmd, lv, "ex", 0))
+ return_0;
+
+ if (cmd->position_argc > 1) {
+ /* First pos arg is required LV, remaining are optional PVs. */
+ if (!(use_pvh = create_pv_list(cmd->mem, vg, cmd->position_argc - 1, cmd->position_argv + 1, 0)))
+ return_0;
+ } else
+ use_pvh = &vg->pvs;
+
+ if (!archive(vg))
+ return_0;
+
+ if (lv_is_raid(lv))
+ ret = lv_add_integrity_to_raid(lv, "external", meta_name, set, use_pvh);
+ else
+ ret = lv_add_integrity(lv, "external", meta_name, set, use_pvh);
+ if (!ret)
+ return 0;
+
+ backup(vg);
+
+ log_print_unless_silent("Logical volume %s has added integrity.", display_lvname(lv));
+ return 1;
+}
+
+static int _lvconvert_integrity_single(struct cmd_context *cmd,
+ struct logical_volume *lv,
+ struct processing_handle *handle)
+{
+ struct integrity_settings settings;
+ const char *meta_name = NULL;
+ const char *arg = NULL;
+ int ret = 0;
+
+ memset(&settings, 0, sizeof(settings));
+
+ if (!get_integrity_options(cmd, &arg, &meta_name, &settings))
+ return 0;
+
+ if (!arg || !strcmp(arg, "external") || !strcmp(arg, "y"))
+ ret = _lvconvert_integrity_add(cmd, lv, meta_name, &settings);
+
+ else if (!strcmp(arg, "none") || !strcmp(arg, "n"))
+ ret = _lvconvert_integrity_remove(cmd, lv);
+
+ else
+ log_error("Invalid integrity option value.");
+
+ if (!ret)
+ return ECMD_FAILED;
+ return ECMD_PROCESSED;
+}
+
+int lvconvert_integrity_cmd(struct cmd_context *cmd, int argc, char **argv)
+{
+ struct processing_handle *handle;
+ int ret;
+
+ if (!(handle = init_processing_handle(cmd, NULL))) {
+ log_error("Failed to initialize processing handle.");
+ return ECMD_FAILED;
+ }
+
+ cmd->cname->flags &= ~GET_VGNAME_FROM_OPTIONS;
+
+ ret = process_each_lv(cmd, cmd->position_argc, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE, handle, NULL,
+ &_lvconvert_integrity_single);
+
+ destroy_processing_handle(cmd, handle);
+
+ return ret;
+}
+
/*
* All lvconvert command defs have their own function,
* so the generic function name is unused.
diff --git a/tools/lvcreate.c b/tools/lvcreate.c
index 448f12588..e2147d6e2 100644
--- a/tools/lvcreate.c
+++ b/tools/lvcreate.c
@@ -794,6 +794,8 @@ static int _lvcreate_params(struct cmd_context *cmd,
mirror_default_cfg = (arg_uint_value(cmd, stripes_ARG, 1) > 1)
? global_raid10_segtype_default_CFG : global_mirror_segtype_default_CFG;
segtype_str = find_config_tree_str(cmd, mirror_default_cfg, NULL);
+ } else if (arg_is_set(cmd, integrity_ARG)) {
+ segtype_str = SEG_TYPE_NAME_INTEGRITY;
} else
segtype_str = SEG_TYPE_NAME_STRIPED;
@@ -828,6 +830,9 @@ static int _lvcreate_params(struct cmd_context *cmd,
readahead_ARG,\
setactivationskip_ARG,\
test_ARG,\
+ integrity_ARG,\
+ integritymetadata_ARG,\
+ integritysettings_ARG,\
type_ARG
#define CACHE_POOL_ARGS \
@@ -1227,6 +1232,11 @@ static int _lvcreate_params(struct cmd_context *cmd,
}
}
+ if (seg_is_integrity(lp) || seg_is_raid(lp)) {
+ if (!get_integrity_options(cmd, &lp->integrity_arg, &lp->integrity_meta_name, &lp->integrity_settings))
+ return 0;
+ }
+
lcp->pv_count = argc;
lcp->pvs = argv;
@@ -1711,6 +1721,16 @@ static int _lvcreate_single(struct cmd_context *cmd, const char *vg_name,
lp->pool_name ? : "with generated name", lp->vg_name, lp->segtype->name);
}
+ if (seg_is_integrity(lp) && lp->integrity_arg &&
+ !strcmp(lp->integrity_arg, "internal")) {
+ log_warn("WARNING: integrity cannot be removed from LV with internal metadata.");
+ if (!arg_count(cmd, yes_ARG) &&
+ (yes_no_prompt("Create LV with internal integrity metadata?") == 'n')) {
+ log_error("LV not created.");
+ goto_out;
+ }
+ }
+
if (vg->lock_type && !strcmp(vg->lock_type, "sanlock")) {
if (!handle_sanlock_lv(cmd, vg)) {
log_error("No space for sanlock lock, extend the internal lvmlock LV.");
diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c
index f147be39c..d13af7009 100644
--- a/tools/lvmcmdline.c
+++ b/tools/lvmcmdline.c
@@ -149,6 +149,9 @@ static const struct command_function _command_functions[CMD_COUNT] = {
{ lvconvert_to_vdopool_CMD, lvconvert_to_vdopool_cmd },
{ lvconvert_to_vdopool_param_CMD, lvconvert_to_vdopool_param_cmd },
+ /* lvconvert for integrity */
+ { lvconvert_integrity_CMD, lvconvert_integrity_cmd },
+
{ pvscan_display_CMD, pvscan_display_cmd },
{ pvscan_cache_CMD, pvscan_cache_cmd },
};
@@ -1098,6 +1101,16 @@ int dumptype_arg(struct cmd_context *cmd, struct arg_values *av)
return 0;
}
+int integritytype_arg(struct cmd_context *cmd, struct arg_values *av)
+{
+ if (!strcmp(av->value, "y") ||
+ !strcmp(av->value, "n") ||
+ !strcmp(av->value, "external") ||
+ !strcmp(av->value, "internal"))
+ return 1;
+ return 0;
+}
+
/*
* FIXME: there's been a confusing mixup among:
* resizeable, resizable, allocatable, allocation.
diff --git a/tools/lvresize.c b/tools/lvresize.c
index 0c72a81ff..c2bfc8b1c 100644
--- a/tools/lvresize.c
+++ b/tools/lvresize.c
@@ -154,6 +154,8 @@ static int _lvresize_params(struct cmd_context *cmd, int argc, char **argv,
lp->resizefs = arg_is_set(cmd, resizefs_ARG);
lp->lockopt = arg_str_value(cmd, lockopt_ARG, NULL);
+ lp->integritymetadata = arg_str_value(cmd, integritymetadata_ARG, NULL);
+
return 1;
}
diff --git a/tools/toollib.c b/tools/toollib.c
index 6386a6906..b4d2196b9 100644
--- a/tools/toollib.c
+++ b/tools/toollib.c
@@ -723,6 +723,7 @@ int lv_change_activate(struct cmd_context *cmd, struct logical_volume *lv,
activation_change_t activate)
{
int r = 1;
+ int integrity_recalculate;
struct logical_volume *snapshot_lv;
if (lv_is_cache_pool(lv)) {
@@ -780,9 +781,34 @@ int lv_change_activate(struct cmd_context *cmd, struct logical_volume *lv,
return 0;
}
+ if ((integrity_recalculate = lv_has_integrity_recalculate_metadata(lv))) {
+ /* Don't want pvscan to write VG while running from systemd service. */
+ if (!strcmp(cmd->name, "pvscan")) {
+ log_error("Cannot activate uninitialized integrity LV %s from pvscan.",
+ display_lvname(lv));
+ return 0;
+ }
+
+ if (vg_is_shared(lv->vg)) {
+ uint32_t lockd_state = 0;
+ if (!lockd_vg(cmd, lv->vg->name, "ex", 0, &lockd_state)) {
+ log_error("Cannot activate uninitialized integrity LV %s without lock.",
+ display_lvname(lv));
+ return 0;
+ }
+ }
+ }
+
if (!lv_active_change(cmd, lv, activate))
return_0;
+ /* Write VG metadata to clear the integrity recalculate flag. */
+ if (integrity_recalculate && lv_is_active(lv)) {
+ log_print_unless_silent("Updating VG to complete initialization of integrity LV %s.",
+ display_lvname(lv));
+ lv_clear_integrity_recalculate_metadata(lv);
+ }
+
set_lv_notify(lv->vg->cmd);
return r;
@@ -1144,6 +1170,174 @@ out:
return ok;
}
+
+static int _get_one_integrity_setting(struct cmd_context *cmd, struct integrity_settings *settings,
+ char *key, char *val)
+{
+ if (!strncmp(key, "mode", strlen("mode"))) {
+ if (*val == 'D')
+ settings->mode[0] = 'D';
+ else if (*val == 'J')
+ settings->mode[0] = 'J';
+ else if (*val == 'B')
+ settings->mode[0] = 'B';
+ else if (*val == 'R')
+ settings->mode[0] = 'R';
+ else
+ goto_bad;
+ /* lvm assigns a default if the user doesn't. */
+ return 1;
+ }
+
+ if (!strncmp(key, "tag_size", strlen("tag_size"))) {
+ if (sscanf(val, "%u", &settings->tag_size) != 1)
+ goto_bad;
+ /* lvm assigns a default if the user doesn't. */
+ return 1;
+ }
+
+ if (!strncmp(key, "internal_hash", strlen("internal_hash"))) {
+ if (!(settings->internal_hash = dm_pool_strdup(cmd->mem, val)))
+ goto_bad;
+ /* lvm assigns a default if the user doesn't. */
+ return 1;
+ }
+
+ /*
+ * For the following settings, lvm does not set a default value if the
+ * user does not specify their own value, and lets dm-integrity use its
+ * own default.
+ */
+
+ if (!strncmp(key, "journal_sectors", strlen("journal_sectors"))) {
+ if (sscanf(val, "%u", &settings->journal_sectors) != 1)
+ goto_bad;
+ settings->journal_sectors_set = 1;
+ return 1;
+ }
+
+ if (!strncmp(key, "interleave_sectors", strlen("interleave_sectors"))) {
+ if (sscanf(val, "%u", &settings->interleave_sectors) != 1)
+ goto_bad;
+ settings->interleave_sectors_set = 1;
+ return 1;
+ }
+
+ if (!strncmp(key, "buffer_sectors", strlen("buffer_sectors"))) {
+ if (sscanf(val, "%u", &settings->buffer_sectors) != 1)
+ goto_bad;
+ settings->buffer_sectors_set = 1;
+ return 1;
+ }
+
+ if (!strncmp(key, "journal_watermark", strlen("journal_watermark"))) {
+ if (sscanf(val, "%u", &settings->journal_watermark) != 1)
+ goto_bad;
+ settings->journal_watermark_set = 1;
+ return 1;
+ }
+
+ if (!strncmp(key, "commit_time", strlen("commit_time"))) {
+ if (sscanf(val, "%u", &settings->commit_time) != 1)
+ goto_bad;
+ settings->commit_time_set = 1;
+ return 1;
+ }
+
+ if (!strncmp(key, "block_size", strlen("block_size"))) {
+ if (sscanf(val, "%u", &settings->block_size) != 1)
+ goto_bad;
+ settings->block_size_set = 1;
+ return 1;
+ }
+
+ if (!strncmp(key, "bitmap_flush_interval", strlen("bitmap_flush_interval"))) {
+ if (sscanf(val, "%u", &settings->bitmap_flush_interval) != 1)
+ goto_bad;
+ settings->bitmap_flush_interval_set = 1;
+ return 1;
+ }
+
+ if (!strncmp(key, "sectors_per_bit", strlen("sectors_per_bit"))) {
+ if (sscanf(val, "%llu", (unsigned long long *)&settings->sectors_per_bit) != 1)
+ goto_bad;
+ settings->sectors_per_bit_set = 1;
+ return 1;
+ }
+
+ log_error("Unknown setting: %s", key);
+ return 0;
+
+ bad:
+ log_error("Invalid setting: %s", key);
+ return 0;
+}
+
+static int _get_integrity_settings(struct cmd_context *cmd, struct integrity_settings *settings)
+{
+ struct arg_value_group_list *group;
+ const char *str;
+ char key[64];
+ char val[64];
+ int num;
+ int pos;
+
+ /*
+ * "grouped" means that multiple --integritysettings options can be used.
+ * Each option is also allowed to contain multiple key = val pairs.
+ */
+
+ dm_list_iterate_items(group, &cmd->arg_value_groups) {
+ if (!grouped_arg_is_set(group->arg_values, integritysettings_ARG))
+ continue;
+
+ if (!(str = grouped_arg_str_value(group->arg_values, integritysettings_ARG, NULL)))
+ break;
+
+ pos = 0;
+
+ while (pos < strlen(str)) {
+ /* scan for "key1=val1 key2 = val2 key3= val3" */
+
+ memset(key, 0, sizeof(key));
+ memset(val, 0, sizeof(val));
+
+ if (sscanf(str + pos, " %63[^=]=%63s %n", key, val, &num) != 2) {
+ log_error("Invalid setting at: %s", str+pos);
+ return 0;
+ }
+
+ pos += num;
+
+ if (!_get_one_integrity_setting(cmd, settings, key, val))
+ return_0;
+ }
+ }
+
+ return 1;
+}
+
+int get_integrity_options(struct cmd_context *cmd, const char **arg, const char **meta_name,
+ struct integrity_settings *set)
+{
+ *arg = NULL;
+ *meta_name = NULL;
+ memset(set, 0, sizeof(struct integrity_settings));
+
+ if (arg_is_set(cmd, integrity_ARG))
+ *arg = arg_str_value(cmd, integrity_ARG, NULL);
+
+ if (arg_is_set(cmd, integritymetadata_ARG))
+ *meta_name = arg_str_value(cmd, integritymetadata_ARG, NULL);
+
+ if (arg_is_set(cmd, integritysettings_ARG)) {
+ if (!_get_integrity_settings(cmd, set))
+ return_0;
+ }
+
+ return 1;
+}
+
/* FIXME move to lib */
static int _pv_change_tag(struct physical_volume *pv, const char *tag, int addtag)
{
@@ -2309,6 +2503,8 @@ static int _lv_is_type(struct cmd_context *cmd, struct logical_volume *lv, int l
return seg_is_raid10(seg);
case writecache_LVT:
return seg_is_writecache(seg);
+ case integrity_LVT:
+ return seg_is_integrity(seg);
case error_LVT:
return !strcmp(seg->segtype->name, SEG_TYPE_NAME_ERROR);
case zero_LVT:
@@ -2367,6 +2563,8 @@ int get_lvt_enum(struct logical_volume *lv)
return raid10_LVT;
if (seg_is_writecache(seg))
return writecache_LVT;
+ if (seg_is_integrity(seg))
+ return integrity_LVT;
if (!strcmp(seg->segtype->name, SEG_TYPE_NAME_ERROR))
return error_LVT;
diff --git a/tools/tools.h b/tools/tools.h
index 3cf4293dd..39417f529 100644
--- a/tools/tools.h
+++ b/tools/tools.h
@@ -186,6 +186,7 @@ int configreport_arg(struct cmd_context *cmd __attribute__((unused)), struct arg
int configtype_arg(struct cmd_context *cmd __attribute__((unused)), struct arg_values *av);
int repairtype_arg(struct cmd_context *cmd __attribute__((unused)), struct arg_values *av);
int dumptype_arg(struct cmd_context *cmd __attribute__((unused)), struct arg_values *av);
+int integritytype_arg(struct cmd_context *cmd __attribute__((unused)), struct arg_values *av);
/* we use the enums to access the switches */
unsigned arg_count(const struct cmd_context *cmd, int a);
@@ -235,6 +236,9 @@ struct lv_prop *get_lv_prop(int lvp_enum);
struct lv_type *get_lv_type(int lvt_enum);
struct command *get_command(int cmd_enum);
+int get_integrity_options(struct cmd_context *cmd, const char **arg, const char **meta_name,
+ struct integrity_settings *set);
+
int lvchange_properties_cmd(struct cmd_context *cmd, int argc, char **argv);
int lvchange_activate_cmd(struct cmd_context *cmd, int argc, char **argv);
int lvchange_refresh_cmd(struct cmd_context *cmd, int argc, char **argv);
@@ -274,6 +278,8 @@ int lvconvert_merge_cmd(struct cmd_context *cmd, int argc, char **argv);
int lvconvert_to_vdopool_cmd(struct cmd_context *cmd, int argc, char **argv);
int lvconvert_to_vdopool_param_cmd(struct cmd_context *cmd, int argc, char **argv);
+int lvconvert_integrity_cmd(struct cmd_context *cmd, int argc, char **argv);
+
int pvscan_display_cmd(struct cmd_context *cmd, int argc, char **argv);
int pvscan_cache_cmd(struct cmd_context *cmd, int argc, char **argv);
diff --git a/tools/vals.h b/tools/vals.h
index 70404436b..5868a5c6f 100644
--- a/tools/vals.h
+++ b/tools/vals.h
@@ -143,6 +143,7 @@ val(configreport_VAL, configreport_arg, "ConfigReport", "log|vg|lv|pv|pvseg|seg"
val(configtype_VAL, configtype_arg, "ConfigType", "current|default|diff|full|list|missing|new|profilable|profilable-command|profilable-metadata")
val(repairtype_VAL, repairtype_arg, "RepairType", "pv_header|metadata|label_header")
val(dumptype_VAL, dumptype_arg, "DumpType", "headers|metadata|metadata_all|metadata_search")
+val(integrity_VAL, integritytype_arg, "IntegrityType", "y|n|external|internal")
/* this should always be last */
val(VAL_COUNT, NULL, NULL, NULL)