diff options
Diffstat (limited to 'lib/metadata')
-rw-r--r-- | lib/metadata/integrity_manip.c | 649 | ||||
-rw-r--r-- | lib/metadata/lv.c | 7 | ||||
-rw-r--r-- | lib/metadata/lv_manip.c | 225 | ||||
-rw-r--r-- | lib/metadata/merge.c | 2 | ||||
-rw-r--r-- | lib/metadata/metadata-exported.h | 31 | ||||
-rw-r--r-- | lib/metadata/segtype.h | 6 |
6 files changed, 918 insertions, 2 deletions
diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c new file mode 100644 index 000000000..ca3094ffb --- /dev/null +++ b/lib/metadata/integrity_manip.c @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2014-2015 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "lib/misc/lib.h" +#include "lib/metadata/metadata.h" +#include "lib/locking/locking.h" +#include "lib/misc/lvm-string.h" +#include "lib/commands/toolcontext.h" +#include "lib/display/display.h" +#include "lib/metadata/segtype.h" +#include "lib/activate/activate.h" +#include "lib/config/defaults.h" +#include "lib/activate/dev_manager.h" + +#define DEFAULT_TAG_SIZE 4 /* bytes */ +#define DEFAULT_MODE 'B' +#define DEFAULT_INTERNAL_HASH "crc32c" + +#define ONE_MB_IN_BYTES 1048576 + +int lv_is_integrity_origin(const struct logical_volume *lv) +{ + struct seg_list *sl; + + dm_list_iterate_items(sl, &lv->segs_using_this_lv) { + if (!sl->seg || !sl->seg->lv || !sl->seg->origin) + continue; + if (lv_is_integrity(sl->seg->lv) && (sl->seg->origin == lv)) + return 1; + } + return 0; +} + +/* + * Every 500M of data needs 4M of metadata. + * (From trial and error testing.) + */ +static uint64_t _lv_size_bytes_to_integrity_meta_bytes(uint64_t lv_size_bytes) +{ + return ((lv_size_bytes / (500 * ONE_MB_IN_BYTES)) + 1) * (4 * ONE_MB_IN_BYTES); +} + +/* + * The user wants external metadata, but did not specify an existing + * LV to hold metadata, so create an LV for metadata. Save it in + * lp->integrity_meta_lv and it will be passed to lv_add_integrity(). + */ +int lv_create_integrity_metadata(struct cmd_context *cmd, + struct volume_group *vg, + struct lvcreate_params *lp) +{ + char metaname[NAME_LEN]; + uint64_t lv_size_bytes, meta_bytes, meta_sectors; + struct logical_volume *lv; + struct lvcreate_params lp_meta = { + .activate = CHANGE_AN, + .alloc = ALLOC_INHERIT, + .major = -1, + .minor = -1, + .permission = LVM_READ | LVM_WRITE, + .pvh = &vg->pvs, + .read_ahead = DM_READ_AHEAD_NONE, + .stripes = 1, + .vg_name = vg->name, + .zero = 0, + .wipe_signatures = 0, + .suppress_zero_warn = 1, + }; + + if (dm_snprintf(metaname, NAME_LEN, "%s_imeta", lp->lv_name) < 0) { + log_error("Failed to create metadata LV name."); + return 0; + } + + if (!(lp_meta.lv_name = strdup(metaname))) + return_0; + + lp_meta.pvh = lp->pvh; + + lv_size_bytes = lp->extents * vg->extent_size * 512; + meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv_size_bytes); + meta_sectors = meta_bytes / 512; + lp_meta.extents = meta_sectors / vg->extent_size; + + log_print("Creating integrity metadata LV %s with size %s.", + metaname, display_size(cmd, meta_sectors)); + + dm_list_init(&lp_meta.tags); + + if (!(lp_meta.segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED))) + return_0; + + if (!(lv = lv_create_single(vg, &lp_meta))) { + log_error("Failed to create integrity metadata LV"); + return 0; + } + + lp->integrity_meta_lv = lv; + return 1; +} + +static int _get_provided_data_sectors(struct logical_volume *lv, uint64_t *provided_data_sectors) +{ + struct lv_with_info_and_seg_status status; + uint64_t data_sectors, extra_sectors; + + memset(&status, 0, sizeof(status)); + status.seg_status.type = SEG_STATUS_NONE; + + status.seg_status.seg = first_seg(lv); + + /* FIXME: why reporter_pool? */ + if (!(status.seg_status.mem = dm_pool_create("reporter_pool", 1024))) { + log_error("Failed to get mem for LV status."); + return 0; + } + + if (!lv_info_with_seg_status(lv->vg->cmd, first_seg(lv), &status, 1, 1)) { + log_error("Failed to get device mapper status for %s", display_lvname(lv)); + goto fail; + } + + if (!status.info.exists) { + log_error("No device mapper info exists for %s", display_lvname(lv)); + goto fail; + } + + if (status.seg_status.type != SEG_STATUS_INTEGRITY) { + log_error("Invalid device mapper status type (%d) for %s", + (uint32_t)status.seg_status.type, display_lvname(lv)); + goto fail; + } + + data_sectors = status.seg_status.integrity->provided_data_sectors; + + if ((extra_sectors = (data_sectors % lv->vg->extent_size))) { + data_sectors -= extra_sectors; + log_debug("Reduce provided_data_sectors by %llu to %llu for extent alignment", + (unsigned long long)extra_sectors, (unsigned long long)data_sectors); + } + + *provided_data_sectors = data_sectors; + + dm_pool_destroy(status.seg_status.mem); + return 1; + +fail: + dm_pool_destroy(status.seg_status.mem); + return 0; +} + +int lv_remove_integrity(struct logical_volume *lv) +{ + struct lv_segment *seg = first_seg(lv); + struct logical_volume *origin; + struct logical_volume *meta_lv; + + /* FIXME: handle removing integrity from raid images. */ + + if (!seg_is_integrity(seg)) { + log_error("LV %s segment is not integrity.", display_lvname(lv)); + return 0; + } + + if (!(meta_lv = seg->integrity_meta_dev)) { + log_error("LV %s segment has no integrity metadata device.", display_lvname(lv)); + return 0; + } + + if (!(origin = seg_lv(seg, 0))) { + log_error("LV %s integrity segment has no origin", display_lvname(lv)); + return 0; + } + + if (!remove_seg_from_segs_using_this_lv(seg->integrity_meta_dev, seg)) + return_0; + + lv_set_visible(seg->integrity_meta_dev); + + lv->status &= ~INTEGRITY; + meta_lv->status &= ~INTEGRITY_METADATA; + + seg->integrity_meta_dev = NULL; + + if (!remove_layer_from_lv(lv, origin)) + return_0; + + if (!lv_remove(origin)) + return_0; + + if (!lv_remove(meta_lv)) + log_warn("WARNING: failed to remove integrity metadata LV."); + + return 1; +} + +/* + * Add integrity to each raid image. + * + * for each rimage_N: + * . create and allocate a new linear LV rimage_N_imeta + * . move the segments from rimage_N to a new rimage_N_iorig + * . add an integrity segment to rimage_N with + * origin=rimage_N_iorig, meta_dev=rimage_N_imeta + * + * Before: + * rimage_0 + * segment1: striped: pv0:A + * rimage_1 + * segment1: striped: pv1:B + * + * After: + * rimage_0 + * segment1: integrity: rimage_0_iorig, rimage_0_imeta + * rimage_1 + * segment1: integrity: rimage_1_iorig, rimage_1_imeta + * rimage_0_iorig + * segment1: striped: pv0:A + * rimage_1_iorig + * segment1: striped: pv1:B + * rimage_0_imeta + * segment1: striped: pv2:A + * rimage_1_imeta + * segment1: striped: pv2:B + * + */ + +static int _lv_add_integrity_to_raid(struct logical_volume *lv, const char *arg, + struct integrity_settings *settings, + struct dm_list *pvh, + uint64_t *data_sectors) +{ + struct lvcreate_params lp; + struct logical_volume *imeta_lvs[DEFAULT_RAID_MAX_IMAGES]; + struct cmd_context *cmd = lv->vg->cmd; + struct volume_group *vg = lv->vg; + struct logical_volume *lv_image, *lv_imeta, *lv_iorig; + struct lv_segment *seg_top, *seg_image; + const struct segment_type *segtype; + struct integrity_settings *set; + uint64_t status_data_sectors = 0; + uint32_t area_count, s; + int internal_metadata; + int ret = 1; + + memset(imeta_lvs, 0, sizeof(imeta_lvs)); + + if (dm_list_size(&lv->segments) != 1) + return_0; + + seg_top = first_seg(lv); + area_count = seg_top->area_count; + + if ((internal_metadata = !strcmp(arg, "internal"))) { + /* + * FIXME: raid on internal integrity might not be used widely + * enough to enable, given the additional complexity/support. + * i.e. nearly everyone may just use external metadata. + * + * FIXME: _info_run() needs code to adjust the length, like + * is done for if (lv_is_integrity()) length = ... + */ + /* goto skip_imeta; */ + log_error("Internal integrity metadata is not yet supported with raid."); + return 0; + } + + /* + * For each rimage, create an _imeta LV for integrity metadata. + * Each needs to be zeroed. + */ + for (s = 0; s < area_count; s++) { + struct logical_volume *meta_lv; + struct wipe_params wipe; + + if (s >= DEFAULT_RAID_MAX_IMAGES) + return_0; + + lv_image = seg_lv(seg_top, s); + + if (!seg_is_striped(first_seg(lv_image))) { + log_error("raid1 image must be linear to add integrity"); + return_0; + } + + /* + * allocate a new linear LV NAME_rimage_N_imeta + * lv_create_integrity_metadata() returns its result in lp + */ + memset(&lp, 0, sizeof(lp)); + lp.lv_name = lv_image->name; + lp.pvh = pvh; + lp.extents = lv_image->size / vg->extent_size; + + if (!lv_create_integrity_metadata(cmd, vg, &lp)) { + return_0; + } + meta_lv = lp.integrity_meta_lv; + + /* + * dm-integrity requires the metadata LV header to be zeroed. + */ + + if (!activate_lv(cmd, meta_lv)) { + log_error("Failed to activate LV %s to zero", display_lvname(meta_lv)); + return 0; + } + + memset(&wipe, 0, sizeof(wipe)); + wipe.do_zero = 1; + wipe.zero_sectors = 8; + + if (!wipe_lv(meta_lv, wipe)) { + log_error("Failed to zero LV for integrity metadata %s", display_lvname(meta_lv)); + return 0; + } + + if (!deactivate_lv(cmd, meta_lv)) { + log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv)); + return 0; + } + + /* Used below to set up the new integrity segment. */ + imeta_lvs[s] = meta_lv; + } + +/* skip_imeta: */ + + /* + * For each rimage, move its segments to a new rimage_iorig and give + * the rimage a new integrity segment. + */ + for (s = 0; s < area_count; s++) { + lv_image = seg_lv(seg_top, s); + + if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_INTEGRITY))) + return_0; + + log_debug("Adding integrity to raid image %s", lv_image->name); + + /* + * "lv_iorig" is a new LV with new id, but with the segments + * from "lv_image". "lv_image" keeps the existing name and id, + * but gets a new integrity segment, in place of the segments + * that were moved to lv_iorig. + */ + if (!(lv_iorig = insert_layer_for_lv(cmd, lv_image, INTEGRITY, "_iorig"))) + return_0; + + lv_image->status |= INTEGRITY; + + /* + * Set up the new first segment of lv_image as integrity. + */ + seg_image = first_seg(lv_image); + seg_image->segtype = segtype; + + if (!internal_metadata) { + lv_imeta = imeta_lvs[s]; /* external metadata lv created above */ + lv_imeta->status |= INTEGRITY_METADATA; + lv_set_hidden(lv_imeta); + seg_image->integrity_data_sectors = lv_image->size; + seg_image->integrity_meta_dev = lv_imeta; + } + + memcpy(&seg_image->integrity_settings, settings, sizeof(struct integrity_settings)); + set = &seg_image->integrity_settings; + + if (!set->mode[0]) + set->mode[0] = DEFAULT_MODE; + + if (!set->tag_size) + set->tag_size = DEFAULT_TAG_SIZE; + + if (!set->internal_hash) + set->internal_hash = DEFAULT_INTERNAL_HASH; + } + + /* + * When using internal metadata, we have to temporarily activate the + * integrity image with size 1 to get provided_data_sectors from the + * dm-integrity module. + */ + if (internal_metadata) { + /* Get size from the first image, others will be the same. */ + lv_image = seg_lv(seg_top, 0); + + lv_image->status |= LV_TEMPORARY; + lv_image->status |= LV_NOSCAN; + lv_image->status |= LV_UNCOMMITTED; + seg_image = first_seg(lv_image); + seg_image->integrity_data_sectors = 1; + + log_debug("Activating temporary integrity LV to get data sectors."); + + if (!activate_lv(cmd, lv_image)) { + log_error("Failed to activate temporary integrity."); + ret = 0; + goto out; + } + + if (!_get_provided_data_sectors(lv_image, &status_data_sectors)) { + log_error("Failed to get data sectors from dm-integrity"); + ret = 0; + } else { + log_print("Found integrity provided_data_sectors %llu", (unsigned long long)status_data_sectors); + ret = 1; + } + + if (!status_data_sectors) { + log_error("LV size too small to include metadata."); + ret = 0; + } + + lv_image->status |= LV_UNCOMMITTED; + + if (!deactivate_lv(cmd, lv_image)) { + log_error("Failed to deactivate temporary integrity."); + ret = 0; + } + + if (!ret) + goto_out; + + lv_image->status &= ~LV_UNCOMMITTED; + lv_image->status &= ~LV_NOSCAN; + lv_image->status &= ~LV_TEMPORARY; + + /* The main point, setting integrity_data_sectors. */ + for (s = 0; s < area_count; s++) { + lv_image = seg_lv(seg_top, s); + seg_image = first_seg(lv_image); + seg_image->integrity_data_sectors = status_data_sectors; + } + } + + if (internal_metadata) + *data_sectors = status_data_sectors; + else + *data_sectors = lv->size; + + log_debug("Write VG with integrity added to LV"); + + if (!vg_write(vg) || !vg_commit(vg)) + ret = 0; +out: + return ret; +} + +int lv_add_integrity(struct logical_volume *lv, const char *arg, + struct logical_volume *meta_lv_created, + const char *meta_name, + struct integrity_settings *settings, + struct dm_list *pvh, + uint64_t *data_sectors) +{ + struct cmd_context *cmd = lv->vg->cmd; + struct volume_group *vg = lv->vg; + struct integrity_settings *set; + struct logical_volume *lv_orig; + struct logical_volume *meta_lv = NULL; + const struct segment_type *segtype; + struct lv_segment *seg; + uint64_t meta_bytes, meta_sectors; + uint64_t lv_size_sectors; + int ret = 1; + + if (lv_is_raid(lv)) + return _lv_add_integrity_to_raid(lv, arg, settings, pvh, data_sectors); + + lv_size_sectors = lv->size; + + if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_INTEGRITY))) + return_0; + + /* + * "lv_orig" is a new LV with new id, but with the segments from "lv". + * "lv" keeps the existing name and id, but gets a new integrity segment, + * in place of the segments that were moved to lv_orig. + */ + + if (!(lv_orig = insert_layer_for_lv(cmd, lv, INTEGRITY, "_iorig"))) + return_0; + + seg = first_seg(lv); + seg->segtype = segtype; + + lv->status |= INTEGRITY; + + memcpy(&seg->integrity_settings, settings, sizeof(struct integrity_settings)); + set = &seg->integrity_settings; + + if (!set->mode[0]) + set->mode[0] = DEFAULT_MODE; + + if (!set->tag_size) + set->tag_size = DEFAULT_TAG_SIZE; + + if (!set->internal_hash) + set->internal_hash = DEFAULT_INTERNAL_HASH; + + /* + * --integrity <arg> is y|external|internal + */ + + if (!arg) + arg = "external"; + + if (!strcmp(arg, "none")) { + log_error("Invalid --integrity arg for lvcreate."); + return 0; + } + + if (!strcmp(arg, "internal") && meta_name) { + log_error("Internal integrity cannot be used with integritymetadata option."); + return 0; + } + + if (meta_lv_created) + meta_lv = meta_lv_created; + else if (meta_name) { + if (!(meta_lv = find_lv(vg, meta_name))) { + log_error("LV %s not found.", meta_name); + return_0; + } + + meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv->size * 512); + meta_sectors = meta_bytes / 512; + + if (meta_lv->size < meta_sectors) { + log_error("Integrity metadata needs %s, metadata LV is only %s.", + display_size(cmd, meta_sectors), display_size(cmd, meta_lv->size)); + return 0; + } + } + + /* + * When not using a meta_dev, dm-integrity needs to tell us what the + * usable size of the LV is, which is the dev size minus the integrity + * overhead. To find that, we need to do a special, temporary activation + * of the new LV, specifying a dm dev size of 1, then check the dm dev + * status field provided_data_sectors, which is the actual size of the + * LV. We need to include provided_data_sectors in the metadata for the + * new LV, and use this as the dm dev size for normal LV activation. + * + * When using a meta_dev, the dm dev size is the size of the data + * device. The necessary size of the meta_dev for the given data + * device needs to be estimated. + */ + + if (meta_lv) { + struct wipe_params wipe; + + if (!sync_local_dev_names(cmd)) { + log_error("Failed to sync local devices."); + return 0; + } + + log_verbose("Zeroing LV for integrity metadata"); + + if (!lv_is_active(meta_lv)) { + if (!activate_lv(cmd, meta_lv)) { + log_error("Failed to activate LV %s to zero", display_lvname(meta_lv)); + return 0; + } + } + + memset(&wipe, 0, sizeof(wipe)); + wipe.do_zero = 1; + wipe.zero_sectors = 8; + + if (!wipe_lv(meta_lv, wipe)) { + log_error("Failed to zero LV for integrity metadata %s", display_lvname(meta_lv)); + return 0; + } + + if (!deactivate_lv(cmd, meta_lv)) { + log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv)); + return 0; + } + + meta_lv->status |= INTEGRITY_METADATA; + seg->integrity_data_sectors = lv_size_sectors; + seg->integrity_meta_dev = meta_lv; + lv_set_hidden(meta_lv); + /* TODO: give meta_lv a suffix? e.g. _imeta */ + } else { + /* dm-integrity wants temp/fake size of 1 to report usable size */ + seg->integrity_data_sectors = 1; + + lv->status |= LV_TEMPORARY; + lv->status |= LV_NOSCAN; + lv->status |= LV_UNCOMMITTED; + + log_debug("Activating temporary integrity LV to get data sectors."); + + if (!activate_lv(cmd, lv)) { + log_error("Failed to activate temporary integrity."); + ret = 0; + goto out; + } + + if (!_get_provided_data_sectors(lv, &seg->integrity_data_sectors)) { + log_error("Failed to get data sectors from dm-integrity"); + ret = 0; + } else { + log_debug("Found integrity provided_data_sectors %llu", (unsigned long long)seg->integrity_data_sectors); + ret = 1; + } + + if (!seg->integrity_data_sectors) { + log_error("LV size too small to include metadata."); + ret = 0; + } + + lv->status |= LV_UNCOMMITTED; + + if (!deactivate_lv(cmd, lv)) { + log_error("Failed to deactivate temporary integrity."); + ret = 0; + } + + lv->status &= ~LV_UNCOMMITTED; + lv->status &= ~LV_NOSCAN; + lv->status &= ~LV_TEMPORARY; + } + + *data_sectors = seg->integrity_data_sectors; + + log_debug("Write VG with integrity added to LV"); + + if (!vg_write(vg) || !vg_commit(vg)) + ret = 0; + out: + return ret; +} + + diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c index 4c2ab2bbf..2ff9887d5 100644 --- a/lib/metadata/lv.c +++ b/lib/metadata/lv.c @@ -585,6 +585,8 @@ struct logical_volume *lv_origin_lv(const struct logical_volume *lv) origin = first_seg(lv)->external_lv; else if (lv_is_writecache(lv) && first_seg(lv)->origin) origin = first_seg(lv)->origin; + else if (lv_is_integrity(lv) && first_seg(lv)->origin) + origin = first_seg(lv)->origin; return origin; } @@ -1200,10 +1202,13 @@ char *lv_attr_dup_with_info_and_seg_status(struct dm_pool *mem, const struct lv_ repstr[0] = (lv_is_merging_origin(lv)) ? 'O' : 'o'; else if (lv_is_pool_metadata(lv) || lv_is_pool_metadata_spare(lv) || - lv_is_raid_metadata(lv)) + lv_is_raid_metadata(lv) || + lv_is_integrity_metadata(lv)) repstr[0] = 'e'; else if (lv_is_cache_type(lv) || lv_is_writecache(lv)) repstr[0] = 'C'; + else if (lv_is_integrity(lv)) + repstr[0] = 'g'; else if (lv_is_raid(lv)) repstr[0] = (lv_is_not_synced(lv)) ? 'R' : 'r'; else if (lv_is_mirror(lv)) diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c index b4daa5633..798f3322d 100644 --- a/lib/metadata/lv_manip.c +++ b/lib/metadata/lv_manip.c @@ -28,6 +28,7 @@ #include "lib/datastruct/str_list.h" #include "lib/config/defaults.h" #include "lib/misc/lvm-exec.h" +#include "lib/misc/lvm-signal.h" #include "lib/mm/memlock.h" #include "lib/locking/lvmlockd.h" #include "lib/label/label.h" @@ -55,6 +56,8 @@ typedef enum { #define A_POSITIONAL_FILL 0x40 /* Slots are positional and filled using PREFERRED */ #define A_PARTITION_BY_TAGS 0x80 /* No allocated area may share any tag with any other */ +#define ONE_MB_IN_BYTES 1048576 + /* * Constant parameters during a single allocation attempt. */ @@ -134,7 +137,9 @@ enum { LV_TYPE_SANLOCK, LV_TYPE_CACHEVOL, LV_TYPE_WRITECACHE, - LV_TYPE_WRITECACHEORIGIN + LV_TYPE_WRITECACHEORIGIN, + LV_TYPE_INTEGRITY, + LV_TYPE_INTEGRITYORIGIN }; static const char *_lv_type_names[] = { @@ -190,6 +195,8 @@ static const char *_lv_type_names[] = { [LV_TYPE_CACHEVOL] = "cachevol", [LV_TYPE_WRITECACHE] = "writecache", [LV_TYPE_WRITECACHEORIGIN] = "writecacheorigin", + [LV_TYPE_INTEGRITY] = "integrity", + [LV_TYPE_INTEGRITYORIGIN] = "integrityorigin", }; static int _lv_layout_and_role_mirror(struct dm_pool *mem, @@ -461,6 +468,43 @@ bad: return 0; } +static int _lv_layout_and_role_integrity(struct dm_pool *mem, + const struct logical_volume *lv, + struct dm_list *layout, + struct dm_list *role, + int *public_lv) +{ + int top_level = 0; + + /* non-top-level LVs */ + if (lv_is_integrity_metadata(lv)) { + if (!str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_INTEGRITY]) || + !str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_METADATA])) + goto_bad; + } else if (lv_is_integrity_origin(lv)) { + if (!str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_INTEGRITY]) || + !str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_ORIGIN]) || + !str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_INTEGRITYORIGIN])) + goto_bad; + } else + top_level = 1; + + if (!top_level) { + *public_lv = 0; + return 1; + } + + /* top-level LVs */ + if (lv_is_integrity(lv)) { + if (!str_list_add_no_dup_check(mem, layout, _lv_type_names[LV_TYPE_INTEGRITY])) + goto_bad; + } + + return 1; +bad: + return 0; +} + static int _lv_layout_and_role_thick_origin_snapshot(struct dm_pool *mem, const struct logical_volume *lv, struct dm_list *layout, @@ -577,6 +621,11 @@ int lv_layout_and_role(struct dm_pool *mem, const struct logical_volume *lv, !_lv_layout_and_role_cache(mem, lv, *layout, *role, &public_lv)) goto_bad; + /* Integrity related */ + if ((lv_is_integrity(lv) || lv_is_integrity_origin(lv) || lv_is_integrity_metadata(lv)) && + !_lv_layout_and_role_integrity(mem, lv, *layout, *role, &public_lv)) + goto_bad; + /* VDO and related */ if (lv_is_vdo_type(lv) && !_lv_layout_and_role_vdo(mem, lv, *layout, *role, &public_lv)) @@ -1457,6 +1506,15 @@ static int _lv_reduce(struct logical_volume *lv, uint32_t extents, int delete) return_0; } + if (delete && seg_is_integrity(seg)) { + /* Remove integrity origin in addition to integrity layer. */ + if (!lv_remove(seg_lv(seg, 0))) + return_0; + /* Remove integrity metadata. */ + if (seg->integrity_meta_dev && !lv_remove(seg->integrity_meta_dev)) + return_0; + } + if ((pool_lv = seg->pool_lv)) { if (!detach_pool_lv(seg)) return_0; @@ -5654,6 +5712,11 @@ int lv_resize(struct logical_volume *lv, return 0; } + if (lv_is_integrity(lv)) { + log_error("Resize not yet allowed on LVs with integrity."); + return 0; + } + if (!_lvresize_check(lv, lp)) return_0; @@ -7410,6 +7473,90 @@ int insert_layer_for_segments_on_pv(struct cmd_context *cmd, return 1; } +int zero_lv_name(struct cmd_context *cmd, const char *vg_name, const char *lv_name, uint64_t lv_size_sectors) +{ + char name[PATH_MAX]; + struct device *dev; + uint64_t off = 0, i = 0, j = 0; + uint64_t lv_size_bytes = lv_size_sectors * 512; + uint64_t zero_bytes; + uint32_t extra_bytes; + + if (dm_snprintf(name, sizeof(name), "%s%s/%s", cmd->dev_dir, vg_name, lv_name) < 0) { + log_error("Device path name too long, device not zeroed (%s).", lv_name); + return 0; + } + + if (!(dev = dev_cache_get(cmd, name, NULL))) { + log_error("Failed to find device %s: device not zeroed.", name); + return 0; + } + + if (!label_scan_open_rw(dev)) { + log_error("Failed to open %s: device not zeroed.", name); + return 0; + } + + zero_bytes = lv_size_bytes; + + log_print("Zeroing %s %s... (cancel command to zero manually)", + name, display_size(cmd, zero_bytes/512)); + + if ((extra_bytes = (zero_bytes % ONE_MB_IN_BYTES))) + zero_bytes -= extra_bytes; + + /* + * Write 1MiB at a time to avoid going over bcache size. + * Then write 128KiB (bcache block sizes) at a time to + * cover remaining dev size. + */ + + sigint_allow(); + + for (i = 0; i < (zero_bytes / ONE_MB_IN_BYTES); i++) { + off = i * ONE_MB_IN_BYTES; + if (!dev_write_zeros(dev, off, (size_t)ONE_MB_IN_BYTES)) + log_error("Failed to zero LV at offset %llu.", (unsigned long long)off); + + if (off && !(off % (1024 * ONE_MB_IN_BYTES))) + log_print("Zeroed %s...", display_size(cmd, off/512)); + + if (sigint_caught()) { + log_print("Zeroing canceled."); + goto out; + } + + } + + if (extra_bytes) { + log_debug("Zeroing final %u bytes at %llu.", extra_bytes, (unsigned long long)off); + + for (j = 0; j < (extra_bytes / BCACHE_BLOCK_SIZE_IN_BYTES); j++) { + off = i * ONE_MB_IN_BYTES + j * BCACHE_BLOCK_SIZE_IN_BYTES; + if (!dev_write_zeros(dev, off, (size_t)BCACHE_BLOCK_SIZE_IN_BYTES)) + log_error("Failed to zero LV at offset %llu.", (unsigned long long)off); + + if (sigint_caught()) { + log_print("Zeroing canceled."); + goto out; + } + } + } + + /* + * FIXME: bcache can't write partial blocks yet. + * This shouldn't actually happen given current + * usage where LV size is a multiple of extents. + */ + if ((extra_bytes = lv_size_bytes - (i * ONE_MB_IN_BYTES + j * BCACHE_BLOCK_SIZE_IN_BYTES))) + log_warn("WARNING: last %llu bytes not zeroed.", (unsigned long long)extra_bytes); + +out: + sigint_restore(); + label_scan_invalidate(dev); + return 1; +} + /* * Initialize the LV with 'value'. */ @@ -7679,6 +7826,15 @@ static int _should_wipe_lv(struct lvcreate_params *lp, first_seg(first_seg(lv)->pool_lv)->zero_new_blocks)) return 0; + /* + * dm-integrity requires the first sector (integrity superblock) to be + * zero when creating a new integrity device. + * TODO: print a warning or error if the user specifically + * asks for no wiping or zeroing? + */ + if (seg_is_integrity(lp) || (seg_is_raid1(lp) && lp->integrity_arg)) + return 1; + /* Cannot zero read-only volume */ if ((lv->status & LVM_WRITE) && (lp->zero || lp->wipe_signatures)) @@ -7934,6 +8090,11 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg, /* FIXME Eventually support raid/mirrors with -m */ if (!(create_segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED))) return_0; + + } else if (seg_is_integrity(lp)) { + if (!(create_segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED))) + return_0; + } else if (seg_is_mirrored(lp) || (seg_is_raid(lp) && !seg_is_any_raid0(lp))) { if (!(lp->region_size = adjusted_mirror_region_size(vg->cmd, vg->extent_size, @@ -8277,6 +8438,19 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg, goto revert_new_lv; } lv->status &= ~LV_TEMPORARY; + + } else if (seg_is_integrity(lp) || (seg_is_raid1(lp) && lp->integrity_arg)) { + /* + * Activate the new origin LV so it can be zeroed/wiped + * below before adding integrity. + */ + lv->status |= LV_TEMPORARY; + if (!activate_lv(cmd, lv)) { + log_error("Failed to activate new LV for wiping."); + goto revert_new_lv; + } + lv->status &= ~LV_TEMPORARY; + } else if (!lv_active_change(cmd, lv, lp->activate)) { log_error("Failed to activate new LV %s.", display_lvname(lv)); goto deactivate_and_revert_new_lv; @@ -8296,6 +8470,55 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg, } } + if (seg_is_integrity(lp) || (seg_is_raid1(lp) && lp->integrity_arg)) { + struct lv_activate_opts laopts = { 0 }; + uint64_t integrity_data_sectors = 0; + + log_verbose("Adding integrity to new LV"); + + /* Origin is active from zeroing, deactivate to add integrity. */ + + if (!deactivate_lv(cmd, lv)) { + log_error("Failed to deactivate LV to add integrity"); + goto revert_new_lv; + } + + if (!lv_add_integrity(lv, lp->integrity_arg, lp->integrity_meta_lv, + lp->integrity_meta_name, &lp->integrity_settings, lp->pvh, + &integrity_data_sectors)) + goto revert_new_lv; + + backup(vg); + + /* + * The integrity metadata (checksums) need to be initialized. + * Activating the new integrity LVs with the recalculate + * option will cause dm-integrity to initialize. + * + * TODO: --integrityinit none|kernel|command + * . none: user will handle it (e.g. zero LV with dd), + * and we follow -an|-ay to decide about activating LV + * . kernel: activate LV with recalculate to start kernel init + * . command: activate LV for zeroing by this lvcreate command. + * if this command does not complete zeroing (e.g. user cancels + * the command or machine crashes), then the user must complete + * the the zeroing manually, or with a lvchange recalculate. + * + * For raid+integrity, recalculate by kernel is the only + * option that works. + */ + laopts.integrity_recalculate = 1; + + if (!activate_lv_opts(cmd, lv, &laopts)) { + log_error("Failed to activate LV %s.", display_lvname(lv)); + log_error("The full LV should be zeroed to initialize integrity metadata."); + goto out; + } + + log_print("LV %s activated to initialize integrity.", display_lvname(lv)); + goto out; + } + if (seg_is_vdo_pool(lp)) { if (!convert_vdo_pool_lv(lv, &lp->vdo_params, &lp->virtual_extents)) { stack; diff --git a/lib/metadata/merge.c b/lib/metadata/merge.c index e482921ce..d6dd7a55a 100644 --- a/lib/metadata/merge.c +++ b/lib/metadata/merge.c @@ -742,6 +742,8 @@ int check_lv_segments(struct logical_volume *lv, int complete_vg) seg_found++; if (seg->metadata_lv == lv || seg->pool_lv == lv || seg->writecache == lv) seg_found++; + if (seg->integrity_meta_dev == lv) + seg_found++; if (seg_is_thin_volume(seg) && (seg->origin == lv || seg->external_lv == lv)) seg_found++; diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h index c61c85c7e..927164510 100644 --- a/lib/metadata/metadata-exported.h +++ b/lib/metadata/metadata-exported.h @@ -84,12 +84,15 @@ #define CONVERTING UINT64_C(0x0000000000400000) /* LV */ #define MISSING_PV UINT64_C(0x0000000000800000) /* PV */ +#define INTEGRITY UINT64_C(0x0000000000800000) /* LV - Internal use only */ #define PV_MOVED_VG UINT64_C(0x4000000000000000) /* PV - Moved to a new VG */ #define PARTIAL_LV UINT64_C(0x0000000001000000) /* LV - derived flag, not written out in metadata*/ //#define POSTORDER_FLAG UINT64_C(0x0000000002000000) /* Not real flags, reserved for //#define POSTORDER_OPEN_FLAG UINT64_C(0x0000000004000000) temporary use inside vg_read_internal. */ +#define INTEGRITY_METADATA UINT64_C(0x0000000004000000) /* LV - Internal use only */ +#define LV_UNCOMMITTED UINT64_C(0x0000000002000000) #define VIRTUAL_ORIGIN UINT64_C(0x0000000008000000) /* LV - internal use only */ #define MERGING UINT64_C(0x0000000010000000) /* LV SEG */ @@ -261,6 +264,8 @@ #define lv_is_pool_metadata_spare(lv) (((lv)->status & POOL_METADATA_SPARE) ? 1 : 0) #define lv_is_lockd_sanlock_lv(lv) (((lv)->status & LOCKD_SANLOCK_LV) ? 1 : 0) #define lv_is_writecache(lv) (((lv)->status & WRITECACHE) ? 1 : 0) +#define lv_is_integrity(lv) (((lv)->status & INTEGRITY) ? 1 : 0) +#define lv_is_integrity_metadata(lv) (((lv)->status & INTEGRITY_METADATA) ? 1 : 0) #define lv_is_vdo(lv) (((lv)->status & LV_VDO) ? 1 : 0) #define lv_is_vdo_pool(lv) (((lv)->status & LV_VDO_POOL) ? 1 : 0) @@ -272,9 +277,11 @@ /* Recognize component LV (matching lib/misc/lvm-string.c _lvname_has_reserved_component_string()) */ #define lv_is_component(lv) (lv_is_cache_origin(lv) || \ lv_is_writecache_origin(lv) || \ + lv_is_integrity_origin(lv) || \ ((lv)->status & (\ CACHE_POOL_DATA |\ CACHE_POOL_METADATA |\ + INTEGRITY_METADATA |\ LV_CACHE_VOL |\ LV_VDO_POOL_DATA |\ MIRROR_IMAGE |\ @@ -519,6 +526,10 @@ struct lv_segment { uint32_t writecache_block_size; /* For writecache */ struct writecache_settings writecache_settings; /* For writecache */ + uint64_t integrity_data_sectors; + struct logical_volume *integrity_meta_dev; + struct integrity_settings integrity_settings; + struct dm_vdo_target_params vdo_params; /* For VDO-pool */ uint32_t vdo_pool_header_size; /* For VDO-pool */ uint32_t vdo_pool_virtual_extents; /* For VDO-pool */ @@ -992,6 +1003,12 @@ struct lvcreate_params { alloc_policy_t alloc; /* all */ struct dm_vdo_target_params vdo_params; /* vdo */ + const char *integrity_arg; + const char *integrity_meta_name; /* external LV is user-specified */ + struct logical_volume *integrity_meta_lv; /* external LV we create */ + struct integrity_settings integrity_settings; + uint64_t zero_data_sectors; /* the resulting size that should be zeroed at the end */ + struct dm_list tags; /* all */ int yes; @@ -1086,6 +1103,8 @@ int lv_is_cache_origin(const struct logical_volume *lv); int lv_is_writecache_origin(const struct logical_volume *lv); int lv_is_writecache_cachevol(const struct logical_volume *lv); +int lv_is_integrity_origin(const struct logical_volume *lv); + int lv_is_merging_cow(const struct logical_volume *cow); uint32_t cow_max_extents(const struct logical_volume *origin, uint32_t chunk_size); int cow_has_min_chunks(const struct volume_group *vg, uint32_t cow_extents, uint32_t chunk_size); @@ -1385,4 +1404,16 @@ int vg_is_foreign(struct volume_group *vg); void vg_write_commit_bad_mdas(struct cmd_context *cmd, struct volume_group *vg); +int lv_add_integrity(struct logical_volume *lv, const char *arg, + struct logical_volume *meta_lv_created, + const char *meta_name, struct integrity_settings *settings, + struct dm_list *pvh, + uint64_t *data_sectors); +int lv_create_integrity_metadata(struct cmd_context *cmd, + struct volume_group *vg, + struct lvcreate_params *lp); +int lv_remove_integrity(struct logical_volume *lv); + +int zero_lv_name(struct cmd_context *cmd, const char *vg_name, const char *lv_name, uint64_t lv_size_sectors); + #endif diff --git a/lib/metadata/segtype.h b/lib/metadata/segtype.h index 22a511eac..08ddc3565 100644 --- a/lib/metadata/segtype.h +++ b/lib/metadata/segtype.h @@ -67,6 +67,7 @@ struct dev_manager; #define SEG_RAID6_N_6 (1ULL << 35) #define SEG_RAID6 SEG_RAID6_ZR #define SEG_WRITECACHE (1ULL << 36) +#define SEG_INTEGRITY (1ULL << 37) #define SEG_STRIPED_TARGET (1ULL << 39) #define SEG_LINEAR_TARGET (1ULL << 40) @@ -84,6 +85,7 @@ struct dev_manager; #define SEG_TYPE_NAME_CACHE "cache" #define SEG_TYPE_NAME_CACHE_POOL "cache-pool" #define SEG_TYPE_NAME_WRITECACHE "writecache" +#define SEG_TYPE_NAME_INTEGRITY "integrity" #define SEG_TYPE_NAME_ERROR "error" #define SEG_TYPE_NAME_FREE "free" #define SEG_TYPE_NAME_ZERO "zero" @@ -117,6 +119,7 @@ struct dev_manager; #define segtype_is_cache(segtype) ((segtype)->flags & SEG_CACHE ? 1 : 0) #define segtype_is_cache_pool(segtype) ((segtype)->flags & SEG_CACHE_POOL ? 1 : 0) #define segtype_is_writecache(segtype) ((segtype)->flags & SEG_WRITECACHE ? 1 : 0) +#define segtype_is_integrity(segtype) ((segtype)->flags & SEG_INTEGRITY ? 1 : 0) #define segtype_is_mirrored(segtype) ((segtype)->flags & SEG_AREAS_MIRRORED ? 1 : 0) #define segtype_is_mirror(segtype) ((segtype)->flags & SEG_MIRROR ? 1 : 0) #define segtype_is_pool(segtype) ((segtype)->flags & (SEG_CACHE_POOL | SEG_THIN_POOL) ? 1 : 0) @@ -179,6 +182,7 @@ struct dev_manager; #define seg_is_cache(seg) segtype_is_cache((seg)->segtype) #define seg_is_cache_pool(seg) segtype_is_cache_pool((seg)->segtype) #define seg_is_writecache(seg) segtype_is_writecache((seg)->segtype) +#define seg_is_integrity(seg) segtype_is_integrity((seg)->segtype) #define seg_is_used_cache_pool(seg) (seg_is_cache_pool(seg) && (!dm_list_empty(&(seg->lv)->segs_using_this_lv))) #define seg_is_linear(seg) (seg_is_striped(seg) && ((seg)->area_count == 1)) #define seg_is_mirror(seg) segtype_is_mirror((seg)->segtype) @@ -347,6 +351,8 @@ int init_vdo_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); int init_writecache_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); +int init_integrity_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); + #define CACHE_FEATURE_POLICY_MQ (1U << 0) #define CACHE_FEATURE_POLICY_SMQ (1U << 1) #define CACHE_FEATURE_METADATA2 (1U << 2) |