/* $NetBSD: gpt.c,v 1.32 2024/03/24 17:29:58 martin Exp $ */
/*
* Copyright 2018 The NetBSD Foundation, Inc.
* All rights reserved.
*
* 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 PIERMONT INFORMATION SYSTEMS INC. ``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 PIERMONT INFORMATION SYSTEMS INC. 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.
*
*/
const struct disk_partitioning_scheme gpt_parts;
struct gpt_disk_partitions {
struct disk_partitions dp;
/*
* We keep a list of our current valid partitions, pointed
* to by "partitions".
* dp.num_part is the number of entries in "partitions".
* When partitions that have a representation on disk already
* are deleted, we move them to the "obsolete" list so we
* can issue the proper commands to remove it when writing back.
*/
struct gpt_part_entry *partitions, /* current partitions */
*obsolete; /* deleted partitions */
size_t max_num_parts; /* how many entries max? */
size_t prologue, epilogue; /* number of sectors res. */
bool has_gpt; /* disk already has a GPT */
};
if (is_boot_disk) {
#ifdef MD_GPT_INITIAL_SIZE
#if MD_GPT_INITIAL_SIZE < 2*512
#error impossible small GPT prologue
#endif
num = ((MD_GPT_INITIAL_SIZE-(2*512))/512)*GPT_PARTS_PER_SEC;
#else
num = GPT_DEFAULT_MAX_PARTS;
#endif
} else {
num = GPT_DEFAULT_MAX_PARTS;
}
*max_parts = num;
*head = 2 + num/GPT_PARTS_PER_SEC;
*tail = 1 + num/GPT_PARTS_PER_SEC;
}
/*
* Parse a part of "gpt show" output into a struct gpt_part_entry.
* Output is from "show -a" format if details = false, otherwise
* from details for a specific partition (show -i or show -b)
*/
static void
gpt_add_info(struct gpt_part_entry *part, const char *tag, char *val,
bool details)
{
char *s, *e;
if (details && strcmp(tag, "Start:") == 0) {
part->gp_start = strtouq(val, NULL, 10);
} else if (details && strcmp(tag, "Size:") == 0) {
part->gp_size = strtouq(val, NULL, 10);
} else if (details && strcmp(tag, "Type:") == 0) {
s = strchr(val, '(');
if (!s)
return;
e = strchr(s, ')');
if (!e)
return;
*e = 0;
part->gp_type = gpt_find_guid_type(s+1);
} else if (strcmp(tag, "TypeID:") == 0) {
part->gp_type = gpt_find_guid_type(val);
} else if (strcmp(tag, "GUID:") == 0) {
strlcpy(part->gp_id, val, sizeof(part->gp_id));
} else if (strcmp(tag, "Label:") == 0) {
strlcpy(part->gp_label, val, sizeof(part->gp_label));
} else if (strcmp(tag, "Attributes:") == 0) {
char *n;
while ((n = strsep(&val, ", ")) != NULL) {
if (*n == 0)
continue;
for (const struct gpt_attr_desc *p = gpt_avail_attrs;
p->name != NULL; p++) {
if (strcmp(p->name, n) == 0)
part->gp_attr |= p->flag;
}
}
}
}
/*
* Find the partition matching this wedge info and record that we
* have a wedge already.
*/
static void
update_part_from_wedge_info(struct gpt_disk_partitions *parts,
const struct dkwedge_info *dkw)
{
for (struct gpt_part_entry *p = parts->partitions; p != NULL;
p = p->gp_next) {
if (p->gp_start != dkw->dkw_offset ||
(uint64_t)p->gp_size != dkw->dkw_size)
continue;
p->gp_flags |= GPEF_WEDGE;
strlcpy(p->gp_dev_name, dkw->dkw_devname,
sizeof p->gp_dev_name);
return;
}
}
/*
* Check if we have any (matching/auto-configured) wedges already
*/
dkw = NULL;
dkwl.dkwl_buf = dkw;
dkwl.dkwl_bufsize = 0;
if (ioctl(fd, DIOCLWEDGES, &dkwl) == 0) {
/* do not even try to deal with any races at this point */
bufsize = dkwl.dkwl_nwedges * sizeof(*dkw);
dkw = malloc(bufsize);
dkwl.dkwl_buf = dkw;
dkwl.dkwl_bufsize = bufsize;
if (dkw != NULL && ioctl(fd, DIOCLWEDGES, &dkwl) == 0) {
for (dk = 0; dk < dkwl.dkwl_ncopied; dk++)
update_part_from_wedge_info(parts, &dkw[dk]);
}
free(dkw);
}
for (no = 0; p != NULL && no < id; no++)
p = p->gp_next;
if (no != id || p == NULL)
return false;
if (flags == NULL)
flags = msg_string(MSG_gpt_flags);
if (avail_space < 2)
return false;
if (p->gp_attr & GPT_ATTR_BOOT)
*str++ = flags[0];
*str = 0;
return true;
}
/*
* Find insert position and check for duplicates.
* If all goes well, insert the new "entry" in the "list".
* If there are collisions, report "no free space".
* We keep all lists sorted by start sector number,
*/
static bool
gpt_insert_part_into_list(struct gpt_disk_partitions *parts,
struct gpt_part_entry **list,
struct gpt_part_entry *entry, const char **err_msg, part_id *new_id)
{
struct gpt_part_entry *p, *last;
part_id pno;
/* find the first entry past the new one (if any) */
for (pno = 0, last = NULL, p = *list; p != NULL;
last = p, p = p->gp_next, pno++) {
if (p->gp_start > entry->gp_start)
break;
}
/* check if last partition overlaps with new one */
if (last) {
if (last->gp_start + last->gp_size > entry->gp_start) {
if (err_msg)
*err_msg = msg_string(MSG_No_free_space);
return false;
}
}
if (p == NULL) {
entry->gp_next = NULL;
if (last != NULL) {
last->gp_next = entry;
}
} else {
/* check if new entry overlaps with next */
if (entry->gp_start + entry->gp_size > p->gp_start) {
if (err_msg)
*err_msg = msg_string(MSG_No_free_space);
return false;
}
for (no = 0; p != NULL && no < id; no++)
p = p->gp_next;
if (no != id || p == NULL)
return false;
/* update target mark - we can only have one */
if (info->flags & PTI_INSTALL_TARGET) {
p->gp_flags |= GPEF_TARGET;
for (n = parts->partitions; n != NULL; n = n->gp_next)
if (n != p)
n->gp_flags &= ~GPEF_TARGET;
} else {
p->gp_flags &= ~GPEF_TARGET;
}
if ((p->gp_flags & GPEF_ON_DISK)) {
if (info->start != p->gp_start) {
/* partition moved, we need to delete and re-add */
n = calloc(1, sizeof(*n));
if (n == NULL) {
if (err_msg)
*err_msg = err_outofmem;
return false;
}
*n = *p;
p->gp_flags &= ~GPEF_ON_DISK;
if (!gpt_insert_part_into_list(parts, &parts->obsolete,
n, err_msg, NULL))
return false;
} else if (info->size != p->gp_size) {
p->gp_flags |= GPEF_RESIZED;
}
}
for (size_t i = 0; i < gpt_ptype_cnt; i++)
if (gent == &gpt_ptype_descs[i].gent)
return &gpt_ptype_descs[i];
gent = gpt_get_generic_type(gent->generic_ptype);
if (gent == NULL)
return NULL;
/* this can not recurse deeper than once, we would not have found a
* generic type a few lines above if it would. */
return gpt_find_native_type(gent);
}
/* Try with complete match (including part_type) first */
for (i = 0; i < __arraycount(gpt_fs_types); i++)
if (fstype == gpt_fs_types[i].fstype &&
pt == gpt_fs_types[i].ptype)
return gpt_find_type(gpt_fs_types[i].name);
/* If that did not work, ignore part_type */
for (i = 0; i < __arraycount(gpt_fs_types); i++)
if (fstype == gpt_fs_types[i].fstype)
return gpt_find_type(gpt_fs_types[i].name);
if (gpt_get_free_spaces_internal(parts, &space, 1, 1, 1,
info->start, -1) < 1) {
if (err_msg)
*err_msg = msg_string(MSG_No_free_space);
return NO_PART;
}
if (parts->dp.num_part >= parts->max_num_parts) {
if (err_msg)
*err_msg = msg_string(MSG_err_too_many_partitions);
return NO_PART;
}
if (data.size > space.size)
data.size = space.size;
p = calloc(1, sizeof(*p));
if (p == NULL) {
if (err_msg != NULL)
*err_msg = INTERNAL_ERROR;
return NO_PART;
}
if (!gpt_info_to_part(p, &data, err_msg)) {
free(p);
return NO_PART;
}
p->gp_flags |= GPEF_MODIFIED;
ok = gpt_insert_part_into_list(parts, &parts->partitions, p,
err_msg, &pno);
if (ok) {
if (info->flags & PTI_INSTALL_TARGET) {
/* update target mark - we can only have one */
p->gp_flags |= GPEF_TARGET;
for (n = parts->partitions; n != NULL; n = n->gp_next)
if (n != p)
n->gp_flags &= ~GPEF_TARGET;
}
/* run gpt show for this partition */
if (collect(T_OUTPUT, &textbuf,
"gpt -r show -b %" PRIu64 " %s 2>/dev/null", start, disk) < 1)
return false;
/*
* gpt show should respond with single partition details, but will
* fall back to "show -a" output if something is wrong
*/
t = strtok(textbuf, "\n"); /* first line is special */
if (strncmp(t, expected_hdr, sizeof(expected_hdr)-1) != 0) {
free(textbuf);
return false;
}
/* parse output into "old" */
while ((t = strtok(NULL, "\n")) != NULL) {
tt = strsep(&t, " \t");
if (strlen(tt) == 0)
continue;
gpt_add_info(p, tt, t, true);
}
free(textbuf);
strcpy(attr_str, "-a ");
for (i = 0; todo != 0; i++) {
if (!(gpt_avail_attrs[i].flag & todo))
continue;
todo &= ~gpt_avail_attrs[i].flag;
if (attr_str[0])
strlcat(attr_str, ",",
sizeof(attr_str));
strlcat(attr_str,
gpt_avail_attrs[i].name,
sizeof(attr_str));
}
if (run_program(RUN_SILENT,
"gpt %s %s -b %" PRIu64 " %s", cmd, attr_str, start, disk) != 0)
return false;
return true;
}
/*
* Modify an existing on-disk partition.
* Start and size can not be changed here, caller needs to deal
* with that kind of changes upfront.
*/
static bool
gpt_modify_part(const char *disk, struct gpt_part_entry *p)
{
struct gpt_part_entry old;
uint todo_set, todo_unset;
/*
* Query current on-disk state
*/
memset(&old, 0, sizeof old);
if (!gpt_read_part(disk, p->gp_start, &old))
return false;
/*
* For each type known to FSTYPE_DEFN (from <sys/disklabel.h>),
* a suitable case branch will convert the type number to a string.
*/
switch (fstype) {
#define FSTYPE_TO_STR_CASE(tag, number, name, fsck, mount) \
case __CONCAT(FS_,tag): str = __CONCAT(DKW_PTYPE_,tag); break;
FSTYPE_DEFN(FSTYPE_TO_STR_CASE)
#undef FSTYPE_TO_STR_CASE
default: str = NULL; break;
}
return (str);
}
/*
* diskfd is an open file descriptor for a disk we had trouble with
* creating some new wedges.
* Go through all wedges actually on that disk, check if we have a
* record for them and remove all others.
* This should sync our internal model of partitions with the real state.
*/
static void
gpt_sanitize(int diskfd, const struct gpt_disk_partitions *parts,
struct gpt_part_entry *ignore)
{
struct dkwedge_info *dkw, delw;
struct dkwedge_list dkwl;
size_t bufsize;
u_int i;
/* get a list of all wedges */
for (;;) {
if (ioctl(diskfd, DIOCLWEDGES, &dkwl) == -1)
return;
if (dkwl.dkwl_nwedges == dkwl.dkwl_ncopied)
break;
bufsize = dkwl.dkwl_nwedges * sizeof(*dkw);
if (dkwl.dkwl_bufsize < bufsize) {
dkw = realloc(dkwl.dkwl_buf, bufsize);
if (dkw == NULL)
return;
dkwl.dkwl_buf = dkw;
dkwl.dkwl_bufsize = bufsize;
}
}
/* try to remove all the ones we do not know about */
for (i = 0; i < dkwl.dkwl_nwedges; i++) {
bool found = false;
const char *devname = dkw[i].dkw_devname;
for (struct gpt_part_entry *pe = parts->partitions;
pe != NULL; pe = pe->gp_next) {
if (pe == ignore)
continue;
if ((pe->gp_flags & GPEF_WEDGE) &&
strcmp(pe->gp_dev_name, devname) == 0) {
found = true;
break;
}
}
if (found)
continue;
memset(&delw, 0, sizeof(delw));
strlcpy(delw.dkw_devname, devname, sizeof(delw.dkw_devname));
(void)ioctl(diskfd, DIOCDWEDGE, &delw);
}
/*
* Remove all wedges on this disk - they may become invalid and we
* have no easy way to associate them with the partitioning data.
* Instead we will explicitly request creation of wedges on demand
* later.
*/
fd = opendisk(arg->disk, O_RDWR, diskpath, sizeof(diskpath), 0);
if (fd < 0)
return false;
if (ioctl(fd, DIOCRMWEDGES, &bits) == -1)
return false;
close(fd);
/*
* Collect first root and efi partition (if available), clear
* "have wedge" flags.
*/
for (pno = 0, p = parts->partitions; p != NULL; p = p->gp_next, pno++) {
p->gp_flags &= ~GPEF_WEDGE;
if (root_id == NO_PART && p->gp_type != NULL) {
if (p->gp_type->gent.generic_ptype == PT_root &&
(p->gp_flags & GPEF_TARGET)) {
root_id = pno;
root_is_new = !(p->gp_flags & GPEF_ON_DISK);
} else if (efi_id == NO_PART &&
p->gp_type->gent.generic_ptype == PT_EFI_SYSTEM) {
efi_id = pno;
efi_is_new = !(p->gp_flags & GPEF_ON_DISK);
}
}
}
/*
* If no GPT on disk yet, create it.
*/
if (!parts->has_gpt) {
char limit[30];
if (!gpt_modify_part(parts->dp.disk, p))
return false;
}
/*
* Add new partitions
*/
for (p = parts->partitions; p != NULL; p = p->gp_next) {
if (p->gp_flags & GPEF_ON_DISK)
continue;
if (!(p->gp_flags & GPEF_MODIFIED))
continue;
for (pno = 0, p = parts->partitions; p != NULL;
p = p->gp_next, pno++) {
if (strcmp(p->gp_label, name) == 0)
return pno;
if (strcmp(p->gp_id, name) == 0)
return pno;
}
if (p->gp_attr & GPT_ATTR_BOOT) {
p->gp_attr &= ~GPT_ATTR_BOOT;
} else {
for (i = 0, p = parts->partitions; p != NULL;
i++, p = p->gp_next)
if (i == ptn)
p->gp_attr |= GPT_ATTR_BOOT;
else
p->gp_attr &= ~GPT_ATTR_BOOT;
}
return true;
}