[lvm-devel] master - pvck: dump headers and metadata

David Teigland teigland at sourceware.org
Mon Jun 3 20:14:34 UTC 2019


Gitweb:        https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=d18e491f6890e15a677e8189de0699ccc7412f16
Commit:        d18e491f6890e15a677e8189de0699ccc7412f16
Parent:        669a834981c0bc808d42dbe5097d7beeead982ad
Author:        David Teigland <teigland at redhat.com>
AuthorDate:    Fri May 31 14:10:44 2019 -0500
Committer:     David Teigland <teigland at redhat.com>
CommitterDate: Mon Jun 3 15:13:32 2019 -0500

pvck: dump headers and metadata

Add 'pvck --dump headers' to print all the
lvm ondisk structs.  Also checks the values
and prints any problems.

The previous dump metadata is also converted to
use these same routines, which do not depend on lvm
fully scanning/reading/processing the headers and
metadata on disk.  This makes it useful to get data in
cases where there is corruption that would otherwise
prevent the normal functions from working.
---
 lib/format_text/format-text.c |  218 ----------
 lib/format_text/format-text.h |   14 -
 tools/args.h                  |    5 +-
 tools/command-lines.in        |    6 +-
 tools/pvck.c                  |  888 ++++++++++++++++++++++++++++++++++++++---
 5 files changed, 848 insertions(+), 283 deletions(-)

diff --git a/lib/format_text/format-text.c b/lib/format_text/format-text.c
index abf8afc..3828d5f 100644
--- a/lib/format_text/format-text.c
+++ b/lib/format_text/format-text.c
@@ -2602,221 +2602,3 @@ bad:
 
 	return NULL;
 }
-
-static char *_read_metadata_text(struct cmd_context *cmd, struct device *dev,
-				 uint64_t area_start, uint64_t area_size,
-				 uint32_t *len, uint64_t *disk_offset)
-{
-	struct mda_header *mh;
-	struct raw_locn *rlocn_slot0;
-	uint64_t text_offset, text_size;
-	char *area_buf;
-	char *text_buf;
-
-	/*
-	 * Read the entire metadata area, including mda_header and entire
-	 * circular buffer.
-	 */
-	if (!(area_buf = malloc(area_size)))
-		return_NULL;
-
-	if (!dev_read_bytes(dev, area_start, area_size, area_buf)) {
-		log_error("Failed to read device %s at %llu size %llu",
-			  dev_name(dev),
-			  (unsigned long long)area_start,
-			  (unsigned long long)area_size);
-		return NULL;
-	}
-
-	mh = (struct mda_header *)area_buf;
-	_xlate_mdah(mh);
-
-	rlocn_slot0 = &mh->raw_locns[0];
-	text_offset = rlocn_slot0->offset;
-	text_size = rlocn_slot0->size;
-
-	/*
-	 * Copy and return the current metadata text out of the metadata area.
-	 */
-
-	if (!(text_buf = malloc(text_size)))
-		return_NULL;
-
-	memcpy(text_buf, area_buf + text_offset, text_size);
-
-	if (len)
-		*len = (uint32_t)text_size;
-	if (disk_offset)
-		*disk_offset = area_start + text_offset;
-
-	free(area_buf);
-
-	return text_buf;
-}
-
-int dump_metadata_text(struct cmd_context *cmd,
-		       const char *vgname,
-		       const char *vgid,
-		       struct device *dev,
-		       struct metadata_area *mda,
-		       const char *tofile)
-{
-	char *textbuf;
-	struct format_instance *fid;
-	struct format_instance_ctx fic;
-	struct mda_context *mdac;
-	struct volume_group *vg;
-	unsigned use_previous_vg = 0;
-	uint32_t textlen = 0;
-	uint32_t textcrc;
-	uint64_t text_disk_offset;
-	int ret = 0;
-
-	/*
-	 * Set up overhead/abstractions for reading a given vgname
-	 * (fmt/fid/fic/vgid).
-	 */
-
-	fic.type = FMT_INSTANCE_MDAS | FMT_INSTANCE_AUX_MDAS;
-	fic.context.vg_ref.vg_name = vgname;
-	fic.context.vg_ref.vg_id = vgid;
-
-	if (!(fid = _text_create_text_instance(cmd->fmt, &fic))) {
-		log_error("Failed to create format instance");
-		return 0;
-	}
-
-	mdac = mda->metadata_locn;
-
-	/*
-	 * Read the VG metadata from the device as a raw chunk of original text.
-	 */
-	textbuf = _read_metadata_text(cmd, dev,
-				      mdac->area.start, mdac->area.size,
-				      &textlen, &text_disk_offset);
-	if (!textbuf || !textlen) {
-		log_error("No metadata text found on %s", dev_name(dev));
-		_text_destroy_instance(fid);
-		return 0;
-	}
-
-	textcrc = calc_crc(INITIAL_CRC, (uint8_t *)textbuf, textlen);
-
-	/*
-	 * Read the same VG metadata, but imported/parsed into a vg struct
-	 * format so we know it's valid/parsable, and can look at values in it.
-	 */
-	if (!(vg = _vg_read_raw(fid, vgname, mda, NULL, &use_previous_vg))) {
-		log_warn("WARNING: parse error for metadata on %s.", dev_name(dev));
-		_text_destroy_instance(fid);
-	}
-
-	log_print("Metadata for %s from %s at %llu size %u with seqno %u checksum 0x%x.",
-		  vgname, dev_name(dev),
-		  (unsigned long long)text_disk_offset, textlen,
-		  vg ? vg->seqno : 0, textcrc);
-
-	if (!tofile) {
-		log_print("---");
-		printf("%s\n", textbuf);
-		log_print("---");
-	} else {
-		FILE *fp;
-		if (!(fp = fopen(tofile, "wx"))) {
-			log_error("Failed to create file %s", tofile);
-			goto out;
-		}
-
-		fprintf(fp, "%s", textbuf);
-
-		if (fflush(fp))
-			stack;
-		if (fclose(fp))
-			stack;
-	}
-
-	if (vg)
-		release_vg(vg);
-
-	free(textbuf);
-	ret = 1;
-out:
-	return ret;
-}
-
-static char *_read_metadata_area(struct cmd_context *cmd, struct device *dev,
-				 uint64_t area_start, uint64_t area_size)
-{
-	char *area_buf;
-
-	/*
-	 * Read the entire metadata area, including mda_header and entire
-	 * circular buffer.
-	 */
-	if (!(area_buf = malloc(area_size)))
-		return_NULL;
-
-	if (!dev_read_bytes(dev, area_start, area_size, area_buf)) {
-		log_error("Failed to read device %s at %llu size %llu",
-			  dev_name(dev),
-			  (unsigned long long)area_start,
-			  (unsigned long long)area_size);
-		return NULL;
-	}
-
-	return area_buf;
-}
-
-int dump_metadata_area(struct cmd_context *cmd,
-		       const char *vgname,
-		       const char *vgid,
-		       struct device *dev,
-		       struct metadata_area *mda,
-		       const char *tofile)
-{
-	char *areabuf;
-	char *textbuf;
-	struct mda_context *mdac;
-	int ret = 0;
-
-	mdac = mda->metadata_locn;
-
-	areabuf = _read_metadata_area(cmd, dev,
-				      mdac->area.start, mdac->area.size);
-	if (!areabuf) {
-		log_error("No metadata area found on %s", dev_name(dev));
-		return 0;
-	}
-
-	log_print("Metadata buffer for %s from %s in area at %llu size %llu offset 512.",
-		  vgname, dev_name(dev),
-		  (unsigned long long)mdac->area.start,
-		  (unsigned long long)mdac->area.size);
-
-	/* text starts after mda_header which uses 512 bytes */
-	textbuf = areabuf + 512;
-
-	if (!tofile) {
-		/* N.B. this will often include unprintable data */
-		log_print("---");
-		fwrite(textbuf, mdac->area.size - 512, 1, stdout);
-		log_print("---");
-	} else {
-		FILE *fp;
-		if (!(fp = fopen(tofile, "wx"))) {
-			log_error("Failed to create file %s", tofile);
-			goto out;
-		}
-
-		fwrite(textbuf, mdac->area.size - 512, 1, fp);
-
-		if (fflush(fp))
-			stack;
-		if (fclose(fp))
-			stack;
-	}
-	ret = 1;
-out:
-	free(areabuf);
-	return ret;
-}
diff --git a/lib/format_text/format-text.h b/lib/format_text/format-text.h
index 6be237b..1300e58 100644
--- a/lib/format_text/format-text.h
+++ b/lib/format_text/format-text.h
@@ -76,18 +76,4 @@ struct data_area_list {
 	struct disk_locn disk_locn;
 };
 
-int dump_metadata_text(struct cmd_context *cmd,
-                       const char *vgname,
-                       const char *vgid,
-                       struct device *dev,
-                       struct metadata_area *mda,
-                       const char *tofile);
-
-int dump_metadata_area(struct cmd_context *cmd,
-                       const char *vgname,
-                       const char *vgid,
-                       struct device *dev,
-                       struct metadata_area *mda,
-                       const char *tofile);
-
 #endif
diff --git a/tools/args.h b/tools/args.h
index c1efdd6..83c1cfe 100644
--- a/tools/args.h
+++ b/tools/args.h
@@ -215,8 +215,9 @@ arg(driverloaded_ARG, '\0', "driverloaded", bool_VAL, 0, 0,
 
 arg(dump_ARG, '\0', "dump", string_VAL, 0, 0,
     "Dump metadata from a PV. Option values include \\fBmetadata\\fP\n"
-    "to extract the current text metadata, and \\fBmetadata_area\\fP\n"
-    "to extract the entire text metadata area.\n")
+    "to extract the current text metadata, \\fBmetadata_area\\fP\n"
+    "to extract the entire text metadata area, and \\fBheaders\\fP\n"
+    "to print and check LVM headers.\n")
 
 arg(errorwhenfull_ARG, '\0', "errorwhenfull", bool_VAL, 0, 0,
     "Specifies thin pool behavior when data space is exhausted.\n"
diff --git a/tools/command-lines.in b/tools/command-lines.in
index 0d97348..03dbf57 100644
--- a/tools/command-lines.in
+++ b/tools/command-lines.in
@@ -1430,9 +1430,9 @@ ID: pvck_general
 DESC: Check for metadata on a device
 
 pvck --dump String PV
-OO: --file String, --pvmetadatacopies MetadataCopiesPV
-ID: pvck_dumpmetadata
-DESC: Dump raw metadata from a device
+OO: --file String, --pvmetadatacopies MetadataCopiesPV, --labelsector Number
+ID: pvck_dump
+DESC: Print metadata from a device
 
 ---
 
diff --git a/tools/pvck.c b/tools/pvck.c
index 15c7e6d..9e690a8 100644
--- a/tools/pvck.c
+++ b/tools/pvck.c
@@ -16,38 +16,757 @@
 #include "base/memory/zalloc.h"
 #include "tools.h"
 #include "lib/format_text/format-text.h"
+#include "lib/format_text/layout.h"
+#include "lib/mm/xlate.h"
+#include "lib/misc/crc.h"
+
+static char *_chars_to_str(void *in, void *out, int num, int max, const char *field)
+{
+	char *i = in;
+	char *o = out;
+	int n;
+
+	memset(out, 0, max);
+
+	if (num > max-1) {
+		log_print("CHECK: abbreviating output for %s", field);
+		num = max - 1;
+	}
+
+	for (n = 0; n < num; n++) {
+		if (isprint((int)*i))
+			*o = *i;
+		else
+			*o = '?';
+		i++;
+		o++;
+	}
+
+	return out;
+}
+
+/* 
+ * This is used to print mda_header.magic as a series of hex values
+ * since it only contains some printable chars.
+ */
+static char *_chars_to_hexstr(void *in, void *out, int num, int max, const char *field)
+{
+	char *tmp;
+	char *i = in;
+	int n;
+	int off = 0;
+	int ret;
+
+	if (!(tmp = zalloc(max))) {
+		log_print("CHECK: no mem for printing %s", field);
+		return out;
+	}
+
+	memset(out, 0, max);
+	memset(tmp, 0, max);
+
+	if (num > max-1) {
+		log_print("CHECK: abbreviating output for %s", field);
+		num = max - 1;
+	}
+
+	for (n = 0; n < num; n++) {
+		ret = sprintf(tmp+off, "%x", *i & 0xFF);
+		off += ret;
+		i++;
+	}
+
+	memcpy(out, tmp, 256);
+
+	free(tmp);
+
+	return out;
+}
+
+static int _check_label_header(struct label_header *lh, uint64_t labelsector)
+{
+	uint32_t crc;
+	int bad = 0;
+
+	if (memcmp(lh->id, LABEL_ID, sizeof(lh->id))) {
+		log_print("CHECK: label_header.id expected %s", LABEL_ID);
+		bad++;
+	}
+
+	if (xlate64(lh->sector_xl) != labelsector) {
+		log_print("CHECK: label_header.sector expected %d", (int)labelsector);
+		bad++;
+	}
+
+	crc = calc_crc(INITIAL_CRC, (uint8_t *)&lh->offset_xl,
+		       LABEL_SIZE - ((uint8_t *) &lh->offset_xl - (uint8_t *) lh));
+		
+	if (crc != xlate32(lh->crc_xl)) {
+		log_print("CHECK: label_header.crc expected 0x%x", crc);
+		bad++;
+	}
+
+	if (xlate32(lh->offset_xl) != 32) {
+		log_print("CHECK: label_header.offset expected 32");
+		bad++;
+	}
+
+	if (memcmp(lh->type, LVM2_LABEL, sizeof(lh->type))) {
+		log_print("CHECK: label_header.type expected %s", LVM2_LABEL);
+		bad++;
+	}
+
+	if (bad)
+		return 0;
+	return 1;
+}
+
+static int _check_pv_header(struct pv_header *ph)
+{
+	struct id id;
+	int bad = 0;
+
+	if (!id_read_format_try(&id, (char *)&ph->pv_uuid)) {
+		log_print("CHECK: pv_header.pv_uuid invalid format");
+		bad++;
+	}
+
+	if (bad)
+		return 0;
+	return 1;
+}
 
 /*
- * TODO: option to dump all copies of metadata that are found
+ * mda_offset/mda_size are from the pv_header/disk_locn and could
+ * be incorrect.
+ */
+static int _check_mda_header(struct mda_header *mh, int mda_num, uint64_t mda_offset, uint64_t mda_size)
+{
+	char str[256];
+	uint32_t crc;
+	int bad = 0;
+
+	crc = calc_crc(INITIAL_CRC, (uint8_t *)mh->magic,
+		       MDA_HEADER_SIZE - sizeof(mh->checksum_xl));
+
+	if (crc != xlate32(mh->checksum_xl)) {
+		log_print("CHECK: mda_header_%d.checksum expected 0x%x", mda_num, crc);
+		bad++;
+	}
+
+	if (memcmp(mh->magic, FMTT_MAGIC, sizeof(mh->magic))) {
+		log_print("CHECK: mda_header_%d.magic expected 0x%s", mda_num, _chars_to_hexstr((void *)&FMTT_MAGIC, str, 16, 256, "mda_header.magic"));
+		bad++;
+	}
+
+	if (xlate32(mh->version) != FMTT_VERSION) {
+		log_print("CHECK: mda_header_%d.version expected %u", mda_num, FMTT_VERSION);
+		bad++;
+	}
+
+	if (xlate64(mh->start) != mda_offset) {
+		log_print("CHECK: mda_header_%d.start does not match pv_header.disk_locn.offset %llu", mda_num, (unsigned long long)mda_offset);
+		bad++;
+	}
+
+	if (xlate64(mh->size) != mda_size) {
+		log_print("CHECK: mda_header_%d.size does not match pv_header.disk_locn.size %llu", mda_num, (unsigned long long)mda_size);
+		bad++;
+	}
+
+	if (bad)
+		return 0;
+	return 1;
+}
+
+/*
+ *
+ * mda_offset, mda_size are from pv_header.disk_locn
+ * (the location of the metadata area.)
  *
- * TODO: option to intelligently search for mda locations on
- * disk in case the pv_header and/or mda_header are damaged.
+ * meta_offset, meta_size, meta_checksum are from mda_header.raw_locn
+ * (the location of the metadata text in the metadata area.)
  */
 
-static int _dump_metadata(struct cmd_context *cmd, int argc, char **argv, int full_area)
+static int _dump_raw_locn(struct device *dev, int print_fields,
+			  struct raw_locn *rlocn, int rlocn_index, uint64_t rlocn_offset,
+			  int mda_num, uint64_t mda_offset, uint64_t mda_size,
+			  uint64_t *meta_offset_ret,
+			  uint64_t *meta_size_ret,
+			  uint32_t *meta_checksum_ret)
 {
-	struct dm_list devs;
-	struct device_list *devl;
-	struct device *dev;
-	const char *pv_name;
-	const char *vgname;
-	const char *vgid;
-	struct lvmcache_info *info;
-	struct metadata_area *mda;
-	const char *tofile = NULL;
-	int mda_num = 1;
-	int ret;
+	uint64_t meta_offset, meta_size;
+	uint32_t meta_checksum;
+	uint32_t meta_flags;
+	int bad = 0;
+	int mn = mda_num; /* 1 or 2 */
+	int ri = rlocn_index; /* 0 or 1 */
+	int wrapped = 0;
+
+	meta_offset = xlate64(rlocn->offset);
+	meta_size = xlate64(rlocn->size);
+	meta_checksum = xlate32(rlocn->checksum);
+	meta_flags = xlate32(rlocn->flags);
+
+	if (meta_offset + meta_size > mda_size)
+		wrapped = 1;
+
+	if (print_fields) {
+		log_print("mda_header_%d.raw_locn[%d] at %llu # %s%s", mn, ri, (unsigned long long)rlocn_offset, (ri == 0) ? "commit" : "precommit", wrapped ? " wrapped" : "");
+		log_print("mda_header_%d.raw_locn[%d].offset %llu", mn, ri, (unsigned long long)meta_offset);
+		log_print("mda_header_%d.raw_locn[%d].size %llu", mn, ri, (unsigned long long)meta_size);
+		log_print("mda_header_%d.raw_locn[%d].checksum 0x%x", mn, ri, meta_checksum);
+
+		if (meta_flags & RAW_LOCN_IGNORED)
+			log_print("mda_header_%d.raw_locn[%d].flags 0x%x # RAW_LOCN_IGNORED", mn, ri, meta_flags);
+		else
+			log_print("mda_header_%d.raw_locn[%d].flags 0x%x", mn, ri, meta_flags);
+	}
 
-	dm_list_init(&devs);
+	/* The precommit pointer will usually be empty. */
+	if ((rlocn_index == 1) && meta_offset)
+		log_print("CHECK: mda_header_%d.raw_locn[%d] for precommit not empty", mn, ri);
+
+	/* This metadata area is not being used to hold text metadata. */
+	/* Old, out of date text metadata may exist if the area was once used. */
+	if (meta_flags & RAW_LOCN_IGNORED)
+		return 1;
+
+	/*
+	 * A valid meta_size can be no larger than the metadata area size minus
+	 * the 512 bytes used by the mda_header sector.
+	 */
+	if (meta_size > (mda_size - 512)) {
+		log_print("CHECK: mda_header_%d.raw_locn[%d].size larger than metadata area size", mn, ri);
+		/* If meta_size is bad, try to continue using a reasonable value */
+		meta_size = (mda_size - 512);
+	}
 
-	if (arg_is_set(cmd, file_ARG)) {
-		if (!(tofile = arg_str_value(cmd, file_ARG, NULL)))
-			return ECMD_FAILED;
+	if (meta_offset_ret)
+		*meta_offset_ret = meta_offset;
+	if (meta_size_ret)
+		*meta_size_ret = meta_size;
+	if (meta_checksum_ret)
+		*meta_checksum_ret = meta_checksum;
+
+	/* No text metadata exists in this metadata area. */
+	if (!meta_offset)
+		return 1;
+
+	if (bad)
+		return 0;
+	return 1;
+}
+
+static int _dump_meta_area(struct device *dev, const char *tofile,
+			   uint64_t mda_offset, uint64_t mda_size)
+{
+	FILE *fp;
+	char *meta_buf;
+
+	if (!tofile)
+		return_0;
+
+	if (!(meta_buf = malloc(mda_size)))
+		return_0;
+	memset(meta_buf, 0, mda_size);
+
+	if (!dev_read_bytes(dev, mda_offset, mda_size, meta_buf)) {
+		log_print("CHECK: failed to read metadata area at offset %llu size %llu",
+			  (unsigned long long)mda_offset, (unsigned long long)mda_size);
+		return 0;
 	}
 
-	/* 1: dump metadata from first mda, 2: dump metadata from second mda */
-	if (arg_is_set(cmd, pvmetadatacopies_ARG))
-		mda_num = arg_int_value(cmd, pvmetadatacopies_ARG, 1);
+	if (!(fp = fopen(tofile, "wx"))) {
+		log_error("Failed to create file %s", tofile);
+		return 0;
+	}
+
+	fwrite(meta_buf, mda_size - 512, 1, fp);
+
+	if (fflush(fp))
+		stack;
+	if (fclose(fp))
+		stack;
+	return 1;
+}
+
+static int _dump_meta_text(struct device *dev,
+			   int print_fields, int print_metadata, const char *tofile,
+			   int mda_num, int rlocn_index,
+			   uint64_t mda_offset, uint64_t mda_size,
+			   uint64_t meta_offset, uint64_t meta_size,
+			   uint32_t meta_checksum)
+{
+	char *meta_buf;
+	struct dm_config_tree *cft;
+	const char *vgname = NULL;
+	uint32_t crc;
+	int mn = mda_num; /* 1 or 2 */
+	int ri = rlocn_index; /* 0 or 1 */
+	int bad = 0;
+
+	if (!(meta_buf = malloc(meta_size))) {
+		log_print("CHECK: mda_header_%d.raw_locn[%d] no mem for metadata text size %llu", mn, ri,
+			  (unsigned long long)meta_size);
+		return 0;
+	}
+	memset(meta_buf, 0, meta_size);
+
+	/*
+	 * Read the metadata text specified by the raw_locn so we can
+	 * check the raw_locn values.
+	 *
+	 * meta_offset is the offset from the start of the mda_header,
+	 * so the text location from the start of the disk is
+	 * mda_offset + meta_offset.
+	 */
+	if (meta_offset + meta_size > mda_size) {
+	 	/* text metadata wraps to start of text metadata area */
+		uint32_t wrap = (uint32_t) ((meta_offset + meta_size) - mda_size);
+		off_t offset_a = mda_offset + meta_offset;
+		uint32_t size_a = meta_size - wrap;
+		off_t offset_b = mda_offset + 512; /* continues after mda_header sector */
+		uint32_t size_b = wrap;
+
+		if (!dev_read_bytes(dev, offset_a, size_a, meta_buf)) {
+			log_print("CHECK: failed to read metadata text at mda_header_%d.raw_locn[%d].offset %llu size %llu part_a %llu %llu", mn, ri,
+				  (unsigned long long)meta_offset, (unsigned long long)meta_size,
+				  (unsigned long long)offset_a, (unsigned long long)size_a);
+			return 0;
+		}
+
+		if (!dev_read_bytes(dev, offset_b, size_b, meta_buf + size_a)) {
+			log_print("CHECK: failed to read metadata text at mda_header_%d.raw_locn[%d].offset %llu size %llu part_b %llu %llu", mn, ri,
+				  (unsigned long long)meta_offset, (unsigned long long)meta_size,
+				  (unsigned long long)offset_b, (unsigned long long)size_b);
+			return 0;
+		}
+	} else {
+		if (!dev_read_bytes(dev, mda_offset + meta_offset, meta_size, meta_buf)) {
+			log_print("CHECK: failed to read metadata text at mda_header_%d.raw_locn[%d].offset %llu size %llu", mn, ri,
+				  (unsigned long long)meta_offset, (unsigned long long)meta_size);
+			return 0;
+		}
+	}
+
+	crc = calc_crc(INITIAL_CRC, (uint8_t *)meta_buf, meta_size);
+	if (crc != meta_checksum) {
+		log_print("CHECK: metadata text at %llu crc does not match mda_header_%d.raw_locn[%d].checksum",
+			  (unsigned long long)(mda_offset + meta_offset), mn, ri);
+		bad++;
+	}
+
+	if (!(cft = config_open(CONFIG_FILE_SPECIAL, NULL, 0))) {
+		log_print("CHECK: failed to set up metadata parsing");
+		bad++;
+	} else {
+		if (!dm_config_parse(cft, meta_buf, meta_buf + meta_size)) {
+			log_print("CHECK: failed to parse metadata text at %llu size %llu",
+				  (unsigned long long)(mda_offset + meta_offset),
+				   (unsigned long long)meta_size);
+			bad++;
+		} else {
+			vgname = strdup(cft->root->key);
+		}
+		config_destroy(cft);
+	}
+
+	log_print("metadata text at %llu crc 0x%x # vgname %s",
+		  (unsigned long long)(mda_offset + meta_offset), crc,
+		  vgname ? vgname : "?");
+
+	if (!print_metadata)
+		goto out;
+
+	if (!tofile) {
+		log_print("---");
+		printf("%s\n", meta_buf);
+		log_print("---");
+	} else {
+		FILE *fp;
+		if (!(fp = fopen(tofile, "wx"))) {
+			log_error("Failed to create file %s", tofile);
+			goto out;
+		}
+
+		fprintf(fp, "%s", meta_buf);
+
+		if (fflush(fp))
+			stack;
+		if (fclose(fp))
+			stack;
+	}
+
+ out:
+	if (bad)
+		return 0;
+	return 1;
+}
+
+static int _dump_label_and_pv_header(struct cmd_context *cmd, int print_fields,
+				     struct device *dev,
+				     uint64_t *mda1_offset, uint64_t *mda1_size,
+				     uint64_t *mda2_offset, uint64_t *mda2_size)
+{
+	char str[256];
+	struct label_header *lh;
+	struct pv_header *pvh;
+	struct pv_header_extension *pvhe;
+	struct disk_locn *dlocn;
+	uint64_t lh_offset;
+	uint64_t pvh_offset;
+	uint64_t pvhe_offset;
+	uint64_t dlocn_offset;
+	char *buf;
+	uint64_t labelsector;
+	uint64_t tmp;
+	int mda_count = 0;
+	int bad = 0;
+	int di;
+
+	/*
+	 * By default LVM skips the first sector (sector 0), and writes
+	 * the label_header in the second sector (sector 1).
+	 * (sector size 512 bytes)
+	 */
+	if (arg_is_set(cmd, labelsector_ARG))
+		labelsector = arg_uint64_value(cmd, labelsector_ARG, UINT64_C(0));
+	else
+		labelsector = 1;
+
+	lh_offset = labelsector * 512; /* from start of disk */
+
+	if (!(buf = zalloc(512)))
+		return_0;
+
+	if (!dev_read_bytes(dev, lh_offset, 512, buf)) {
+		log_print("CHECK: failed to read label_header at %llu",
+			  (unsigned long long)lh_offset);
+		return 0;
+	}
+
+	lh = (struct label_header *)buf;
+
+	if (print_fields) {
+		log_print("label_header at %llu", (unsigned long long)lh_offset);
+		log_print("label_header.id %s", _chars_to_str(lh->id, str, 8, 256, "label_header.id"));
+		log_print("label_header.sector %llu", (unsigned long long)xlate64(lh->sector_xl));
+		log_print("label_header.crc 0x%x", xlate32(lh->crc_xl));
+		log_print("label_header.offset %u", xlate32(lh->offset_xl));
+		log_print("label_header.type %s", _chars_to_str(lh->type, str, 8, 256, "label_header.type"));
+	}
+
+	if (!_check_label_header(lh, labelsector))
+		bad++;
+
+	/*
+	 * The label_header is 32 bytes in size (size of struct label_header).
+	 * The pv_header should begin immediately after the label_header.
+	 * The label_header.offset gives the offset of pv_header from the
+	 * start of the label_header, which should always be 32.
+	 *
+	 * If label_header.offset is corrupted, then we should print a
+	 * warning about the bad value, and read the pv_header from the
+	 * correct location instead of the bogus location.
+	 */
+
+	pvh = (struct pv_header *)(buf + 32);
+	pvh_offset = lh_offset + 32; /* from start of disk */
+
+	/* sanity check */
+	if ((void *)pvh != (void *)(buf + pvh_offset - lh_offset))
+		log_print("CHECK: problem with pv_header offset calculation");
+
+	if (print_fields) {
+		log_print("pv_header at %llu", (unsigned long long)pvh_offset);
+		log_print("pv_header.pv_uuid %s", _chars_to_str(pvh->pv_uuid, str, ID_LEN, 256, "pv_header.pv_uuid"));
+		log_print("pv_header.device_size %llu", (unsigned long long)xlate64(pvh->device_size_xl));
+	}
+
+	if (!_check_pv_header(pvh))
+		bad++;
+
+	/*
+	 * The pv_header is 40 bytes, excluding disk_locn's.
+	 * disk_locn structs immediately follow the pv_header.
+	 * Each disk_locn is 16 bytes.
+	 */
+	di = 0;
+	dlocn = pvh->disk_areas_xl;
+	dlocn_offset = pvh_offset + 40; /* from start of disk */
+
+	/* sanity check */
+	if ((void *)dlocn != (void *)(buf + dlocn_offset - lh_offset))
+		log_print("CHECK: problem with pv_header.disk_locn[%d] offset calculation", di);
+
+	while ((tmp = xlate64(dlocn->offset))) {
+		if (print_fields) {
+			log_print("pv_header.disk_locn[%d] at %llu # location of data area", di,
+				  (unsigned long long)dlocn_offset);
+			log_print("pv_header.disk_locn[%d].offset %llu", di,
+				  (unsigned long long)xlate64(dlocn->offset));
+			log_print("pv_header.disk_locn[%d].size %llu", di,
+				  (unsigned long long)xlate64(dlocn->size));
+		}
+		di++;
+		dlocn++;
+		dlocn_offset += 16;
+	}
+
+	/* all-zero dlocn struct is area list end */
+	if (print_fields) {
+		log_print("pv_header.disk_locn[%d] at %llu # location list end", di,
+			  (unsigned long long) dlocn_offset);
+		log_print("pv_header.disk_locn[%d].offset %llu", di,
+			  (unsigned long long)xlate64(dlocn->offset));
+		log_print("pv_header.disk_locn[%d].size %llu", di,
+			  (unsigned long long)xlate64(dlocn->size));
+	}
+
+	/* advance past the all-zero dlocn struct */
+	di++;
+	dlocn++;
+	dlocn_offset += 16;
+
+	/* sanity check */
+	if ((void *)dlocn != (void *)(buf + dlocn_offset - lh_offset))
+		log_print("CHECK: problem with pv_header.disk_locn[%d] offset calculation", di);
+
+	while ((tmp = xlate64(dlocn->offset))) {
+		if (print_fields) {
+			log_print("pv_header.disk_locn[%d] at %llu # location of metadata area", di,
+				  (unsigned long long)dlocn_offset);
+			log_print("pv_header.disk_locn[%d].offset %llu", di,
+				  (unsigned long long)xlate64(dlocn->offset));
+			log_print("pv_header.disk_locn[%d].size %llu", di,
+				  (unsigned long long)xlate64(dlocn->size));
+		}
+
+		if (!mda_count) {
+			*mda1_offset = xlate64(dlocn->offset);
+			*mda1_size = xlate64(dlocn->size);
+
+			if (*mda1_offset != 4096) {
+				log_print("CHECK: pv_header.disk_locn[%d].offset expected 4096 # for first mda", di);
+				bad++;
+			}
+		} else {
+			*mda2_offset = xlate64(dlocn->offset);
+			*mda2_size = xlate64(dlocn->size);
+
+			/*
+			 * No fixed location for second mda, so we have to look for
+			 * mda_header at this offset to see if it's correct.
+			 */
+		}
+
+		di++;
+		dlocn++;
+		dlocn_offset += 16;
+		mda_count++;
+	}
+
+	/* all-zero dlocn struct is area list end */
+	if (print_fields) {
+		log_print("pv_header.disk_locn[%d] at %llu # location list end", di,
+			  (unsigned long long) dlocn_offset);
+		log_print("pv_header.disk_locn[%d].offset %llu", di,
+			  (unsigned long long)xlate64(dlocn->offset));
+		log_print("pv_header.disk_locn[%d].size %llu", di,
+			  (unsigned long long)xlate64(dlocn->size));
+	}
+
+	/* advance past the all-zero dlocn struct */
+	di++;
+	dlocn++;
+	dlocn_offset += 16;
+
+	/*
+	 * pv_header_extension follows the last disk_locn
+	 * terminating struct, so it's not always at the
+	 * same location.
+	 */
+
+	pvhe = (struct pv_header_extension *)dlocn;
+	pvhe_offset = dlocn_offset; /* from start of disk */
+
+	/* sanity check */
+	if ((void *)pvhe != (void *)(buf + pvhe_offset - lh_offset))
+		log_print("CHECK: problem with pv_header_extension offset calculation");
+
+	if (print_fields) {
+		log_print("pv_header_extension at %llu", (unsigned long long)pvhe_offset);
+		log_print("pv_header_extension.version %u", xlate32(pvhe->version));
+		log_print("pv_header_extension.flags %u", xlate32(pvhe->flags));
+	}
+
+	/*
+	 * The pv_header_extension is 8 bytes, excluding disk_locn's.
+	 * disk_locn structs immediately follow the pv_header_extension.
+	 * Each disk_locn is 16 bytes.
+	 */
+	di = 0;
+	dlocn = pvhe->bootloader_areas_xl;
+	dlocn_offset = pvhe_offset + 8;
+
+	while ((tmp = xlate64(dlocn->offset))) {
+		if (print_fields) {
+			log_print("pv_header_extension.disk_locn[%d] at %llu # bootloader area", di,
+				  (unsigned long long)dlocn_offset);
+			log_print("pv_header_extension.disk_locn[%d].offset %llu", di,
+				  (unsigned long long)xlate64(dlocn->offset));
+			log_print("pv_header_extension.disk_locn[%d].size %llu", di,
+				  (unsigned long long)xlate64(dlocn->size));
+		}
+
+		di++;
+		dlocn++;
+		dlocn_offset += 16;
+	}
+
+	/* all-zero dlocn struct is area list end */
+	if (print_fields) {
+		log_print("pv_header_extension.disk_locn[%d] at %llu # location list end", di,
+			  (unsigned long long) dlocn_offset);
+		log_print("pv_header_extension.disk_locn[%d].offset %llu", di,
+			  (unsigned long long)xlate64(dlocn->offset));
+		log_print("pv_header_extension.disk_locn[%d].size %llu", di,
+			  (unsigned long long)xlate64(dlocn->size));
+	}
+
+	if (bad)
+		return 0;
+	return 1;
+}
+
+/*
+ * mda_offset and mda_size are the location/size of the metadata area,
+ * which starts with the mda_header and continues through the circular
+ * buffer of text.
+ *
+ * mda_offset and mda_size values come from the pv_header/disk_locn,
+ * which could be incorrect.
+ *
+ * We know that the first mda_offset will always be 4096, so we use
+ * that value regardless of what the first mda_offset value in the
+ * pv_header is.
+ */
+
+static int _dump_mda_header(struct cmd_context *cmd,
+			    int print_fields, int print_metadata, int print_area,
+			    const char *tofile,
+			    struct device *dev,
+			    uint64_t mda_offset, uint64_t mda_size,
+			    uint32_t *checksum0_ret)
+{
+	char str[256];
+	char *buf;
+	struct mda_header *mh;
+	struct raw_locn *rlocn0, *rlocn1;
+	uint64_t rlocn0_offset, rlocn1_offset;
+	uint64_t meta_offset = 0;
+	uint64_t meta_size = 0;
+	uint32_t meta_checksum = 0;
+	int mda_num = (mda_offset == 4096) ? 1 : 2;
+	int bad = 0;
+
+	*checksum0_ret = 0; /* checksum from raw_locn[0] */
+
+	if (!(buf = zalloc(512)))
+		return_0;
+
+	/*
+	 * The first mda_header is 4096 bytes from the start
+	 * of the device.  Each mda_header is 512 bytes.
+	 *
+	 * The start/size values in the mda_header should
+	 * match the mda_offset/mda_size values that came
+	 * from the pv_header/disk_locn.
+	 *
+	 * (Why was mda_header magic made only partially printable?)
+	 */
+
+	if (!dev_read_bytes(dev, mda_offset, 512, buf)) {
+		log_print("CHECK: failed to read mda_header at %llu", (unsigned long long)mda_offset);
+		return 0;
+	}
+
+	mh = (struct mda_header *)buf;
+
+	if (print_fields) {
+		log_print("mda_header_%d at %llu # metadata area", mda_num, (unsigned long long)mda_offset);
+		log_print("mda_header_%d.checksum 0x%x", mda_num, xlate32(mh->checksum_xl));
+		log_print("mda_header_%d.magic 0x%s", mda_num, _chars_to_hexstr(mh->magic, str, 16, 256, "mda_header.magic"));
+		log_print("mda_header_%d.version %u", mda_num, xlate32(mh->version));
+		log_print("mda_header_%d.start %llu", mda_num, (unsigned long long)xlate64(mh->start));
+		log_print("mda_header_%d.size %llu", mda_num, (unsigned long long)xlate64(mh->size));
+	}
+
+	if (!_check_mda_header(mh, mda_num, mda_offset, mda_size))
+		bad++;
+
+	if (print_area) {
+		if (!_dump_meta_area(dev, tofile, mda_offset, mda_size))
+			bad++;
+		goto out;
+	}
+
+	/*
+	 * mda_header is 40 bytes, the raw_locn structs
+	 * follow immediately after, each raw_locn struct
+	 * is 24 bytes.
+	 */
+
+	rlocn0 = mh->raw_locns;
+	rlocn0_offset = mda_offset + 40; /* from start of disk */
+
+	/* sanity check */
+	if ((void *)rlocn0 != (void *)(buf + rlocn0_offset - mda_offset))
+		log_print("CHECK: problem with rlocn0 offset calculation");
+
+	meta_offset = 0;
+	meta_size = 0;
+	meta_checksum = 0;
+
+	if (!_dump_raw_locn(dev, print_fields, rlocn0, 0, rlocn0_offset, mda_num, mda_offset, mda_size,
+			    &meta_offset, &meta_size, &meta_checksum))
+		bad++;
+
+	*checksum0_ret = meta_checksum;
+
+	rlocn1 = (struct raw_locn *)((char *)mh->raw_locns + 24);
+	rlocn1_offset = rlocn0_offset + 24;
+
+	/* sanity check */
+	if ((void *)rlocn1 != (void *)(buf + rlocn1_offset - mda_offset))
+		log_print("CHECK: problem with rlocn1 offset calculation");
+
+	if (!_dump_raw_locn(dev, print_fields, rlocn1, 1, rlocn1_offset, mda_num, mda_offset, mda_size,
+			   NULL, NULL, NULL))
+		bad++;
+
+	if (!meta_offset)
+		goto out;
+
+	if (!_dump_meta_text(dev, print_fields, print_metadata, tofile, mda_num, 0, mda_offset, mda_size, meta_offset, meta_size, meta_checksum))
+		bad++;
+
+	/* Should we also check text metadata if it exists in rlocn1? */
+ out:
+	if (bad)
+		return 0;
+	return 1;
+}
+
+static int _dump_headers(struct cmd_context *cmd,
+			 int argc, char **argv)
+{
+	struct device *dev;
+	const char *pv_name;
+	uint64_t mda1_offset = 0, mda1_size = 0, mda2_offset = 0, mda2_size = 0;
+	uint32_t mda1_checksum, mda2_checksum;
+	int bad = 0;
 
 	pv_name = argv[0];
 
@@ -56,47 +775,121 @@ static int _dump_metadata(struct cmd_context *cmd, int argc, char **argv, int fu
 		return ECMD_FAILED;
 	}
 
-	if (!(devl = zalloc(sizeof(*devl))))
-		return ECMD_FAILED;
-
-	devl->dev = dev;
-	dm_list_add(&devs, &devl->list);
-
 	label_scan_setup_bcache();
-	label_scan_devs(cmd, cmd->filter, &devs);
 
-	if (!dev->pvid[0]) {
-		log_error("No PV ID found for %s", dev_name(dev));
-		return ECMD_FAILED;
+	if (!_dump_label_and_pv_header(cmd, 1, dev,
+			&mda1_offset, &mda1_size, &mda2_offset, &mda2_size))
+		bad++;
+
+	/* N.B. mda1_size and mda2_size may be different */
+
+	/*
+	 * The first mda is always 4096 bytes from the start of the device.
+	 *
+	 * TODO: A second mda may not exist.  If the pv_header says there
+	 * is no second mda, we may still want to check for a second mda
+	 * in case it's the pv_header that is wrong.  Try looking for
+	 * an mda_header at 1MB prior to the end of the device, if
+	 * mda2_offset is 0 or if we don't find an mda_header at mda2_offset
+	 * which may have been corrupted.
+	 */
+
+	if (!_dump_mda_header(cmd, 1, 0, 0, NULL, dev, 4096, mda1_size, &mda1_checksum))
+		bad++;
+
+	/*
+	 * mda2_offset may be incorrect.  Probe for a valid mda_header at
+	 * mda2_offset and at other possible/expected locations, e.g.
+	 * 1MB before end of device.  Call dump_mda_header with a different
+	 * offset than mda2_offset if there's no valid header at mda2_offset 
+	 * but there is a valid header elsewhere.
+	 */
+	if (mda2_offset) {
+		if (!_dump_mda_header(cmd, 1, 0, 0, NULL, dev, mda2_offset, mda2_size, &mda2_checksum))
+			bad++;
+
+		/* This probably indicates that one was committed and the other not. */
+		if (mda1_checksum && mda2_checksum && (mda1_checksum != mda2_checksum))
+			log_print("CHECK: mdas have different raw_locn[0].checksum values");
 	}
 
-	if (!(info = lvmcache_info_from_pvid(dev->pvid, dev, 0))) {
-		log_error("No VG info found for %s", dev_name(dev));
+	if (bad) {
+		log_error("Found bad header or metadata values.");
 		return ECMD_FAILED;
 	}
+	return ECMD_PROCESSED;
+}
 
-	if (!(vgname = lvmcache_vgname_from_info(info))) {
-		log_error("No VG name found for %s", dev_name(dev));
-		return ECMD_FAILED;
+static int _dump_metadata(struct cmd_context *cmd,
+			 int argc, char **argv, int full_area)
+{
+	struct device *dev;
+	const char *pv_name;
+	const char *tofile = NULL;
+	uint64_t mda1_offset = 0, mda1_size = 0, mda2_offset = 0, mda2_size = 0;
+	uint32_t mda1_checksum, mda2_checksum;
+	int print_metadata = !full_area;
+	int print_area = full_area;
+	int mda_num = 1;
+	int bad = 0;
+
+	if (arg_is_set(cmd, file_ARG)) {
+		if (!(tofile = arg_str_value(cmd, file_ARG, NULL)))
+			return ECMD_FAILED;
 	}
 
-        if (!(vgid = lvmcache_vgid_from_vgname(cmd, vgname))) {
-		log_error("No VG ID found for %s", dev_name(dev));
-		return ECMD_FAILED;
-        }
+	/* 1: dump metadata from first mda, 2: dump metadata from second mda */
+	if (arg_is_set(cmd, pvmetadatacopies_ARG))
+		mda_num = arg_int_value(cmd, pvmetadatacopies_ARG, 1);
+
+	pv_name = argv[0];
 
-	if (!(mda = lvmcache_get_mda(cmd, vgname, dev, mda_num))) {
-		log_error("No mda %d found for %s", mda_num, dev_name(dev));
+	if (!(dev = dev_cache_get(cmd, pv_name, cmd->filter))) {
+		log_error("No device found for %s %s.", pv_name, dev_cache_filtered_reason(pv_name));
 		return ECMD_FAILED;
 	}
 
-	if (full_area)
-		ret = dump_metadata_area(cmd, vgname, vgid, dev, mda, tofile);
-	else
-		ret = dump_metadata_text(cmd, vgname, vgid, dev, mda, tofile);
+	label_scan_setup_bcache();
+
+	if (!_dump_label_and_pv_header(cmd, 0, dev,
+			&mda1_offset, &mda1_size, &mda2_offset, &mda2_size))
+		bad++;
+
+
+	/*
+	 * The first mda is always 4096 bytes from the start of the device.
+	 *
+	 * TODO: A second mda may not exist.  If the pv_header says there
+	 * is no second mda, we may still want to check for a second mda
+	 * in case it's the pv_header that is wrong.  Try looking for
+	 * an mda_header at 1MB prior to the end of the device, if
+	 * mda2_offset is 0 or if we don't find an mda_header at mda2_offset
+	 * which may have been corrupted.
+	 *
+	 * mda2_offset may be incorrect.  Probe for a valid mda_header at
+	 * mda2_offset and at other possible/expected locations, e.g.
+	 * 1MB before end of device.  Call dump_mda_header with a different
+	 * offset than mda2_offset if there's no valid header at mda2_offset 
+	 * but there is a valid header elsewhere.
+	 */
+
+	if (mda_num == 1) {
+		if (!_dump_mda_header(cmd, 0, print_metadata, print_area, tofile, dev, 4096, mda1_size, &mda1_checksum))
+			bad++;
+	} else if (mda_num == 2) {
+		if (!mda2_offset) {
+			log_print("CHECK: second mda not found");
+			bad++;
+		} else {
+			if (!_dump_mda_header(cmd, 0, print_metadata, print_area, tofile, dev, mda2_offset, mda2_size, &mda2_checksum))
+				bad++;
+		}
+	}
 
-	if (!ret)
+	if (bad) {
+		log_error("Found bad header or metadata values.");
 		return ECMD_FAILED;
+	}
 	return ECMD_PROCESSED;
 }
 
@@ -120,6 +913,9 @@ int pvck(struct cmd_context *cmd, int argc, char **argv)
 		if (!strcmp(dump, "metadata_area"))
 			return _dump_metadata(cmd, argc, argv, 1);
 
+		if (!strcmp(dump, "headers"))
+			return _dump_headers(cmd, argc, argv);
+
 		log_error("Unknown dump value.");
 		return ECMD_FAILED;
 	}




More information about the lvm-devel mailing list