From 8ac1361dec159d5fc16e5128d4e80011ace56648 Mon Sep 17 00:00:00 2001 From: David Teigland Date: Wed, 20 Nov 2019 16:07:27 -0600 Subject: dm-integrity support 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 y [options] Add a dm-integrity layer to an existing linear LV, or to each image of an existing raid1 LV: lvconvert --integrity y LV Remove the dm-integrity layer from a linear LV, or from the images of a raid1 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 raid1 --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 raid1 LV.] 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 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 --- configure.ac | 18 + device_mapper/all.h | 39 +++ device_mapper/libdm-deptree.c | 130 +++++++ device_mapper/libdm-targets.c | 29 ++ include/configure.h.in | 3 + lib/Makefile.in | 2 + lib/activate/activate.c | 65 +--- lib/activate/activate.h | 10 +- lib/activate/dev_manager.c | 18 +- lib/commands/toolcontext.c | 5 + lib/format_text/flags.c | 2 + lib/integrity/integrity.c | 338 ++++++++++++++++++ lib/metadata/integrity_manip.c | 738 +++++++++++++++++++++++++++++++++++++++ lib/metadata/lv.c | 7 +- lib/metadata/lv_manip.c | 130 ++++++- lib/metadata/merge.c | 2 + lib/metadata/metadata-exported.h | 25 ++ lib/metadata/segtype.h | 6 + lib/misc/lvm-string.c | 6 +- lib/raid/raid.c | 36 ++ test/shell/integrity.sh | 167 +++++++++ tools/args.h | 12 + tools/command-lines.in | 31 +- tools/command.c | 1 + tools/lv_types.h | 1 + tools/lvconvert.c | 144 ++++++++ tools/lvcreate.c | 27 ++ tools/lvmcmdline.c | 13 + tools/toollib.c | 172 +++++++++ tools/tools.h | 6 + tools/vals.h | 1 + 31 files changed, 2128 insertions(+), 56 deletions(-) create mode 100644 lib/integrity/integrity.c create mode 100644 lib/metadata/integrity_manip.c create mode 100644 test/shell/integrity.sh diff --git a/configure.ac b/configure.ac index 74ca20191..755892d9e 100644 --- a/configure.ac +++ b/configure.ac @@ -667,6 +667,24 @@ case "$WRITECACHE" in *) AC_MSG_ERROR([--with-writecache parameter invalid]) ;; 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], 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/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 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..3607e5e43 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,18 @@ 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; 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,14 +2853,18 @@ 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)); + ret = _lv_activate(cmd, NULL, laopts, 1, lv_committed(lv)); 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) { int 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..342a6ade1 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..27724b33a --- /dev/null +++ b/lib/integrity/integrity.c @@ -0,0 +1,338 @@ +/* + * 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; + uint32_t maj, min, patchlevel; + + if (!activation()) + return 0; + + if (!_integrity_checked) { + _integrity_checked = 1; + _integrity_present = target_present(cmd, TARGET_NAME_INTEGRITY, 0); + + 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, + 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/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c new file mode 100644 index 000000000..a29ddc6f8 --- /dev/null +++ b/lib/metadata/integrity_manip.c @@ -0,0 +1,738 @@ +/* + * 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. + */ +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; + } + + if (!lp->lv_name && + !generate_lv_name(vg, "lvol%d_imeta", metaname, sizeof(metaname))) { + log_error("Failed to generate 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; + } + + *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_from_raid(struct logical_volume *lv) +{ + 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; + + seg_top = first_seg(lv); + + if (!seg_is_raid1(seg_top)) { + log_error("LV %s segment is not raid1.", 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; + + lv_set_visible(seg_image->integrity_meta_dev); + + lv_image->status &= ~INTEGRITY; + lv_imeta->status &= ~INTEGRITY_METADATA; + + 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 (!lv_remove(lv_iorig)) + return_0; + + if (!lv_remove(lv_imeta)) + log_warn("WARNING: failed to remove integrity metadata LV."); + } + + return 1; +} + +int lv_remove_integrity(struct logical_volume *lv) +{ + struct lv_segment *seg = first_seg(lv); + struct logical_volume *origin; + struct logical_volume *meta_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 (!(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 + * + */ + +int lv_add_integrity_to_raid(struct logical_volume *lv, const char *arg, + struct integrity_settings *settings, + struct dm_list *pvh) +{ + 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 external = 0, internal = 0; + int ret = 1; + + memset(imeta_lvs, 0, sizeof(imeta_lvs)); + + 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 (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)) { + log_error("Integrity can only be added to raid1."); + 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: _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 + */ + 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, &meta_lv)) + return_0; + + /* + * 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 (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; + } + + 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"); + ret = 0; + goto out; + } + + 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; + } + + if (!deactivate_lv(cmd, lv_image)) { + log_error("Failed to deactivate temporary integrity."); + ret = 0; + } + + if (!ret) + goto_out; + + 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; + } + } + + 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, + 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 lv_segment *seg; + uint64_t lv_size_sectors; + int external = 0, internal = 0; + int ret = 1; + + lv_size_sectors = lv->size; + + /* + * --integrity 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 (external && !meta_name) { + struct lvcreate_params lp; + memset(&lp, 0, sizeof(lp)); + lp.lv_name = lv->name; + lp.pvh = pvh; + lp.extents = lv->size / vg->extent_size; + + if (!_lv_create_integrity_metadata(cmd, vg, &lp, &meta_lv)) + goto_out; + + } else if (external && meta_name) { + uint64_t meta_bytes, meta_sectors; + + 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_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)); + return 0; + } + } + + 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)); + 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 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; + } + + if (meta_name) { + /* 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)); + return 0; + } + if (!lv_rename_update(cmd, meta_lv, imeta_name, 0)) + 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); + } else { + /* dm-integrity wants temp/fake size of 1 to report usable size */ + seg->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"); + return 0; + } + + 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."); + 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; + } + + if (!deactivate_lv(cmd, lv)) { + log_error("Failed to deactivate temporary integrity."); + ret = 0; + } + + lv->status &= ~LV_NOSCAN; + lv->status &= ~LV_TEMPORARY; + } + + 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..4f0a821db 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; @@ -5654,6 +5709,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; @@ -7679,6 +7739,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 +8003,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 +8351,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 +8383,47 @@ 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 }; + + 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 (seg_is_raid1(lp)) { + if (!lv_add_integrity_to_raid(lv, lp->integrity_arg, + &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); + + /* + * The integrity metadata (checksums) need to be initialized. + * Activating the new integrity LVs with the recalculate + * option will cause dm-integrity to initialize. + */ + 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 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 c61c85c7e..bb98179c6 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,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 +1002,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 +1100,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 +1401,13 @@ 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, + 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, + 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); + #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/misc/lvm-string.c b/lib/misc/lvm-string.c index 0ee3403d5..b82b448f9 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; @@ -252,10 +254,12 @@ char *build_dm_uuid(struct dm_pool *mem, const struct logical_volume *lv, /* Suffixes used here MUST match lib/activate/dev_manager.c */ layer = lv_is_cache_origin(lv) ? "real" : lv_is_writecache_origin(lv) ? "real" : + lv_is_integrity_origin(lv) ? "real" : (lv_is_cache(lv) && lv_is_pending_delete(lv)) ? "real" : lv_is_cache_pool_data(lv) ? "cdata" : lv_is_cache_pool_metadata(lv) ? "cmeta" : lv_is_cache_vol(lv) ? "cvol" : + lv_is_integrity_metadata(lv) ? "imeta" : // FIXME: dm-tree needs fixes for mirrors/raids //lv_is_mirror_image(lv) ? "mimage" : //lv_is_mirror_log(lv) ? "mlog" : 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/test/shell/integrity.sh b/test/shell/integrity.sh new file mode 100644 index 000000000..e685e03ec --- /dev/null +++ b/test/shell/integrity.sh @@ -0,0 +1,167 @@ +#!/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 4 64 + +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 + +_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 + vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" +} + +_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_raid1() { + 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 raid1 + dd if=$mnt/fileB of=tmp bs=1k + ls -l tmp + stat -c %s tmp | grep 16384 + diff fileB tmp + rm tmp + + umount $mnt +} + +_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_raid1 +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_raid1 +lvchange -an $vg/$lv1 +lvconvert --integrity n $vg/$lv1 +lvremove $vg/$lv1 +vgremove -ff $vg + diff --git a/tools/args.h b/tools/args.h index 999d891f7..ad73673ce 100644 --- a/tools/args.h +++ b/tools/args.h @@ -274,6 +274,18 @@ 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", 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 37a01cb55..6b490697e 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 LV, --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 LV, --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, --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 LV, --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 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/command.c b/tools/command.c index 0cbd8773c..567881ca1 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 d0d277bcd..e87bbd18b 100644 --- a/tools/lvconvert.c +++ b/tools/lvconvert.c @@ -5730,6 +5730,150 @@ 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; + + if (!lv_is_integrity(lv) && !lv_is_raid(lv)) { + log_error("LV does not have integrity."); + return 0; + } + + /* TODO: lift this restriction */ + if (lv_info(cmd, lv, 1, NULL, 0, 0)) { + log_error("LV must be inactive to remove integrity."); + return 0; + } + + if (!archive(vg)) + return_0; + + if (lv_is_integrity(lv)) { + if (!lv_remove_integrity(lv)) + return_0; + } else if (lv_is_raid(lv)) { + if (!lv_remove_integrity_from_raid(lv)) + return_0; + } + + if (!vg_write(vg) || !vg_commit(vg)) + 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 lv_activate_opts laopts = { 0 }; + struct volume_group *vg = lv->vg; + struct lv_segment *seg; + struct dm_list *use_pvh; + int ret; + + seg = first_seg(lv); + + /* TODO: lift this restriction, if we can reload table to set recalculate */ + if (lv_info(cmd, lv, 1, NULL, 0, 0)) { + log_error("LV must be inactive to add integrity."); + return 0; + } + + /* 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 (seg_is_raid(seg)) + ret = lv_add_integrity_to_raid(lv, "external", set, use_pvh); + else + ret = lv_add_integrity(lv, "external", meta_name, set, use_pvh); + if (!ret) + return 0; + + backup(vg); + + /* + * The LV needs to be activated to start the integrity metadata + * initialization. + */ + + 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."); + } else { + log_print("LV %s activated to initialize integrity.", display_lvname(lv)); + } + + 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 4a1534cc2..1ab5de6f6 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,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."); @@ -1782,5 +1808,6 @@ int lvcreate(struct cmd_context *cmd, int argc, char **argv) _destroy_lvcreate_params(&lp); destroy_processing_handle(cmd, handle); + return ret; } 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/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 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) -- cgit v1.2.1