/* $NetBSD: gptsubr.c,v 1.5 2025/03/02 01:07:11 riastradh Exp $ */

/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: gptsubr.c,v 1.5 2025/03/02 01:07:11 riastradh Exp $");
#endif /* not lint */

#include <sys/param.h>

#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/disklabel_gpt.h>
#include <sys/dkio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/statvfs.h>

#include <err.h>
#include <fcntl.h>
#include <libgen.h>
#include <paths.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <uuid.h>

#include "defs.h"
#include "gpt.h"
#include "gptsubr.h"
#include "map.h"

#define DEBUG

#ifdef DEBUG
#define DPRINTF(fmt, ...)	printf(fmt, __VA_ARGS__)
#else
#define DPRINTF(fmt, ...)	do { } while (/*CONSTCOND*/0)
#endif

static const char *
map_type_name(int map_type)
{
	const char *xtbl[] = {
		"UNUSED",	// 0
		"MBR",		// 1
		"MBR_PART",	// 2
		"PRI_GPT_HDR",	// 3
		"SEC_GPT_HDR",	// 4
		"PRI_GPT_TBL",	// 5
		"SEC_GPT_TBL",	// 6
		"GPT_PART",	// 7
		"PMBR",		// 8
	};

	if (map_type >= (int)__arraycount(xtbl))
		return "UNKNOWN";

	return xtbl[map_type];
}

#if 0
struct map {
	off_t	map_start;
	off_t	map_size;
	struct map *map_next;
	struct map *map_prev;
	int	map_type;
#define	MAP_TYPE_UNUSED		0
#define	MAP_TYPE_MBR		1
#define	MAP_TYPE_MBR_PART	2
#define	MAP_TYPE_PRI_GPT_HDR	3
#define	MAP_TYPE_SEC_GPT_HDR	4
#define	MAP_TYPE_PRI_GPT_TBL	5
#define	MAP_TYPE_SEC_GPT_TBL	6
#define	MAP_TYPE_GPT_PART	7
#define	MAP_TYPE_PMBR		8
	unsigned int map_index;
	void 	*map_data;
	int	map_alloc;
};

struct gpt_ent {
	uint8_t		ent_type[16];	/* partition type GUID */
	uint8_t		ent_guid[16];	/* unique partition GUID */
	uint64_t	ent_lba_start;	/* start of partition */
	uint64_t	ent_lba_end;	/* end of partition */
	uint64_t	ent_attr;	/* partition attributes */
	uint16_t	ent_name[36];	/* partition name in UCS-2 */
};

struct gpt_hdr {
	int8_t		hdr_sig[8];	/* identifies GUID Partition Table */
	uint32_t	hdr_revision;	/* GPT specification revision */
	uint32_t	hdr_size;	/* size of GPT Header */
	uint32_t	hdr_crc_self;	/* CRC32 of GPT Header */
	uint32_t	hdr__rsvd0;	/* must be zero */
	uint64_t	hdr_lba_self;	/* LBA that contains this Header */
	uint64_t	hdr_lba_alt;	/* LBA of backup GPT Header */
	uint64_t	hdr_lba_start;	/* first LBA usable for partitions */
	uint64_t	hdr_lba_end;	/* last LBA usable for partitions */
	uint8_t		hdr_guid[16];	/* GUID to identify the disk */
	uint64_t	hdr_lba_table;	/* first LBA of GPE array */
	uint32_t	hdr_entries;	/* number of entries in GPE array */
	uint32_t	hdr_entsz;	/* size of each GPE */
	uint32_t	hdr_crc_table;	/* CRC32 of GPE array */
	/*
	 * The remainder of the block that contains the GPT Header
	 * is reserved by EFI for future GPT Header expansion, and
	 * must be zero.
	 */
};
#endif

struct map_widths {
	uint start;
	uint size;
	uint type_name;
};

static struct map_widths
get_map_widths(gpt_t gpt)
{
	struct map_widths w;
	off_t max_start = 0;
	off_t max_size = 0;
	map_t m;

	w.type_name = 0;
	for (m = map_first(gpt); m != NULL; m = m->map_next) {
		uint n;

		if (max_start < m->map_start)
			max_start = m->map_start;
		if (max_size < m->map_size)
			max_size = m->map_size;
		if (m->map_type == MAP_TYPE_GPT_PART) {
			struct gpt_ent *ent = m->map_data;
			gpt_uuid_t gpt_uuid;
			char ent_type[128];

			memcpy(&gpt_uuid, ent->ent_type, sizeof(gpt_uuid));
			n = (uint)gpt_uuid_snprintf(ent_type, sizeof(ent_type),
			    "%s", gpt_uuid);
			if (w.type_name < n)
				w.type_name = n;
		}

	}
	w.start = (uint)snprintf(NULL, 0, "%" PRIx64, max_start);
	w.size = (uint)snprintf(NULL, 0, "%" PRIx64, max_size);

#define MIN_WIDTH_OF(s)	(sizeof(s) - 1)
	if (w.type_name < MIN_WIDTH_OF("TYPE_NAME"))
		w.type_name = MIN_WIDTH_OF("TYPE_NAME");
	if (w.start < MIN_WIDTH_OF("START"))
		w.start = MIN_WIDTH_OF("START");
	if (w.size < MIN_WIDTH_OF("SIZE"))
		w.size = MIN_WIDTH_OF("SIZE");
#undef MIN_WIDTH_OF

	return w;
}

static void
show_map_banner(struct map_widths w)
{

	printf("IDX %*s %*s   ATTR            PARTITION_GUID"
	    "                         TYPE_UUID       %*s "
	    "TYPE_NAME ENTRY_NAME  DESCRIPTION\n",
	    w.start, "START",
	    w.size, "SIZE",
	    w.type_name, "");
}

static void
show_map(map_t m, struct map_widths w)
{
	struct gpt_ent *ent;
	struct gpt_hdr *hdr;
	gpt_uuid_t gpt_uuid;
	uuid_t uuid;
	uint32_t status;
	char *type_uuid;
	char *part_guid;
	uint64_t ent_attr;
	uint8_t  ent_desc[128];
	char     ent_type[128];

	ent_desc[0] = '\0';
	ent_type[0] = '\0';
	ent_attr = 0;

	switch (m->map_type) {
	case MAP_TYPE_PRI_GPT_HDR:
	case MAP_TYPE_SEC_GPT_HDR:
		hdr = m->map_data;
		memcpy(&uuid, hdr->hdr_guid, sizeof(uuid));
		uuid_to_string(&uuid, &part_guid, &status);
		type_uuid = estrdup("");
		break;

	case MAP_TYPE_GPT_PART:
		ent = m->map_data;

		memcpy(&uuid, ent->ent_type, sizeof(uuid));
		uuid_to_string(&uuid, &type_uuid, &status);

		memcpy(&uuid, ent->ent_guid, sizeof(uuid));
		uuid_to_string(&uuid, &part_guid, &status);

		ent_attr = ent->ent_attr;

		memcpy(&gpt_uuid, ent->ent_type, sizeof(uuid));
		gpt_uuid_snprintf(ent_type, sizeof(ent_type), "%s", gpt_uuid);

		/*
		 * Use the gpt.c code here rather than our
		 * ucs2_to_utf8() as we are in their world.
		 */
		utf16_to_utf8(ent->ent_name, sizeof(ent->ent_name),
		    ent_desc, sizeof(ent_desc));

		break;

	case MAP_TYPE_MBR_PART:
		part_guid = estrdup("");
		type_uuid = estrdup("");
		break;

	case MAP_TYPE_MBR: {
		struct mbr *mbr = m->map_data;

		easprintf(&part_guid,
		    "%02x%02x%02x%02x-0000-0000-0000-000000000000",
		    mbr->mbr_code[440],
		    mbr->mbr_code[441],
		    mbr->mbr_code[442],
		    mbr->mbr_code[443]);
		uuid_to_string(&(uuid_t)GPT_ENT_TYPE_MBR, &type_uuid, &status);
		break;
	}
	default:
		part_guid = estrdup("");
		type_uuid = estrdup("");
		break;
	}

	printf("%2u: %*" PRIx64 " %*" PRIx64 " %08" PRIx64
	    " %36s %36s %*s %-11s %s\n",
	    m->map_index,
	    w.start,
	    m->map_start,
	    w.size,
	    m->map_size,
	    ent_attr,
	    part_guid,
	    type_uuid,
	    w.type_name,
	    ent_type,
	    map_type_name(m->map_type),
	    ent_desc);

	free(part_guid);
	free(type_uuid);
}

PUBLIC map_t
find_gpt_map(const char *dev, uint idx)
{
	gpt_t gpt;
	map_t m;

	gpt = gpt_open(dev, GPT_READONLY, 0, 0, 0, 0);

	if (gpt == NULL)
		err(EXIT_FAILURE, "gpt_open");

	if (map_find(gpt, MAP_TYPE_PRI_GPT_HDR) == NULL)
		printf("GPT not found, displaying data from MBR.\n");

	for (m = map_first(gpt); m != NULL; m = m->map_next) {
		if (m->map_index == idx)
			break;
	}
	gpt_close(gpt);
	return m;
}

PUBLIC char *
parent_of_fname(const char *fname)
{
	struct dkwedge_info dkinfo;
	struct statvfs vfsb;
	struct stat sb;
	const char *d, *b;
	char *p;
	size_t n;
	int fd, rv;

	rv = stat(fname, &sb);
	if (rv == -1)
		err(EXIT_FAILURE, "stat: %s", fname);

	if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode))
		return estrdup(fname);

	rv = statvfs(fname, &vfsb);
	if (rv == -1)
		err(EXIT_FAILURE, "statvfs: %s", fname);

	b = basename(vfsb.f_mntfromname);
	d = dirname(vfsb.f_mntfromname);
	easprintf(&p, "%s/r%s", d, b);

	fd = open(p, O_RDONLY);
	if (fd == -1)
		err(EXIT_FAILURE, "open");

	rv = ioctl(fd, DIOCGWEDGEINFO, &dkinfo);
	close(fd);

	if (rv != -1) {
		free(p);
		return estrdup(dkinfo.dkw_parent);
	}

	warn("ioctl: DIOCGWEDGEINFO");

	/*
	 * Hum.  No wedges?  Assume we have the old disklabel
	 * "/dev/rwd0x" syntax.  Convert it to "/dev/rwd0d".
	 * XXX: this probably won't work.
	 */
	n = strlen(p);
	p[n - 1] = 'd';

	return p;
}

PUBLIC int
mbr_sig_write(const char *fname, uint32_t new_sig, bool force, int verbose)
{
	gpt_t gpt;
	map_t m;
	struct mbr *mbr;
	const char *dev;
	uint32_t old_sig;

	if (fname == NULL)
		errx(EXIT_FAILURE, "please specify a device");

	dev = parent_of_fname(fname);
	if (dev == NULL) {
		warnx("unable to find parent device of %s\n", fname);
		return -1;
	}

	gpt = gpt_open(dev, GPT_MODIFIED, verbose, 0, 0, 0);

	if (gpt == NULL)
		err(EXIT_FAILURE, "gpt_open");

	m = map_find(gpt, MAP_TYPE_MBR);
	if (m == NULL)
		printf("No MBR partition found!\n");
	else {
		mbr = m->map_data;

		memcpy(&old_sig, &mbr->mbr_code[440], 4);

		if (old_sig == 0)
			force = true;

		if (force) {
			memcpy(&mbr->mbr_code[440], &new_sig, 4);
			if (gpt_write(gpt, m) == -1)
				warn("gpt_write");
			else if (verbose)
				printf("sig: 0x%08x -> 0x%08x\n",
				    old_sig, new_sig);
		}
		else if (verbose)
			printf("sig: 0x%08x (unchanged)\n", old_sig);
	}

	gpt_close(gpt);
	return 0;
}

PUBLIC int
show_gpt(const char *fname, int verbose)
{
	gpt_t gpt;
	map_t m;
	struct map_widths w;
	const char *dev;

	dev = parent_of_fname(fname);
	if (dev == NULL)
		return -1;

	gpt = gpt_open(dev, GPT_READONLY, verbose, 0, 0, 0);

	if (gpt == NULL)
		err(EXIT_FAILURE, "gpt_open");

	if (map_find(gpt, MAP_TYPE_PRI_GPT_HDR) == NULL)
		warnx("GPT not found, displaying data from MBR.");

	w = get_map_widths(gpt);
	show_map_banner(w);

	for (m = map_first(gpt); m != NULL; m = m->map_next)
		show_map(m, w);

	gpt_close(gpt);
	return 0;
}

#if 0 /* UNUSED */

PUBLIC char *
wedge_of_fname(const char *fname)
{
	struct statvfs vfsbuf;
	int rv;

	rv = statvfs(fname, &vfsbuf);
	if (rv) {
		warn("statvfs: %s", fname);
		return NULL;
	}

	return estrdup(vfsbuf.f_mntfromname);
}

PUBLIC int
find_partition_idx(const char *fname, int verbose)
{
	struct dkwedge_info dkinfo;
	struct stat sb;
	struct statvfs vfsbuf;
	gpt_t gpt;
	map_t m;
	off_t offset;
	size_t size;
	const char *parent;
	char *b, *d;
	int rv;

	/* the following are for gpt_open() */
	off_t mediasz = 0;
	u_int secsz = 0;
	time_t timestamp = 0;

	rv = stat(fname, &sb);
	if (rv == -1)
		err(EXIT_FAILURE, "stat: %s", fname);

	if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
		parent = fname;
		offset = 0;
		size = 0;
		goto doit;
	}

	rv = statvfs(fname, &vfsbuf);
	if (rv == -1)
		err(EXIT_FAILURE, "statvfs: %s", fname);

	b = basename(vfsbuf.f_mntfromname);
	d = dirname(vfsbuf.f_mntfromname);

	{
		char *p;

		easprintf(&p, "%s/r%s", d, b);

		int fd = open(p, O_RDONLY);
		if (fd == -1)
			err(EXIT_FAILURE, "open");

		rv = ioctl(fd, DIOCGWEDGEINFO, &dkinfo);
		if (rv != -1) {
			parent = dkinfo.dkw_parent;
			offset = dkinfo.dkw_offset;
			size   = dkinfo.dkw_size;
		}
		else {
			struct disklabel dl;

			warn("ioctl: DIOCGWEDGEINFO");

			rv = ioctl(fd, DIOCGDINFO, &dl);
			if (rv == -1)
				err(EXIT_FAILURE, "ioctl: DIOCGDINFO");

			size_t n = strlen(p);

			int pnum = p[n - 1] - 'a';
			p[n - 1] = 'd';

			printf("num_parts: %u\n", dl.d_npartitions);
			printf("partition %d\n", pnum);
			printf("  offset = %u (%#x)\n",
			    dl.d_partitions[pnum].p_offset,
			    dl.d_partitions[pnum].p_offset);
			printf("  size = %u (%#x)\n",
			    dl.d_partitions[pnum].p_size,
			    dl.d_partitions[pnum].p_size);

			parent = p;	// vfsbuf.f_mntfromname;
			offset = dl.d_partitions[pnum].p_offset;
			size   = dl.d_partitions[pnum].p_size;
		}

		close(fd);
		free(p);
	}

 doit:
	DPRINTF("parent: %s\n", parent);
	DPRINTF("offset: 0x%lx\n", offset);
	DPRINTF("size: 0x%lx\n", size);

	gpt = gpt_open(parent, GPT_READONLY,
	    verbose, mediasz, secsz, timestamp);

	if (gpt == NULL)
		err(EXIT_FAILURE, "gpt_open");

	if (map_find(gpt, MAP_TYPE_PRI_GPT_HDR) == NULL)
		printf("GPT not found, displaying data from MBR.\n");

	int index = -1;
	struct map_widths w;
	if (verbose) {
		w = get_map_widths(gpt);
		show_map_banner(w);
	}
	for (m = map_first(gpt); m != NULL; m = m->map_next) {
		if (verbose)
			show_map(m, w);

		if (m->map_type != MAP_TYPE_GPT_PART || m->map_index < 1) {
			continue;
		}

		if ((off_t)offset == m->map_start &&
		    (off_t)size   == m->map_size) {
			if (index != -1)
				printf("match: %u\n", index);
			index = (int)m->map_index;
		}
	}
	return index;
}

PUBLIC char *
find_partition_pathname(const char *fname)
{
	char *pname;	/* partition name */
	char *rname;	/* real name */
	struct statvfs vfsbuf;
	int i, rv;

	rname = realpath(fname, NULL);

	DPRINTF("fname: %s\n", fname);
	DPRINTF("rname: %s\n", rname);

	rv = statvfs(rname, &vfsbuf);
	if (rv) {
		warn("statvfs: %s", rname);
		free(rname);
		return NULL;
	}

	DPRINTF("mount: %s\n", vfsbuf.f_mntonname);

	i = 0;
	while (vfsbuf.f_mntonname[i] == rname[i] &&
	    vfsbuf.f_mntonname[i] != '\0')
		i++;

	if (vfsbuf.f_mntonname[i] != '\0')
		errx(EXIT_FAILURE, "mntonname mismatch: %s",
		    vfsbuf.f_mntonname + i);

	pname = estrdup(rname + i);
	free(rname);

	return pname;
}

#endif