From a0bbc94900bfab7101b033b8b474fae7331408c0 Mon Sep 17 00:00:00 2001 From: David Teigland Date: Wed, 20 Nov 2019 16:07:27 -0600 Subject: Add dm-integrity support Create a linear LV with integrity added to it: lvcreate --type integrity -n Name -L Size VG or lvcreate --integrity y -n Name -L Size VG When creating, --zero y will zero the entire LV. This is suggested to initialize integrity checksums and avoid causing read errors in unwritten portions of the new LV. Options: --integrity internal integrity checksum metadata is interleaved with data. --integrity external integrity checksum metadata on separate LV, allows adding integrity to existing LV, or removing integrity. --integritymetadata LV use the specified LV for external metadata. --integritysettings String set dm-integrity parameters. TODO: - for internal, zero start of origin LV - increase allocated LV size by an extra extent, and then round provided_data_sectors down to be a multiple of extent size - add command to remove intregrity from an LV (only for external), i.e. lvconvert --integrity none LV - let integrity be used by raid images --- device_mapper/all.h | 38 +++++ device_mapper/libdm-deptree.c | 120 +++++++++++++++ device_mapper/libdm-targets.c | 29 ++++ lib/Makefile.in | 2 + lib/activate/activate.c | 16 +- lib/activate/activate.h | 4 + lib/activate/dev_manager.c | 16 ++ lib/commands/toolcontext.c | 3 + lib/format_text/flags.c | 2 + lib/integrity/integrity.c | 325 +++++++++++++++++++++++++++++++++++++++ lib/metadata/integrity_manip.c | 264 +++++++++++++++++++++++++++++++ lib/metadata/lv.c | 2 + lib/metadata/lv_manip.c | 99 +++++++++++- lib/metadata/merge.c | 2 + lib/metadata/metadata-exported.h | 20 +++ lib/metadata/segtype.h | 6 + tools/args.h | 9 ++ tools/command-lines.in | 14 ++ tools/lv_types.h | 1 + tools/lvcreate.c | 25 +++ tools/toollib.c | 172 +++++++++++++++++++++ tools/tools.h | 3 + 22 files changed, 1168 insertions(+), 4 deletions(-) create mode 100644 lib/integrity/integrity.c create mode 100644 lib/metadata/integrity_manip.c diff --git a/device_mapper/all.h b/device_mapper/all.h index 57673b44a..780cdb9da 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,35 @@ 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); /* * VDO target diff --git a/device_mapper/libdm-deptree.c b/device_mapper/libdm-deptree.c index 7fac6ab20..d20f2f7a8 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,10 @@ 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 */ }; /* Per-device properties */ @@ -2705,6 +2711,82 @@ 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 (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 (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 +2971,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 +2987,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 +3825,39 @@ 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) +{ + 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)); + + return 1; +} + int dm_tree_node_add_replicator_target(struct dm_tree_node *node, uint64_t size, const char *rlog_uuid, diff --git a/device_mapper/libdm-targets.c b/device_mapper/libdm-targets.c index d82e28b13..4289927bd 100644 --- a/device_mapper/libdm-targets.c +++ b/device_mapper/libdm-targets.c @@ -380,6 +380,35 @@ int dm_get_status_writecache(struct dm_pool *mem, const char *params, return 1; } +int dm_get_status_integrity(struct dm_pool *mem, const char *params, + struct dm_status_integrity **status) +{ + struct dm_status_integrity *s; + char recalc_str[8]; + + if (!(s = dm_pool_zalloc(mem, sizeof(struct dm_status_integrity)))) + return_0; + + memset(recalc_str, 0, sizeof(recalc_str)); + + if (sscanf(params, "%llu %llu %s", + (unsigned long long *)&s->number_of_mismatches, + (unsigned long long *)&s->provided_data_sectors, + recalc_str) != 3) { + log_error("Failed to parse integrity params: %s.", params); + dm_pool_free(mem, s); + return 0; + } + + if (recalc_str[0] == '-') + s->recalc_sector = 0; + else + s->recalc_sector = strtoull(recalc_str, NULL, 0); + + *status = s; + return 1; +} + int parse_thin_pool_status(const char *params, struct dm_status_thin_pool *s) { int pos; diff --git a/lib/Makefile.in b/lib/Makefile.in index c037b4162..86ced01ab 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -20,6 +20,7 @@ SOURCES =\ activate/activate.c \ cache/lvmcache.c \ writecache/writecache.c \ + integrity/integrity.c \ cache_segtype/cache.c \ commands/toolcontext.c \ config/config.c \ @@ -67,6 +68,7 @@ SOURCES =\ log/log.c \ metadata/cache_manip.c \ metadata/writecache_manip.c \ + metadata/integrity_manip.c \ metadata/lv.c \ metadata/lv_manip.c \ metadata/merge.c \ diff --git a/lib/activate/activate.c b/lib/activate/activate.c index a82a5cbc4..a0a0c3bdc 100644 --- a/lib/activate/activate.c +++ b/lib/activate/activate.c @@ -2870,6 +2870,7 @@ int deactivate_lv_with_sub_lv(const struct logical_volume *lv) int activate_lv(struct cmd_context *cmd, const struct logical_volume *lv) { const struct logical_volume *active_lv; + const struct logical_volume *lv_use; int ret; /* @@ -2888,19 +2889,30 @@ int activate_lv(struct cmd_context *cmd, const struct logical_volume *lv) goto out; } + if (lv->status & LV_UNCOMMITTED) + lv_use = lv; + else + lv_use = lv_committed(lv); + ret = lv_activate_with_filter(cmd, NULL, 0, (lv->status & LV_NOSCAN) ? 1 : 0, (lv->status & LV_TEMPORARY) ? 1 : 0, - lv_committed(lv)); + lv_use); out: return ret; } int deactivate_lv(struct cmd_context *cmd, const struct logical_volume *lv) { + const struct logical_volume *lv_use; int ret; - ret = lv_deactivate(cmd, NULL, lv_committed(lv)); + if (lv->status & LV_UNCOMMITTED) + lv_use = lv; + else + lv_use = lv_committed(lv); + + ret = lv_deactivate(cmd, NULL, lv_use); return ret; } diff --git a/lib/activate/activate.h b/lib/activate/activate.h index a5ee438ad..e3c1bb35e 100644 --- a/lib/activate/activate.h +++ b/lib/activate/activate.h @@ -39,6 +39,7 @@ typedef enum { SEG_STATUS_THIN_POOL, SEG_STATUS_VDO_POOL, SEG_STATUS_WRITECACHE, + SEG_STATUS_INTEGRITY, SEG_STATUS_UNKNOWN } lv_seg_status_type_t; @@ -53,6 +54,7 @@ struct lv_seg_status { struct dm_status_thin *thin; struct dm_status_thin_pool *thin_pool; struct dm_status_writecache *writecache; + struct dm_status_integrity *integrity; struct lv_status_vdo vdo_pool; }; }; @@ -260,6 +262,7 @@ void fs_unlock(void); #define TARGET_NAME_CACHE "cache" #define TARGET_NAME_WRITECACHE "writecache" +#define TARGET_NAME_INTEGRITY "integrity" #define TARGET_NAME_ERROR "error" #define TARGET_NAME_ERROR_OLD "erro" /* Truncated in older kernels */ #define TARGET_NAME_LINEAR "linear" @@ -277,6 +280,7 @@ void fs_unlock(void); #define MODULE_NAME_CLUSTERED_MIRROR "clog" #define MODULE_NAME_CACHE TARGET_NAME_CACHE #define MODULE_NAME_WRITECACHE TARGET_NAME_WRITECACHE +#define MODULE_NAME_INTEGRITY TARGET_NAME_INTEGRITY #define MODULE_NAME_ERROR TARGET_NAME_ERROR #define MODULE_NAME_LOG_CLUSTERED "log-clustered" #define MODULE_NAME_LOG_USERSPACE "log-userspace" diff --git a/lib/activate/dev_manager.c b/lib/activate/dev_manager.c index 8569e860b..b31cfa0ab 100644 --- a/lib/activate/dev_manager.c +++ b/lib/activate/dev_manager.c @@ -222,6 +222,10 @@ static int _get_segment_status_from_target_params(const char *target_name, if (!dm_get_status_writecache(seg_status->mem, params, &(seg_status->writecache))) return_0; seg_status->type = SEG_STATUS_WRITECACHE; + } else if (segtype_is_integrity(segtype)) { + if (!dm_get_status_integrity(seg_status->mem, params, &(seg_status->integrity))) + return_0; + seg_status->type = SEG_STATUS_INTEGRITY; } else /* * TODO: Add support for other segment types too! @@ -299,6 +303,9 @@ static int _info_run(const char *dlid, struct dm_info *dminfo, if (lv_is_vdo_pool(seg_status->seg->lv)) length = get_vdo_pool_virtual_size(seg_status->seg); + if (lv_is_integrity(seg_status->seg->lv)) + length = seg_status->seg->integrity_data_sectors; + do { target = dm_get_next_target(dmt, target, &target_start, &target_length, &target_name, &target_params); @@ -2620,6 +2627,10 @@ static int _add_lv_to_dtree(struct dev_manager *dm, struct dm_tree *dtree, if (!_add_lv_to_dtree(dm, dtree, seg->writecache, dm->activation ? origin_only : 1)) return_0; } + if (seg->integrity_meta_dev && seg_is_integrity(seg)) { + if (!_add_lv_to_dtree(dm, dtree, seg->integrity_meta_dev, dm->activation ? origin_only : 1)) + return_0; + } if (seg->pool_lv && (lv_is_cache_pool(seg->pool_lv) || lv_is_cache_vol(seg->pool_lv) || dm->track_external_lv_deps) && /* When activating and not origin_only detect linear 'overlay' over pool */ @@ -3076,6 +3087,11 @@ static int _add_segment_to_dtree(struct dev_manager *dm, lv_layer(seg->writecache))) return_0; + if (seg->integrity_meta_dev && !laopts->origin_only && + !_add_new_lv_to_dtree(dm, dtree, seg->integrity_meta_dev, laopts, + lv_layer(seg->integrity_meta_dev))) + return_0; + /* Add any LVs used by this segment */ for (s = 0; s < seg->area_count; ++s) { if ((seg_type(seg, s) == AREA_LV) && diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c index 479d4991c..7f8e346c6 100644 --- a/lib/commands/toolcontext.c +++ b/lib/commands/toolcontext.c @@ -1362,6 +1362,9 @@ static int _init_segtypes(struct cmd_context *cmd) return 0; #endif + if (!init_integrity_segtypes(cmd, &seglib)) + return 0; + return 1; } diff --git a/lib/format_text/flags.c b/lib/format_text/flags.c index 2873ba632..bfb6c6403 100644 --- a/lib/format_text/flags.c +++ b/lib/format_text/flags.c @@ -104,8 +104,10 @@ static const struct flag _lv_flags[] = { {LV_VDO_POOL, NULL, 0}, {LV_VDO_POOL_DATA, NULL, 0}, {WRITECACHE, NULL, 0}, + {INTEGRITY, NULL, 0}, {LV_PENDING_DELETE, NULL, 0}, /* FIXME Display like COMPATIBLE_FLAG */ {LV_REMOVED, NULL, 0}, + {LV_UNCOMMITTED, NULL, 0}, {0, NULL, 0} }; diff --git a/lib/integrity/integrity.c b/lib/integrity/integrity.c new file mode 100644 index 000000000..c897c9a60 --- /dev/null +++ b/lib/integrity/integrity.c @@ -0,0 +1,325 @@ +/* + * 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 && !add_seg_to_segs_using_this_lv(meta_lv, seg)) + return_0; + + return 1; +} + +static int _integrity_text_import_area_count(const struct dm_config_node *sn, + uint32_t *area_count) +{ + *area_count = 1; + + return 1; +} + +static int _integrity_text_export(const struct lv_segment *seg, + struct formatter *f) +{ + const struct integrity_settings *set = &seg->integrity_settings; + + outf(f, "origin = \"%s\"", seg_lv(seg, 0)->name); + outf(f, "data_sectors = %llu", (unsigned long long)seg->integrity_data_sectors); + + outf(f, "mode = \"%s\"", set->mode); + outf(f, "tag_size = %u", set->tag_size); + outf(f, "internal_hash = \"%s\"", set->internal_hash); + + if (seg->integrity_meta_dev) + outf(f, "meta_dev = \"%s\"", seg->integrity_meta_dev->name); + + if (set->journal_sectors_set) + outf(f, "journal_sectors = %u", set->journal_sectors); + + if (set->interleave_sectors_set) + outf(f, "interleave_sectors = %u", set->interleave_sectors); + + if (set->buffer_sectors_set) + outf(f, "buffer_sectors = %u", set->buffer_sectors); + + if (set->journal_watermark_set) + outf(f, "journal_watermark = %u", set->journal_watermark); + + if (set->commit_time_set) + outf(f, "commit_time = %u", set->commit_time); + + if (set->block_size_set) + outf(f, "block_size = %u", set->block_size); + + if (set->bitmap_flush_interval) + outf(f, "bitmap_flush_interval = %u", set->bitmap_flush_interval); + + if (set->sectors_per_bit) + outf(f, "sectors_per_bit = %llu", (unsigned long long)set->sectors_per_bit); + + return 1; +} + +static void _destroy(struct segment_type *segtype) +{ + free((void *) segtype); +} + +#ifdef DEVMAPPER_SUPPORT + +static int _target_present(struct cmd_context *cmd, + const struct lv_segment *seg __attribute__((unused)), + unsigned *attributes __attribute__((unused))) +{ + static int _integrity_checked = 0; + static int _integrity_present = 0; + + if (!activation()) + return 0; + + if (!_integrity_checked) { + _integrity_checked = 1; + _integrity_present = target_present(cmd, TARGET_NAME_INTEGRITY, 0); + } + + return _integrity_present; +} + +static int _modules_needed(struct dm_pool *mem, + const struct lv_segment *seg __attribute__((unused)), + struct dm_list *modules) +{ + if (!str_list_add(mem, modules, MODULE_NAME_INTEGRITY)) { + log_error("String list allocation failed for integrity module."); + return 0; + } + + return 1; +} +#endif /* DEVMAPPER_SUPPORT */ + +#ifdef DEVMAPPER_SUPPORT +static int _integrity_add_target_line(struct dev_manager *dm, + struct dm_pool *mem, + struct cmd_context *cmd __attribute__((unused)), + void **target_state __attribute__((unused)), + struct lv_segment *seg, + const struct lv_activate_opts *laopts __attribute__((unused)), + 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)) + 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..339b4a4ba --- /dev/null +++ b/lib/metadata/integrity_manip.c @@ -0,0 +1,264 @@ +/* + * 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 + +/* + * The user wants external metadata, but did not specify an existing + * LV to hold metadata, so create an LV for metadata. Save it in + * lp->integrity_meta_lv and it will be passed to lv_add_integrity(). + */ +int lv_create_integrity_metadata(struct cmd_context *cmd, + struct volume_group *vg, + struct lvcreate_params *lp) +{ + char metaname[NAME_LEN]; + uint32_t extent_bytes; + 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, + }; + + if (dm_snprintf(metaname, NAME_LEN, "%s_imeta", lp->lv_name) < 0) { + log_error("Failed to create metadata LV name."); + return 0; + } + + if (!(lp_meta.lv_name = strdup(metaname))) + return_0; + + /* TODO: set pvh per command line */ + /* TODO: scale size according to LV size. */ + extent_bytes = vg->extent_size * SECTOR_SIZE; + lp_meta.extents = (4 * ONE_MB_IN_BYTES) / extent_bytes; + + log_debug("Creating integrity metadata LV with %u extents", lp_meta.extents); + + dm_list_init(&lp_meta.tags); + + if (!(lp_meta.segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED))) + return_0; + + if (!(lv = lv_create_single(vg, &lp_meta))) { + log_error("Failed to create integrity metadata LV"); + return 0; + } + + lp->integrity_meta_lv = lv; + return 1; +} + +static int _get_provided_data_sectors(struct logical_volume *lv, uint64_t *provided_data_sectors) +{ + struct lv_with_info_and_seg_status status; + + 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; + } + + *provided_data_sectors = status.seg_status.integrity->provided_data_sectors; + + dm_pool_destroy(status.seg_status.mem); + return 1; + +fail: + dm_pool_destroy(status.seg_status.mem); + return 0; +} + +int lv_add_integrity(struct logical_volume *lv, const char *arg, + struct logical_volume *meta_lv_created, + const char *meta_name, + struct integrity_settings *settings) +{ + struct cmd_context *cmd = lv->vg->cmd; + struct integrity_settings *set; + struct logical_volume *lv_orig; + struct logical_volume *meta_lv = NULL; + const struct segment_type *segtype; + struct lv_segment *seg; + int ret; + + if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_INTEGRITY))) + return_0; + + /* + * "lv_orig" is a new LV with new id, but with the segments from "lv". + * "lv" keeps the existing name and id, but gets a new integrity segment, + * in place of the segments that were moved to lv_orig. + */ + + if (!(lv_orig = insert_layer_for_lv(cmd, lv, INTEGRITY, "_iorig"))) + return_0; + + seg = first_seg(lv); + seg->segtype = segtype; + + lv->status |= INTEGRITY; + + memcpy(&seg->integrity_settings, settings, sizeof(struct integrity_settings)); + set = &seg->integrity_settings; + + if (!set->mode[0]) + set->mode[0] = DEFAULT_MODE; + + if (!set->tag_size) + set->tag_size = DEFAULT_TAG_SIZE; + + if (!set->internal_hash) + set->internal_hash = DEFAULT_INTERNAL_HASH; + + /* + * --integrity is y|external|internal + */ + + if (!arg) + arg = "external"; + + if (!strcmp(arg, "none")) { + log_error("Invalid --integrity arg for lvcreate."); + return 0; + } + + if (!strcmp(arg, "internal") && meta_name) { + log_error("Internal integrity cannot be used with integritymetadata option."); + return 0; + } + + if (meta_lv_created) + meta_lv = meta_lv_created; + else if (meta_name) { + if (!(meta_lv = find_lv(lv->vg, meta_name))) { + log_error("LV %s not found.", meta_name); + return_0; + } + } + + /* + * When not using a meta_dev, dm-integrity needs to tell us what the + * usable size of the LV is, which is the dev size minus the integrity + * overhead. To find that, we need to do a special, temporary activation + * of the new LV, specifying a dm dev size of 1, then check the dm dev + * status field provided_data_sectors, which is the actual size of the + * LV. We need to include provided_data_sectors in the metadata for the + * new LV, and use this as the dm dev size for normal LV activation. + * + * When using a meta_dev, the dm dev size is the size of the data + * device. The necessary size of the meta_dev for the given data + * device needs to be estimated. + */ + + if (meta_lv) { + if (!sync_local_dev_names(cmd)) { + log_error("Failed to sync local devices."); + return 0; + } + + if (!activate_and_wipe_lv(meta_lv, 0)) { + log_error("LV %s could not be zeroed.", display_lvname(meta_lv)); + return 0; + } + seg->integrity_data_sectors = seg->len; + seg->integrity_meta_dev = meta_lv; + lv_set_hidden(meta_lv); + /* TODO: give meta_lv a suffix? e.g. _imeta */ + ret = 1; + } else { + /* dm-integrity wants temp/fake size of 1 to report usable size */ + seg->integrity_data_sectors = 1; + + lv->status |= LV_TEMPORARY; + lv->status |= LV_NOSCAN; + lv->status |= LV_UNCOMMITTED; + + log_debug("Activating temporary integrity LV to get data sectors."); + + if (!activate_lv(cmd, lv)) { + log_error("Failed to activate temporary integrity."); + ret = 0; + goto out; + } + + if (!_get_provided_data_sectors(lv, &seg->integrity_data_sectors)) { + log_error("Failed to get data sectors from dm-integrity"); + ret = 0; + } else { + log_debug("Found integrity provided_data_sectors %llu", (unsigned long long)seg->integrity_data_sectors); + ret = 1; + } + + lv->status |= LV_UNCOMMITTED; + + if (!deactivate_lv(cmd, lv)) { + log_error("Failed to deactivate temporary integrity."); + ret = 0; + } + + lv->status &= ~LV_UNCOMMITTED; + lv->status &= ~LV_NOSCAN; + lv->status &= ~LV_TEMPORARY; + } + out: + return ret; +} + + diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c index 4c2ab2bbf..92ba9179c 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; } diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c index b4daa5633..1893bb6c0 100644 --- a/lib/metadata/lv_manip.c +++ b/lib/metadata/lv_manip.c @@ -1457,6 +1457,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 +5663,11 @@ int lv_resize(struct logical_volume *lv, return 0; } + if (lv_is_integrity(lv)) { + log_error("Resize not yet allowed on LVs with integrity."); + return 0; + } + if (!_lvresize_check(lv, lp)) return_0; @@ -7410,6 +7424,12 @@ int insert_layer_for_segments_on_pv(struct cmd_context *cmd, return 1; } +/* FIXME: copied from label.c */ +#define BCACHE_BLOCK_SIZE_IN_SECTORS 256 /* 256*512 = 128K */ +#define BCACHE_BLOCK_SIZE_IN_BYTES 131072 +#define ONE_MB_IN_BYTES 1048576 +#define ONE_MB_IN_SECTORS 2048 /* 2048 * 512 = 1048576 */ + /* * Initialize the LV with 'value'. */ @@ -7468,7 +7488,44 @@ int wipe_lv(struct logical_volume *lv, struct wipe_params wp) stack; } - if (wp.do_zero) { + if (wp.do_zero && !wp.zero_value && (wp.zero_sectors >= ONE_MB_IN_SECTORS)) { + uint64_t off = 0, i = 0, j = 0; + uint64_t zero_bytes; + uint32_t extra_bytes; + + zero_bytes = wp.zero_sectors * 512; + + if ((extra_bytes = (zero_bytes % ONE_MB_IN_BYTES))) + zero_bytes -= extra_bytes; + + log_print("Zeroing %llu MiB...", (unsigned long long)(zero_bytes / ONE_MB_IN_BYTES)); + + /* + * Write 1MiB at a time to avoid going over bcache size. + * Then write 128KiB at a time to cover remaining dev size. + */ + + for (i = 0; i < (zero_bytes / ONE_MB_IN_BYTES); i++) { + off = i * ONE_MB_IN_BYTES; + if (!dev_write_zeros(dev, off, (size_t)ONE_MB_IN_BYTES)) + log_error("Failed to zero LV at offset %llu.", (unsigned long long)off); + } + + if (extra_bytes) { + log_warn("Zeroing %u bytes at %llu...", extra_bytes, (unsigned long long)off); + + for (j = 0; j < (extra_bytes / BCACHE_BLOCK_SIZE_IN_BYTES); j++) { + off = i * ONE_MB_IN_BYTES + j * BCACHE_BLOCK_SIZE_IN_BYTES; + if (!dev_write_zeros(dev, off, (size_t)BCACHE_BLOCK_SIZE_IN_BYTES)) + log_error("Failed to zero LV at offset %llu.", (unsigned long long)off); + } + } + + /* FIXME: bcache can't write partial block yet */ + if ((extra_bytes = (wp.zero_sectors * 512) - (i * ONE_MB_IN_BYTES + j * BCACHE_BLOCK_SIZE_IN_BYTES))) + log_warn("WARNING: last %llu bytes not zeroed.", (unsigned long long)extra_bytes); + + } else if (wp.do_zero) { zero_sectors = wp.zero_sectors ? : UINT64_C(4096) >> SECTOR_SHIFT; if (zero_sectors > lv->size) @@ -7679,6 +7736,11 @@ static int _should_wipe_lv(struct lvcreate_params *lp, first_seg(first_seg(lv)->pool_lv)->zero_new_blocks)) return 0; + if (seg_is_integrity(lp) && (!lp->zero || !(lv->status & LVM_WRITE))) { + log_warn("WARNING: --zero not enabled, integrity will not be initialized and may cause read errors."); + return 0; + } + /* Cannot zero read-only volume */ if ((lv->status & LVM_WRITE) && (lp->zero || lp->wipe_signatures)) @@ -7934,6 +7996,18 @@ 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)) { + /* + * TODO: if using internal metadata, estimate the amount of metadata + * that will be needed, and add this to the amount of PV space being + * allocated so that the usable LV size is what the user requested. + * Or, just request an extra extent_size bytes, then round the + * provided_data_sectors down to be an extent_size multiple. + */ + 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, @@ -8158,6 +8232,12 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg, } } + if (seg_is_integrity(lp)) { + if (!lv_add_integrity(lv, lp->integrity_arg, lp->integrity_meta_lv, + lp->integrity_meta_name, &lp->integrity_settings)) + return_NULL; + } + lv_set_activation_skip(lv, lp->activation_skip & ACTIVATION_SKIP_SET, lp->activation_skip & ACTIVATION_SKIP_SET_ENABLED); /* @@ -8282,7 +8362,22 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg, goto deactivate_and_revert_new_lv; } - if (_should_wipe_lv(lp, lv, !lp->suppress_zero_warn)) { + if (seg_is_integrity(lp)) { + struct wipe_params wipe; + + memset(&wipe, 0, sizeof(wipe)); + wipe.do_zero = 1; + wipe.zero_sectors = first_seg(lv)->integrity_data_sectors; + + if (!_should_wipe_lv(lp, lv, 1)) + goto_out; + + if (!wipe_lv(lv, wipe)) + log_error("Failed to zero LV."); + + goto out; + + } else if (_should_wipe_lv(lp, lv, !lp->suppress_zero_warn)) { if (!wipe_lv(lv, (struct wipe_params) { .do_zero = lp->zero, diff --git a/lib/metadata/merge.c b/lib/metadata/merge.c index e482921ce..d6dd7a55a 100644 --- a/lib/metadata/merge.c +++ b/lib/metadata/merge.c @@ -742,6 +742,8 @@ int check_lv_segments(struct logical_volume *lv, int complete_vg) seg_found++; if (seg->metadata_lv == lv || seg->pool_lv == lv || seg->writecache == lv) seg_found++; + if (seg->integrity_meta_dev == lv) + seg_found++; if (seg_is_thin_volume(seg) && (seg->origin == lv || seg->external_lv == lv)) seg_found++; diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h index c61c85c7e..5e83fb251 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 LV_UNCOMMITTED UINT64_C(0x0000000002000000) #define VIRTUAL_ORIGIN UINT64_C(0x0000000008000000) /* LV - internal use only */ #define MERGING UINT64_C(0x0000000010000000) /* LV SEG */ @@ -261,6 +263,7 @@ #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_vdo(lv) (((lv)->status & LV_VDO) ? 1 : 0) #define lv_is_vdo_pool(lv) (((lv)->status & LV_VDO_POOL) ? 1 : 0) @@ -519,6 +522,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 +999,11 @@ struct lvcreate_params { alloc_policy_t alloc; /* all */ struct dm_vdo_target_params vdo_params; /* vdo */ + const char *integrity_arg; + const char *integrity_meta_name; /* external LV is user-specified */ + struct logical_volume *integrity_meta_lv; /* external LV we create */ + struct integrity_settings integrity_settings; + struct dm_list tags; /* all */ int yes; @@ -1385,4 +1397,12 @@ int vg_is_foreign(struct volume_group *vg); void vg_write_commit_bad_mdas(struct cmd_context *cmd, struct volume_group *vg); +int lv_add_integrity(struct logical_volume *lv, const char *arg, + struct logical_volume *meta_lv_created, + const char *meta_name, struct integrity_settings *settings); +int lv_create_integrity_metadata(struct cmd_context *cmd, + struct volume_group *vg, + struct lvcreate_params *lp); + + #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/tools/args.h b/tools/args.h index bd07aa3d7..105f908ce 100644 --- a/tools/args.h +++ b/tools/args.h @@ -272,6 +272,15 @@ arg(ignoreunsupported_ARG, '\0', "ignoreunsupported", 0, 0, 0, "and \\fBdiff\\fP types include unsupported settings in their output by default,\n" "all the other types ignore unsupported settings.\n") +arg(integrity_ARG, '\0', "integrity", string_VAL, 0, 0, + "Controls if integrity metadata should be stored and checked for an LV.\n") + +arg(integritymetadata_ARG, '\0', "integritymetadata", lv_VAL, 0, 0, + "The name of an LV to hold integrity metadata.\n") + +arg(integritysettings_ARG, '\0', "integritysettings", string_VAL, ARG_GROUPABLE, 0, + "Set dm-integrity parameters.\n") + arg(labelsector_ARG, '\0', "labelsector", number_VAL, 0, 0, "By default the PV is labelled with an LVM2 identifier in its second\n" "sector (sector 1). This lets you use a different sector near the\n" diff --git a/tools/command-lines.in b/tools/command-lines.in index 7be471557..0f4237e9d 100644 --- a/tools/command-lines.in +++ b/tools/command-lines.in @@ -1269,6 +1269,20 @@ FLAGS: SECONDARY_SYNTAX --- +lvcreate --type integrity --size SizeMB VG +OO: --integrity String, --integritymetadata LV, --integritysettings String, OO_LVCREATE +OP: PV ... +ID: lvcreate_integrity +DESC: Create a LV with integrity. + +lvcreate --integrity String --size SizeMB VG +OO: --type integrity, --integritymetadata LV, --integritysettings String, OO_LVCREATE +OP: PV ... +ID: lvcreate_integrity +DESC: Create a LV with integrity (infers --type integrity). + +--- + lvdisplay OO: --aligned, --all, --binary, --colon, --columns, --configreport ConfigReport, --foreign, --history, --ignorelockingfailure, diff --git a/tools/lv_types.h b/tools/lv_types.h index 778cd541d..d1c94ccd8 100644 --- a/tools/lv_types.h +++ b/tools/lv_types.h @@ -34,5 +34,6 @@ lvt(raid10_LVT, "raid10", NULL) lvt(error_LVT, "error", NULL) lvt(zero_LVT, "zero", NULL) lvt(writecache_LVT, "writecache", NULL) +lvt(integrity_LVT, "integrity", NULL) lvt(LVT_COUNT, "", NULL) diff --git a/tools/lvcreate.c b/tools/lvcreate.c index ebe6c9f46..fc83b76bc 100644 --- a/tools/lvcreate.c +++ b/tools/lvcreate.c @@ -785,6 +785,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; @@ -1218,6 +1220,11 @@ static int _lvcreate_params(struct cmd_context *cmd, } } + if (seg_is_integrity(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; @@ -1702,6 +1709,24 @@ static int _lvcreate_single(struct cmd_context *cmd, const char *vg_name, lp->pool_name ? : "with generated name", lp->vg_name, lp->segtype->name); } + /* + * Create an LV to hold integrity metadata when: + * + * . The user wants external (not internal) metadata. External + * is indicated by: no option set (external is default), or + * --integrity y|external. + * + * . The user did not specify an existing metadata LV with + * --integritymetadata, saved as lp->integrity_meta_name. + */ + if (seg_is_integrity(lp) && !lp->integrity_meta_name && + (!lp->integrity_arg || + !strcmp(lp->integrity_arg, "external") || + !strcmp(lp->integrity_arg, "y"))) { + if (!lv_create_integrity_metadata(cmd, vg, lp)) + goto_out; + } + if (vg->lock_type && !strcmp(vg->lock_type, "sanlock")) { if (!handle_sanlock_lv(cmd, vg)) { log_error("No space for sanlock lock, extend the internal lvmlock LV."); diff --git a/tools/toollib.c b/tools/toollib.c index ee2419b8c..86eb2d0f4 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 b78c47116..15efe2707 100644 --- a/tools/tools.h +++ b/tools/tools.h @@ -233,6 +233,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); -- cgit v1.2.1