summaryrefslogtreecommitdiff
path: root/lib/metadata
diff options
context:
space:
mode:
Diffstat (limited to 'lib/metadata')
-rw-r--r--lib/metadata/integrity_manip.c649
-rw-r--r--lib/metadata/lv.c7
-rw-r--r--lib/metadata/lv_manip.c225
-rw-r--r--lib/metadata/merge.c2
-rw-r--r--lib/metadata/metadata-exported.h31
-rw-r--r--lib/metadata/segtype.h6
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)