diff options
author | David Teigland <teigland@redhat.com> | 2019-11-20 16:07:27 -0600 |
---|---|---|
committer | David Teigland <teigland@redhat.com> | 2019-12-06 14:14:57 -0600 |
commit | bf71ad27198b55fd443a18445db651556c46de97 (patch) | |
tree | 22ff65261f196cee214cbc5555b17e4fa595f617 | |
parent | ec71df6fec481b688a7c4db3171a451c18dbdc52 (diff) | |
download | lvm2-dev-dct-integrity10.tar.gz |
dm-integrity supportdev-dct-integrity10
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.
Create a linear LV with a dm-integrity layer above it:
lvcreate --type integrity --integrity String [options]
Create a raid1 LV with a dm-integrity layer over each
raid image:
lvcreate --type raid1 --integrity String [options]
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)
Store integrity metadata on a separate LV.
Allows removing integrity from the LV later.
--integrity internal
Store integrity metadata interleaved with data
on the same LV. Around 1% of the LV size will
be used for integrity metadata. (Not currently
allowed with raid1.)
--integrity y
Enable default integrity settings (external).
Command variations:
lvcreate --type integrity -n Name -L Size VG
[Uses integrity external, the default.]
lvcreate --integrity external -n Name -L Size VG
[Uses type integrity, which is implied.]
lvcreate --integrity y -n Name -L Size VG
[Uses integrity external, the default, and
uses type integrity, which is implied.]
lvcreate --integrity internal -n Name -L Size VG
[Uses type integrity, which is implied.]
lvcreate --type raid1 --integrity y -m Num -n Name -L Size VG
[Uses type integrity for each raid image.]
lvconvert --integrity none|n LV
[Removes external integrity.]
Options:
--integritymetadata LV
Use the specified LV for external metadata.
Allows specific device placement of metadata.
Without this option, the command creates a
hidden LV (with an _imeta suffix) to hold the
metadata. (Not usable with raid1+integrity.)
--integritysettings String
set dm-integrity parameters, e.g. to use a journal
instead of bitmap, --integritysettings "mode=J".
Example:
$ lvcreate --integrity external -n lvex -L1G vg
$ lvs -a vg
LV VG Attr LSize Origin
lvex vg -wi-a----- 1.00g [lvex_iorig]
[lvex_imeta] vg -wi-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 -wi-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 dd rwi-a-r--- 1.00g
[lver_rimage_0] dd gwi-aor--- 1.00g [lver_rimage_0_iorig]
[lver_rimage_0_imeta] dd ewi-ao---- 12.00m
[lver_rimage_0_iorig] dd -wi-ao---- 1.00g
[lver_rimage_1] dd gwi-aor--- 1.00g [lver_rimage_1_iorig]
[lver_rimage_1_imeta] dd ewi-ao---- 12.00m
[lver_rimage_1_iorig] dd -wi-ao---- 1.00g
[lver_rmeta_0] dd ewi-aor--- 4.00m
[lver_rmeta_1] dd ewi-aor--- 4.00m
-rw-r--r-- | device_mapper/all.h | 39 | ||||
-rw-r--r-- | device_mapper/libdm-deptree.c | 130 | ||||
-rw-r--r-- | device_mapper/libdm-targets.c | 29 | ||||
-rw-r--r-- | lib/Makefile.in | 2 | ||||
-rw-r--r-- | lib/activate/activate.c | 79 | ||||
-rw-r--r-- | lib/activate/activate.h | 10 | ||||
-rw-r--r-- | lib/activate/dev_manager.c | 16 | ||||
-rw-r--r-- | lib/commands/toolcontext.c | 3 | ||||
-rw-r--r-- | lib/format_text/flags.c | 3 | ||||
-rw-r--r-- | lib/integrity/integrity.c | 329 | ||||
-rw-r--r-- | lib/label/label.c | 2 | ||||
-rw-r--r-- | lib/label/label.h | 8 | ||||
-rw-r--r-- | lib/metadata/integrity_manip.c | 649 | ||||
-rw-r--r-- | lib/metadata/lv.c | 7 | ||||
-rw-r--r-- | lib/metadata/lv_manip.c | 225 | ||||
-rw-r--r-- | lib/metadata/merge.c | 2 | ||||
-rw-r--r-- | lib/metadata/metadata-exported.h | 31 | ||||
-rw-r--r-- | lib/metadata/segtype.h | 6 | ||||
-rw-r--r-- | lib/raid/raid.c | 36 | ||||
-rw-r--r-- | tools/args.h | 9 | ||||
-rw-r--r-- | tools/command-lines.in | 25 | ||||
-rw-r--r-- | tools/lv_types.h | 1 | ||||
-rw-r--r-- | tools/lvconvert.c | 69 | ||||
-rw-r--r-- | tools/lvcreate.c | 38 | ||||
-rw-r--r-- | tools/lvmcmdline.c | 3 | ||||
-rw-r--r-- | tools/toollib.c | 172 | ||||
-rw-r--r-- | tools/tools.h | 5 |
27 files changed, 1871 insertions, 57 deletions
diff --git a/device_mapper/all.h b/device_mapper/all.h index 57673b44a..61b7493f8 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 d82e28b13..4289927bd 100644 --- a/device_mapper/libdm-targets.c +++ b/device_mapper/libdm-targets.c @@ -380,6 +380,35 @@ 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]; + + if (!(s = dm_pool_zalloc(mem, sizeof(struct dm_status_integrity)))) + return_0; + + memset(recalc_str, 0, sizeof(recalc_str)); + + 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/lib/Makefile.in b/lib/Makefile.in index c037b4162..86ced01ab 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.c b/lib/activate/activate.c index a82a5cbc4..b4c39d544 100644 --- a/lib/activate/activate.c +++ b/lib/activate/activate.c @@ -325,25 +325,11 @@ int lv_resume_if_active(struct cmd_context *cmd, const char *lvid_s, unsigned or { return 1; } -int lv_deactivate(struct cmd_context *cmd, const char *lvid_s, const struct logical_volume *lv) -{ - return 1; -} int lv_activation_filter(struct cmd_context *cmd, const char *lvid_s, int *activate_lv, const struct logical_volume *lv) { return 1; } -int lv_activate(struct cmd_context *cmd, const char *lvid_s, int exclusive, int noscan, - int temporary, const struct logical_volume *lv) -{ - return 1; -} -int lv_activate_with_filter(struct cmd_context *cmd, const char *lvid_s, int exclusive, - int noscan, int temporary, const struct logical_volume *lv) -{ - return 1; -} int lv_mknodes(struct cmd_context *cmd, const struct logical_volume *lv) { return 1; @@ -2413,7 +2399,7 @@ static int _lv_has_open_snapshots(const struct logical_volume *lv) return r; } -int lv_deactivate(struct cmd_context *cmd, const char *lvid_s, const struct logical_volume *lv) +static int lv_deactivate(struct cmd_context *cmd, const char *lvid_s, const struct logical_volume *lv) { struct lvinfo info; static const struct lv_activate_opts laopts = { .skip_in_use = 1 }; @@ -2609,34 +2595,6 @@ out: return r; } -/* Activate LV */ -int lv_activate(struct cmd_context *cmd, const char *lvid_s, int exclusive, - int noscan, int temporary, const struct logical_volume *lv) -{ - struct lv_activate_opts laopts = { .exclusive = exclusive, - .noscan = noscan, - .temporary = temporary }; - - if (!_lv_activate(cmd, lvid_s, &laopts, 0, lv)) - return_0; - - return 1; -} - -/* Activate LV only if it passes filter */ -int lv_activate_with_filter(struct cmd_context *cmd, const char *lvid_s, int exclusive, - int noscan, int temporary, const struct logical_volume *lv) -{ - struct lv_activate_opts laopts = { .exclusive = exclusive, - .noscan = noscan, - .temporary = temporary }; - - if (!_lv_activate(cmd, lvid_s, &laopts, 1, lv)) - return_0; - - return 1; -} - int lv_mknodes(struct cmd_context *cmd, const struct logical_volume *lv) { int r; @@ -2867,11 +2825,19 @@ int deactivate_lv_with_sub_lv(const struct logical_volume *lv) return 1; } -int activate_lv(struct cmd_context *cmd, const struct logical_volume *lv) +int activate_lv_opts(struct cmd_context *cmd, const struct logical_volume *lv, + struct lv_activate_opts *laopts) { const struct logical_volume *active_lv; + const struct logical_volume *lv_use; int ret; + if (lv->status & LV_NOSCAN) + laopts->noscan = 1; + + if (lv->status & LV_TEMPORARY) + laopts->temporary = 1; + /* * When trying activating component LV, make sure none of sub component * LV or LVs that are using it are active. @@ -2888,19 +2854,34 @@ int activate_lv(struct cmd_context *cmd, const struct logical_volume *lv) goto out; } - ret = lv_activate_with_filter(cmd, NULL, 0, - (lv->status & LV_NOSCAN) ? 1 : 0, - (lv->status & LV_TEMPORARY) ? 1 : 0, - lv_committed(lv)); + if (lv->status & LV_UNCOMMITTED) + lv_use = lv; + else + lv_use = lv_committed(lv); + + ret = _lv_activate(cmd, NULL, laopts, 1, lv_use); out: return ret; } +int activate_lv(struct cmd_context *cmd, const struct logical_volume *lv) +{ + struct lv_activate_opts laopts = { 0 }; + + return activate_lv_opts(cmd, lv, &laopts); +} + int deactivate_lv(struct cmd_context *cmd, const struct logical_volume *lv) { + const struct logical_volume *lv_use; int ret; - ret = lv_deactivate(cmd, NULL, lv_committed(lv)); + if (lv->status & LV_UNCOMMITTED) + lv_use = lv; + else + lv_use = lv_committed(lv); + + ret = lv_deactivate(cmd, NULL, lv_use); return ret; } diff --git a/lib/activate/activate.h b/lib/activate/activate.h index a5ee438ad..221510b78 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; }; }; @@ -72,6 +74,7 @@ struct lv_activate_opts { int no_merging; int send_messages; int skip_in_use; + int integrity_recalculate; unsigned revert; unsigned read_only; unsigned noscan; /* Mark this LV to avoid its scanning. This also @@ -122,9 +125,6 @@ int lv_resume_if_active(struct cmd_context *cmd, const char *lvid_s, unsigned origin_only, unsigned exclusive, unsigned revert, const struct logical_volume *lv); int lv_activate(struct cmd_context *cmd, const char *lvid_s, int exclusive, int noscan, int temporary, const struct logical_volume *lv); -int lv_activate_with_filter(struct cmd_context *cmd, const char *lvid_s, int exclusive, - int noscan, int temporary, const struct logical_volume *lv); -int lv_deactivate(struct cmd_context *cmd, const char *lvid_s, const struct logical_volume *lv); int lv_mknodes(struct cmd_context *cmd, const struct logical_volume *lv); @@ -132,6 +132,8 @@ int lv_deactivate_any_missing_subdevs(const struct logical_volume *lv); int activate_lv(struct cmd_context *cmd, const struct logical_volume *lv); int deactivate_lv(struct cmd_context *cmd, const struct logical_volume *lv); +int activate_lv_opts(struct cmd_context *cmd, const struct logical_volume *lv, + struct lv_activate_opts *laopts); int suspend_lv(struct cmd_context *cmd, const struct logical_volume *lv); int suspend_lv_origin(struct cmd_context *cmd, const struct logical_volume *lv); int resume_lv(struct cmd_context *cmd, const struct logical_volume *lv); @@ -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 8569e860b..b31cfa0ab 100644 --- a/lib/activate/dev_manager.c +++ b/lib/activate/dev_manager.c @@ -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..7f8e346c6 100644 --- a/lib/commands/toolcontext.c +++ b/lib/commands/toolcontext.c @@ -1362,6 +1362,9 @@ static int _init_segtypes(struct cmd_context *cmd) return 0; #endif + if (!init_integrity_segtypes(cmd, &seglib)) + return 0; + return 1; } diff --git a/lib/format_text/flags.c b/lib/format_text/flags.c index 2873ba632..a86732865 100644 --- a/lib/format_text/flags.c +++ b/lib/format_text/flags.c @@ -104,8 +104,11 @@ 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}, + {LV_UNCOMMITTED, NULL, 0}, {0, NULL, 0} }; diff --git a/lib/integrity/integrity.c b/lib/integrity/integrity.c new file mode 100644 index 000000000..f3e465bd1 --- /dev/null +++ b/lib/integrity/integrity.c @@ -0,0 +1,329 @@ +/* + * 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 = strdup(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); + } + + /* 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 (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; + + if (!activation()) + return 0; + + if (!_integrity_checked) { + _integrity_checked = 1; + _integrity_present = target_present(cmd, TARGET_NAME_INTEGRITY, 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, + laopts->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/label/label.c b/lib/label/label.c index 680630b8e..82814faa9 100644 --- a/lib/label/label.c +++ b/lib/label/label.c @@ -254,8 +254,6 @@ struct label *label_create(struct labeller *labeller) /* global variable for accessing the bcache populated by label scan */ struct bcache *scan_bcache; -#define BCACHE_BLOCK_SIZE_IN_SECTORS 256 /* 256*512 = 128K */ - static bool _in_bcache(struct device *dev) { if (!dev) diff --git a/lib/label/label.h b/lib/label/label.h index 410890621..06d0f29a1 100644 --- a/lib/label/label.h +++ b/lib/label/label.h @@ -121,6 +121,14 @@ int label_scan_open_rw(struct device *dev); int label_scan_for_pvid(struct cmd_context *cmd, char *pvid, struct device **dev_out); /* + * These are the sizes the label.c uses to set up + * and use bcache (they are not bcache restrictions + * or defs.) + */ +#define BCACHE_BLOCK_SIZE_IN_SECTORS 256 /* 256*512 = 128K */ +#define BCACHE_BLOCK_SIZE_IN_BYTES 131072 + +/* * Wrappers around bcache equivalents. * (these make it easier to disable bcache and revert to direct rw if needed) */ diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c new file mode 100644 index 000000000..ca3094ffb --- /dev/null +++ b/lib/metadata/integrity_manip.c @@ -0,0 +1,649 @@ +/* + * 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; +} + +/* + * 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. Save it in + * lp->integrity_meta_lv and it will be passed to lv_add_integrity(). + */ +int lv_create_integrity_metadata(struct cmd_context *cmd, + struct volume_group *vg, + struct lvcreate_params *lp) +{ + 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 (dm_snprintf(metaname, NAME_LEN, "%s_imeta", lp->lv_name) < 0) { + log_error("Failed to create metadata LV name."); + return 0; + } + + if (!(lp_meta.lv_name = strdup(metaname))) + return_0; + + lp_meta.pvh = lp->pvh; + + lv_size_bytes = lp->extents * 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("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; + } + + lp->integrity_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; +} + +int lv_remove_integrity(struct logical_volume *lv) +{ + struct lv_segment *seg = first_seg(lv); + struct logical_volume *origin; + struct logical_volume *meta_lv; + + /* FIXME: handle removing integrity from raid images. */ + + if (!seg_is_integrity(seg)) { + log_error("LV %s segment is not integrity.", display_lvname(lv)); + return 0; + } + + if (!(meta_lv = seg->integrity_meta_dev)) { + log_error("LV %s segment has no integrity metadata device.", display_lvname(lv)); + return 0; + } + + if (!(origin = 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_set_visible(seg->integrity_meta_dev); + + lv->status &= ~INTEGRITY; + meta_lv->status &= ~INTEGRITY_METADATA; + + seg->integrity_meta_dev = NULL; + + if (!remove_layer_from_lv(lv, origin)) + return_0; + + if (!lv_remove(origin)) + return_0; + + if (!lv_remove(meta_lv)) + log_warn("WARNING: failed to remove integrity metadata LV."); + + 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 + * + */ + +static int _lv_add_integrity_to_raid(struct logical_volume *lv, const char *arg, + struct integrity_settings *settings, + struct dm_list *pvh, + uint64_t *data_sectors) +{ + struct lvcreate_params lp; + 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; + uint64_t status_data_sectors = 0; + uint32_t area_count, s; + int internal_metadata; + int ret = 1; + + memset(imeta_lvs, 0, sizeof(imeta_lvs)); + + if (dm_list_size(&lv->segments) != 1) + return_0; + + seg_top = first_seg(lv); + area_count = seg_top->area_count; + + if ((internal_metadata = !strcmp(arg, "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: _info_run() needs code to adjust the length, like + * is done for if (lv_is_integrity()) length = ... + */ + /* goto skip_imeta; */ + log_error("Internal integrity metadata is not yet 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; + + if (s >= DEFAULT_RAID_MAX_IMAGES) + return_0; + + lv_image = seg_lv(seg_top, s); + + if (!seg_is_striped(first_seg(lv_image))) { + log_error("raid1 image must be linear to add integrity"); + return_0; + } + + /* + * allocate a new linear LV NAME_rimage_N_imeta + * lv_create_integrity_metadata() returns its result in lp + */ + memset(&lp, 0, sizeof(lp)); + lp.lv_name = lv_image->name; + lp.pvh = pvh; + lp.extents = lv_image->size / vg->extent_size; + + if (!lv_create_integrity_metadata(cmd, vg, &lp)) { + return_0; + } + meta_lv = lp.integrity_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)); + return 0; + } + + memset(&wipe, 0, sizeof(wipe)); + wipe.do_zero = 1; + wipe.zero_sectors = 8; + + if (!wipe_lv(meta_lv, wipe)) { + log_error("Failed to zero LV for integrity metadata %s", display_lvname(meta_lv)); + return 0; + } + + if (!deactivate_lv(cmd, meta_lv)) { + log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv)); + return 0; + } + + /* Used below to set up the new integrity segment. */ + imeta_lvs[s] = meta_lv; + } + +/* 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); + + if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_INTEGRITY))) + return_0; + + 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"))) + return_0; + + 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 (!internal_metadata) { + 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; + } + + 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_metadata) { + /* 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; + lv_image->status |= LV_UNCOMMITTED; + seg_image = first_seg(lv_image); + seg_image->integrity_data_sectors = 1; + + log_debug("Activating temporary integrity LV to get data sectors."); + + if (!activate_lv(cmd, lv_image)) { + log_error("Failed to activate temporary integrity."); + ret = 0; + goto out; + } + + if (!_get_provided_data_sectors(lv_image, &status_data_sectors)) { + log_error("Failed to get data sectors from dm-integrity"); + ret = 0; + } else { + log_print("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; + } + + lv_image->status |= LV_UNCOMMITTED; + + if (!deactivate_lv(cmd, lv_image)) { + log_error("Failed to deactivate temporary integrity."); + ret = 0; + } + + if (!ret) + goto_out; + + lv_image->status &= ~LV_UNCOMMITTED; + 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; + } + } + + if (internal_metadata) + *data_sectors = status_data_sectors; + else + *data_sectors = lv->size; + + log_debug("Write VG with integrity added to LV"); + + if (!vg_write(vg) || !vg_commit(vg)) + ret = 0; +out: + return ret; +} + +int lv_add_integrity(struct logical_volume *lv, const char *arg, + struct logical_volume *meta_lv_created, + const char *meta_name, + struct integrity_settings *settings, + struct dm_list *pvh, + uint64_t *data_sectors) +{ + struct cmd_context *cmd = lv->vg->cmd; + struct volume_group *vg = lv->vg; + struct integrity_settings *set; + struct logical_volume *lv_orig; + struct logical_volume *meta_lv = NULL; + const struct segment_type *segtype; + struct lv_segment *seg; + uint64_t meta_bytes, meta_sectors; + uint64_t lv_size_sectors; + int ret = 1; + + if (lv_is_raid(lv)) + return _lv_add_integrity_to_raid(lv, arg, settings, pvh, data_sectors); + + lv_size_sectors = lv->size; + + if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_INTEGRITY))) + return_0; + + /* + * "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"))) + return_0; + + seg = first_seg(lv); + seg->segtype = segtype; + + lv->status |= INTEGRITY; + + memcpy(&seg->integrity_settings, settings, sizeof(struct integrity_settings)); + set = &seg->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; + + /* + * --integrity <arg> is y|external|internal + */ + + if (!arg) + arg = "external"; + + if (!strcmp(arg, "none")) { + log_error("Invalid --integrity arg for lvcreate."); + return 0; + } + + if (!strcmp(arg, "internal") && meta_name) { + log_error("Internal integrity cannot be used with integritymetadata option."); + return 0; + } + + if (meta_lv_created) + meta_lv = meta_lv_created; + else if (meta_name) { + if (!(meta_lv = find_lv(vg, meta_name))) { + log_error("LV %s not found.", meta_name); + return_0; + } + + meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv->size * 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)); + return 0; + } + } + + /* + * When not using a meta_dev, 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 meta_dev, the dm dev size is the size of the data + * device. The necessary size of the meta_dev for the given data + * device needs to be estimated. + */ + + if (meta_lv) { + struct wipe_params wipe; + + if (!sync_local_dev_names(cmd)) { + log_error("Failed to sync local devices."); + return 0; + } + + 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)); + return 0; + } + } + + memset(&wipe, 0, sizeof(wipe)); + wipe.do_zero = 1; + wipe.zero_sectors = 8; + + if (!wipe_lv(meta_lv, wipe)) { + log_error("Failed to zero LV for integrity metadata %s", display_lvname(meta_lv)); + return 0; + } + + if (!deactivate_lv(cmd, meta_lv)) { + log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv)); + return 0; + } + + meta_lv->status |= INTEGRITY_METADATA; + seg->integrity_data_sectors = lv_size_sectors; + seg->integrity_meta_dev = meta_lv; + lv_set_hidden(meta_lv); + /* TODO: give meta_lv a suffix? e.g. _imeta */ + } else { + /* dm-integrity wants temp/fake size of 1 to report usable size */ + seg->integrity_data_sectors = 1; + + lv->status |= LV_TEMPORARY; + lv->status |= LV_NOSCAN; + lv->status |= LV_UNCOMMITTED; + + log_debug("Activating temporary integrity LV to get data sectors."); + + if (!activate_lv(cmd, lv)) { + log_error("Failed to activate temporary integrity."); + ret = 0; + goto out; + } + + 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; + } + + lv->status |= LV_UNCOMMITTED; + + if (!deactivate_lv(cmd, lv)) { + log_error("Failed to deactivate temporary integrity."); + ret = 0; + } + + lv->status &= ~LV_UNCOMMITTED; + lv->status &= ~LV_NOSCAN; + lv->status &= ~LV_TEMPORARY; + } + + *data_sectors = seg->integrity_data_sectors; + + log_debug("Write VG with integrity added to LV"); + + if (!vg_write(vg) || !vg_commit(vg)) + ret = 0; + out: + return ret; +} + + diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c index 4c2ab2bbf..2ff9887d5 100644 --- a/lib/metadata/lv.c +++ b/lib/metadata/lv.c @@ -585,6 +585,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; } @@ -1200,10 +1202,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 b4daa5633..798f3322d 100644 --- a/lib/metadata/lv_manip.c +++ b/lib/metadata/lv_manip.c @@ -28,6 +28,7 @@ #include "lib/datastruct/str_list.h" #include "lib/config/defaults.h" #include "lib/misc/lvm-exec.h" +#include "lib/misc/lvm-signal.h" #include "lib/mm/memlock.h" #include "lib/locking/lvmlockd.h" #include "lib/label/label.h" @@ -55,6 +56,8 @@ typedef enum { #define A_POSITIONAL_FILL 0x40 /* Slots are positional and filled using PREFERRED */ #define A_PARTITION_BY_TAGS 0x80 /* No allocated area may share any tag with any other */ +#define ONE_MB_IN_BYTES 1048576 + /* * Constant parameters during a single allocation attempt. */ @@ -134,7 +137,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 +195,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 +468,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 +621,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 +1506,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; @@ -5654,6 +5712,11 @@ int lv_resize(struct logical_volume *lv, return 0; } + if (lv_is_integrity(lv)) { + log_error("Resize not yet allowed on LVs with integrity."); + return 0; + } + if (!_lvresize_check(lv, lp)) return_0; @@ -7410,6 +7473,90 @@ int insert_layer_for_segments_on_pv(struct cmd_context *cmd, return 1; } +int zero_lv_name(struct cmd_context *cmd, const char *vg_name, const char *lv_name, uint64_t lv_size_sectors) +{ + char name[PATH_MAX]; + struct device *dev; + uint64_t off = 0, i = 0, j = 0; + uint64_t lv_size_bytes = lv_size_sectors * 512; + uint64_t zero_bytes; + uint32_t extra_bytes; + + if (dm_snprintf(name, sizeof(name), "%s%s/%s", cmd->dev_dir, vg_name, lv_name) < 0) { + log_error("Device path name too long, device not zeroed (%s).", lv_name); + return 0; + } + + if (!(dev = dev_cache_get(cmd, name, NULL))) { + log_error("Failed to find device %s: device not zeroed.", name); + return 0; + } + + if (!label_scan_open_rw(dev)) { + log_error("Failed to open %s: device not zeroed.", name); + return 0; + } + + zero_bytes = lv_size_bytes; + + log_print("Zeroing %s %s... (cancel command to zero manually)", + name, display_size(cmd, zero_bytes/512)); + + if ((extra_bytes = (zero_bytes % ONE_MB_IN_BYTES))) + zero_bytes -= extra_bytes; + + /* + * Write 1MiB at a time to avoid going over bcache size. + * Then write 128KiB (bcache block sizes) at a time to + * cover remaining dev size. + */ + + sigint_allow(); + + for (i = 0; i < (zero_bytes / ONE_MB_IN_BYTES); i++) { + off = i * ONE_MB_IN_BYTES; + if (!dev_write_zeros(dev, off, (size_t)ONE_MB_IN_BYTES)) + log_error("Failed to zero LV at offset %llu.", (unsigned long long)off); + + if (off && !(off % (1024 * ONE_MB_IN_BYTES))) + log_print("Zeroed %s...", display_size(cmd, off/512)); + + if (sigint_caught()) { + log_print("Zeroing canceled."); + goto out; + } + + } + + if (extra_bytes) { + log_debug("Zeroing final %u bytes at %llu.", extra_bytes, (unsigned long long)off); + + for (j = 0; j < (extra_bytes / BCACHE_BLOCK_SIZE_IN_BYTES); j++) { + off = i * ONE_MB_IN_BYTES + j * BCACHE_BLOCK_SIZE_IN_BYTES; + if (!dev_write_zeros(dev, off, (size_t)BCACHE_BLOCK_SIZE_IN_BYTES)) + log_error("Failed to zero LV at offset %llu.", (unsigned long long)off); + + if (sigint_caught()) { + log_print("Zeroing canceled."); + goto out; + } + } + } + + /* + * FIXME: bcache can't write partial blocks yet. + * This shouldn't actually happen given current + * usage where LV size is a multiple of extents. + */ + if ((extra_bytes = lv_size_bytes - (i * ONE_MB_IN_BYTES + j * BCACHE_BLOCK_SIZE_IN_BYTES))) + log_warn("WARNING: last %llu bytes not zeroed.", (unsigned long long)extra_bytes); + +out: + sigint_restore(); + label_scan_invalidate(dev); + return 1; +} + /* * Initialize the LV with 'value'. */ @@ -7679,6 +7826,15 @@ static int _should_wipe_lv(struct lvcreate_params *lp, first_seg(first_seg(lv)->pool_lv)->zero_new_blocks)) return 0; + /* + * dm-integrity requires the first sector (integrity superblock) to be + * zero when creating a new integrity device. + * TODO: print a warning or error if the user specifically + * asks for no wiping or zeroing? + */ + if (seg_is_integrity(lp) || (seg_is_raid1(lp) && lp->integrity_arg)) + return 1; + /* Cannot zero read-only volume */ if ((lv->status & LVM_WRITE) && (lp->zero || lp->wipe_signatures)) @@ -7934,6 +8090,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, @@ -8277,6 +8438,19 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg, goto revert_new_lv; } lv->status &= ~LV_TEMPORARY; + + } else if (seg_is_integrity(lp) || (seg_is_raid1(lp) && lp->integrity_arg)) { + /* + * Activate the new origin LV so it can be zeroed/wiped + * below before adding integrity. + */ + lv->status |= LV_TEMPORARY; + if (!activate_lv(cmd, lv)) { + log_error("Failed to activate new LV for wiping."); + goto revert_new_lv; + } + lv->status &= ~LV_TEMPORARY; + } else if (!lv_active_change(cmd, lv, lp->activate)) { log_error("Failed to activate new LV %s.", display_lvname(lv)); goto deactivate_and_revert_new_lv; @@ -8296,6 +8470,55 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg, } } + if (seg_is_integrity(lp) || (seg_is_raid1(lp) && lp->integrity_arg)) { + struct lv_activate_opts laopts = { 0 }; + uint64_t integrity_data_sectors = 0; + + log_verbose("Adding integrity to new LV"); + + /* Origin is active from zeroing, deactivate to add integrity. */ + + if (!deactivate_lv(cmd, lv)) { + log_error("Failed to deactivate LV to add integrity"); + goto revert_new_lv; + } + + if (!lv_add_integrity(lv, lp->integrity_arg, lp->integrity_meta_lv, + lp->integrity_meta_name, &lp->integrity_settings, lp->pvh, + &integrity_data_sectors)) + goto revert_new_lv; + + backup(vg); + + /* + * The integrity metadata (checksums) need to be initialized. + * Activating the new integrity LVs with the recalculate + * option will cause dm-integrity to initialize. + * + * TODO: --integrityinit none|kernel|command + * . none: user will handle it (e.g. zero LV with dd), + * and we follow -an|-ay to decide about activating LV + * . kernel: activate LV with recalculate to start kernel init + * . command: activate LV for zeroing by this lvcreate command. + * if this command does not complete zeroing (e.g. user cancels + * the command or machine crashes), then the user must complete + * the the zeroing manually, or with a lvchange recalculate. + * + * For raid+integrity, recalculate by kernel is the only + * option that works. + */ + laopts.integrity_recalculate = 1; + + if (!activate_lv_opts(cmd, lv, &laopts)) { + log_error("Failed to activate LV %s.", display_lvname(lv)); + log_error("The full LV should be zeroed to initialize integrity metadata."); + goto out; + } + + log_print("LV %s activated to initialize integrity.", display_lvname(lv)); + goto out; + } + if (seg_is_vdo_pool(lp)) { if (!convert_vdo_pool_lv(lv, &lp->vdo_params, &lp->virtual_extents)) { stack; diff --git a/lib/metadata/merge.c b/lib/metadata/merge.c index e482921ce..d6dd7a55a 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 c61c85c7e..927164510 100644 --- a/lib/metadata/metadata-exported.h +++ b/lib/metadata/metadata-exported.h @@ -84,12 +84,15 @@ #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 LV_UNCOMMITTED UINT64_C(0x0000000002000000) #define VIRTUAL_ORIGIN UINT64_C(0x0000000008000000) /* LV - internal use only */ #define MERGING UINT64_C(0x0000000010000000) /* LV SEG */ @@ -261,6 +264,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 +277,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 +526,10 @@ 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; + 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 */ @@ -992,6 +1003,12 @@ 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 logical_volume *integrity_meta_lv; /* external LV we create */ + struct integrity_settings integrity_settings; + uint64_t zero_data_sectors; /* the resulting size that should be zeroed at the end */ + 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); @@ -1385,4 +1404,16 @@ int vg_is_foreign(struct volume_group *vg); void vg_write_commit_bad_mdas(struct cmd_context *cmd, struct volume_group *vg); +int lv_add_integrity(struct logical_volume *lv, const char *arg, + struct logical_volume *meta_lv_created, + const char *meta_name, struct integrity_settings *settings, + struct dm_list *pvh, + uint64_t *data_sectors); +int lv_create_integrity_metadata(struct cmd_context *cmd, + struct volume_group *vg, + struct lvcreate_params *lp); +int lv_remove_integrity(struct logical_volume *lv); + +int zero_lv_name(struct cmd_context *cmd, const char *vg_name, const char *lv_name, uint64_t lv_size_sectors); + #endif 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/raid/raid.c b/lib/raid/raid.c index e88a15408..48b63a31a 100644 --- a/lib/raid/raid.c +++ b/lib/raid/raid.c @@ -240,6 +240,26 @@ static int _raid_text_export(const struct lv_segment *seg, struct formatter *f) return _raid_text_export_raid(seg, f); } +static int _image_integrity_data_sectors(struct lv_segment *seg, uint64_t *data_sectors) +{ + struct logical_volume *lv_image; + struct lv_segment *seg_image; + int s; + + *data_sectors = 0; + + for (s = 0; s < seg->area_count; s++) { + lv_image = seg_lv(seg, s); + + if (lv_is_integrity(lv_image)) { + seg_image = first_seg(lv_image); + *data_sectors = seg_image->integrity_data_sectors; + return 1; + } + } + return 1; +} + static int _raid_add_target_line(struct dev_manager *dm __attribute__((unused)), struct dm_pool *mem __attribute__((unused)), struct cmd_context *cmd __attribute__((unused)), @@ -256,6 +276,7 @@ static int _raid_add_target_line(struct dev_manager *dm __attribute__((unused)), uint64_t writemostly[RAID_BITMAP_SIZE] = { 0 }; struct dm_tree_node_raid_params_v2 params = { 0 }; unsigned attrs; + uint64_t integrity_data_sectors = 0; if (seg_is_raid4(seg)) { if (!_raid_target_present(cmd, NULL, &attrs) || @@ -351,6 +372,21 @@ static int _raid_add_target_line(struct dev_manager *dm __attribute__((unused)), params.stripe_size = seg->stripe_size; params.flags = flags; + /* + * The len needs to be reduced only when the raid images are using + * integrity with internal metadata, which reduces the usable + * size of the image, and needs to be reflected in the dm table + * that's loaded. (internal metadata is not currently allowed + * with raid, so this is unused.) + */ + if (!_image_integrity_data_sectors(seg, &integrity_data_sectors)) + return_0; + if (integrity_data_sectors) { + log_debug("Reducing raid size from %llu to integrity_data_sectors %llu", + (unsigned long long)len, (unsigned long long)integrity_data_sectors); + len = integrity_data_sectors; + } + if (!dm_tree_node_add_raid_target_with_params_v2(node, len, ¶ms)) return_0; diff --git a/tools/args.h b/tools/args.h index 533cc5479..03202d99f 100644 --- a/tools/args.h +++ b/tools/args.h @@ -274,6 +274,15 @@ 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", string_VAL, 0, 0, + "Controls if integrity metadata should be stored and checked for an LV.\n") + +arg(integritymetadata_ARG, '\0', "integritymetadata", lv_VAL, 0, 0, + "The name of an LV to hold integrity metadata.\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 10165ea8f..58ffa265f 100644 --- a/tools/command-lines.in +++ b/tools/command-lines.in @@ -757,6 +757,14 @@ FLAGS: SECONDARY_SYNTAX --- +lvconvert --integrity String LV +OO: OO_LVCONVERT +OP: PV ... +ID: lvconvert_integrity +DESC: Remove integrity from an LV. + +--- + # --extents is not specified; it's an automatic alternative for --size OO_LVCREATE: --addtag Tag, --alloc Alloc, --autobackup Bool, --activate Active, @@ -870,7 +878,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 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 +1278,20 @@ FLAGS: SECONDARY_SYNTAX --- +lvcreate --type integrity --size SizeMB VG +OO: --integrity String, --integritymetadata LV, --integritysettings String, OO_LVCREATE +OP: PV ... +ID: lvcreate_integrity +DESC: Create a LV with integrity. + +lvcreate --integrity String --size SizeMB VG +OO: --type integrity, --integritymetadata LV, --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, 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 d0d277bcd..a4fa3e195 100644 --- a/tools/lvconvert.c +++ b/tools/lvconvert.c @@ -5730,6 +5730,75 @@ int lvconvert_to_cache_with_cachevol_cmd(struct cmd_context *cmd, int argc, char return ret; } +static int _lvconvert_integrity_single(struct cmd_context *cmd, + struct logical_volume *lv, + struct processing_handle *handle) +{ + struct volume_group *vg = lv->vg; + struct lv_segment *seg; + const char *arg = arg_str_value(cmd, integrity_ARG, NULL); + + if (strcmp(arg, "none") && strcmp(arg, "n")) { + log_error("Integrity can only be removed from an existing LV (see --integrity none)."); + return ECMD_FAILED; + } + + if (!lv_is_integrity(lv)) { + log_error("LV does not have integrity."); + return ECMD_FAILED; + } + + seg = first_seg(lv); + + if (!seg->integrity_meta_dev) { + log_error("Internal integrity cannot be removed."); + return ECMD_FAILED; + } + + /* TODO: lift this restriction */ + if (lv_info(cmd, lv, 1, NULL, 0, 0)) { + log_error("LV must be inactive to remove integrity."); + return ECMD_FAILED; + } + + if (!archive(vg)) + return ECMD_FAILED; + + if (!lv_remove_integrity(lv)) + return ECMD_FAILED; + + if (!vg_write(vg) || !vg_commit(vg)) + return ECMD_FAILED; + + backup(vg); + + log_print_unless_silent("Logical volume %s has removed integrity.", display_lvname(lv)); + return ECMD_PROCESSED; +} + +int lvconvert_integrity_cmd(struct cmd_context *cmd, int argc, char **argv) +{ + struct processing_handle *handle; + struct lvconvert_result lr = { 0 }; + int ret; + + if (!(handle = init_processing_handle(cmd, NULL))) { + log_error("Failed to initialize processing handle."); + return ECMD_FAILED; + } + + handle->custom_handle = &lr; + + 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 4a1534cc2..ff620f456 100644 --- a/tools/lvcreate.c +++ b/tools/lvcreate.c @@ -792,6 +792,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; @@ -826,6 +828,8 @@ static int _lvcreate_params(struct cmd_context *cmd, readahead_ARG,\ setactivationskip_ARG,\ test_ARG,\ + integrity_ARG,\ + integritysettings_ARG,\ type_ARG #define CACHE_POOL_ARGS \ @@ -1225,6 +1229,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; @@ -1582,6 +1591,13 @@ static int _check_zero_parameters(struct cmd_context *cmd, struct lvcreate_param if (seg_is_thin(lp)) return 1; + if (seg_is_integrity(lp)) { + if (lp->zero && !is_change_activating(lp->activate)) { + log_error("Zeroing integrity is not compatible with inactive creation (-an)."); + return 0; + } + } + /* If there is some problem, buffer will not be empty */ if (dm_snprintf(buf, sizeof(buf), "%s%s%s%s%s%s%s", lp->origin_name ? "origin " : "", @@ -1709,6 +1725,24 @@ static int _lvcreate_single(struct cmd_context *cmd, const char *vg_name, lp->pool_name ? : "with generated name", lp->vg_name, lp->segtype->name); } + /* + * Create an LV to hold integrity metadata when: + * + * . The user wants external (not internal) metadata. External + * is indicated by: no option set (external is default), or + * --integrity y|external. + * + * . The user did not specify an existing metadata LV with + * --integritymetadata, saved as lp->integrity_meta_name. + */ + if (seg_is_integrity(lp) && !lp->integrity_meta_name && + (!lp->integrity_arg || + !strcmp(lp->integrity_arg, "external") || + !strcmp(lp->integrity_arg, "y"))) { + if (!lv_create_integrity_metadata(cmd, vg, lp)) + 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."); @@ -1782,5 +1816,9 @@ int lvcreate(struct cmd_context *cmd, int argc, char **argv) _destroy_lvcreate_params(&lp); destroy_processing_handle(cmd, handle); + + if (lp.zero_data_sectors) + zero_lv_name(cmd, lp.vg_name, lp.lv_name, lp.zero_data_sectors); + return ret; } diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c index 3d79e9739..5f5557603 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 }, }; diff --git a/tools/toollib.c b/tools/toollib.c index a5304bf63..baff0976b 100644 --- a/tools/toollib.c +++ b/tools/toollib.c @@ -1414,6 +1414,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 = strdup(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) { @@ -2579,6 +2747,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: @@ -2637,6 +2807,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 a2baaa5da..ab5866466 100644 --- a/tools/tools.h +++ b/tools/tools.h @@ -234,6 +234,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); @@ -273,6 +276,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); |