From f3bcd94524bafc6418c73d777ae1aa341ea4140c Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Thu, 7 Dec 2023 11:22:45 +0100 Subject: [PATCH] zdump: Add zgetdump -d support for ECKD ldipl-dump Make 'zgetdump -d' to identify ldipl-dump tool (ngdump) installed on DASD volume just like it does for NVMe ngdump. Output sample: ------------- Dump device info: Dump tool.........: Next Generation (NGDump) dump tool Version...........: 1 Architecture......: s390x (64 bit) Partition info: Partition number..: 2 Signed-off-by: Mikhail Zaslonko Reviewed-by: Alexander Egorenkov Signed-off-by: Steffen Eiden --- include/lib/vtoc.h | 1 + zdump/Makefile | 5 +- zdump/dt_ngdump.c | 4 +- zdump/ngdump.c | 304 ++++++++++++++++++++++++++++++++++++++------- zdump/ngdump.h | 6 +- 5 files changed, 267 insertions(+), 53 deletions(-) diff --git a/include/lib/vtoc.h b/include/lib/vtoc.h index 971fdaa8..35f1bf38 100644 --- a/include/lib/vtoc.h +++ b/include/lib/vtoc.h @@ -40,6 +40,7 @@ #define LV_COMPAT_CYL 0xFFFE #define VTOC_ERROR "VTOC error:" +#define MAX_VTOC_ENTRIES 9 /* max number of VTOC labels for cdl formatted DASD */ typedef struct ttr { diff --git a/zdump/Makefile b/zdump/Makefile index 37059a9d..015656c8 100644 --- a/zdump/Makefile +++ b/zdump/Makefile @@ -105,7 +105,10 @@ ifneq ("$(HAVE_FUSE)","0") OBJECTS += zfuse.o endif -libs = $(rootdir)/libutil/libutil.a $(LIBPV) +libs = $(rootdir)/libutil/libutil.a \ + $(rootdir)/libvtoc/libvtoc.a \ + $(rootdir)/libdasd/libdasd.a \ + $(LIBPV) all: $(BUILD_TARGETS) diff --git a/zdump/dt_ngdump.c b/zdump/dt_ngdump.c index a1fbe2a1..408ab183 100644 --- a/zdump/dt_ngdump.c +++ b/zdump/dt_ngdump.c @@ -27,11 +27,9 @@ static int dt_ngdump_init(void) char *part_path = NULL; int rc; - l.part_num = ngdump_get_dump_part(g.fh); + l.part_num = ngdump_get_dump_part(g.fh, &part_path); if (l.part_num <= 0) return -1; - if (ngdump_get_disk_part_path(g.fh->path, l.part_num, &part_path) < 0) - return -1; rc = ngdump_read_meta_from_device(part_path, &l.meta); free(part_path); if (rc) diff --git a/zdump/ngdump.c b/zdump/ngdump.c index 9788439d..e79542e8 100644 --- a/zdump/ngdump.c +++ b/zdump/ngdump.c @@ -18,9 +18,11 @@ #include #include +#include "lib/dasd_base.h" #include "lib/util_libc.h" #include "lib/util_part.h" #include "lib/util_log.h" +#include "lib/vtoc.h" #include "boot/boot_defs.h" #include "boot/linux_layout.h" @@ -30,6 +32,16 @@ #define NGDUMP_META_VERSION 1 #define NGDUMP_META_FILENAME "ngdump.meta" +enum ngdump_disk_type { + NG_TYPE_DASD, + NG_TYPE_NVME, +}; + +static const char *const ngtype2str[] = { + [NG_TYPE_DASD] = "DASD", + [NG_TYPE_NVME] = "NVME" +}; + static int read_meta_from_file(const char *filename, struct ngdump_meta *meta) { FILE *fp = NULL; @@ -212,56 +224,71 @@ out: } /* - * This function parses the bootloader program table stored on the given device - * and returns the partition index where the dumper's kernel image is stored. + * Convert disk blockpointer to the offset in blocks. + * Use eckd blockpointer format if hd_geometry is provided, otherwise linear blockpointer. + * Return u64(-1) in case the blockpointer contains zeroes. */ -int ngdump_get_dump_part(struct zg_fh *zg_fh) +static uint64_t blockptr2blk(union disk_blockptr *ptr, const struct hd_geometry *geo) +{ + uint64_t blk; + + /* For NVMe or SCSI use linear blockpointer format. */ + /* For DASD use eckd blockpointer format. */ + if (!geo) { + blk = ptr->linear.blockno; + if (blk == 0) + return U64_MAX; + } else { + if (ptr->eckd.sec == 0) + return U64_MAX; + blk = ptr->eckd.cyl * geo->heads + ptr->eckd.head; /* Track number */ + blk *= geo->sectors; /* Track offset in records */ + blk += ptr->eckd.sec - 1; /* Record offset (skipping R0) */ + } + return blk; +} + +/* + * Based on the provided program table blockpointer find kernel image Boot Map Section + * for dump Boot Map Script. Read the first data blockpointer from the section into + * blockptr area. + */ +static int get_bootmap_dump_image_blkptr(struct zg_fh *zg_fh, union disk_blockptr *program_table, + struct hd_geometry *geo, int blk_size, + union disk_blockptr *blockptr) { - int i, blk_size, max_entries, part_ext; struct component_entry comp_entry; struct component_header comp_hdr; - struct linear_blockptr blockptr; - struct scsi_mbr mbr; - uint64_t off; - - if (zg_ioctl(zg_fh, BLKSSZGET, &blk_size, "BLKSSZGET", ZG_CHECK_NONE)) - return -1; - - util_log_print(UTIL_LOG_TRACE, "%s: Block size %d\n", - __func__, blk_size); - - /* Read Master Boot Record (MBR) and check its magic */ - zg_read(zg_fh, &mbr, sizeof(mbr), ZG_CHECK); - if (memcmp(mbr.magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE)) - return -1; + int i, max_entries; + uint64_t blk; /* Read Boot Map Table and check its magic */ - off = mbr.program_table_pointer.blockno * blk_size; + blk = blockptr2blk(program_table, geo); + if (blk == U64_MAX) + return -1; util_log_print(UTIL_LOG_TRACE, "%s: Reading program table at offset 0x%016lx\n", - __func__, off); - zg_seek(zg_fh, off, ZG_CHECK); - zg_read(zg_fh, &blockptr, sizeof(blockptr), ZG_CHECK); - if (memcmp((const char*)&blockptr, ZIPL_MAGIC, ZIPL_MAGIC_SIZE)) + __func__, blk * blk_size); + zg_seek(zg_fh, blk * blk_size, ZG_CHECK); + zg_read(zg_fh, blockptr, sizeof(*blockptr), ZG_CHECK); + if (memcmp((const char *)blockptr, ZIPL_MAGIC, ZIPL_MAGIC_SIZE)) return -1; - /* Read Boot Map Script Pointer 0 */ - zg_read(zg_fh, &blockptr, sizeof(blockptr), ZG_CHECK); - if (blockptr.blockno == 0) + zg_read(zg_fh, blockptr, sizeof(*blockptr), ZG_CHECK); + blk = blockptr2blk(blockptr, geo); + if (blk == U64_MAX) return -1; /* Read 1st Boot Map Script, check its magic and type */ - off = blockptr.blockno * blk_size; util_log_print(UTIL_LOG_TRACE, "%s: Reading component header at offset 0x%016lx\n", - __func__, off); - zg_seek(zg_fh, off, ZG_CHECK_ERR); + __func__, blk * blk_size); + zg_seek(zg_fh, blk * blk_size, ZG_CHECK_ERR); zg_read(zg_fh, &comp_hdr, sizeof(comp_hdr), ZG_CHECK_ERR); if (memcmp(comp_hdr.magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE)) return -1; /* We want only dump script */ if (comp_hdr.type != COMPONENT_HEADER_DUMP) return -1; - /* Find kernel's Boot Map Section Pointer */ max_entries = (blk_size - sizeof(comp_hdr)) / sizeof(comp_entry); for (i = 0; i < max_entries; i++) { @@ -282,27 +309,178 @@ int ngdump_get_dump_part(struct zg_fh *zg_fh) } /* Read 1st Boot Map Data Pointer in kernel's Boot Map Section */ - off = ((struct linear_blockptr *)comp_entry.data)->blockno * blk_size; + blk = blockptr2blk((union disk_blockptr *)comp_entry.data, geo); + if (blk == U64_MAX) + return -1; util_log_print(UTIL_LOG_TRACE, "%s: Reading component block pointer at offset 0x%016lx\n", - __func__, off); - zg_seek(zg_fh, off, ZG_CHECK_ERR); - zg_read(zg_fh, &blockptr, sizeof(blockptr), ZG_CHECK); - util_log_print(UTIL_LOG_TRACE, - "%s: Component block address 0x%016lx block count %d\n", - __func__, blockptr.blockno, blockptr.blockct); + __func__, blk * blk_size); + zg_seek(zg_fh, blk * blk_size, ZG_CHECK_ERR); + zg_read(zg_fh, blockptr, sizeof(*blockptr), ZG_CHECK); - return util_part_search_fh(zg_fh->fh, blockptr.blockno, - blockptr.blockct, blk_size, &part_ext); + return 0; } -int ngdump_get_disk_part_path(const char *disk_path, int part_num, - char **part_path) +/* + * This function parses the bootloader program table stored on the nvme device + * and returns the partition index where the dumper's kernel image is stored. + */ +static int ngdump_get_nvme_part_num(struct zg_fh *zg_fh) +{ + union disk_blockptr dump_image_blkptr; + int blk_size, part_num, part_ext; + struct linear_blockptr *blockptr; + struct scsi_mbr mbr; + + if (zg_ioctl(zg_fh, BLKSSZGET, &blk_size, "BLKSSZGET", ZG_CHECK_NONE)) + return -1; + util_log_print(UTIL_LOG_TRACE, "%s: Block size %d\n", + __func__, blk_size); + /* Read Master Boot Record (MBR) and check its magic */ + zg_read(zg_fh, &mbr, sizeof(mbr), ZG_CHECK); + if (memcmp(mbr.magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE)) + return -1; + blockptr = &mbr.program_table_pointer; + /* + * Cast linear_blockptr to disk_blockptr before passing it to the function. + * Since no hd_geometry provided, it will be treated as linear_blockptr later on. + */ + if (get_bootmap_dump_image_blkptr(zg_fh, (union disk_blockptr *)blockptr, + NULL, blk_size, &dump_image_blkptr)) + return -1; + blockptr = &dump_image_blkptr.linear; + util_log_print(UTIL_LOG_TRACE, + "%s: Component block address 0x%016lx block count %d\n", + __func__, blockptr->blockno, + blockptr->blockct); + if (blockptr->blockno == 0) + return -1; + part_num = util_part_search_fh(zg_fh->fh, blockptr->blockno, blockptr->blockct, + blk_size, &part_ext); + return part_num; +} + +/* + * This function scans a VTOC record of cdl formatted DASD to identify a partition + * the specified blockno (0-indexed) belongs to. + */ +static int find_vol1_cdl_part_fh(struct zg_fh *zg_fh, uint64_t blockno, int blk_size, + cchhb_t *vtoc, struct hd_geometry *geo) +{ + unsigned int part_count, i; + struct format1_label f1; + uint64_t blk; + + blk = cchhb2blk(vtoc, geo); + if (!blk) + return -1; + /* Get actual offset in blocks (special record zero accounted) */ + blk--; + part_count = 0; + for (i = 0; i < MAX_VTOC_ENTRIES; i++) { + zg_seek(zg_fh, blk * blk_size, ZG_CHECK); + zg_read(zg_fh, &f1, sizeof(f1), ZG_CHECK_ERR); + /* Skip FMT4 / FMT5 / FMT7 / FMT9 labels */ + if (f1.DS1FMTID == 0xf4 || + f1.DS1FMTID == 0xf5 || + f1.DS1FMTID == 0xf7 || + f1.DS1FMTID == 0xf9) { + blk++; + continue; + } + /* only FMT1 and FMT8 labels valid at this point */ + if (f1.DS1FMTID != 0xf1 && + f1.DS1FMTID != 0xf8) + break; + /* OK, we got valid partition data. Check for partition boundaries */ + if (blockno >= cchh2blk(&f1.DS1EXT1.llimit, geo) && + blockno < cchh2blk(&f1.DS1EXT1.ulimit, geo) + geo->sectors) { + return part_count + 1; + } + part_count++; + blk++; + } + /* No matching partition found */ + return -1; +} + +/* + * This function parses the bootloader program table stored on the eckd device + * and returns the partition index where the dumper's kernel image is stored. + */ +static int ngdump_get_eckd_part_num(struct zg_fh *zg_fh) +{ + union disk_blockptr dump_image_blkptr; + struct eckd_blockptr *blockptr; + struct eckd_boot_record br; + struct vol_label_cdl vl; + struct hd_geometry geo; + uint64_t blk, off; + cchhb_t *vtoc; + int blk_size; + + if (zg_ioctl(zg_fh, BLKSSZGET, &blk_size, "BLKSSZGET", ZG_CHECK_NONE)) + return -1; + util_log_print(UTIL_LOG_TRACE, "%s: Block size %d\n", + __func__, blk_size); + /* Obtain DASD geometry */ + if (dasd_get_geo(zg_fh->path, &geo) != 0) + return -1; + util_log_print(UTIL_LOG_TRACE, + "%s: DASD geometry: cyl=%d, heads=%d, sect=%d\n", + __func__, geo.cylinders, geo.heads, geo.sectors); + /* Read a volume label from CDL-formatted DASD */ + off = 2 * blk_size; + util_log_print(UTIL_LOG_TRACE, + "%s: Reading a volume label at offset 0x%016lx\n", + __func__, off); + zg_seek(zg_fh, off, ZG_CHECK_ERR); + zg_read(zg_fh, &vl, sizeof(vl), ZG_CHECK); + /* Verify that we have a VOL1 label */ + if (!is_vol1(vl.vollbl)) + return -1; + /* Read Master Boot Record and check its magic */ + blk = cchhb2blk(&vl.br, &geo); + if (blk == 0) + return -1; + off = (blk - 1) * blk_size; + util_log_print(UTIL_LOG_TRACE, + "%s: Reading Master Boot Record at offset 0x%016lx\n", + __func__, off); + zg_seek(zg_fh, off, ZG_CHECK_ERR); + zg_read(zg_fh, &br, sizeof(br), ZG_CHECK); + if (memcmp(br.magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE)) + return -1; + + blockptr = (struct eckd_blockptr *)&br.program_table_pointer; + /* + * Cast eckd_blockptr to disk_blockptr before passing it to the function. + * With hd_geometry provided, it will be treated as eckd_blockptr later on. + */ + if (get_bootmap_dump_image_blkptr(zg_fh, (union disk_blockptr *)blockptr, + &geo, blk_size, &dump_image_blkptr)) + return -1; + blk = blockptr2blk(&dump_image_blkptr, &geo); + if (blk == U64_MAX) + return -1; + util_log_print(UTIL_LOG_TRACE, + "%s: Segment0 block number 0x%016lx\n", + __func__, blk); + vtoc = &((volume_label_t *)&vl)->vtoc; + return find_vol1_cdl_part_fh(zg_fh, blk, blk_size, vtoc, &geo); +} + +/* + * This function composes the absolute partition device node name + * based on the device name, device type and the partition number. + */ +static int ngdump_get_part_path(const char *disk_path, int part_num, + enum ngdump_disk_type ng_type, char **part_path) { char *real_path; - util_log_print(UTIL_LOG_TRACE, "%s: Disk path %s\n", - __func__, disk_path); + util_log_print(UTIL_LOG_TRACE, "%s: Disk path %s; disk type: %s\n", + __func__, disk_path, ngtype2str[ng_type]); real_path = util_malloc(PATH_MAX); @@ -315,7 +493,18 @@ int ngdump_get_disk_part_path(const char *disk_path, int part_num, __func__, real_path); *part_path = NULL; - util_asprintf(part_path, "%sp%d", real_path, part_num); + switch (ng_type) { + case NG_TYPE_DASD: + util_asprintf(part_path, "%s%d", real_path, part_num); + break; + case NG_TYPE_NVME: + util_asprintf(part_path, "%sp%d", real_path, part_num); + break; + default: /* Unknown type, bail out */ + free(real_path); + return -1; + } + free(real_path); util_log_print(UTIL_LOG_TRACE, "%s: Disk partition path %s\n", @@ -323,3 +512,30 @@ int ngdump_get_disk_part_path(const char *disk_path, int part_num, return 0; } + +/* + * This function checks for the ngdump device type in order to parse the + * bootloader program table and identify the partition where the dumper's + * kernel image is stored. + * part_path is set to the absolute partition device node name and + * the partition number is returned (or -1 when no partition found). + */ +int ngdump_get_dump_part(struct zg_fh *zg_fh, char **part_path) +{ + dasd_information2_t dasd_info; + enum ngdump_disk_type ng_type; + int part_num; + + if (dasd_get_info(zg_fh->path, &dasd_info) == 0) { + ng_type = NG_TYPE_DASD; + part_num = ngdump_get_eckd_part_num(zg_fh); + } else { + ng_type = NG_TYPE_NVME; + part_num = ngdump_get_nvme_part_num(zg_fh); + } + if (part_num <= 0 || + ngdump_get_part_path(zg_fh->path, part_num, ng_type, part_path) < 0) + return -1; + + return part_num; +} diff --git a/zdump/ngdump.h b/zdump/ngdump.h index 2b6ac8b3..0a36e9f6 100644 --- a/zdump/ngdump.h +++ b/zdump/ngdump.h @@ -21,10 +21,6 @@ struct ngdump_meta { }; int ngdump_read_meta_from_device(const char *device, struct ngdump_meta *meta); - -int ngdump_get_dump_part(struct zg_fh *zg_fh); - -int ngdump_get_disk_part_path(const char *disk_path, int part_num, - char **part_path); +int ngdump_get_dump_part(struct zg_fh *zg_fh, char **part_path); #endif /* ZGETDUMP_NGDUMP_H */