#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: gptsubr.c,v 1.5 2025/03/02 01:07:11 riastradh Exp $");
#endif
#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 (0)
#endif
static const char *
map_type_name(int map_type)
{
const char *xtbl[] = {
"UNUSED",
"MBR",
"MBR_PART",
"PRI_GPT_HDR",
"SEC_GPT_HDR",
"PRI_GPT_TBL",
"SEC_GPT_TBL",
"GPT_PART",
"PMBR",
};
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];
uint8_t ent_guid[16];
uint64_t ent_lba_start;
uint64_t ent_lba_end;
uint64_t ent_attr;
uint16_t ent_name[36];
};
struct gpt_hdr {
int8_t hdr_sig[8];
uint32_t hdr_revision;
uint32_t hdr_size;
uint32_t hdr_crc_self;
uint32_t hdr__rsvd0;
uint64_t hdr_lba_self;
uint64_t hdr_lba_alt;
uint64_t hdr_lba_start;
uint64_t hdr_lba_end;
uint8_t hdr_guid[16];
uint64_t hdr_lba_table;
uint32_t hdr_entries;
uint32_t hdr_entsz;
uint32_t hdr_crc_table;
};
#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);
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");
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
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;
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;
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;
char *rname;
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