diff options
author | Karol Lewandowski <k.lewandowsk@samsung.com> | 2024-01-23 12:58:00 +0100 |
---|---|---|
committer | Karol Lewandowski <k.lewandowsk@samsung.com> | 2024-01-23 12:58:00 +0100 |
commit | cbab226a74fbaaa43220dee80e8435555c6506ce (patch) | |
tree | 1bbd14ec625ea85d0bcc32232d51c1f71e2604d2 /tools/pvck.c | |
parent | 44a3c2255bc480c82f34db156553a595606d8a0b (diff) | |
download | device-mapper-cbab226a74fbaaa43220dee80e8435555c6506ce.tar.gz device-mapper-cbab226a74fbaaa43220dee80e8435555c6506ce.tar.bz2 device-mapper-cbab226a74fbaaa43220dee80e8435555c6506ce.zip |
Imported Upstream version 2.03.22upstream/libdevmapper-1.02.196upstream/2.03.22upstreamsandbox/klewandowski/upstream_2.03.22
Diffstat (limited to 'tools/pvck.c')
-rw-r--r-- | tools/pvck.c | 3197 |
1 files changed, 3184 insertions, 13 deletions
diff --git a/tools/pvck.c b/tools/pvck.c index e45e77a..f56d2c3 100644 --- a/tools/pvck.c +++ b/tools/pvck.c @@ -10,32 +10,3203 @@ * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#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" +#include "lib/device/device_id.h" + +#define ONE_MB_IN_BYTES 1048576 + +#define PRINT_CURRENT 1 +#define PRINT_ALL 2 + +#define ID_STR_SIZE 40 /* uuid formatted with dashes is 38 chars */ + +/* + * command line input from --settings + */ +struct settings { + uint64_t metadata_offset; /* bytes, start of text metadata (from start of disk) */ + uint64_t mda_offset; /* bytes, start of mda_header (from start of disk) */ + uint64_t mda_size; /* bytes, size of metadata area (mda_header + text area) */ + uint64_t mda2_offset; /* bytes */ + uint64_t mda2_size; /* bytes */ + uint64_t device_size; /* bytes */ + uint64_t data_offset; /* bytes, start of data (pe_start) */ + uint32_t seqno; + struct id pv_id; + + int mda_num; /* 1 or 2 for first or second mda */ + char *backup_file; + + unsigned metadata_offset_set:1; + unsigned mda_offset_set:1; + unsigned mda_size_set:1; + unsigned mda2_offset_set; + unsigned mda2_size_set; + unsigned device_size_set:1; + unsigned data_offset_set:1; + unsigned seqno_set:1; + unsigned pvid_set:1; +}; + +/* + * command line input from --file + */ +struct metadata_file { + const char *filename; + char *text_buf; + uint64_t text_size; /* bytes */ + uint32_t text_crc; + char vgid_str[ID_STR_SIZE]; +}; + +static char *_chars_to_str(const void *in, void *out, int num, int max, const char *field) +{ + const 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(const void *in, void *out, int num, int max, const char *field) +{ + char *tmp; + const 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_vgname_start(char *buf, int *len) +{ + int chars = 0; + int space = 0; + int i; + char c; + + /* + * Valid metadata begins: 'vgname {' + */ + for (i = 0; i <= NAME_LEN + 2; i++) { + c = buf[i]; + + if (isalnum(c) || c == '.' || c == '_' || c == '-' || c == '+') { + if (space) + return 0; + chars++; + continue; + } + + if (c == ' ') { + if (!chars || space) + return 0; + space++; + continue; + } + + if (c == '{') { + if (chars && space) { + *len = chars; + return 1; + } + return 0; + } + + return 0; + } + return 0; +} + +/* all sizes and offsets in bytes */ + +static void _copy_out_metadata(char *buf, uint32_t start, uint32_t first_start, uint64_t mda_size, char **meta_buf, uint64_t *meta_size, int *bad_end) +{ + char *new_buf; + uint64_t i; + uint64_t new_len; + uint64_t len_a = 0, len_b = 0; + uint32_t stop; + int found_end; + + /* + * If we wrap around the buffer searching for the + * end of some metadata, either stop when we reach + * where we began (start), or stop where we found + * the first copy of metadata (first_start). + */ + if (!first_start) + stop = start; + else + stop = first_start; + + found_end = 0; + for (i = start; i < mda_size; i++) { + if (buf[i] == '\0') { + found_end = 1; + break; + } + } + + if (found_end) { + new_len = i - start; + } else { + len_a = i - start; + + found_end = 0; + for (i = 512; i < stop; i++) { + if (buf[i] == '\0') { + found_end = 1; + break; + } + } + + if (!found_end) + return; + + len_b = i - 512; + new_len = len_a + len_b; + } + + if (new_len < 256) { + log_print("skip invalid metadata with len %llu at %llu", + (unsigned long long)new_len, (unsigned long long)start); + return; + } + + /* terminating 0 byte */ + new_len++; + + if (!(new_buf = zalloc(new_len))) + return; + + if (len_a) { + memcpy(new_buf, buf+start, len_a); + memcpy(new_buf+len_a, buf+512, len_b); + } else { + memcpy(new_buf, buf+start, new_len); + } + + /* \0 should be preceded by \n\n (0x0a0a) */ + if (new_buf[new_len-1] != 0 || new_buf[new_len-2] != 0x0a || new_buf[new_len-3] != 0x0a) + *bad_end = 1; + + *meta_buf = new_buf; + *meta_size = new_len; +} + +/* all sizes and offsets in bytes */ + +static int _text_buf_parse(char *text_buf, uint64_t text_size, struct dm_config_tree **cft_out) +{ + struct dm_config_tree *cft; + + *cft_out = NULL; + + if (!(cft = config_open(CONFIG_FILE_SPECIAL, NULL, 0))) { + return 0; + } + + if (!dm_config_parse_without_dup_node_check(cft, text_buf, text_buf + text_size)) { + config_destroy(cft); + return 0; + } + + *cft_out = cft; + return 1; +} + +/* all sizes and offsets in bytes */ + +static int _text_buf_parsable(char *text_buf, uint64_t text_size) +{ + struct dm_config_tree *cft = NULL; + + if (!_text_buf_parse(text_buf, text_size, &cft)) + return 0; + + config_destroy(cft); + return 1; +} + +#define MAX_LINE_CHECK 128 + +#define MAX_DESC 1024 +char desc_line[MAX_DESC]; + +static void _copy_line(char *in, char *out, int *len, int linesize) +{ + int i; + + *len = 0; + + for (i = 0; i < linesize; i++) { + out[i] = in[i]; + if ((in[i] == '\n') || (in[i] == '\0')) + break; + } + *len = i+1; +} + +static uint64_t mda2_offset_from_size(struct device *dev, uint64_t mda2_size) +{ + uint64_t dev_sectors = 0; + uint64_t dev_bytes; + uint64_t extra_bytes; + uint64_t mda2_offset; + + if (dev_get_size(dev, &dev_sectors)) + stack; + + dev_bytes = dev_sectors * 512; + extra_bytes = dev_bytes % ONE_MB_IN_BYTES; + + if (dev_bytes < (2 * ONE_MB_IN_BYTES)) + return_0; + + mda2_offset = dev_bytes - extra_bytes - mda2_size; + + return mda2_offset; +} + +static uint64_t mda2_size_from_offset(struct device *dev, uint64_t mda2_offset) +{ + uint64_t dev_sectors = 0; + uint64_t dev_bytes; + uint64_t extra_bytes; + uint64_t mda2_size; + + if (dev_get_size(dev, &dev_sectors)) + stack; + + dev_bytes = dev_sectors * 512; + extra_bytes = dev_bytes % ONE_MB_IN_BYTES; + + if (dev_bytes < (2 * ONE_MB_IN_BYTES)) + return_0; + + mda2_size = dev_bytes - extra_bytes - mda2_offset; + + return mda2_size; +} + +struct devicefile { + int fd; + char path[0]; +}; + +static struct devicefile *get_devicefile(struct cmd_context *cmd, const char *path) +{ + struct stat sb; + struct devicefile *def; + size_t len; + + if (stat(path, &sb)) + return_NULL; + + if ((sb.st_mode & S_IFMT) != S_IFREG) + return_NULL; + + len = strlen(path) + 1; + if (!(def = dm_pool_alloc(cmd->mem, sizeof(struct devicefile) + len))) + return_NULL; + + memcpy(def->path, path, len); + + if ((def->fd = open(path, O_RDONLY)) < 0) + return_NULL; + + return def; +} + +static bool _read_bytes(struct device *dev, struct devicefile *def, uint64_t start, size_t len, void *data) +{ + off_t off; + ssize_t rv; + + if (dev) + return dev_read_bytes(dev, start, len, data); + + if (!def) + return false; + + off = lseek(def->fd, start, SEEK_SET); + if (off != (off_t)start) + return false; + + rv = read(def->fd, data, len); + if (rv < 0) + return false; + if ((size_t)rv != len) + return false; + return true; +} + +/* all sizes and offsets in bytes */ + +static int _dump_all_text(struct cmd_context *cmd, struct settings *set, const char *tofile, + struct device *dev, struct devicefile *def, + int mda_num, uint64_t mda_offset, uint64_t mda_size, char *buf) +{ + FILE *fp = NULL; + char line[MAX_LINE_CHECK]; + char vgname[NAME_LEN+1]; + char id_str[ID_STR_SIZE]; + char id_first[ID_STR_SIZE]; + char *text_buf = NULL; + char *p; + uint32_t buf_off; /* offset with buf which begins with mda_header, bytes */ + uint32_t buf_off_first = 0; + uint32_t seqno; + uint32_t crc; + uint64_t text_size; /* bytes */ + uint64_t meta_size; /* bytes */ + int print_count = 0; + int one_found = 0; + int multiple_vgs = 0; + int bad_end; + int vgnamelen; + unsigned count; + int len; + + if (tofile) { + if (!(fp = fopen(tofile, "wx"))) { + log_error("Failed to create file %s", tofile); + return 0; + } + } + + /* + * If metadata has not wrapped, and the metadata area beginning + * has not been damaged, the text area will begin with vgname {. + * Wrapping or damage would mean we find no metadata starting at + * the start of the area. + * + * Try looking at each 512 byte offset within the area for the start + * of another copy of metadata. Metadata copies have begun at 512 + * aligned offsets since very early lvm2 code in 2002. + * + * (We could also search for something definitive like + * "# Generated by LVM2" in the area, and then work backward to find + * a likely beginning.) + * + * N.B. This relies on VG metadata first having the id = "..." field + * followed by the "seqno = N" field. + */ + + memset(id_first, 0, sizeof(id_str)); + + /* + * A count of 512 byte chunks within the metadata area. + */ + count = 0; + + meta_size = mda_size - 512; + + /* + * Search 512 byte boundaries for the start of new metadata copies. + */ + while (count < (meta_size / 512)) { + memset(vgname, 0, sizeof(vgname)); + memset(id_str, 0, sizeof(id_str)); + seqno = 0; + vgnamelen = 0; + text_size = 0; + bad_end = 0; + + if (one_found) + break; + + /* + * Check for a new metadata copy at each 512 offset + * (after skipping 512 bytes for mda_header at the + * start of the buf). + * + * If a line looks like it begins with a vgname + * it could be a new copy of metadata, but it could + * also be a random bit of metadata that looks like + * a vgname, so confirm it's the start of metadata + * by looking for id and seqno lines following the + * possible vgname. + */ + buf_off = 512 + (count * 512); + p = buf + buf_off; + + /* + * user specified metadata in one location + */ + if (set->metadata_offset_set && (set->metadata_offset != (mda_offset + buf_off))) { + count++; + continue; + } + if (set->metadata_offset_set) + one_found = 1; + + /* + * copy line of possible metadata to check for vgname + */ + memset(line, 0, sizeof(line)); + _copy_line(p, line, &len, sizeof(line)-1); + p += len; + + if (!_check_vgname_start(line, &vgnamelen)) { + count++; + continue; + } + + memcpy(vgname, line, vgnamelen); + + /* + * copy next line of metadata, which should contain id + */ + memset(line, 0, sizeof(line)); + _copy_line(p, line, &len, sizeof(line)-1); + p += len; + + if (strncmp(line, "id = ", 5)) { + count++; + continue; + } + + memcpy(id_str, line + 6, 38); + + /* + * copy next line of metadata, which should contain seqno + */ + memset(line, 0, sizeof(line)); + _copy_line(p, line, &len, sizeof(line)-1); + p += len; + + if (strncmp(line, "seqno = ", 8)) { + count++; + continue; + } + if (sscanf(line, "seqno = %u", &seqno) != 1) { + count++; + continue; + } + + /* + * user specified metadata with one seqno + * (this is not good practice since multiple old copies of metadata + * can have the same seqno; this is mostly to simplify testing) + */ + if (set->seqno_set && (set->seqno != seqno)) { + count++; + continue; + } + if (set->seqno_set) + one_found = 1; + + /* + * The first three lines look like metadata with + * vgname/id/seqno, so copy out the full metadata. + * + * If this reaches the end of buf without reaching the + * end marker of metadata, it will wrap around to the + * start of buf and continue copying until it reaches + * a NL or until it reaches buf_off_first (which is + * where we've already taken text from.) + */ + _copy_out_metadata(buf, buf_off, buf_off_first, mda_size, &text_buf, &text_size, &bad_end); + + if (!text_buf) { + log_warn("Failed to extract full metadata text at %llu, skipping.", + (unsigned long long)(mda_offset + buf_off)); + count++; + continue; + } + + /* + * check if it's finding metadata from different vgs + */ + if (!id_first[0]) + memcpy(id_first, id_str, sizeof(id_first)); + else if (memcmp(id_first, id_str, sizeof(id_first))) + multiple_vgs = 1; + + crc = calc_crc(INITIAL_CRC, (uint8_t *)text_buf, text_size); + + log_print("metadata at %llu length %llu crc %08x vg %s seqno %u id %s", + (unsigned long long)(mda_offset + buf_off), + (unsigned long long)text_size, + crc, vgname, seqno, id_str); + + /* + * save the location of the first metadata we've found so + * we know where to stop after wrapping buf. + */ + if (!buf_off_first) + buf_off_first = buf_off; + + /* + * check if the full metadata is parsable + */ + + if (!_text_buf_parsable(text_buf, text_size)) + log_warn("WARNING: parse error for metadata at %llu", (unsigned long long)(mda_offset + buf_off)); + if (bad_end) + log_warn("WARNING: unexpected terminating bytes for metadata at %llu", (unsigned long long)(mda_offset + buf_off)); + + if (arg_is_set(cmd, verbose_ARG)) { + char *str1, *str2; + if ((str1 = strstr(text_buf, "description = "))) { + memset(desc_line, 0, sizeof(desc_line)); + _copy_line(str1, desc_line, &len, sizeof(desc_line)-1); + if ((p = strchr(desc_line, '\n'))) + *p = '\0'; + log_print("%s", desc_line); + } + if (str1 && (str2 = strstr(str1, "creation_time = "))) { + memset(desc_line, 0, sizeof(desc_line)); + _copy_line(str2, desc_line, &len, sizeof(desc_line)-1); + if ((p = strchr(desc_line, '\n'))) + *p = '\0'; + log_print("%s\n", desc_line); + } + } + + if (fp) { + if (print_count++) + fprintf(fp, "\n--\n"); + fprintf(fp, "%s", text_buf); + } + + free(text_buf); + text_buf = NULL; + + if (text_size < 512) + count++; + else if (!(text_size % 512)) + count += (text_size / 512); + else + count += ((text_size / 512) + 1); + } + + if (multiple_vgs) + log_warn("WARNING: metadata from multiple VGs was found."); + + if (fp) { + if (fflush(fp)) + stack; + if (fclose(fp)) + stack; + } + + return 1; +} + +/* all sizes and offsets in bytes */ + +static int _check_label_header(struct label_header *lh, uint64_t labelsector, + int *found_label) +{ + uint32_t crc; + int good_id = 1, good_type = 1; + int bad = 0; + + if (memcmp(lh->id, LABEL_ID, sizeof(lh->id))) { + log_print("CHECK: label_header.id expected %s", LABEL_ID); + good_id = 0; + 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); + good_type = 0; + bad++; + } + + /* Report a label is found if at least id and type are correct. */ + if (found_label && good_id && good_type) + *found_label = 1; + + 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; +} + +/* + * all sizes and offsets in bytes + * + * 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, int *found_header) +{ + char str[256]; + uint32_t crc; + int good_magic = 1; + 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((const void *)&FMTT_MAGIC, str, 16, 256, "mda_header.magic")); + good_magic = 0; + 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++; + } + + /* Report a header is found if at least magic is correct. */ + if (found_header && good_magic) + *found_header = 1; + + if (bad) + return 0; + return 1; +} + +/* + * all sizes and offsets in bytes + * + * mda_offset, mda_size are from pv_header.disk_locn + * (the location of the metadata area.) + * + * 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_raw_locn(struct device *dev, struct devicefile *def, 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) +{ + 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); + } + + /* 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 (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, struct devicefile *def, const char *tofile, + uint64_t mda_offset, uint64_t mda_size) +{ + FILE *fp; + char *meta_buf; + int ret = 1; + + if (!tofile) + return_0; + + if (!(meta_buf = zalloc(mda_size))) + return_0; + + if (!_read_bytes(dev, def, 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); + free(meta_buf); + return 0; + } + + if (!(fp = fopen(tofile, "wx"))) { + log_error("Failed to create file %s", tofile); + free(meta_buf); + return 0; + } + + if (fwrite(meta_buf, mda_size - 512, 1, fp) != 1) { + log_error("Failed to write file %s metadata area size %llu.", + tofile, (unsigned long long)mda_size); + ret = 0; + } + + free(meta_buf); + + if (fflush(fp)) + stack; + if (fclose(fp)) + stack; + return ret; +} + +/* all sizes and offsets in bytes */ + +static int _dump_current_text(struct device *dev, struct devicefile *def, + 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; + char *vgname = NULL; + uint32_t crc; + uint32_t seqno = 0; + int mn = mda_num; /* 1 or 2 */ + int ri = rlocn_index; /* 0 or 1 */ + int bad = 0; + + if (!(meta_buf = malloc(meta_size + 1))) { + 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; + } + + /* + * 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 (!_read_bytes(dev, def, 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); + free(meta_buf); + return 0; + } + + if (!_read_bytes(dev, def, 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); + free(meta_buf); + return 0; + } + } else { + if (!_read_bytes(dev, def, 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); + free(meta_buf); + return 0; + } + } + + meta_buf[meta_size] = 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_without_dup_node_check(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 { + if (cft->root && cft->root->key) + vgname = strdup(cft->root->key); + if (cft->root && cft->root->child) + dm_config_get_uint32(cft->root->child, "seqno", &seqno); + } + config_destroy(cft); + } + + if (print_fields || print_metadata) + log_print("metadata text at %llu crc 0x%x # vgname %s seqno %u", + (unsigned long long)(mda_offset + meta_offset), crc, + vgname ? vgname : "?", seqno); + + 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: + free(meta_buf); + free(vgname); + if (bad) + return 0; + return 1; +} + +/* all sizes and offsets in bytes */ + +static int _dump_label_and_pv_header(struct cmd_context *cmd, uint64_t labelsector, + struct device *dev, struct devicefile *def, + int print_fields, + int *found_label, + uint64_t *mda1_offset, uint64_t *mda1_size, + uint64_t *mda2_offset, uint64_t *mda2_size, + int *mda_count_out) +{ + char buf[512]; + char str[256]; + struct label_header *lh; + struct pv_header *pvh; + struct pv_header_extension *pvhe; + struct disk_locn *dlocn; + uint64_t lh_offset; /* bytes */ + uint64_t pvh_offset; /* bytes */ + uint64_t pvhe_offset; /* bytes */ + uint64_t dlocn_offset; /* bytes */ + int mda_count = 0; + int bad = 0; + int di; + + lh_offset = labelsector * 512; /* from start of disk */ + + if (!_read_bytes(dev, def, 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, found_label)) + 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 (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 (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); + + /* + * mda1 offset is page size from machine that created it, + * warn if it's not one of the expected page sizes. + */ + if ((*mda1_offset != 4096) && + (*mda1_offset != 8192) && + (*mda1_offset != 16384) && + (*mda1_offset != 65536)) { + log_print("WARNING: pv_header.disk_locn[%d].offset %llu is unexpected # for first mda", + di, (unsigned long long)*mda1_offset); + } + } 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++; + } + + *mda_count_out = 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 (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; +} + +/* + * all sizes and offsets in bytes + * + * 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, struct settings *set, + int print_fields, int print_metadata, int print_area, + const char *tofile, + struct device *dev, struct devicefile *def, + uint64_t mda_offset, uint64_t mda_size, + uint32_t *checksum0_ret, + int *found_header) +{ + char buf[512]; + char str[256]; + char *mda_buf; + struct mda_header *mh; + struct raw_locn *rlocn0, *rlocn1; + uint64_t rlocn0_offset, rlocn1_offset; + uint64_t meta_offset = 0; /* bytes */ + uint64_t meta_size = 0; /* bytes */ + uint32_t meta_checksum = 0; + int mda_num = (mda_offset <= 65536) ? 1 : 2; + int bad = 0; + + *checksum0_ret = 0; /* checksum from raw_locn[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 (!_read_bytes(dev, def, 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, found_header)) + bad++; + + if (print_area) { + if (!_dump_meta_area(dev, def, 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, def, 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, def, print_fields, rlocn1, 1, rlocn1_offset, mda_num, mda_offset, mda_size, + NULL, NULL, NULL)) + bad++; + + if (!meta_offset) + goto out; + + /* + * looking at the current copy of metadata referenced by raw_locn + */ + if (print_metadata <= PRINT_CURRENT) { + if (!_dump_current_text(dev, def, print_fields, print_metadata, tofile, mda_num, 0, mda_offset, mda_size, meta_offset, meta_size, meta_checksum)) + bad++; + } + + /* + * looking at all copies of the metadata in the area + */ + if (print_metadata == PRINT_ALL) { + if (!(mda_buf = zalloc(mda_size))) + goto_out; + + if (!_read_bytes(dev, def, mda_offset, mda_size, mda_buf)) { + log_print("CHECK: failed to read metadata area at offset %llu size %llu", + (unsigned long long)mda_offset, (unsigned long long)mda_size); + bad++; + free(mda_buf); + goto out; + } + + _dump_all_text(cmd, set, tofile, dev, def, mda_num, mda_offset, mda_size, mda_buf); + free(mda_buf); + } + + /* Should we also check text metadata if it exists in rlocn1? */ + out: + if (bad) + return 0; + return 1; +} + +/* all sizes and offsets in bytes */ + +static int _dump_headers(struct cmd_context *cmd, const char *dump, struct settings *set, + uint64_t labelsector, struct device *dev, struct devicefile *def) +{ + uint64_t mda1_offset = 0, mda1_size = 0, mda2_offset = 0, mda2_size = 0; /* bytes */ + uint32_t mda1_checksum, mda2_checksum; + int mda_count = 0; + int bad = 0; + + if (!_dump_label_and_pv_header(cmd, labelsector, dev, def, 1, NULL, + &mda1_offset, &mda1_size, &mda2_offset, &mda2_size, &mda_count)) + bad++; + + if (!mda_count) { + log_print("zero metadata copies"); + return 1; + } + + /* + * The first mda is usually 4096 bytes from the start of the device. + * (If created by a machine with larger pages it could be 8k/16k/64k.) + */ + if (!_dump_mda_header(cmd, set, 1, 0, 0, NULL, dev, def, mda1_offset, mda1_size, &mda1_checksum, NULL)) + bad++; + + if (mda2_offset) { + if (!_dump_mda_header(cmd, set, 1, 0, 0, NULL, dev, def, mda2_offset, mda2_size, &mda2_checksum, NULL)) + 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 (bad) { + log_error("Found bad header or metadata values."); + return 0; + } + return 1; +} + +/* all sizes and offsets in bytes */ + +static int _dump_metadata(struct cmd_context *cmd, const char *dump, struct settings *set, + uint64_t labelsector, struct device *dev, struct devicefile *def, + int print_metadata, int print_area) +{ + const char *tofile = NULL; + uint64_t mda1_offset = 0, mda1_size = 0, mda2_offset = 0, mda2_size = 0; /* bytes */ + uint32_t mda1_checksum, mda2_checksum; + int mda_count = 0; + int mda_num = 1; + int bad = 0; + + if (arg_is_set(cmd, file_ARG)) { + struct stat sb; + if (!(tofile = arg_str_value(cmd, file_ARG, NULL))) + return 0; + if (!stat(tofile, &sb)) { + log_error("File already exists."); + return 0; + } + } + + if (set->mda_num) + mda_num = set->mda_num; + else if (arg_is_set(cmd, pvmetadatacopies_ARG)) + mda_num = arg_int_value(cmd, pvmetadatacopies_ARG, 1); + + if (!_dump_label_and_pv_header(cmd, labelsector, dev, def, 0, NULL, + &mda1_offset, &mda1_size, &mda2_offset, &mda2_size, &mda_count)) + bad++; + + if (!mda_count) { + log_print("zero metadata copies"); + return 1; + } + + /* + * The first mda is always 4096 bytes from the start of the device. + */ + if (mda_num == 1) { + if (!_dump_mda_header(cmd, set, 0, print_metadata, print_area, tofile, dev, def, mda1_offset, mda1_size, &mda1_checksum, NULL)) + bad++; + } else if (mda_num == 2) { + if (!mda2_offset) { + log_print("CHECK: second mda not found"); + bad++; + } else { + if (!_dump_mda_header(cmd, set, 0, print_metadata, print_area, tofile, dev, def, mda2_offset, mda2_size, &mda2_checksum, NULL)) + bad++; + } + } + + if (bad) { + log_error("Found bad header or metadata values."); + return 0; + } + return 1; +} + +/* all sizes and offsets in bytes */ + +static int _dump_found(struct cmd_context *cmd, struct settings *set, uint64_t labelsector, struct device *dev) +{ + uint64_t mda1_offset = 0, mda1_size = 0, mda2_offset = 0, mda2_size = 0; /* bytes */ + uint32_t mda1_checksum = 0, mda2_checksum = 0; + int found_label = 0, found_header1 = 0, found_header2 = 0; + int mda_count = 0; + int bad = 0; + + if (!_dump_label_and_pv_header(cmd, labelsector, dev, NULL, 0, &found_label, + &mda1_offset, &mda1_size, &mda2_offset, &mda2_size, &mda_count)) + bad++; + + if (found_label && mda1_offset) { + if (!_dump_mda_header(cmd, set, 0, 0, 0, NULL, dev, NULL, mda1_offset, mda1_size, &mda1_checksum, &found_header1)) + bad++; + } + + if (found_label && mda2_offset) { + if (!_dump_mda_header(cmd, set, 0, 0, 0, NULL, dev, NULL, mda2_offset, mda2_size, &mda2_checksum, &found_header2)) + bad++; + } + + if (found_label) + log_print("Found label on %s, sector %llu, type=LVM2 001", + dev_name(dev), (unsigned long long)labelsector); + else { + log_error("Could not find LVM label on %s", dev_name(dev)); + return 0; + } + + if (found_header1) + log_print("Found text metadata area: offset=%llu, size=%llu", + (unsigned long long)mda1_offset, + (unsigned long long)mda1_size); + + if (found_header2) + log_print("Found text metadata area: offset=%llu, size=%llu", + (unsigned long long)mda2_offset, + (unsigned long long)mda2_size); + + if (bad) + return 0; + return 1; +} + +/* + * all sizes and offsets in bytes (except dev_sectors from dev_get_size) + * + * Look for metadata text in common locations, without using any headers + * (pv_header/mda_header) to find the location, since the headers may be + * zeroed/damaged. + */ + +static int _dump_search(struct cmd_context *cmd, const char *dump, struct settings *set, + uint64_t labelsector, struct device *dev, struct devicefile *def) +{ + const char *tofile = NULL; + char *buf; + uint64_t mda1_offset = 0, mda1_size = 0, mda2_offset = 0, mda2_size = 0; /* bytes */ + uint64_t mda_offset = 0, mda_size = 0; /* bytes */ + int mda_num = 0; + int found_label = 0; + int mda_count = 0; + int set_vals = 0; + + if (arg_is_set(cmd, file_ARG)) { + if (!(tofile = arg_str_value(cmd, file_ARG, NULL))) + return_0; + } + + _dump_label_and_pv_header(cmd, labelsector, dev, def, 0, &found_label, + &mda1_offset, &mda1_size, &mda2_offset, &mda2_size, &mda_count); + + /* + * For mda1, mda_offset is always 4096 bytes from the start of + * device, and mda_size is the space between mda_offset and + * the first PE which is usually at 1MB. + * + * For mda2, take the dev_size, reduce that to be a 1MB + * multiple. The mda_offset is then 1MB prior to that, + * and mda_size is the amount of space between that offset + * and the end of the device. + * + * The second mda is generally 4K larger (at least) than the + * first mda because the first mda begins at a 4K offset from + * the start of the device and ends on a 1MB boundary. + * The second mda begins on a 1MB boundary (no 4K offset like + * mda1), then goes to the end of the device. Extra space + * at the end of device (mod 1MB extra) can make mda2 even + * larger. + */ + + /* + * When mda offset or size is set in command line settings, + * use what is set and calculate an unspecified value. + */ + if (set->mda_offset_set || set->mda_size_set) { + if (set->mda_offset_set) { + mda_offset = set->mda_offset; + set_vals++; + } + if (set->mda_size_set) { + mda_size = set->mda_size; + set_vals++; + } + if ((mda_num = set->mda_num)) + set_vals++; + + if (mda_offset && mda_size) + goto search; + + if (set_vals < 2) { + log_error("Specify at least two values from: mda_num, mda_offset, mda_size."); + return 0; + } + + if (!mda_size) { + if ((mda_num == 1) && mda1_size) + mda_size = mda1_size; /* from headers */ + else if ((mda_num == 2) && mda2_size) + mda_size = mda2_size; /* from headers */ + else if (mda_num == 1) + mda_size = ONE_MB_IN_BYTES - 4096; + else if (mda_num == 2) + mda_size = mda2_size_from_offset(dev, mda_offset); + } + if (!mda_offset) { + if (mda_num == 1) + mda_offset = 4096; + else if (mda_num == 2) + mda_offset = mda2_offset_from_size(dev, mda_size); + } + + if (set->mda2_offset_set || set->mda2_size_set) + log_print("Ignoring mda2 values from settings."); + + goto search; + } + if (set->mda2_offset_set || set->mda2_size_set) { + if (set->mda2_offset_set) { + mda_offset = set->mda2_offset; + set_vals++; + } + if (set->mda_size_set) { + mda_size = set->mda2_size; + set_vals++; + } + if ((mda_num = set->mda_num)) + set_vals++; + + if (mda_offset && mda_size) + goto search; + + if (set_vals < 2) { + log_error("Specify at least two values from: mda_num, mda2_offset, mda2_size."); + return 0; + } + + if (mda_num == 1) { + log_error("Invalid mda_num=1 and mda2 settings."); + return 0; + } + + if (!mda_size) { + if (mda2_size) + mda_size = mda2_size; /* from headers */ + else + mda_size = mda2_size_from_offset(dev, mda_offset); + } + if (!mda_offset) { + if (mda2_offset) + mda_offset = mda2_offset; /* from headers */ + else + mda_offset = mda2_offset_from_size(dev, mda_size); + } + + goto search; + } + + /* + * When no mda offset or size is set in command line settings, + * the user can just set mda_num=1|2 to control if we pick defaults + * for mda1 or mda2. If unspecified, we search in the first mda. + */ + if (set->mda_num) + mda_num = set->mda_num; + else if (arg_is_set(cmd, pvmetadatacopies_ARG)) + mda_num = arg_int_value(cmd, pvmetadatacopies_ARG, 1); + else + mda_num = 1; + + /* + * No mda offset or size was set in command line settings, + * so we use what's in the headers or defaults. + */ + if ((mda_num == 1) && found_label && mda1_offset && mda1_size) { + /* use header values when available */ + mda_offset = mda1_offset; + mda_size = mda1_size; + + } else if (mda_num == 1) { + /* use default values when header values are not available */ + mda_offset = 4096; + mda_size = ONE_MB_IN_BYTES - 4096; + + log_print("Using common defaults for first mda: offset %llu size %llu.", + (unsigned long long)mda_offset, (unsigned long long)mda_size); + log_print("Override defaults with --settings \"mda_offset=<bytes> mda_size=<bytes>\""); + + } else if ((mda_num == 2) && found_label && mda2_offset && mda2_size) { + /* use header values when available */ + mda_offset = mda2_offset; + mda_size = mda2_size; + + } else if (mda_num == 2) { + /* use default values when header values are not available */ + uint64_t dev_sectors = 0; + uint64_t dev_bytes; + uint64_t extra_bytes; + + if (dev_get_size(dev, &dev_sectors)) + stack; + + dev_bytes = dev_sectors * 512; + extra_bytes = dev_bytes % ONE_MB_IN_BYTES; + + if (dev_bytes < (2 * ONE_MB_IN_BYTES)) + return_0; + + mda_offset = dev_bytes - extra_bytes - ONE_MB_IN_BYTES; + mda_size = dev_bytes - mda_offset; + + log_print("Using defaults for second mda: offset %llu size %llu.", + (unsigned long long)mda_offset, (unsigned long long)mda_size); + log_print("Override defaults with --settings \"mda_offset=<bytes> mda_size=<bytes>\""); + } else { + log_error("No mda location."); + return 0; + } + + search: + if ((mda_num == 1) && ((mda1_offset && mda1_offset != mda_offset) || (mda1_size && mda1_size != mda_size))) { + log_print("Ignoring mda1_offset %llu mda1_size %llu from pv_header.", + (unsigned long long)mda1_offset, + (unsigned long long)mda1_size); + } + + if ((mda_num == 2) && ((mda2_offset && mda2_offset != mda_offset) || (mda2_size && mda2_size != mda_size))) { + log_print("Ignoring mda2_offset %llu mda2_size %llu from pv_header.", + (unsigned long long)mda2_offset, + (unsigned long long)mda2_size); + } + + log_print("Searching for metadata at offset %llu size %llu", + (unsigned long long)mda_offset, (unsigned long long)mda_size); + + if (!(buf = zalloc(mda_size))) + return_0; + + if (!_read_bytes(dev, def, mda_offset, mda_size, buf)) { + log_print("CHECK: failed to read metadata area at offset %llu size %llu", + (unsigned long long)mda_offset, (unsigned long long)mda_size); + free(buf); + return 0; + } + + _dump_all_text(cmd, set, tofile, dev, def, mda_num, mda_offset, mda_size, buf); + + free(buf); + return 1; +} + +/* all sizes and offsets in bytes */ + +static int _get_one_setting(struct cmd_context *cmd, struct settings *set, char *key, char *val) +{ + if (!strncmp(key, "metadata_offset", strlen("metadata_offset"))) { + if (sscanf(val, "%llu", (unsigned long long *)&set->metadata_offset) != 1) + goto_bad; + set->metadata_offset_set = 1; + return 1; + } + + if (!strncmp(key, "seqno", strlen("seqno"))) { + if (sscanf(val, "%u", &set->seqno) != 1) + goto_bad; + set->seqno_set = 1; + return 1; + } + + if (!strncmp(key, "backup_file", strlen("backup_file"))) { + if ((set->backup_file = dm_pool_strdup(cmd->mem, val))) + return 1; + return 0; + } + + if (!strncmp(key, "mda_offset", strlen("mda_offset"))) { + if (sscanf(val, "%llu", (unsigned long long *)&set->mda_offset) != 1) + goto_bad; + set->mda_offset_set = 1; + return 1; + } + + if (!strncmp(key, "mda_size", strlen("mda_size"))) { + if (sscanf(val, "%llu", (unsigned long long *)&set->mda_size) != 1) + goto_bad; + set->mda_size_set = 1; + return 1; + } + + if (!strncmp(key, "mda2_offset", strlen("mda2_offset"))) { + if (sscanf(val, "%llu", (unsigned long long *)&set->mda2_offset) != 1) + goto_bad; + set->mda2_offset_set = 1; + return 1; + } + + if (!strncmp(key, "mda2_size", strlen("mda2_size"))) { + if (sscanf(val, "%llu", (unsigned long long *)&set->mda2_size) != 1) + goto_bad; + set->mda2_size_set = 1; + return 1; + } + + if (!strncmp(key, "device_size", strlen("device_size"))) { + if (sscanf(val, "%llu", (unsigned long long *)&set->device_size) != 1) + goto_bad; + set->device_size_set = 1; + return 1; + } + + if (!strncmp(key, "data_offset", strlen("data_offset"))) { + if (sscanf(val, "%llu", (unsigned long long *)&set->data_offset) != 1) + goto_bad; + set->data_offset_set = 1; + return 1; + } + + if (!strncmp(key, "pv_uuid", strlen("pv_uuid"))) { + if (strchr(val, '-') && (strlen(val) == 32)) { + memcpy(&set->pv_id, val, 32); + set->pvid_set = 1; + return 1; + } else if (id_read_format_try(&set->pv_id, val)) { + set->pvid_set = 1; + return 1; + } else { + log_error("Failed to parse UUID from pv_uuid setting."); + goto bad; + } + } + + if (!strncmp(key, "mda_num", strlen("mda_num"))) { + if (sscanf(val, "%u", (int *)&set->mda_num) != 1) + goto_bad; + return 1; + } +bad: + log_error("Invalid setting: %s", key); + return 0; +} + +static int _get_settings(struct cmd_context *cmd, struct settings *set) +{ + struct arg_value_group_list *group; + const char *str; + char key[64]; + char val[64]; + unsigned num; + unsigned pos; + + /* + * "grouped" means that multiple --settings options can be used. + * Each option is also allowed to contain multiple key = val pairs. + */ + + dm_list_iterate_items(group, &cmd->arg_value_groups) { + if (!grouped_arg_is_set(group->arg_values, settings_ARG)) + continue; + + if (!(str = grouped_arg_str_value(group->arg_values, settings_ARG, NULL))) + break; + + pos = 0; + + while (pos < strlen(str)) { + /* scan for "key1=val1 key2 = val2 key3= val3" */ + + memset(key, 0, sizeof(key)); + memset(val, 0, sizeof(val)); + + if (sscanf(str + pos, " %63[^=]=%63s %n", key, val, &num) != 2) { + log_error("Invalid setting at: %s", str+pos); + return 0; + } + + pos += num; + + if (!_get_one_setting(cmd, set, key, val)) + return_0; + } + } + + return 1; +} + +/* + * pvck --repairtype label_header + * + * Writes new label_header without changing pv_header fields. + * All constant values except for recalculated crc. + * + * all sizes and offsets in bytes + */ + +static int _repair_label_header(struct cmd_context *cmd, const char *repair, + struct settings *set, uint64_t labelsector, struct device *dev) +{ + char buf[512]; + struct label_header *lh; + struct pv_header *pvh; + uint64_t mda1_offset = 0, mda1_size = 0, mda2_offset = 0, mda2_size = 0; /* bytes */ + uint64_t lh_offset; /* bytes */ + uint64_t pvh_offset; /* bytes */ + uint32_t crc; + int mda_count; + int found_label = 0; + + lh_offset = labelsector * 512; /* from start of disk */ + + _dump_label_and_pv_header(cmd, labelsector, dev, NULL, 0, &found_label, + &mda1_offset, &mda1_size, &mda2_offset, &mda2_size, &mda_count); + + if (!found_label) { + log_warn("WARNING: No LVM label found on %s. It may not be an LVM device.", dev_name(dev)); + if (!arg_count(cmd, yes_ARG) && + yes_no_prompt("Write LVM header to device? ") == 'n') + return 0; + } + + if (!dev_read_bytes(dev, lh_offset, 512, buf)) { + log_error("Failed to read label_header at %llu", (unsigned long long)lh_offset); + return 0; + } + + lh = (struct label_header *)buf; + 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_error("Problem with pv_header offset calculation"); + return 0; + } + + memcpy(lh->id, LABEL_ID, sizeof(lh->id)); + memcpy(lh->type, LVM2_LABEL, sizeof(lh->type)); + lh->sector_xl = xlate64(labelsector); + lh->offset_xl = xlate32(32); + + crc = calc_crc(INITIAL_CRC, (uint8_t *)&lh->offset_xl, + LABEL_SIZE - ((uint8_t *) &lh->offset_xl - (uint8_t *) lh)); + + lh->crc_xl = xlate32(crc); + + log_print("Writing label_header.crc 0x%08x", crc); + + if (arg_is_set(cmd, test_ARG)) { + log_warn("Skip writing in test mode."); + return 1; + } + + if (!arg_count(cmd, yes_ARG) && + yes_no_prompt("Write new LVM header to %s? ", dev_name(dev)) == 'n') + return 0; + + if (!dev_write_bytes(dev, lh_offset, 512, buf)) { + log_error("Failed to write new header"); + return 0; + } + return 1; +} + +static int _get_pv_info_from_metadata(struct cmd_context *cmd, struct settings *set, + struct device *dev, + struct pv_header *pvh, int found_label, + char *text_buf, uint64_t text_size, + char *pvid, + uint64_t *device_size_sectors, + uint64_t *pe_start_sectors) +{ + char pvid_cur[ID_LEN + 1] = { 0 }; /* found in existing pv_header */ + char pvid_set[ID_LEN + 1] = { 0 }; /* set by user in --settings */ + char pvid_use[ID_LEN + 1] = { 0 }; /* the pvid chosen to use */ + int pvid_cur_valid = 0; /* pvid_cur is valid */ + int pvid_use_valid = 0; /* pvid_use is valid */ + struct dm_config_tree *cft = NULL; + struct volume_group *vg = NULL; + struct pv_list *pvl; + + /* + * Check if there's a valid existing PV UUID at the expected location. + */ + if (!id_read_format_try((struct id *)&pvid_cur, (char *)&pvh->pv_uuid)) + memset(&pvid_cur, 0, ID_LEN); + else { + memcpy(&pvid_use, &pvid_cur, ID_LEN); + pvid_use_valid = 1; + pvid_cur_valid = 1; + } + + if (set->pvid_set) { + memcpy(&pvid_set, &set->pv_id, ID_LEN); + memcpy(&pvid_use, &pvid_set, ID_LEN); + pvid_use_valid = 1; + } + + if (pvid_cur_valid && set->pvid_set && memcmp(&pvid_cur, &pvid_set, ID_LEN)) { + log_warn("WARNING: existing PV UUID %s does not match pv_uuid setting %s.", + pvid_cur, pvid_set); + + memcpy(&pvid_use, &pvid_set, ID_LEN); + pvid_use_valid = 1; + } + + if (!_text_buf_parse(text_buf, text_size, &cft)) { + log_error("Invalid metadata file."); + return 0; + } + + if (!(vg = vg_from_config_tree(cmd, cft))) { + config_destroy(cft); + log_error("Invalid metadata file."); + return 0; + } + + config_destroy(cft); + + /* + * If pvid_use is set, look for metadata PV section with matching PV UUID. + * Otherwise, look for metadata PV section with device name matching dev. + * + * pvid_use will be empty if there's no valid UUID in the existing + * pv_header, and the user did not specify a UUID in --settings. + * + * Choosing the PV UUID based only on a matching device name is somewhat + * weak since device names are dynamic, but we do scan devs to verify the + * chosen PV UUID is not in use elsewhere, which should avoid most of the + * risk of picking a wrong UUID. + */ + if (!pvid_use_valid) { + dm_list_iterate_items(pvl, &vg->pvs) { + if (!strcmp(pvl->pv->device_hint, dev_name(dev))) + goto copy_pv; + } + } else { + dm_list_iterate_items(pvl, &vg->pvs) { + if (!memcmp(&pvl->pv->id.uuid, &pvid_use, ID_LEN)) + goto copy_pv; + } + } + + release_vg(vg); + + /* + * Don't know what PV UUID to use, possibly: + * . the user set a PV UUID that does not exist in the metadata file + * . the UUID in the existing pv_header does not exist in the metadata file + * . the metadata has no PV with a device name hint matching this device + */ + if (set->pvid_set) + log_error("PV UUID %s not found in metadata file.", pvid_set); + else if (pvid_cur_valid) + log_error("PV UUID %s in existing pv_header not found in metadata file.", pvid_cur); + else if (!pvid_use_valid) + log_error("PV name %s not found in metadata file.", dev_name(dev)); + + log_error("No valid PV UUID, specify a PV UUID from metadata in --settings."); + return 0; + + copy_pv: + *device_size_sectors = pvl->pv->size; + *pe_start_sectors = pvl->pv->pe_start; + memcpy(pvid, &pvl->pv->id, ID_LEN); + + release_vg(vg); + return 1; +} + +/* + * Checking for mda1 is simple because it's always at the same location, + * and when a PV is set to use zero metadata areas, this space is just + * unused. We could look for any surviving metadata text in mda1 + * containing the VG UUID to confirm that this PV has been used for + * metadata, but if the start of the disk has been zeroed, then we + * may not find any. + */ +static int _check_for_mda1(struct cmd_context *cmd, struct device *dev) +{ + char buf[512]; + struct mda_header *mh; + + if (!dev_read_bytes(dev, 4096, 512, buf)) + return_0; + + mh = (struct mda_header *)buf; + + if (!memcmp(mh->magic, FMTT_MAGIC, sizeof(mh->magic))) + return 1; + return 0; +} + +/* + * Checking for mda2 is more complicated. Very often PVs will not use + * a second mda2, and the location is not quite as predictable. Also, + * if we mistakenly conclude that an mda2 belongs on the PV, we'd end + * up writing into the data area. + * + * all sizes and offsets in bytes + */ +static int _check_for_mda2(struct cmd_context *cmd, struct device *dev, + uint64_t device_size, struct metadata_file *mf, + uint64_t *mda2_offset, uint64_t *mda2_size) +{ + struct mda_header *mh; + char buf2[256]; + char *buf; + uint64_t mda_offset, mda_size, extra_bytes; /* bytes */ + unsigned i, found = 0; + + if (device_size < (2 * ONE_MB_IN_BYTES)) + return_0; + + extra_bytes = device_size % ONE_MB_IN_BYTES; + mda_offset = device_size - extra_bytes - ONE_MB_IN_BYTES; + mda_size = device_size - mda_offset; + + if (!(buf = malloc(mda_size))) + return_0; + + if (!dev_read_bytes(dev, mda_offset, mda_size, buf)) + goto fail; + + mh = (struct mda_header *)buf; + + /* + * To be certain this is really an mda_header before writing it, + * require that magic, version and start are all correct. + */ + + if (memcmp(mh->magic, FMTT_MAGIC, sizeof(mh->magic))) + goto fail; + + if (xlate32(mh->version) != FMTT_VERSION) { + log_print("Skipping mda2 (wrong mda_header.version)"); + goto fail; + } + + if (xlate64(mh->start) != mda_offset) { + log_print("Skipping mda2 (wrong mda_header.start)"); + goto fail; + } + + /* + * Search text area for an instance of current metadata before enabling + * mda2, in case this mda_header is from a previous generation PV and + * is not actually used by the current PV. An mda_header and metadata + * area from a previous PV (in a previous VG) that used mda2 might + * still exist, while the current PV does not use an mda2. + * + * Search for the vgid in the first 256 bytes at each 512 byte boundary + * in the first half of the metadata area. + */ + for (i = 0; i < (mda_size / 1024); i++) { + memcpy(buf2, buf + 512 + (i * 512), sizeof(buf2)); + + if (strstr(buf2, mf->vgid_str)) { + log_print("Found mda2 header at offset %llu size %llu", + (unsigned long long)mda_offset, (unsigned long long)mda_size); + *mda2_offset = mda_offset; + *mda2_size = mda_size; + found = 1; + break; + } + } + if (!found) { + log_print("Skipping mda2 (no matching VG UUID in metadata area)"); + goto fail; + } + + free(buf); + return 1; + + fail: + free(buf); + *mda2_offset = 0; + *mda2_size = 0; + return 0; +} + +/* + * pvck --repairtype pv_header --file input --settings + * + * Writes new pv_header and label_header. + * + * pv_header.pv_uuid + * If a uuid is given in --settings, that is used. + * Else if existing pv_header has a valid uuid, that is used. + * Else if the metadata file has a matching device name, that uuid is used. + * + * pv_header.device_size + * Use device size from metadata file. + * + * pv_header.disk_locn[0].offset (data area start) + * Use pe_start from metadata file. + * + * pv_header.disk_locn[2].offset/size (first metadata area) + * offset always 4096. size is pe_start - offset. + * + * pv_header.disk_locn[3].offset/size (second metadata area) + * Look for existing mda_header at expected offset, and if + * found use that value. Otherwise second mda is not used. + * + * The size/offset variables in sectors have a _sectors suffix, + * any other size/offset variables in bytes. + */ + +static int _repair_pv_header(struct cmd_context *cmd, const char *repair, + struct settings *set, struct metadata_file *mf, + uint64_t labelsector, struct device *dev) +{ + char head_buf[512]; + char pvid[ID_LEN + 1] __attribute__((aligned(8))) = { 0 }; + struct device *dev_with_pvid = NULL; + struct label_header *lh; + struct pv_header *pvh; + struct pv_header_extension *pvhe; + uint64_t mda1_offset = 0, mda1_size = 0, mda2_offset = 0, mda2_size = 0; /* bytes */ + uint64_t lh_offset; /* in bytes, from start of disk */ + uint64_t device_size = 0; /* in bytes, as stored in pv_header */ + uint64_t device_size_sectors = 0; /* in sectors, as stored in metadata */ + uint64_t get_size_sectors = 0; /* in sectors, as dev_get_size returns */ + uint64_t get_size = 0; /* in bytes */ + uint64_t pe_start_sectors; /* in sectors, as stored in metadata */ + uint64_t data_offset; /* in bytes, as stored in pv_header */ + uint32_t head_crc; + int mda_count = 0; + int found_label = 0; + int di; + + lh_offset = labelsector * 512; /* from start of disk */ + + if (!dev_get_size(dev, &get_size_sectors)) + log_warn("WARNING: Cannot get device size."); + get_size = get_size_sectors << SECTOR_SHIFT; + + _dump_label_and_pv_header(cmd, labelsector, dev, NULL, 0, &found_label, + &mda1_offset, &mda1_size, &mda2_offset, &mda2_size, &mda_count); + + /* + * The header sector may have been zeroed, or the user may have + * accidentally given the wrong device. + */ + if (!found_label) + log_warn("WARNING: No LVM label found on %s. It may not be an LVM device.", dev_name(dev)); + + /* + * The PV may have had no metadata areas, or one, or two. + * + * Try to avoid writing new metadata areas where they didn't exist + * before. Writing mda1 when it didn't exist previously would not be + * terrible since the space is unused anyway, but wrongly writing mda2 + * could end up in the data area. + * + * When the pv_header has no mda1 or mda2 locations, check for evidence + * of prior mda headers for mda1 and mda2. + * + * When the pv_header has an mda1 location and no mda2 location, just + * use mda1 and don't look for mda2 (unless requested by user setting) + * since it probably did not exist. (It's very unlikely that only the + * mda2 location was zeroed in the pv_header.) + */ + if (!mda_count && !mda1_offset && !mda2_offset) { + if (_check_for_mda1(cmd, dev)) + mda_count = 1; + + if (_check_for_mda2(cmd, dev, get_size, mf, &mda2_offset, &mda2_size)) + mda_count = 2; + } + + /* + * The PV may have had zero metadata areas (not common), or the + * pv_header and the mda1 header at 4096 may have been zeroed + * (more likely). Ask the user if metadata in mda1 should be + * included; it would usually be yes. To repair a PV and use + * zero metadata areas, require the user to specify + * --settings "mda_offset=0 mda_size=0". + * + * NOTE: mda1 is not written by repair pv_header, this will only + * include a pointer to mda1 in the pv_header so that a subsequent + * repair metadata will use that to write an mda_header and metadata. + */ + if (!mda_count && set->mda_offset_set && set->mda_size_set && + !set->mda_offset && !set->mda_size) { + log_warn("WARNING: PV will have no metadata with zero metadata areas."); + + } else if (!mda_count) { + log_warn("WARNING: no previous metadata areas found on device."); + + if (arg_count(cmd, yes_ARG) || + yes_no_prompt("Should a metadata area be included? ") == 'y') { + /* mda1_offset/mda1_size are set below */ + mda_count = 1; + } else { + log_error("To repair with zero metadata areas, use --settings \"mda_offset=0 mda_size=0\"."); + goto fail; + } + } + + /* + * The user has provided offset or size for mda2. This would + * usually be done when these values do not exist on disk, + * but if mda2 *is* found on disk, ensure it agrees with the + * user's setting. + */ + if (mda_count && (set->mda2_offset_set || set->mda2_size_set)) { + if (mda2_offset && (mda2_offset != set->mda2_offset)) { + log_error("mda2_offset setting %llu does not match mda2_offset found on disk %llu.", + (unsigned long long)set->mda2_offset, (unsigned long long)mda2_offset); + goto fail; + } + if (mda2_size && (mda2_size != set->mda2_size)) { + log_error("mda2_size setting %llu does not match mda2_size found on disk %llu.", + (unsigned long long)set->mda2_size, (unsigned long long)mda2_size); + goto fail; + } + mda2_offset = set->mda2_offset; + mda2_size = set->mda2_size; + mda_count = 2; + } + + /* + * The header sector is read into this buffer. + * This same buffer is modified and written back. + */ + if (!dev_read_bytes(dev, lh_offset, 512, head_buf)) { + log_error("Failed to read label_header at %llu", (unsigned long long)lh_offset); + goto fail; + } + + lh = (struct label_header *)head_buf; + pvh = (struct pv_header *)(head_buf + 32); + + /* + * Metadata file is not needed if user provides pvid/device_size/data_offset. + * All values in settings are in bytes. + */ + if (set->device_size_set && set->pvid_set && set->data_offset_set && !mf->filename) { + device_size = set->device_size; + pe_start_sectors = set->data_offset >> SECTOR_SHIFT; + memcpy(pvid, &set->pv_id, ID_LEN); + + if (get_size && (get_size != device_size)) { + log_warn("WARNING: device_size setting %llu bytes does not match device size %llu bytes.", + (unsigned long long)set->device_size, (unsigned long long)get_size); + } + goto scan; + } + + if (!mf->filename) { + log_error("Metadata input file is needed for pv_header info."); + log_error("See pvck --dump to locate and create a metadata file."); + goto fail; + } + + /* + * Look in the provided copy of VG metadata for info that determines + * pv_header fields. + * + * pv<N> { + * id = <uuid> + * device = <path> # device path hint, set when metadata was last written + * ... + * dev_size = <num> # in 512 sectors + * pe_start = <num> # in 512 sectors + * } + * + * Select the right pv entry by matching an existing pv uuid, or the + * current device name to the device path hint. Take the pv uuid, + * dev_size and pe_start from the metadata to use in the pv_header. + */ + if (!_get_pv_info_from_metadata(cmd, set, dev, pvh, found_label, + mf->text_buf, mf->text_size, pvid, + &device_size_sectors, &pe_start_sectors)) + goto fail; + + /* + * In pv_header, device_size is bytes, but in metadata dev_size is in sectors. + */ + device_size = device_size_sectors << SECTOR_SHIFT; + + scan: + /* + * Read all devs to verify the pvid that will be written does not exist + * on another device. + */ + if (!label_scan_for_pvid(cmd, pvid, &dev_with_pvid)) { + log_error("Failed to scan devices to check PV UUID."); + goto fail; + } + + if (dev_with_pvid && (dev_with_pvid != dev)) { + log_error("Cannot use PV UUID %s which exists on %s", pvid, dev_name(dev_with_pvid)); + goto fail; + } + + /* + * Set new label_header and pv_header fields. + */ + + /* set label_header (except crc) */ + memcpy(lh->id, LABEL_ID, sizeof(lh->id)); + memcpy(lh->type, LVM2_LABEL, sizeof(lh->type)); + lh->sector_xl = xlate64(labelsector); + lh->offset_xl = xlate32(32); + + /* set pv_header */ + memcpy(pvh->pv_uuid, &pvid, ID_LEN); + pvh->device_size_xl = xlate64(device_size); + + /* set data area location */ + data_offset = (pe_start_sectors << SECTOR_SHIFT); + pvh->disk_areas_xl[0].offset = xlate64(data_offset); + pvh->disk_areas_xl[0].size = 0; + + /* set end of data areas */ + pvh->disk_areas_xl[1].offset = 0; + pvh->disk_areas_xl[1].size = 0; + + di = 2; + + /* set first metadata area location */ + if (mda_count > 0) { + mda1_offset = 4096; + mda1_size = (pe_start_sectors << SECTOR_SHIFT) - 4096; + pvh->disk_areas_xl[di].offset = xlate64(mda1_offset); + pvh->disk_areas_xl[di].size = xlate64(mda1_size); + di++; + } + + /* set second metadata area location */ + if (mda_count > 1) { + pvh->disk_areas_xl[di].offset = xlate64(mda2_offset); + pvh->disk_areas_xl[di].size = xlate64(mda2_size); + di++; + } + + /* set end of metadata areas */ + pvh->disk_areas_xl[di].offset = 0; + pvh->disk_areas_xl[di].size = 0; + di++; + + /* set pv_header_extension */ + pvhe = (struct pv_header_extension *)((char *)pvh + sizeof(struct pv_header) + (di * sizeof(struct disk_locn))); + pvhe->version = xlate32(PV_HEADER_EXTENSION_VSN); + pvhe->flags = xlate32(PV_EXT_USED); + pvhe->bootloader_areas_xl[0].offset = 0; + pvhe->bootloader_areas_xl[0].size = 0; + + head_crc = calc_crc(INITIAL_CRC, (uint8_t *)&lh->offset_xl, + LABEL_SIZE - ((uint8_t *) &lh->offset_xl - (uint8_t *) lh)); + + /* set label_header crc (last) */ + lh->crc_xl = xlate32(head_crc); + + /* + * Write the updated header sector. + */ + + log_print("Writing label_header.crc 0x%08x pv_header uuid %s device_size %llu", + head_crc, pvid, (unsigned long long)device_size); + + log_print("Writing data_offset %llu mda1_offset %llu mda1_size %llu mda2_offset %llu mda2_size %llu", + (unsigned long long)data_offset, + (unsigned long long)mda1_offset, + (unsigned long long)mda1_size, + (unsigned long long)mda2_offset, + (unsigned long long)mda2_size); + + if (arg_is_set(cmd, test_ARG)) { + log_warn("Skip writing in test mode."); + return 1; + } + + if (!arg_count(cmd, yes_ARG) && + yes_no_prompt("Write new LVM header to %s? ", dev_name(dev)) == 'n') + goto fail; + + if (!dev_write_bytes(dev, lh_offset, 512, head_buf)) { + log_error("Failed to write new header"); + goto fail; + } + + return 1; +fail: + return 0; +} + +/* all sizes and offsets in bytes */ + +static int _update_mda(struct cmd_context *cmd, struct metadata_file *mf, struct device *dev, + int mda_num, uint64_t mda_offset, uint64_t mda_size) +{ + char buf[512]; + struct mda_header *mh; + struct raw_locn *rlocn0, *rlocn1; + uint64_t max_size; + uint64_t text_offset; + uint32_t crc; + + max_size = ((mda_size - 512) / 2) - 512; + if (mf->text_size > mda_size) { + log_error("Metadata text %llu too large for mda_size %llu max %llu", + (unsigned long long)mf->text_size, + (unsigned long long)mda_size, + (unsigned long long)max_size); + goto fail; + } + + if (!dev_read_bytes(dev, mda_offset, sizeof(buf), buf)) { + log_print("CHECK: failed to read mda_header_%d at %llu", + mda_num, (unsigned long long)mda_offset); + goto fail; + } + + text_offset = mda_offset + 512; + + mh = (struct mda_header *)buf; + memcpy(mh->magic, FMTT_MAGIC, sizeof(mh->magic)); + mh->version = xlate32(FMTT_VERSION); + mh->start = xlate64(mda_offset); + mh->size = xlate64(mda_size); + + rlocn0 = mh->raw_locns; + rlocn0->flags = 0; + rlocn0->offset = xlate64(512); /* text begins 512 from start of mda_header */ + rlocn0->size = xlate64(mf->text_size); + rlocn0->checksum = xlate32(mf->text_crc); + + rlocn1 = (struct raw_locn *)((char *)mh->raw_locns + 24); + rlocn1->flags = 0; + rlocn1->offset = 0; + rlocn1->size = 0; + rlocn1->checksum = 0; + + crc = calc_crc(INITIAL_CRC, (uint8_t *)mh->magic, + MDA_HEADER_SIZE - sizeof(mh->checksum_xl)); + mh->checksum_xl = xlate32(crc); + + log_print("Writing metadata at %llu length %llu crc 0x%08x mda%d", + (unsigned long long)(mda_offset + 512), + (unsigned long long)mf->text_size, mf->text_crc, mda_num); + + log_print("Writing mda_header at %llu mda%d", + (unsigned long long)mda_offset, mda_num); + + if (arg_is_set(cmd, test_ARG)) { + log_warn("Skip writing in test mode."); + return 1; + } + + if (!arg_count(cmd, yes_ARG) && + yes_no_prompt("Write new LVM metadata to %s? ", dev_name(dev)) == 'n') + goto fail; + + if (!dev_write_bytes(dev, text_offset, mf->text_size, mf->text_buf)) { + log_error("Failed to write new mda text"); + goto fail; + } + + if (!dev_write_bytes(dev, mda_offset, 512, buf)) { + log_error("Failed to write new mda header"); + goto fail; + } + + return 1; + fail: + return 0; +} + +/* + * pvck --repairtype metadata --file input --settings + * + * Writes new metadata into the text area and writes new + * mda_header for it. Requires valid mda locations in pv_header. + * Metadata is written immediately after mda_header. + * + * all sizes and offsets in bytes + */ + +static int _repair_metadata(struct cmd_context *cmd, const char *repair, + struct settings *set, struct metadata_file *mf, + uint64_t labelsector, struct device *dev) +{ + uint64_t mda1_offset = 0, mda1_size = 0, mda2_offset = 0, mda2_size = 0; /* bytes */ + int found_label = 0; + int mda_count = 0; + int mda_num; + int bad = 0; + + mda_num = set->mda_num; + + if (!mf->filename) { + log_error("Metadata input file is required."); + return 0; + } + + _dump_label_and_pv_header(cmd, labelsector, dev, NULL, 0, &found_label, + &mda1_offset, &mda1_size, &mda2_offset, &mda2_size, &mda_count); + + if (!found_label) { + log_error("No lvm label found on device."); + log_error("See --repairtype pv_header to repair headers."); + return 0; + } + + if (!mda_count && set->mda_offset_set && set->mda_size_set && + !set->mda_offset && !set->mda_size) { + log_print("No metadata areas on device to repair."); + return 1; + } + + if (!mda_count) { + log_error("No metadata areas found on device."); + log_error("See --repairtype pv_header to repair headers."); + return 0; + } + + if ((mda_num == 1) && !mda1_offset) { + log_error("No mda1 offset found."); + log_error("See --repairtype pv_header to repair headers."); + return 0; + } + + if ((mda_num == 2) && !mda2_offset) { + log_error("No mda2 offset found."); + log_error("See --repairtype pv_header to repair headers."); + return 0; + } + + if ((!mda_num || mda_num == 1) && mda1_offset) { + if (!_update_mda(cmd, mf, dev, 1, mda1_offset, mda1_size)) + bad++; + } + + if ((!mda_num || mda_num == 2) && mda2_offset) { + if (!_update_mda(cmd, mf, dev, 2, mda2_offset, mda2_size)) + bad++; + } + + if (bad) + return 0; + + return 1; +} + +static void _strip_backup_line(char *line1, int len1, char *line2, int *len2) +{ + int copying = 0; + int i, j = 0; + + for (i = 0; i < len1; i++) { + if (line1[i] == '\0') + break; + + if (line1[i] == '\n') + break; + + /* omit tabs at start of line */ + if (!copying && (line1[i] == '\t')) + continue; + + /* omit tabs and comment at end of line (can tabs occur without comment?) */ + if (copying && (line1[i] == '\t') && strchr(line1 + i, '#')) + break; + + copying = 1; + + line2[j++] = line1[i]; + } + + line2[j++] = '\n'; + *len2 = j; +} + +#define MAX_META_LINE 4096 + +/* all sizes and offsets in bytes */ + +static int _backup_file_to_raw_metadata(char *back_buf, uint64_t back_size, + char **text_buf_out, uint64_t *text_size_out) +{ + char line[MAX_META_LINE]; + char line2[MAX_META_LINE]; + char *p, *text_buf; + uint32_t text_pos, pre_len = 0, back_pos, text_max; + int len, len2, vgnamelen; + + text_max = back_size * 2; + + if (!(text_buf = zalloc(text_max))) + return_0; + + p = back_buf; + text_pos = 0; + back_pos = 0; + + while (1) { + if (back_pos >= back_size) + break; + + memset(line, 0, sizeof(line)); + len = 0; + + _copy_line(p, line, &len, sizeof(line)-1); + p += len; + back_pos += len; + + if (len < 3) + continue; + + if (_check_vgname_start(line, &vgnamelen)) { + /* vg name is first line of text_buf */ + memcpy(text_buf, line, len); + text_pos = len; + + pre_len = back_pos - len; + break; + } + } + + while (1) { + if (back_pos >= back_size) + break; + + memset(line, 0, sizeof(line)); + memset(line2, 0, sizeof(line2)); + len = 0; + len2 = 0; + + _copy_line(p, line, &len, sizeof(line)-1); + + if (line[0] == '\0') + break; + + p += len; + back_pos += len; + + /* shouldn't happen */ + if (text_pos + len > text_max) { + free(text_buf); + return_0; + } + + if (len == 1) { + text_buf[text_pos++] = '\n'; + continue; + } + + _strip_backup_line(line, len, line2, &len2); + + memcpy(text_buf + text_pos, line2, len2); + text_pos += len2; + } + + /* shouldn't happen */ + if (text_pos + pre_len + 3 > text_max) { + free(text_buf); + return_0; + } + + if (pre_len) { + /* copy first pre_len bytes of back_buf into text_buf */ + memcpy(text_buf + text_pos, back_buf, pre_len); + text_pos += pre_len; + } + + text_pos++; /* null termination */ + + *text_size_out = text_pos; + *text_buf_out = text_buf; + + return 1; +} + +static int _is_backup_file(struct cmd_context *cmd, char *text_buf, uint64_t text_size) +{ + if ((text_buf[0] == '#') && !strncmp(text_buf, "# Generated", 11)) + return 1; + return 0; +} + +/* all sizes and offsets in bytes */ + +static int _dump_backup_to_raw(struct cmd_context *cmd, struct settings *set) +{ + const char *input = set->backup_file; + const char *tofile = NULL; + struct stat sb; + char *back_buf, *text_buf; + uint64_t back_size, text_size; + int fd, rv, ret; + + if (arg_is_set(cmd, file_ARG)) { + if (!(tofile = arg_str_value(cmd, file_ARG, NULL))) + return_0; + } + + if (!input) { + log_error("Set backup file in --settings backup_file=path"); + return 0; + } + + if ((fd = open(input, O_RDONLY)) < 0) { + log_error("Cannot open file: %s", input); + return 0; + } + + if (fstat(fd, &sb)) { + log_error("Cannot access file: %s", input); + goto fail_close; + } + + if (!(back_size = (uint64_t)sb.st_size)) { + log_error("Empty file: %s", input); + goto fail_close; + } + + if (!(back_buf = zalloc(back_size))) + goto fail_close; + + rv = read(fd, back_buf, back_size); + if (rv != (int)back_size) { + log_error("Cannot read file: %s", input); + free(back_buf); + goto fail_close; + } + + if (close(fd)) + stack; + + if (!_is_backup_file(cmd, back_buf, back_size)) { + log_error("File does not appear to contain a metadata backup."); + free(back_buf); + return 0; + } + + if (!_backup_file_to_raw_metadata(back_buf, back_size, &text_buf, &text_size)) { + free(back_buf); + return_0; + } + + if (!tofile) { + log_print("---"); + printf("%s\n", text_buf); + log_print("---"); + } else { + FILE *fp; + if (!(fp = fopen(tofile, "wx"))) { + log_error("Failed to create file %s", tofile); + ret = 0; + goto out; + } + + fprintf(fp, "%s", text_buf); + + if (fflush(fp)) + stack; + if (fclose(fp)) + stack; + } + ret = 1; +out: + free(back_buf); + free(text_buf); + return ret; + +fail_close: + if (close(fd)) + stack; + return 0; +} + +/* all sizes and offsets in bytes */ + +static int _check_metadata_file(struct cmd_context *cmd, struct metadata_file *mf, + char *text_buf, int text_size) +{ + char *vgid; + int namelen; + + if (text_size < NAME_LEN+1) { + log_error("Invalid raw text metadata in file. File size is too small."); + return 0; + } + + /* + * Using pvck --dump metadata output redirected to file may be a common + * mistake, so check and warn about that specifically. + */ + if (isspace(text_buf[0]) && isspace(text_buf[1]) && strstr(text_buf, "---")) { + log_error("Invalid raw text metadata in file."); + log_error("(pvck stdout is not valid input, see pvck -f.)"); + return 0; + } + + /* + * Using a metadata backup file may be another common mistake. + */ + if ((text_buf[0] == '#') && !strncmp(text_buf, "# Generated", 11)) { + log_error("Invalid raw text metadata in file."); + log_error("(metadata backup file is not valid input.)"); + return 0; + } + + if (text_buf[text_size-1] != '\0' || + text_buf[text_size-2] != '\n' || + text_buf[text_size-3] != '\n') + log_warn("WARNING: unexpected final bytes of raw metadata, expected \\n\\n\\0."); + + if (_check_vgname_start(text_buf, &namelen)) { + if (!(vgid = strstr(text_buf, "id = "))) { + log_error("Invalid raw text metadata in file. (No VG UUID found.)"); + return 0; + } + memcpy(mf->vgid_str, vgid + 6, 38); + return 1; + } + + log_warn("WARNING: file data does not begin with a VG name and may be invalid."); + + if (!arg_count(cmd, yes_ARG) && + yes_no_prompt("Write input file data to disk?") == 'n') { + log_error("Invalid raw text metadata in file."); + return 0; + } + + return 1; +} + +/* all sizes and offsets in bytes */ + +static int _read_metadata_file(struct cmd_context *cmd, struct metadata_file *mf) +{ + struct stat sb; + char *text_buf; + uint64_t text_size; + uint32_t text_crc; + int fd, rv; + + if ((fd = open(mf->filename, O_RDONLY)) < 0) { + log_error("Cannot open file: %s", mf->filename); + return 0; + } + + if (fstat(fd, &sb)) { + log_error("Cannot access file: %s", mf->filename); + goto out; + } + + if (!(text_size = (uint64_t)sb.st_size)) { + log_error("Empty file: %s", mf->filename); + goto out; + } + + if (!(text_buf = malloc(text_size + 1))) + goto_out; + + rv = read(fd, text_buf, text_size); + if (rv != (int)text_size) { + log_error("Cannot read file: %s", mf->filename); + free(text_buf); + goto out; + } + text_buf[text_size++] = 0; /* null terminating byte */ + + if (close(fd)) + stack; + + if (_is_backup_file(cmd, text_buf, text_size)) { + char *back_buf = text_buf; + uint64_t back_size = text_size; + text_buf = NULL; + text_size = 0; + if (!_backup_file_to_raw_metadata(back_buf, back_size, &text_buf, &text_size)) { + free(back_buf); + return_0; + } + free(back_buf); + } + + if (!_check_metadata_file(cmd, mf, text_buf, text_size)) { + free(text_buf); + return_0; + } + + text_crc = calc_crc(INITIAL_CRC, (uint8_t *)text_buf, text_size); + + mf->text_size = text_size; + mf->text_buf = text_buf; + mf->text_crc = text_crc; + return 1; + +out: + if (close(fd)) + stack; + return 0; +} int pvck(struct cmd_context *cmd, int argc, char **argv) { + struct settings set; + struct metadata_file mf; + struct device *dev = NULL; + struct devicefile *def = NULL; + const char *dump, *repair; + const char *pv_name = ""; + uint64_t labelsector = 1; + int bad = 0; + int ret = 0; int i; - /* FIXME: validate cmdline options */ - /* FIXME: what does the cmdline look like? */ + memset(&set, 0, sizeof(set)); + memset(&mf, 0, sizeof(mf)); + + /* + * 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)); + + if (arg_is_set(cmd, repairtype_ARG) || arg_is_set(cmd, repair_ARG)) { + pv_name = argv[0]; + + if (!lock_global(cmd, "ex")) + return ECMD_FAILED; + + clear_hint_file(cmd); + + if (!setup_device(cmd, pv_name)) { + log_error("Failed to set up device %s.", pv_name); + return ECMD_FAILED; + } + + if (!(dev = dev_cache_get(cmd, pv_name, NULL))) { + log_error("Cannot use %s: %s.", pv_name, devname_error_reason(pv_name)); + return ECMD_FAILED; + } + } + + if ((dump = arg_str_value(cmd, dump_ARG, NULL))) { + struct stat sb; + + pv_name = argv[0]; + + if (stat(pv_name, &sb) < 0) { + log_error("Cannot access %s.", pv_name); + return ECMD_FAILED; + } + if (S_ISREG(sb.st_mode)) + def = get_devicefile(cmd, pv_name); + else if (S_ISBLK(sb.st_mode)) { + if (!setup_device(cmd, pv_name)) { + log_error("Failed to set up device %s.", pv_name); + return ECMD_FAILED; + } + dev = dev_cache_get(cmd, pv_name, NULL); + } + if (!dev && !def) { + log_error("Cannot use %s: %s.", pv_name, devname_error_reason(pv_name)); + return ECMD_FAILED; + } + } + + if (!_get_settings(cmd, &set)) + return_ECMD_FAILED; + + if (arg_is_set(cmd, file_ARG) && (arg_is_set(cmd, repairtype_ARG) || arg_is_set(cmd, repair_ARG))) { + if (!(mf.filename = arg_str_value(cmd, file_ARG, NULL))) + return_ECMD_FAILED; + + if (!_read_metadata_file(cmd, &mf)) + return_ECMD_FAILED; + } + + label_scan_setup_bcache(); + + if (dev) { + char buf[4096]; + + /* + * Check nodata filters including device_id filter, + * then clear the result so the full filter can be + * checked after reading the dev. + */ + cmd->filter_nodata_only = 1; + if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) { + log_error("Cannot use %s: %s.", pv_name, dev_filtered_reason(dev)); + return ECMD_FAILED; + } + cmd->filter_nodata_only = 0; + cmd->filter->wipe(cmd, cmd->filter, dev, NULL); + + /* + * This buf is not used, but bcache data is used for subsequent + * reads in the filters and by _read_bytes for other disk structs. + */ + if (!dev_read_bytes(dev, 0, 4096, buf)) { + log_error("Failed to read the first 4096 bytes of device %s.", dev_name(dev)); + return ECMD_FAILED; + } + + if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) { + log_error("Cannot use %s: %s.", pv_name, dev_filtered_reason(dev)); + return ECMD_FAILED; + } + } + + if (dump) { + cmd->use_hints = 0; + + if (!strcmp(dump, "metadata")) + ret = _dump_metadata(cmd, dump, &set, labelsector, dev, def, PRINT_CURRENT, 0); + + else if (!strcmp(dump, "metadata_all")) + ret = _dump_metadata(cmd, dump, &set, labelsector, dev, def, PRINT_ALL, 0); + + else if (!strcmp(dump, "metadata_area")) + ret = _dump_metadata(cmd, dump, &set, labelsector, dev, def, 0, 1); + + else if (!strcmp(dump, "metadata_search")) + ret = _dump_search(cmd, dump, &set, labelsector, dev, def); + + else if (!strcmp(dump, "headers")) + ret = _dump_headers(cmd, dump, &set, labelsector, dev, def); + + else if (!strcmp(dump, "backup_to_raw")) { + ret = _dump_backup_to_raw(cmd, &set); + + } else + log_error("Unknown dump value."); + + if (!ret) + return_ECMD_FAILED; + return ECMD_PROCESSED; + } + + if ((repair = arg_str_value(cmd, repairtype_ARG, NULL))) { + cmd->use_hints = 0; + + if (!strcmp(repair, "label_header")) + ret = _repair_label_header(cmd, repair, &set, labelsector, dev); + + else if (!strcmp(repair, "pv_header")) + ret = _repair_pv_header(cmd, repair, &set, &mf, labelsector, dev); + + else if (!strcmp(repair, "metadata")) + ret = _repair_metadata(cmd, repair, &set, &mf, labelsector, dev); + else + log_error("Unknown repair value."); + + if (!ret) + return_ECMD_FAILED; + return ECMD_PROCESSED; + } + + if (arg_is_set(cmd, repair_ARG)) { + cmd->use_hints = 0; + + /* repair is a combination of repairtype pv_header+metadata */ + + if (!_repair_pv_header(cmd, "pv_header", &set, &mf, labelsector, dev)) + return_ECMD_FAILED; + + if (!_repair_metadata(cmd, "metadata", &set, &mf, labelsector, dev)) + return_ECMD_FAILED; + + return ECMD_PROCESSED; + } + /* - * Use what's on the cmdline directly, and avoid calling into - * some of the other infrastructure functions, so as to avoid - * hitting some of the lvmcache behavior, scanning other devices, - * etc. + * The old/original form of pvck, which did not do much, + * but this is here to preserve the historical output. */ + + if (argc == 1) { + if (!setup_device(cmd, argv[0])) + return_ECMD_FAILED; + } else if (!setup_devices(cmd)) + return_ECMD_FAILED; + for (i = 0; i < argc; i++) { - /* FIXME: warning and/or check if in use? */ - log_verbose("Scanning %s", argv[i]); + pv_name = argv[i]; + + if (!(dev = dev_cache_get(cmd, argv[i], cmd->filter))) { + log_error("Cannot use %s: %s.", pv_name, devname_error_reason(pv_name)); + continue; + } - dm_unescape_colons_and_at_signs(argv[i], NULL, NULL); - pv_analyze(cmd, argv[i], - arg_uint64_value(cmd, labelsector_ARG, - UINT64_C(0))); + if (!_dump_found(cmd, &set, labelsector, dev)) + bad++; } + if (bad) + return_ECMD_FAILED; return ECMD_PROCESSED; } |