root/usr/src/cmd/mdb/common/modules/disk_label/disk_label.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright (c) 2019, Joyent, Inc.
 * Copyright 2024 MNX Cloud, Inc.
 */

/*
 * The on-disk elements here are all little-endian, and this code doesn't make
 * any attempt to adjust for running on a big-endian system.
 */

#include <sys/types.h>
#include <sys/crc32.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/dktp/fdisk.h>
#include <sys/efi_partition.h>
#include <sys/fs/pc_fs.h>
#include <sys/vtoc.h>

#include <assert.h>
#include <ctype.h>
#include <uuid/uuid.h>
#include <stdbool.h>

#include <mdb/mdb_modapi.h>
#include <mdb/mdb_debug.h>

#include "installboot.h"

#ifdef _BIG_ENDIAN
#error needs porting for big-endian system
#endif

/* See usr/src/grub/grub-0.97/stage1/stage1.h */
#define GRUB_VERSION_OFF (0x3e)
#define GRUB_COMPAT_VERSION_MAJOR 3
#define GRUB_COMPAT_VERSION_MINOR 2
#define GRUB_VERSION (2 << 8 | 3) /* 3.2 */

#define LOADER_VERSION (1)
#define LOADER_JOYENT_VERSION (2)

typedef enum {
        MBR_TYPE_UNKNOWN,
        MBR_TYPE_GRUB1,
        MBR_TYPE_LOADER,
        MBR_TYPE_LOADER_JOYENT,
} mbr_type_t;

typedef struct stringval {
        const char      *sv_text;
        int             sv_value;
} stringval_t;

stringval_t ptag_array[] = {
        { "unassigned",         V_UNASSIGNED    },
        { "boot",               V_BOOT          },
        { "root",               V_ROOT          },
        { "swap",               V_SWAP          },
        { "usr",                V_USR           },
        { "backup",             V_BACKUP        },
        { "stand",              V_STAND         },
        { "var",                V_VAR           },
        { "home",               V_HOME          },
        { "alternates",         V_ALTSCTR       },
        { "reserved",           V_RESERVED      },
        { "system",             V_SYSTEM        },
        { "BIOS_boot",          V_BIOS_BOOT     },
        { "FreeBSD boot",       V_FREEBSD_BOOT  },
        { "FreeBSD swap",       V_FREEBSD_SWAP  },
        { "FreeBSD UFS",        V_FREEBSD_UFS   },
        { "FreeBSD ZFS",        V_FREEBSD_ZFS   },
        { "FreeBSD NANDFS",     V_FREEBSD_NANDFS },

        { NULL }
};

stringval_t pflag_array[] = {
        { "wm", 0                       },
        { "wu", V_UNMNT                 },
        { "rm", V_RONLY                 },
        { "ru", V_RONLY | V_UNMNT       },
        { NULL }
};

size_t sector_size = SECTOR_SIZE;

static const char *
array_find_string(stringval_t *array, int match_value)
{
        for (; array->sv_text != NULL; array++) {
                if (array->sv_value == match_value) {
                        return (array->sv_text);
                }
        }

        return (NULL);
}

static int
array_widest_str(stringval_t *array)
{
        int     i;
        int     width;

        width = 0;
        for (; array->sv_text != NULL; array++) {
                if ((i = strlen(array->sv_text)) > width)
                        width = i;
        }

        return (width);
}

static void
print_fdisk_part(struct ipart *ip, size_t nr)
{
        char typestr[128];
        char begchs[128];
        char endchs[128];
        char *c = NULL;

        if (ip->systid == UNUSED) {
                mdb_printf("%-4llu %s:%#lx\n", nr, "UNUSED", ip->systid);
                return;
        }

        switch (ip->systid) {
        case DOSOS12: c = "DOSOS12"; break;
        case PCIXOS: c = "PCIXOS"; break;
        case DOSOS16: c = "DOSOS16"; break;
        case EXTDOS: c = "EXTDOS"; break;
        case DOSHUGE: c = "DOSHUGE"; break;
        case FDISK_IFS: c = "FDISK_IFS"; break;
        case FDISK_AIXBOOT: c = "FDISK_AIXBOOT"; break;
        case FDISK_AIXDATA: c = "FDISK_AIXDATA"; break;
        case FDISK_OS2BOOT: c = "FDISK_OS2BOOT"; break;
        case FDISK_WINDOWS: c = "FDISK_WINDOWS"; break;
        case FDISK_EXT_WIN: c = "FDISK_EXT_WIN"; break;
        case FDISK_FAT95: c = "FDISK_FAT95"; break;
        case FDISK_EXTLBA: c = "FDISK_EXTLBA"; break;
        case DIAGPART: c = "DIAGPART"; break;
        case FDISK_LINUX: c = "FDISK_LINUX"; break;
        case FDISK_LINUXDSWAP: c = "FDISK_LINUXDSWAP"; break;
        case FDISK_LINUXDNAT: c = "FDISK_LINUXDNAT"; break;
        case FDISK_CPM: c = "FDISK_CPM"; break;
        case DOSDATA: c = "DOSDATA"; break;
        case OTHEROS: c = "OTHEROS"; break;
        case UNIXOS: c = "UNIXOS"; break;
        case FDISK_NOVELL2: c = "FDISK_NOVELL2"; break;
        case FDISK_NOVELL3: c = "FDISK_NOVELL3"; break;
        case FDISK_QNX4: c = "FDISK_QNX4"; break;
        case FDISK_QNX42: c = "FDISK_QNX42"; break;
        case FDISK_QNX43: c = "FDISK_QNX43"; break;
        case SUNIXOS: c = "SUNIXOS"; break;
        case FDISK_LINUXNAT: c = "FDISK_LINUXNAT"; break;
        case FDISK_NTFSVOL1: c = "FDISK_NTFSVOL1"; break;
        case FDISK_NTFSVOL2: c = "FDISK_NTFSVOL2"; break;
        case FDISK_BSD: c = "FDISK_BSD"; break;
        case FDISK_NEXTSTEP: c = "FDISK_NEXTSTEP"; break;
        case FDISK_BSDIFS: c = "FDISK_BSDIFS"; break;
        case FDISK_BSDISWAP: c = "FDISK_BSDISWAP"; break;
        case X86BOOT: c = "X86BOOT"; break;
        case SUNIXOS2: c = "SUNIXOS2"; break;
        case EFI_PMBR: c = "EFI_PMBR"; break;
        case EFI_FS: c = "EFI_FS"; break;
        default: c = NULL; break;
        }

        if (c != NULL) {
                mdb_snprintf(typestr, sizeof (typestr), "%s:%#lx",
                    c, ip->systid);
        } else {
                mdb_snprintf(typestr, sizeof (typestr), "%#lx", ip->systid);
        }

        mdb_snprintf(begchs, sizeof (begchs), "%hu/%hu/%hu",
            (uint16_t)ip->begcyl | (uint16_t)(ip->begsect & ~0x3f) << 2,
            (uint16_t)ip->beghead, (uint16_t)ip->begsect & 0x3f);
        mdb_snprintf(endchs, sizeof (endchs), "%hu/%hu/%hu",
            (uint16_t)ip->endcyl | (uint16_t)(ip->endsect & ~0x3f) << 2,
            (uint16_t)ip->endhead, (uint16_t)ip->endsect & 0x3f);

        mdb_printf("%-4llu %-21s %#-7x %-11s %-11s %-10u %-9u\n",
            nr, typestr, ip->bootid, begchs, endchs, ip->relsect, ip->numsect);
}

/*
 * Based on pcfs driver and:
 * "Microsoft Extensible Firmware Initiative FAT32 File System Specification,
 *  FAT: General Overview of On-Disk Format"
 *
 * http://download.microsoft.com/download/1/6/1/
 * 161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc
 */
static void
show_bpb(char *bpb)
{
        bool            fat32 = false;
        uint32_t        reserved, rdirsec, spc;
        uint32_t        rec, fsisec, bkbootsec;
        uint32_t        totsec16, totsec32, totsec;
        uint32_t        fatsec16, fatsec32, fatsec;
        uint32_t        numfat, datasec;
        uint32_t        ncl;
        char            buf[12];

        mdb_printf("\n");
        mdb_printf("BPB JMP: ");
        if (VALID_JMPBOOT(bpb_jmpBoot(bpb))) {
                mdb_printf("valid");
        } else {
                mdb_printf("invalid");
        }
        mdb_printf("\n");

        mdb_printf("BPB OEMName: ");
        if (VALID_OEMNAME(bpb_OEMName(bpb))) {
                mdb_printf("valid");
                mdb_printf(" : %*s", 8, bpb_OEMName(bpb));
        } else {
                mdb_printf("invalid");
        }
        mdb_printf("\n");

        mdb_printf("BPB Bytes per Sector: ");
        if (VALID_SECSIZE(bpb_get_BytesPerSec(bpb))) {
                mdb_printf("valid");
        } else {
                mdb_printf("invalid");
        }
        mdb_printf(" : %hu", bpb_get_BytesPerSec(bpb));
        mdb_printf("\n");

        mdb_printf("BPB Sectors per Cluster: ");
        spc = bpb_get_SecPerClus(bpb);
        if (VALID_SPCL(spc)) {
                mdb_printf("valid");
        } else {
                mdb_printf("invalid");
        }
        mdb_printf(" : %u", spc);
        mdb_printf("\n");

        reserved = bpb_get_RsvdSecCnt(bpb);
        mdb_printf("BPB Reserved Sectors: ");
        if (VALID_RSVDSEC(reserved)) {
                mdb_printf("valid");
        } else {
                mdb_printf("invalid");
        }
        mdb_printf(" : %u", reserved);
        mdb_printf("\n");

        mdb_printf("BPB Number of FATs: ");
        numfat = bpb_get_NumFATs(bpb);
        if (VALID_NUMFATS(numfat)) {
                mdb_printf("valid");
        } else {
                mdb_printf("invalid");
        }
        mdb_printf(" : %u", numfat);
        mdb_printf("\n");

        rec = bpb_get_RootEntCnt(bpb);
        mdb_printf("BPB Root Entry Count: ");
        mdb_printf("%u", rec);
        mdb_printf("\n");

        totsec16 = bpb_get_TotSec16(bpb);
        mdb_printf("BPB Total Sectors 16: ");
        mdb_printf("%u", totsec16);
        mdb_printf("\n");

        mdb_printf("BPB Media Type: ");
        if (VALID_MEDIA(bpb_get_Media(bpb))) {
                mdb_printf("valid");
        } else {
                mdb_printf("invalid");
        }
        mdb_printf(" : 0x%02x", bpb_get_Media(bpb));
        mdb_printf("\n");

        fatsec16 = bpb_get_FatSz16(bpb);
        mdb_printf("BPB FAT Sectors 16: ");
        mdb_printf("%u", fatsec16);
        mdb_printf("\n");

        mdb_printf("BPB Sectors Per Track: ");
        mdb_printf("%u", bpb_get_SecPerTrk(bpb));
        mdb_printf("\n");

        mdb_printf("BPB Number of Heads: ");
        mdb_printf("%u", bpb_get_NumHeads(bpb));
        mdb_printf("\n");

        mdb_printf("BPB Hidden Sectors: ");
        mdb_printf("%u", bpb_get_HiddSec(bpb));
        mdb_printf("\n");

        totsec32 = bpb_get_TotSec32(bpb);
        mdb_printf("BPB Total Sectors 32: ");
        mdb_printf("%u", totsec32);
        mdb_printf("\n");

        mdb_printf("BPB Signature: ");
        if (VALID_BPBSIG(bpb_get_BPBSig(bpb))) {
                mdb_printf("valid");
        } else {
                mdb_printf("invalid");
        }
        mdb_printf("\n");
        /*
         * This does conclude the legacy BPB fields.
         * FAT12/FAT16 and FAT32 have overlapping fields,
         * and we need to determine the fat type to decide which
         * alternative we will print (if any).
         */
        fatsec32 = bpb_get_FatSz32(bpb);
        if (totsec16 == 0 && fatsec16 == 0) {
                totsec = totsec32;
                fatsec = fatsec32;
                fat32 = true;           /* there is no FAT12/FAT16 */
        } else {
                totsec = totsec16;
                fatsec = fatsec16;
        }
        if (totsec == 0 || fatsec == 0) {
                mdb_printf("There is no FAT file system\n");
                return;
        }
        if (!fat32) {
                mdb_printf("BPB Drive Number: ");
                mdb_printf("0x%02x", bpb_get_DrvNum16(bpb));
                mdb_printf("\n");

                mdb_printf("BPB Extended Boot Signature: ");
                mdb_printf("0x%02x", bpb_get_BootSig16(bpb));
                mdb_printf("\n");

                if (bpb_get_BootSig16(bpb) == 0x29) {
                        mdb_printf("BPB Volume ID: ");
                        mdb_printf("0x%04x", bpb_get_VolID16(bpb));
                        mdb_printf("\n");

                        mdb_printf("BPB Volume Label: ");
                        bcopy(bpb_VolLab16(bpb), buf, 11);
                        buf[11] = '\0';
                        mdb_printf("\"%s\"", buf);
                        mdb_printf("\n");

                        mdb_printf("BPB File System Type: ");
                        if (VALID_FSTYPSTR16(bpb_FilSysType16(bpb))) {
                                mdb_printf("valid");
                        } else {
                                mdb_printf("invalid");
                        }
                        bcopy(bpb_FilSysType16(bpb), buf, 8);
                        buf[8] = '\0';
                        mdb_printf(" : \"%s\"", buf);
                        mdb_printf("\n");
                }
        } else {
                mdb_printf("BPB FAT Sectors 32: ");
                mdb_printf("%u", fatsec32);
                mdb_printf("\n");

                mdb_printf("BPB Extended Flags 32: ");
                mdb_printf("0x%04x", bpb_get_ExtFlags32(bpb));
                mdb_printf("\n");

                mdb_printf("BPB FS Version: ");
                mdb_printf("0x%02x", bpb_get_FSVer32(bpb));
                mdb_printf("\n");

                mdb_printf("BPB Root Cluster: ");
                mdb_printf("%u", bpb_get_RootClus32(bpb));
                mdb_printf("\n");

                fsisec = bpb_get_FSInfo32(bpb);
                mdb_printf("BPB FS Info Sector: ");
                mdb_printf("%u", fsisec);
                mdb_printf("\n");

                bkbootsec = bpb_get_BkBootSec32(bpb);
                mdb_printf("BPB Backup Boot Sector: ");
                mdb_printf("%u", bkbootsec);
                mdb_printf("\n");

                mdb_printf("BPB Drive Number: ");
                mdb_printf("0x%02x", bpb_get_DrvNum32(bpb));
                mdb_printf("\n");

                mdb_printf("BPB Boot Signature: ");
                mdb_printf("0x%02x", bpb_get_BootSig32(bpb));
                mdb_printf("\n");

                if (bpb_get_BootSig32(bpb) == 0x29) {
                        mdb_printf("BPB Volume ID: ");
                        mdb_printf("0x%04x", bpb_get_VolID32(bpb));
                        mdb_printf("\n");

                        mdb_printf("BPB Volume Label: ");
                        bcopy(bpb_VolLab32(bpb), buf, 11);
                        buf[11] = '\0';
                        mdb_printf("\"%s\"", buf);
                        mdb_printf("\n");

                        mdb_printf("BPB File System Type: ");
                        if (VALID_FSTYPSTR32(bpb_FilSysType32(bpb))) {
                                mdb_printf("valid");
                        } else {
                                mdb_printf("invalid");
                        }
                        bcopy(bpb_FilSysType32(bpb), buf, 8);
                        buf[8] = '\0';
                        mdb_printf(" : \"%s\"", buf);
                        mdb_printf("\n");
                }
        }

        rdirsec = (rec * 32 + (sector_size - 1)) / sector_size;
        datasec = totsec - fatsec * numfat - rdirsec - reserved;
        ncl = datasec / spc;
        mdb_printf("count of clusters on the volume: %u\n", ncl);
        if (ncl < 4085)
                mdb_printf("Volume should be FAT12\n");
        else if (ncl < 65525)
                mdb_printf("Volume should be FAT16\n");
        else
                mdb_printf("Volume should be FAT32\n");
}

static mbr_type_t
mbr_info(struct mboot *mbr, uint_t bpb)
{
        mbr_type_t type = MBR_TYPE_UNKNOWN;

        if (*((uint16_t *)&mbr->bootinst[GRUB_VERSION_OFF]) == GRUB_VERSION) {
                type = MBR_TYPE_GRUB1;
        } else if (mbr->bootinst[STAGE1_MBR_VERSION] == LOADER_VERSION) {
                type = MBR_TYPE_LOADER;
        } else if (mbr->bootinst[STAGE1_MBR_VERSION] == LOADER_JOYENT_VERSION) {
                type = MBR_TYPE_LOADER_JOYENT;
        }

        switch (type) {
        case MBR_TYPE_UNKNOWN:
                mdb_printf("Format: unknown\n");
                break;
        case MBR_TYPE_GRUB1:
                mdb_printf("Format: grub1\n");
                break;
        case MBR_TYPE_LOADER:
                mdb_printf("Format: loader (illumos)\n");
                break;
        case MBR_TYPE_LOADER_JOYENT:
                mdb_printf("Format: loader (joyent)\n");
                break;
        }

        mdb_printf("Signature: 0x%hx (%s)\n", mbr->signature,
            mbr->signature == MBB_MAGIC ? "valid" : "invalid");

        mdb_printf("UniqueMBRDiskSignature: %#lx\n",
            *(uint32_t *)&mbr->bootinst[STAGE1_SIG]);

        if (type == MBR_TYPE_LOADER || type == MBR_TYPE_LOADER_JOYENT) {
                char uuid[UUID_PRINTABLE_STRING_LENGTH];

                mdb_printf("Loader STAGE1_STAGE2_LBA: %llu\n",
                    *(uint64_t *)&mbr->bootinst[STAGE1_STAGE2_LBA]);

                mdb_printf("Loader STAGE1_STAGE2_SIZE: %hu\n",
                    *(uint16_t *)&mbr->bootinst[STAGE1_STAGE2_SIZE]);

                uuid_unparse((uchar_t *)&mbr->bootinst[STAGE1_STAGE2_UUID],
                    uuid);

                mdb_printf("Loader STAGE1_STAGE2_UUID: %s\n", uuid);
        }

        if (bpb) {
                show_bpb(mbr->bootinst);
        }
        return (type);
}

static void
mbr_help(void)
{
        mdb_printf("Display a Master Boot Record.\n\n"
            "-b Show BIOS Parameter Block (BPB)\n");
}

static int
cmd_mbr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        struct mboot *mbr;
        mbr_type_t type;
        uint_t opt_b = FALSE;

        CTASSERT(sizeof (*mbr) == SECTOR_SIZE);

        if (mdb_getopts(argc, argv,
            'b', MDB_OPT_SETBITS, TRUE, &opt_b, NULL) != argc)
                return (DCMD_USAGE);

        if (!(flags & DCMD_ADDRSPEC))
                addr = 0;

        mbr = mdb_zalloc(sector_size, UM_SLEEP | UM_GC);

        if (mdb_vread(mbr, sector_size, addr) == -1) {
                mdb_warn("failed to read MBR");
                return (DCMD_ERR);
        }

        if (VALID_SECSIZE(bpb_get_BytesPerSec(mbr->bootinst))) {
                sector_size = bpb_get_BytesPerSec(mbr->bootinst);
        }
        type = mbr_info(mbr, opt_b);

        /* If the magic is wrong, stop here. */
        if (mbr->signature != MBB_MAGIC)
                return (DCMD_ERR);

        /* Also print volume boot record */
        switch (type) {
        case MBR_TYPE_LOADER:
        case MBR_TYPE_LOADER_JOYENT:
                if (*(uint16_t *)&mbr->bootinst[STAGE1_STAGE2_SIZE] == 1) {
                        struct mboot vbr;
                        uintptr_t vbrp;

                        vbrp = *(uint64_t *)&mbr->bootinst[STAGE1_STAGE2_LBA];
                        vbrp *= sector_size;
                        vbrp += addr;
                        if (mdb_vread(&vbr, sizeof (vbr), vbrp) == -1) {
                                mdb_warn("failed to read VBR");
                        } else {
                                mdb_printf("\nSTAGE1 in VBR:\n");
                                (void) mbr_info(&vbr, opt_b);
                        }
                }
                break;
        default:
                break;
        }

        if (*(uint16_t *)&mbr->bootinst[STAGE1_STAGE2_SIZE] == 1) {
                /*
                 * This is MBR, display partition information.
                 */
                mdb_printf(
                    "\n%<u>%-4s %-21s %-7s %-11s %-11s %-10s %-9s%</u>\n",
                    "PART", "TYPE", "ACTIVE", "STARTCHS", "ENDCHS",
                    "SECTOR", "NUMSECT");

                for (size_t i = 0; i < FD_NUMPART; i++) {
                        struct ipart *ip = (struct ipart *)
                            (mbr->parts + (sizeof (struct ipart) * i));
                        print_fdisk_part(ip, i);
                }
        }

        return (DCMD_OK);
}

static unsigned int crc32_tab[] = { CRC32_TABLE };

static unsigned int
efi_crc32(const unsigned char *s, unsigned int len)
{
        unsigned int crc32val;

        CRC32(crc32val, s, len, -1U, crc32_tab);

        return (crc32val ^ -1U);
}

typedef struct {
        struct uuid eg_uuid;
        const char *eg_name;
} efi_guid_t;

static efi_guid_t efi_guids[] = {
        { EFI_UNUSED, "EFI_UNUSED" },
        { EFI_RESV1, "EFI_RESV1" },
        { EFI_BOOT, "EFI_BOOT" },
        { EFI_ROOT, "EFI_ROOT" },
        { EFI_SWAP, "EFI_SWAP" },
        { EFI_USR, "EFI_USR" },
        { EFI_BACKUP, "EFI_BACKUP" },
        { EFI_RESV2, "EFI_RESV2" },
        { EFI_VAR, "EFI_VAR" },
        { EFI_HOME, "EFI_HOME" },
        { EFI_ALTSCTR, "EFI_ALTSCTR" },
        { EFI_RESERVED, "EFI_RESERVED" },
        { EFI_SYSTEM, "EFI_SYSTEM" },
        { EFI_LEGACY_MBR, "EFI_LEGACY_MBR" },
        { EFI_SYMC_PUB, "EFI_SYMC_PUB" },
        { EFI_SYMC_CDS, "EFI_SYMC_CDS" },
        { EFI_MSFT_RESV, "EFI_MSFT_RESV" },
        { EFI_DELL_BASIC, "EFI_DELL_BASIC" },
        { EFI_DELL_RAID, "EFI_DELL_RAID" },
        { EFI_DELL_SWAP, "EFI_DELL_SWAP" },
        { EFI_DELL_LVM, "EFI_DELL_LVM" },
        { EFI_DELL_RESV, "EFI_DELL_RESV" },
        { EFI_AAPL_BOOT, "EFI_AAPL_BOOT" },
        { EFI_AAPL_HFS, "EFI_AAPL_HFS" },
        { EFI_AAPL_UFS, "EFI_AAPL_UFS" },
        { EFI_AAPL_ZFS, "EFI_AAPL_ZFS" },
        { EFI_AAPL_APFS, "EFI_AAPL_APFS" },
        { EFI_FREEBSD_BOOT, "EFI_FREEBSD_BOOT" },
        { EFI_FREEBSD_NANDFS, "EFI_FREEBSD_NANDFS" },
        { EFI_FREEBSD_SWAP, "EFI_FREEBSD_SWAP" },
        { EFI_FREEBSD_UFS, "EFI_FREEBSD_UFS" },
        { EFI_FREEBSD_VINUM, "EFI_FREEBSD_VINUM" },
        { EFI_FREEBSD_ZFS, "EFI_FREEBSD_ZFS" },
        { EFI_BIOS_BOOT, "EFI_BIOS_BOOT" },
};

static void
print_gpe(efi_gpe_t *gpe, size_t nr, int show_guid)
{
        const char *type = "unknown";

        for (size_t i = 0; i < ARRAY_SIZE(efi_guids); i++) {
                if (memcmp((void *)&efi_guids[i].eg_uuid,
                    (void *)&gpe->efi_gpe_PartitionTypeGUID,
                    sizeof (efi_guids[i].eg_uuid)) == 0) {
                        type = efi_guids[i].eg_name;
                        break;
                }
        }

        if (strcmp(type, "EFI_UNUSED") == 0) {
                mdb_printf("%-4u %-19s\n", nr, type);
                return;
        }

        if (show_guid) {
                char guid[UUID_PRINTABLE_STRING_LENGTH];

                uuid_unparse((uchar_t *)&gpe->efi_gpe_UniquePartitionGUID,
                    guid);

                mdb_printf("%-4u %-19s %s\n", nr, type, guid);
        } else {
                char name[EFI_PART_NAME_LEN + 1] = "";

                /*
                 * Hopefully, ASCII is sufficient for any naming we care about.
                 */
                for (size_t i = 0; i < sizeof (name); i++) {
                        ushort_t wchar = gpe->efi_gpe_PartitionName[i];

                        name[i] = (char)(isascii(wchar) ? wchar : '?');
                }

                mdb_printf("%-4u %-19s %-13llu %-13llu %#-8llx %s\n",
                    nr, type, gpe->efi_gpe_StartingLBA, gpe->efi_gpe_EndingLBA,
                    gpe->efi_gpe_Attributes, name);
        }
}

static int
cmd_gpt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv __unused)
{
        char uuid[UUID_PRINTABLE_STRING_LENGTH];
        int show_alternate = B_FALSE;
        int show_guid = B_FALSE;
        efi_gpt_t *altheader;
        size_t table_size;
        efi_gpt_t *header;
        efi_gpe_t *gpet;
        uint_t orig_crc;
        uint_t crc;

        if (mdb_getopts(argc, argv,
            'a', MDB_OPT_SETBITS, TRUE, &show_alternate,
            'g', MDB_OPT_SETBITS, TRUE, &show_guid,
            NULL) != argc)
                return (DCMD_USAGE);

        /* Primary header is at LBA 1. */
        if (!(flags & DCMD_ADDRSPEC))
                addr = sector_size;

        header = mdb_zalloc(sector_size, UM_SLEEP | UM_GC);
        if (mdb_vread(header, sector_size, addr) == -1) {
                mdb_warn("failed to read GPT header");
                return (DCMD_ERR);
        }

        if (show_alternate) {
                addr = header->efi_gpt_AlternateLBA * sector_size;

                if (mdb_vread(header, sector_size, addr) == -1) {
                        mdb_warn("failed to read GPT header");
                        return (DCMD_ERR);
                }
        }

        mdb_printf("Signature: %s (%s)\n", (char *)&header->efi_gpt_Signature,
            strncmp((char *)&header->efi_gpt_Signature, "EFI PART", 8) == 0 ?
            "valid" : "invalid");

        mdb_printf("Revision: %hu.%hu\n", header->efi_gpt_Revision >> 16,
            header->efi_gpt_Revision);

        mdb_printf("HeaderSize: %u bytes\n", header->efi_gpt_HeaderSize);

        if (header->efi_gpt_HeaderSize > SECTOR_SIZE) {
                mdb_warn("invalid header size: skipping CRC\n");
        } else {
                orig_crc = header->efi_gpt_HeaderCRC32;

                header->efi_gpt_HeaderCRC32 = 0;

                crc = efi_crc32((unsigned char *)header,
                    header->efi_gpt_HeaderSize);

                mdb_printf("HeaderCRC32: %#x (should be %#x)\n", orig_crc, crc);
        }

        mdb_printf("Reserved1: %#x (should be 0x0)\n",
            header->efi_gpt_Reserved1);

        mdb_printf("MyLBA: %llu (should be %llu)\n",
            header->efi_gpt_MyLBA, addr / sector_size);

        mdb_printf("AlternateLBA: %llu\n", header->efi_gpt_AlternateLBA);
        mdb_printf("FirstUsableLBA: %llu\n", header->efi_gpt_FirstUsableLBA);
        mdb_printf("LastUsableLBA: %llu\n", header->efi_gpt_LastUsableLBA);

        if (header->efi_gpt_MyLBA >= header->efi_gpt_FirstUsableLBA &&
            header->efi_gpt_MyLBA <= header->efi_gpt_LastUsableLBA) {
                mdb_warn("MyLBA is within usable LBA range\n");
        }

        if (header->efi_gpt_AlternateLBA >= header->efi_gpt_FirstUsableLBA &&
            header->efi_gpt_AlternateLBA <= header->efi_gpt_LastUsableLBA) {
                mdb_warn("AlternateLBA is within usable LBA range\n");
        }

        altheader = mdb_zalloc(sector_size, UM_SLEEP | UM_GC);
        if (mdb_vread(altheader, sector_size,
            header->efi_gpt_AlternateLBA * sector_size) == -1) {
                mdb_warn("failed to read alternate GPT header");
        } else {
                if (strncmp((char *)&altheader->efi_gpt_Signature,
                    "EFI PART", 8) != 0) {
                        mdb_warn("found invalid alternate GPT header with "
                            "Signature: %s\n",
                            (char *)&altheader->efi_gpt_Signature);
                }

                if (altheader->efi_gpt_MyLBA != header->efi_gpt_AlternateLBA) {
                        mdb_warn("alternate GPT header at offset %#llx has "
                            "invalid MyLBA %llu\n",
                            header->efi_gpt_AlternateLBA * sector_size,
                            altheader->efi_gpt_MyLBA);
                }

                if (altheader->efi_gpt_AlternateLBA != header->efi_gpt_MyLBA) {
                        mdb_warn("alternate GPT header at offset %#llx has "
                            "invalid AlternateLBA %llu\n",
                            header->efi_gpt_AlternateLBA * sector_size,
                            altheader->efi_gpt_AlternateLBA);
                }

                /*
                 * We could go ahead and verify all the alternate checksums,
                 * etc. here too...
                 */
        }

        uuid_unparse((uchar_t *)&header->efi_gpt_DiskGUID, uuid);
        mdb_printf("DiskGUID: %s\n", uuid);

        mdb_printf("PartitionEntryLBA: %llu\n",
            header->efi_gpt_PartitionEntryLBA);

        mdb_printf("NumberOfPartitionEntries: %u\n",
            header->efi_gpt_NumberOfPartitionEntries);

        /*
         * While the spec allows a different size, in practice the table
         * is always packed.
         */
        if (header->efi_gpt_SizeOfPartitionEntry != sizeof (efi_gpe_t)) {
                mdb_warn("SizeOfPartitionEntry: %#x bytes "
                    "(expected %#x bytes)\n",
                    header->efi_gpt_SizeOfPartitionEntry, sizeof (efi_gpe_t));
                return (DCMD_ERR);
        }

        mdb_printf("SizeOfPartitionEntry: %#x bytes\n",
            header->efi_gpt_SizeOfPartitionEntry);

        table_size = header->efi_gpt_SizeOfPartitionEntry *
            header->efi_gpt_NumberOfPartitionEntries;

        /*
         * While this is a minimum reservation, it serves us ably as a
         * maximum value to reasonably expect.
         */
        if (table_size > EFI_MIN_ARRAY_SIZE) {
                mdb_warn("Skipping GPT array of %#lx bytes.\n", table_size);
                return (DCMD_ERR);
        }

        table_size = P2ROUNDUP(table_size, sector_size);
        gpet = mdb_alloc(table_size, UM_SLEEP | UM_GC);

        if (mdb_vread(gpet, table_size,
            header->efi_gpt_PartitionEntryLBA * sector_size) == -1) {
                mdb_warn("couldn't read GPT array");
                return (DCMD_ERR);
        }

        crc = efi_crc32((unsigned char *)gpet,
            header->efi_gpt_SizeOfPartitionEntry *
            header->efi_gpt_NumberOfPartitionEntries);

        mdb_printf("PartitionEntryArrayCRC32: %#x (should be %#x)\n",
            header->efi_gpt_PartitionEntryArrayCRC32, crc);

        if (show_guid) {
                mdb_printf("\n%<u>%-4s %-19s %-37s%</u>\n",
                    "PART", "TYPE", "GUID");
        } else {
                mdb_printf("\n%<u>%-4s %-19s %-13s %-13s %-8s %s%</u>\n",
                    "PART", "TYPE", "STARTLBA", "ENDLBA", "ATTR", "NAME");
        }

        for (size_t i = 0; i < header->efi_gpt_NumberOfPartitionEntries; i++)
                print_gpe(&gpet[i], i, show_guid);

        return (DCMD_OK);
}

static void
gpt_help(void)
{
        mdb_printf("Display an EFI GUID Partition Table.\n\n"
            "-a Display the alternate GPT\n"
            "-g Show unique GUID for each table entry\n");
}

static int
cmd_vtoc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        uint8_t *buf;
        struct dk_label *dl;
        struct dk_vtoc *dv;
        uintptr_t vaddr;
        int i, tag_width, cyl_width;
        int show_absolute = B_TRUE;
        int show_sectors = B_TRUE;
        uint32_t cyl;

        if (mdb_getopts(argc, argv,
            'c', MDB_OPT_CLRBITS, TRUE, &show_sectors,
            'r', MDB_OPT_CLRBITS, TRUE, &show_absolute,
            NULL) != argc)
                return (DCMD_USAGE);

        if (!(flags & DCMD_ADDRSPEC))
                addr = 0;
        else
                addr *= sector_size;

        buf = mdb_zalloc(sector_size, UM_SLEEP | UM_GC);

#if defined(_SUNOS_VTOC_16)
        if (mdb_vread(buf, sector_size, addr) == -1) {
                mdb_warn("failed to read VBR");
                return (DCMD_ERR);
        }

        mdb_printf("VBR info:\n");
        (void) mbr_info((struct mboot *)buf, FALSE);
#endif

        vaddr = addr + DK_LABEL_LOC * sector_size;

        if (mdb_vread(buf, sector_size, vaddr) == -1) {
                mdb_warn("failed to read VTOC");
                return (DCMD_ERR);
        }

        dl = (struct dk_label *)buf;
        dv = (struct dk_vtoc *)&dl->dkl_vtoc;

        mdb_printf("Label magic: 0x%hx (%s)\n", dl->dkl_magic,
            dl->dkl_magic == DKL_MAGIC ? "valid" : "invalid");
        if (dl->dkl_magic != DKL_MAGIC)
                return (DCMD_ERR);
        mdb_printf("Label %s sane\n", dv->v_sanity == VTOC_SANE ?
            "is" : "is not");

        mdb_printf("Label version: %#x\n", dv->v_version);
        mdb_printf("Volume name = <%s>\n", dv->v_volume);
        mdb_printf("ASCII name  = <%s>\n", dv->v_asciilabel);
        mdb_printf("pcyl        = %4d\n", dl->dkl_pcyl);
        mdb_printf("ncyl        = %4d\n", dl->dkl_ncyl);
        mdb_printf("acyl        = %4d\n", dl->dkl_acyl);

#if defined(_SUNOS_VTOC_16)
        mdb_printf("bcyl        = %4d\n", dl->dkl_bcyl);
#endif /* defined(_SUNOS_VTOC_16) */

        mdb_printf("nhead       = %4d\n", dl->dkl_nhead);
        mdb_printf("nsect       = %4d\n", dl->dkl_nsect);


        if (!show_absolute)
                addr = 0;
        cyl = dl->dkl_nhead * dl->dkl_nsect;
        if (show_sectors)
                cyl = 1;
        else
                addr /= (cyl * sector_size);

        tag_width = array_widest_str(ptag_array);

        cyl_width = sizeof ("CYLINDERS");
        for (i = 0; i < dv->v_nparts; i++) {
                uint32_t start, end, size;
                int w;

#if defined(_SUNOS_VTOC_16)
                start = addr + (dv->v_part[i].p_start / cyl);
                size = dv->v_part[i].p_size;
#elif defined(_SUNOS_VTOC_8)
                start = dl->dkl_map[i].dkl_cylno;
                start *= dl->dkl_nhead * dl->dkl_nsect; /* compute bytes */
                start /= cyl;
                start += addr;
                size = dl->dkl_map[i].dkl_nblk;
#else
#error "No VTOC format defined."
#endif
                if (size == 0)
                        end = start = 0;
                else
                        end = start + size / cyl - 1;

                w = mdb_snprintf(NULL, 0, "%u - %u", start, end);
                if (w > cyl_width)
                        cyl_width = w;
        }

        if (show_sectors == B_TRUE) {
                mdb_printf("\n%<u>%-4s %-*s %-7s %-11s %-11s %-*s "
                    "%-10s%</u>\n", "PART", tag_width, "TAG", "FLAG",
                    "STARTLBA", "ENDLBA", MDB_NICENUM_BUFLEN, "SIZE", "BLOCKS");
        } else {
                mdb_printf("\n%<u>%-4s %-*s %-7s %-*s %-*s %-10s%</u>\n",
                    "PART", tag_width, "TAG", "FLAG", cyl_width, "CYLINDERS",
                    MDB_NICENUM_BUFLEN, "SIZE", "BLOCKS");
        }

        for (i = 0; i < dv->v_nparts; i++) {
                uint16_t tag, flag;
                uint32_t start, end, size;
                const char *stag, *sflag;
                char nnum[MDB_NICENUM_BUFLEN];

#if defined(_SUNOS_VTOC_16)
                tag = dv->v_part[i].p_tag;
                flag = dv->v_part[i].p_flag;
                start = addr + (dv->v_part[i].p_start / cyl);
                size = dv->v_part[i].p_size;
#elif defined(_SUNOS_VTOC_8)
                tag = dv->v_part[i].p_tag;
                flag = dv->v_part[i].p_flag;
                start = dl->dkl_map[i].dkl_cylno;
                start *= dl->dkl_nhead * dl->dkl_nsect; /* compute bytes */
                start /= cyl;
                start += addr;
                size = dl->dkl_map[i].dkl_nblk;
#else
#error "No VTOC format defined."
#endif
                if (size == 0)
                        end = start = 0;
                else
                        end = start + size / cyl - 1;

                stag = array_find_string(ptag_array, tag);
                if (stag == NULL)
                        stag = "?";
                sflag = array_find_string(pflag_array, flag);
                if (sflag == NULL)
                        sflag = "?";

                mdb_printf("%-4d %-*s %-7s ", i, tag_width, stag, sflag);
                mdb_nicenum(size * sector_size, nnum);
                if (show_sectors) {
                        mdb_printf("%-11u %-11u %-*s %-10u\n", start, end,
                            MDB_NICENUM_BUFLEN, nnum, size);
                } else {
                        char cyls[10 * 2 + 4];

                        if (size == 0) {
                                mdb_snprintf(cyls, sizeof (cyls), "%-*u",
                                    cyl_width, size);
                        } else {
                                mdb_snprintf(cyls, sizeof (cyls), "%u - %u",
                                    start, end);
                        }
                        mdb_printf("%-*s %-*s %-10u\n", cyl_width, cyls,
                            MDB_NICENUM_BUFLEN, nnum, size);
                }
        }

        return (DCMD_OK);
}

static void
vtoc_help(void)
{
        mdb_printf("Display a Virtual Table of Content (VTOC).\n\n"
            "-r Display relative addresses\n"
            "-c Use cylinder based addressing\n");
        mdb_printf("\nThe addr is in %u-byte disk blocks.\n", sector_size);
}

static int
cmd_sect(uintptr_t addr __unused, uint_t flags __unused, int argc,
    const mdb_arg_t *argv)
{
        uint64_t size = SECTOR_SIZE;

        if (argc < 1) {
                mdb_printf("Current sector size is %u (%#x)\n", sector_size,
                    sector_size);
                return (DCMD_OK);
        }

        if (argc != 1)
                return (DCMD_USAGE);

        switch (argv[0].a_type) {
        case MDB_TYPE_STRING:
                size = mdb_strtoull(argv[0].a_un.a_str);
                break;
        case MDB_TYPE_IMMEDIATE:
                size = argv[0].a_un.a_val;
                break;
        default:
                return (DCMD_USAGE);
        }

        if (!ISP2(size)) {
                mdb_printf("sector size must be power of 2\n");
                return (DCMD_USAGE);
        }
        sector_size = size;
        return (DCMD_OK);
}

static void
sect_help(void)
{
        mdb_printf("Show or set sector size.\n");
}

static const mdb_dcmd_t dcmds[] = {
        { "mbr", "?[-b]", "dump Master Boot Record information", cmd_mbr,
            mbr_help },
        { "gpt", "?[-ag]", "dump an EFI GPT", cmd_gpt, gpt_help },
        { "vtoc", "?[-cr]", "dump VTOC information", cmd_vtoc, vtoc_help },
        { "sectorsize", NULL, "set or show sector size", cmd_sect, sect_help },
        { NULL }
};

static const mdb_modinfo_t modinfo = {
        MDB_API_VERSION, dcmds, NULL
};

const mdb_modinfo_t *
_mdb_init(void)
{
        return (&modinfo);
}