diff options
Diffstat (limited to 'lib/metadata/integrity_manip.c')
-rw-r--r-- | lib/metadata/integrity_manip.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c new file mode 100644 index 000000000..c3dde2555 --- /dev/null +++ b/lib/metadata/integrity_manip.c @@ -0,0 +1,372 @@ +/* + * 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 + +/* + * 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; + + 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; + 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; +} + +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 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; + + 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; + } + + 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 */ + 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; + } + + 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; + } + + log_debug("Write VG with integrity added to LV"); + + if (!vg_write(vg) || !vg_commit(vg)) + ret = 0; + out: + return ret; +} + + |