gpt.c revision 1.4
1/*	$NetBSD: gpt.c,v 1.4 2019/07/26 08:18:47 martin Exp $	*/
2
3/*
4 * Copyright 2018 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
26 * THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 */
29
30#include "defs.h"
31#include "mbr.h"
32#include "md.h"
33#include "gpt_uuid.h"
34#include <assert.h>
35#include <paths.h>
36#include <sys/param.h>
37#include <sys/ioctl.h>
38#include <util.h>
39
40bool	gpt_parts_check(void);	/* check for needed binaries */
41
42
43/*************** GPT ************************************************/
44/* a GPT based disk_partitions interface */
45
46#define GUID_STR_LEN	40
47#define	GPT_PTYPE_MAX	32	/* should be >  gpt type -l | wc -l */
48#define	GPT_DEV_LEN	16	/* dkNN */
49
50#define	GPT_PARTS_PER_SEC	4	/* a 512 byte sector hols 4 entries */
51#define	GPT_DEFAULT_MAX_PARTS	128
52
53/* a usable label will be short, so we can get away with an arbitrary limit */
54#define	GPT_LABEL_LEN		96
55
56#define	GPT_ATTR_BIOSBOOT	1
57#define	GPT_ATTR_BOOTME		2
58#define	GPT_ATTR_BOOTONCE	4
59#define	GPT_ATTR_BOOTFAILED	8
60#define	GPT_ATTR_NOBLOCKIO	16
61#define	GPT_ATTR_REQUIRED	32
62
63/* when we don't care for BIOS or UEFI boot, use the combined boot flags */
64#define	GPT_ATTR_BOOT	(GPT_ATTR_BIOSBOOT|GPT_ATTR_BOOTME)
65
66struct gpt_attr_desc {
67	const char *name;
68	uint flag;
69};
70static const struct gpt_attr_desc gpt_avail_attrs[] = {
71	{ "biosboot", GPT_ATTR_BIOSBOOT },
72	{ "bootme", GPT_ATTR_BOOTME },
73	{ "bootonce", GPT_ATTR_BOOTONCE },
74	{ "bootfailed", GPT_ATTR_BOOTFAILED },
75	{ "noblockio", GPT_ATTR_NOBLOCKIO },
76	{ "required", GPT_ATTR_REQUIRED },
77	{ NULL, 0 }
78};
79
80struct gpt_ptype_desc {
81	struct part_type_desc gent;
82	char tid[GUID_STR_LEN];
83	uint fsflags, default_fs_type;
84};
85
86static const
87struct {
88	const char *name;
89	uint fstype;
90	enum part_type ptype;
91	uint fsflags;
92} gpt_fs_types[] = {
93	{ .name = "ffs",	.fstype = FS_BSDFFS,	.ptype = PT_root,
94	  .fsflags = GLM_LIKELY_FFS },
95	{ .name = "swap",	.fstype = FS_SWAP,	.ptype = PT_swap },
96	{ .name = "windows",	.fstype = FS_MSDOS,	.ptype = PT_FAT,
97	  .fsflags = GLM_MAYBE_FAT32|GLM_MAYBE_NTFS },
98	{ .name = "windows",	.fstype = FS_NTFS,	.ptype = PT_FAT,
99	  .fsflags = GLM_MAYBE_FAT32|GLM_MAYBE_NTFS },
100	{ .name = "efi",	.fstype = FS_MSDOS,	.ptype = PT_EFI_SYSTEM,
101	  .fsflags = GLM_MAYBE_FAT32 },
102	{ .name = "bios",	.fstype = FS_MSDOS,	.ptype = PT_FAT,
103	  .fsflags = GLM_MAYBE_FAT32 },
104	{ .name = "lfs",	.fstype = FS_BSDLFS,	.ptype = PT_root },
105	{ .name = "linux-data",	.fstype = FS_EX2FS,	.ptype = PT_root },
106	{ .name = "apple",	.fstype = FS_HFS,	.ptype = PT_unknown },
107	{ .name = "ccd",	.fstype = FS_CCD,	.ptype = PT_unknown },
108	{ .name = "cgd",	.fstype = FS_CGD,	.ptype = PT_unknown },
109	{ .name = "raid",	.fstype = FS_RAID,	.ptype = PT_root },
110	{ .name = "vmcore",	.fstype = FS_VMKCORE,	.ptype = PT_unknown },
111	{ .name = "vmfs",	.fstype = FS_VMFS,	.ptype = PT_unknown },
112	{ .name = "vmresered",	.fstype = FS_VMWRESV,	.ptype = PT_unknown }
113};
114
115static size_t gpt_ptype_cnt;
116static struct gpt_ptype_desc gpt_ptype_descs[GPT_PTYPE_MAX];
117
118/* similar to struct gpt_ent, but matching our needs */
119struct gpt_part_entry {
120	const struct gpt_ptype_desc *gp_type;
121	char gp_id[GUID_STR_LEN];	/* partition guid as string */
122	daddr_t gp_start, gp_size;
123	uint gp_attr;			/* various attribute bits */
124	char gp_label[GPT_LABEL_LEN];	/* user defined label */
125	char gp_dev_name[GPT_DEV_LEN];	/* name of wedge */
126	const char *last_mounted;	/* last mounted if known */
127	uint fs_type, fs_sub_type;	/* FS_* and maybe sub type */
128	uint gp_flags;
129#define	GPEF_ON_DISK	1		/* This entry exists on-disk */
130#define	GPEF_MODIFIED	2		/* this entry has been changed */
131#define	GPEF_WEDGE	4		/* wedge for this exists */
132#define	GPEF_RESIZED	8		/* size has changed */
133	struct gpt_part_entry *gp_next;
134};
135
136static const struct gpt_ptype_desc *gpt_find_native_type(
137    const struct part_type_desc *gent);
138static const struct gpt_ptype_desc *gpt_find_guid_type(const char*);
139static bool
140gpt_info_to_part(struct gpt_part_entry *p, const struct disk_part_info *info,
141    const char **err_msg);
142
143const struct disk_partitioning_scheme gpt_parts;
144struct gpt_disk_partitions {
145	struct disk_partitions dp;
146	/*
147	 * We keep a list of our current valid partitions, pointed
148	 * to by "partitions".
149	 * dp.num_part is the number of entries in "partitions".
150	 * When partitions that have a representation on disk already
151	 * are deleted, we move them to the "obsolete" list so we
152	 * can issue the proper commands to remove it when writing back.
153	 */
154	struct gpt_part_entry *partitions,	/* current partitions */
155	    *obsolete;				/* deleted partitions */
156	size_t max_num_parts;			/* how many entries max? */
157	size_t prologue, epilogue;		/* number of sectors res. */
158	bool has_gpt;	/* disk already has a GPT */
159};
160
161/*
162 * Init global variables from MD details
163 */
164static void
165gpt_md_init(bool is_boot_disk, size_t *max_parts, size_t *head, size_t *tail)
166{
167	size_t num;
168
169	if (is_boot_disk) {
170#ifdef MD_GPT_INITIAL_SIZE
171#if MD_GPT_INITIAL_SIZE < 2*512
172#error	impossible small GPT prologue
173#endif
174		num = ((MD_GPT_INITIAL_SIZE-(2*512))/512)*GPT_PARTS_PER_SEC;
175#else
176		num = GPT_DEFAULT_MAX_PARTS;
177#endif
178	} else {
179		num = GPT_DEFAULT_MAX_PARTS;
180	}
181	*max_parts = num;
182	*head = 2 + num/GPT_PARTS_PER_SEC;
183	*tail = 1 + num/GPT_PARTS_PER_SEC;
184}
185
186/*
187 * Parse a part of "gpt show" output into a struct gpt_part_entry.
188 * Output is from "show -a" format if details = false, otherwise
189 * from details for a specific partition (show -i or show -b)
190 */
191static void
192gpt_add_info(struct gpt_part_entry *part, const char *tag, char *val,
193    bool details)
194{
195	char *s, *e;
196
197	if (details && strcmp(tag, "Start:") == 0) {
198		part->gp_start = strtouq(val, NULL, 10);
199	} else if (details && strcmp(tag, "Size:") == 0) {
200		part->gp_size = strtouq(val, NULL, 10);
201	} else if (details && strcmp(tag, "Type:") == 0) {
202		s = strchr(val, '(');
203		if (!s)
204			return;
205		e = strchr(s, ')');
206		if (!e)
207			return;
208		*e = 0;
209		part->gp_type = gpt_find_guid_type(s+1);
210	} else if (strcmp(tag, "TypeID:") == 0) {
211		part->gp_type = gpt_find_guid_type(val);
212	} else if (strcmp(tag, "GUID:") == 0) {
213		strlcpy(part->gp_id, val, sizeof(part->gp_id));
214	} else if (strcmp(tag, "Label:") == 0) {
215		if (strlen(val) > 0)
216			strlcpy(part->gp_label, val, sizeof(part->gp_label));
217	} else if (strcmp(tag, "Attributes:") == 0) {
218		char *n;
219
220		while ((n = strsep(&val, ", ")) != NULL) {
221			if (*n == 0)
222				continue;
223			for (const struct gpt_attr_desc *p = gpt_avail_attrs;
224			    p->name != NULL; p++) {
225				if (strcmp(p->name, n) == 0)
226					part->gp_attr |= p->flag;
227			}
228		}
229	}
230}
231
232static struct disk_partitions *
233gpt_read_from_disk(const char *dev, daddr_t start, daddr_t len)
234{
235	char diskpath[MAXPATHLEN];
236	int fd;
237
238	assert(start == 0);
239	assert(have_gpt);
240
241	if (run_program(RUN_SILENT | RUN_ERROR_OK,
242	    "gpt -rq header %s", dev) != 0)
243		return NULL;
244
245	/* read the partitions */
246	int i;
247	unsigned int p_index;
248	daddr_t p_start = 0, p_size = 0, avail_start = 0, avail_size = 0,
249	    disk_size = 0;
250	char *textbuf, *t, *tt, p_type[STRSIZE];
251	static const char regpart_prefix[] = "GPT part - ";
252	struct gpt_disk_partitions *parts;
253	struct gpt_part_entry *last = NULL, *add_to = NULL;
254
255	if (collect(T_OUTPUT, &textbuf, "gpt -r show -a %s 2>/dev/null", dev)
256	    < 1)
257		return NULL;
258
259	/* parse output and create our list */
260	parts = calloc(1, sizeof(*parts));
261	if (parts == NULL)
262		return NULL;
263
264	(void)strtok(textbuf, "\n"); /* ignore first line */
265	while ((t = strtok(NULL, "\n")) != NULL) {
266		i = 0; p_start = 0; p_size = 0; p_index = 0;
267		p_type[0] = 0;
268		while ((tt = strsep(&t, " \t")) != NULL) {
269			if (strlen(tt) == 0)
270				continue;
271			if (i == 0) {
272				if (add_to != NULL)
273					gpt_add_info(add_to, tt, t, false);
274				p_start = strtouq(tt, NULL, 10);
275				if (p_start == 0 && add_to != NULL)
276					break;
277				else
278					add_to = NULL;
279			}
280			if (i == 1)
281				p_size = strtouq(tt, NULL, 10);
282			if (i == 2)
283				p_index = strtouq(tt, NULL, 10);
284			if (i > 2 || (i == 2 && p_index == 0)) {
285				if (p_type[0])
286					strlcat(p_type, " ", STRSIZE);
287				strlcat(p_type, tt, STRSIZE);
288			}
289			i++;
290		}
291
292		if (p_start == 0 || p_size == 0)
293			continue;
294		else if (strcmp(p_type, "Pri GPT table") == 0) {
295			avail_start = p_start + p_size;
296			parts->prologue = avail_start;
297			parts->epilogue = p_size + 1;
298			parts->max_num_parts = p_size * GPT_PARTS_PER_SEC;
299		} else if (strcmp(p_type, "Sec GPT table") == 0)
300			avail_size = p_start - avail_start;
301		else if(strcmp(p_type, "Sec GPT header") == 0)
302			disk_size = p_start + p_size;
303		else if (p_index == 0 && strlen(p_type) > 0)
304			/* Utilitary entry (PMBR, etc) */
305			continue;
306		else if (p_index == 0) {
307			/* Free space */
308			continue;
309		} else {
310			/* Usual partition */
311			tt = p_type;
312			if (strncmp(tt, regpart_prefix,
313			    strlen(regpart_prefix)) == 0)
314				tt += strlen(regpart_prefix);
315
316			/* Add to our linked list */
317			struct gpt_part_entry *np = calloc(1, sizeof(*np));
318			if (np == NULL)
319				break;
320
321			strlcpy(np->gp_label, tt, sizeof(np->gp_label));
322			np->gp_start = p_start;
323			np->gp_size = p_size;
324			np->gp_flags |= GPEF_ON_DISK;
325
326			if (last == NULL)
327				parts->partitions = np;
328			else
329				last->gp_next = np;
330			last = np;
331			add_to = np;
332			parts->dp.num_part++;
333		}
334	}
335	free(textbuf);
336
337	/* If the GPT was not complete (e.g. truncated image), barf */
338	if (disk_size <= 0) {
339		free(parts);
340		return NULL;
341	}
342
343	parts->dp.pscheme = &gpt_parts;
344	parts->dp.disk = dev;
345	parts->dp.disk_start = start;
346	parts->dp.disk_size = disk_size;
347	parts->dp.free_space = avail_size;
348	parts->has_gpt = true;
349
350	fd = opendisk(parts->dp.disk, O_RDONLY, diskpath, sizeof(diskpath), 0);
351	for (struct gpt_part_entry *p = parts->partitions; p != NULL;
352	    p = p->gp_next) {
353#ifdef DEFAULT_UFS2
354		bool fs_is_default = false;
355#endif
356
357		if (p->gp_type->fsflags != 0) {
358			const char *lm = get_last_mounted(fd, p->gp_start,
359			    &p->fs_type, &p->fs_sub_type, p->gp_type->fsflags);
360			if (lm != NULL && *lm != 0) {
361				char *path = strdup(lm);
362				canonicalize_last_mounted(path);
363				p->last_mounted = path;
364			} else {
365				p->fs_type = p->gp_type->default_fs_type;
366#ifdef DEFAULT_UFS2
367				fs_is_default = true;
368#endif
369			}
370		} else {
371			p->fs_type = p->gp_type->default_fs_type;
372#ifdef DEFAULT_UFS2
373			fs_is_default = true;
374#endif
375		}
376#ifdef DEFAULT_UFS2
377		if (fs_is_default && p->fs_type == FS_BSDFFS)
378			p->fs_sub_type = 2;
379#endif
380
381		parts->dp.free_space -= p->gp_size;
382	}
383	close(fd);
384
385	return &parts->dp;
386}
387
388static struct disk_partitions *
389gpt_create_new(const char *disk, daddr_t start, daddr_t len, daddr_t total,
390    bool is_boot_drive)
391{
392	struct gpt_disk_partitions *parts;
393
394	if (start != 0) {
395		assert(0);
396		return NULL;
397	}
398
399	parts = calloc(1, sizeof(*parts));
400	if (!parts)
401		return NULL;
402
403	parts->dp.pscheme = &gpt_parts;
404	parts->dp.disk = disk;
405
406	gpt_md_init(is_boot_drive, &parts->max_num_parts, &parts->prologue,
407	    &parts->epilogue);
408
409	parts->dp.disk_start = start;
410	parts->dp.disk_size = len;
411	parts->dp.free_space = len - start - parts->prologue - parts->epilogue;
412	parts->has_gpt = false;
413
414	return &parts->dp;
415}
416
417static bool
418gpt_get_part_info(const struct disk_partitions *arg, part_id id,
419    struct disk_part_info *info)
420{
421	const struct gpt_disk_partitions *parts =
422	    (const struct gpt_disk_partitions*)arg;
423	const struct gpt_part_entry *p = parts->partitions;
424	part_id no;
425
426	for (no = 0; p != NULL && no < id; no++)
427		p = p->gp_next;
428
429	if (no != id || p == NULL)
430		return false;
431
432	memset(info, 0, sizeof(*info));
433	info->start = p->gp_start;
434	info->size = p->gp_size;
435	if (p->gp_type)
436		info->nat_type = &p->gp_type->gent;
437	info->last_mounted = p->last_mounted;
438	info->fs_type = p->fs_type;
439	info->fs_sub_type = p->fs_sub_type;
440
441	return true;
442}
443
444static bool
445gpt_get_part_attr_str(const struct disk_partitions *arg, part_id id,
446    char *str, size_t avail_space)
447{
448	const struct gpt_disk_partitions *parts =
449	    (const struct gpt_disk_partitions*)arg;
450	const struct gpt_part_entry *p = parts->partitions;
451	part_id no;
452	static const char *flags = NULL;
453
454	for (no = 0; p != NULL && no < id; no++)
455		p = p->gp_next;
456
457	if (no != id || p == NULL)
458		return false;
459
460	if (flags == NULL)
461		flags = msg_string(MSG_gpt_flags);
462
463	if (avail_space < 2)
464		return false;
465
466	if (p->gp_attr & GPT_ATTR_BOOT)
467		*str++ = flags[0];
468	*str = 0;
469
470	return true;
471}
472
473/*
474 * Find insert position and check for duplicates.
475 * If all goes well, insert the new "entry" in the "list".
476 * If there are collisions, report "no free space".
477 * We keep all lists sorted by start sector number,
478 */
479static bool
480gpt_insert_part_into_list(struct gpt_disk_partitions *parts,
481    struct gpt_part_entry **list,
482    struct gpt_part_entry *entry, const char **err_msg)
483{
484	struct gpt_part_entry *p, *last;
485
486	/* find the first entry past the new one (if any) */
487	for (last = NULL, p = *list; p != NULL; last = p, p = p->gp_next) {
488		if (p->gp_start > entry->gp_start)
489			break;
490	}
491
492	/* check if last partition overlaps with new one */
493	if (last) {
494		if (last->gp_start + last->gp_size > entry->gp_start) {
495			if (err_msg)
496				*err_msg = msg_string(MSG_No_free_space);
497			return false;
498		}
499	}
500
501	if (p == NULL) {
502		entry->gp_next = NULL;
503		if (last != NULL) {
504			last->gp_next = entry;
505		}
506	} else {
507		/* check if new entry overlaps with next */
508		if (entry->gp_start + entry->gp_size > p->gp_start) {
509			if (err_msg)
510				*err_msg = msg_string(MSG_No_free_space);
511			return false;
512		}
513
514		entry->gp_next = p;
515		if (last != NULL)
516			last->gp_next = entry;
517		else
518			*list = entry;
519	}
520	if (*list == NULL)
521		*list = entry;
522
523	return true;
524}
525
526static bool
527gpt_set_part_info(struct disk_partitions *arg, part_id id,
528    const struct disk_part_info *info, const char **err_msg)
529{
530	struct gpt_disk_partitions *parts =
531	    (struct gpt_disk_partitions*)arg;
532	struct gpt_part_entry *p = parts->partitions, *n;
533	part_id no;
534	daddr_t lendiff;
535
536	for (no = 0; p != NULL && no < id; no++)
537		p = p->gp_next;
538
539	if (no != id || p == NULL)
540		return false;
541
542	if ((p->gp_flags & GPEF_ON_DISK)) {
543		if (info->start != p->gp_start) {
544			/* partition moved, we need to delete and re-add */
545			n = calloc(1, sizeof(*n));
546			if (n == NULL) {
547				if (err_msg)
548					*err_msg = err_outofmem;
549				return false;
550			}
551			*n = *p;
552			p->gp_flags &= ~GPEF_ON_DISK;
553			if (!gpt_insert_part_into_list(parts, &parts->obsolete,
554			    n, err_msg))
555				return false;
556		} else if (info->size != p->gp_size) {
557			p->gp_flags |= GPEF_RESIZED;
558		}
559	}
560
561	p->gp_flags |= GPEF_MODIFIED;
562
563	lendiff = info->size - p->gp_size;
564	parts->dp.free_space -= lendiff;
565	return gpt_info_to_part(p, info, err_msg);
566}
567
568static size_t
569gpt_get_free_spaces_internal(const struct gpt_disk_partitions *parts,
570    struct disk_part_free_space *result, size_t max_num_result,
571    daddr_t min_space_size, daddr_t align, daddr_t start, daddr_t ignore)
572{
573	size_t cnt = 0;
574	daddr_t s, e, from, size, end_of_disk;
575	struct gpt_part_entry *p;
576
577	if (align > 1)
578		start = max(roundup(start, align), align);
579	if (start < 0 || start < (daddr_t)parts->prologue)
580		start = parts->prologue;
581	if (parts->dp.disk_start != 0 && parts->dp.disk_start > start)
582		start = parts->dp.disk_start;
583	if (min_space_size < 1)
584		min_space_size = 1;
585	end_of_disk = parts->dp.disk_start + parts->dp.disk_size
586	    - parts->epilogue;
587	from = start;
588	while (from < end_of_disk && cnt < max_num_result) {
589again:
590		size = parts->dp.disk_start + parts->dp.disk_size - from;
591		start = from;
592		if (start + size > end_of_disk)
593			size = end_of_disk - start;
594		for (p = parts->partitions; p != NULL; p = p->gp_next) {
595			s = p->gp_start;
596			e = p->gp_size + s;
597			if (s == ignore)
598				continue;
599			if (e < from)
600				continue;
601			if (s <= from && e > from) {
602				if (e - 1 >= end_of_disk)
603					return cnt;
604				from = e + 1;
605				if (align > 1) {
606					from = max(roundup(from, align), align);
607					if (from >= end_of_disk) {
608						size = 0;
609						break;
610					}
611				}
612				goto again;
613			}
614			if (s > from && s - from < size) {
615				size = s - from;
616			}
617		}
618		if (size >= min_space_size) {
619			result->start = start;
620			result->size = size;
621			result++;
622			cnt++;
623		}
624		from += size + 1;
625		if (align > 1)
626			from = max(roundup(from, align), align);
627	}
628
629	return cnt;
630}
631
632static daddr_t
633gpt_max_free_space_at(const struct disk_partitions *arg, daddr_t start)
634{
635	const struct gpt_disk_partitions *parts =
636	    (const struct gpt_disk_partitions*)arg;
637	struct disk_part_free_space space;
638
639	if (gpt_get_free_spaces_internal(parts, &space, 1, 1, 0,
640	    start, start) == 1)
641		return space.size;
642
643	return 0;
644}
645
646static size_t
647gpt_get_free_spaces(const struct disk_partitions *arg,
648    struct disk_part_free_space *result, size_t max_num_result,
649    daddr_t min_space_size, daddr_t align, daddr_t start,
650    daddr_t ignore)
651{
652	const struct gpt_disk_partitions *parts =
653	    (const struct gpt_disk_partitions*)arg;
654
655	return gpt_get_free_spaces_internal(parts, result,
656	    max_num_result, min_space_size, align, start, ignore);
657}
658
659
660static bool
661gpt_adapt(const struct disk_partitions *arg,
662    const struct disk_part_info *src, struct disk_part_info *dest)
663{
664	/* slightly simplistic, enhance when needed */
665	memcpy(dest, src, sizeof(*dest));
666
667	if (src->nat_type == NULL)
668		return false;
669
670	dest->nat_type = arg->pscheme->get_generic_part_type(
671	    src->nat_type->generic_ptype);
672	if (dest->nat_type == NULL)
673		dest->nat_type = arg->pscheme->get_generic_part_type(
674		    PT_unknown);
675
676	return true;
677}
678
679static void
680gpt_match_ptype(const char *name, struct gpt_ptype_desc *t)
681{
682	size_t i;
683
684	for (i = 0; i < __arraycount(gpt_fs_types); i++) {
685		if (strcmp(name, gpt_fs_types[i].name) == 0) {
686			t->gent.generic_ptype = gpt_fs_types[i].ptype;
687			t->fsflags = gpt_fs_types[i].fsflags;
688			t->default_fs_type = gpt_fs_types[i].fstype;
689			return;
690		}
691	}
692
693	t->gent.generic_ptype = PT_unknown;
694	t->fsflags = 0;
695	t->default_fs_type = FS_BSDFFS;
696}
697
698static void
699gpt_internal_add_ptype(const char *uid, const char *name, const char *desc)
700{
701	strlcpy(gpt_ptype_descs[gpt_ptype_cnt].tid, uid,
702	    sizeof(gpt_ptype_descs[gpt_ptype_cnt].tid));
703	gpt_ptype_descs[gpt_ptype_cnt].gent.short_desc = name;
704	gpt_ptype_descs[gpt_ptype_cnt].gent.description = desc;
705	gpt_match_ptype(name, &gpt_ptype_descs[gpt_ptype_cnt]);
706	gpt_ptype_cnt++;
707}
708
709static void
710gpt_init_ptypes(void)
711{
712	if (gpt_ptype_cnt == 0)
713		gpt_uuid_query(gpt_internal_add_ptype);
714}
715
716static size_t
717gpt_type_count(void)
718{
719	if (gpt_ptype_cnt == 0)
720		gpt_init_ptypes();
721
722	return gpt_ptype_cnt;
723}
724
725static const struct part_type_desc *
726gpt_get_ptype(size_t ndx)
727{
728	if (gpt_ptype_cnt == 0)
729		gpt_init_ptypes();
730
731	if (ndx >= gpt_ptype_cnt)
732		return NULL;
733
734	return &gpt_ptype_descs[ndx].gent;
735}
736
737static const struct part_type_desc *
738gpt_get_generic_type(enum part_type gent)
739{
740	if (gpt_ptype_cnt == 0)
741		gpt_init_ptypes();
742
743	for (size_t i = 0; i < gpt_ptype_cnt; i++)
744		if (gpt_ptype_descs[i].gent.generic_ptype == gent)
745			return &gpt_ptype_descs[i].gent;
746
747	return NULL;
748}
749
750static const struct gpt_ptype_desc *
751gpt_find_native_type(const struct part_type_desc *gent)
752{
753	if (gpt_ptype_cnt == 0)
754		gpt_init_ptypes();
755
756	if (gent == NULL)
757		return NULL;
758
759	for (size_t i = 0; i < gpt_ptype_cnt; i++)
760		if (gent == &gpt_ptype_descs[i].gent)
761			return &gpt_ptype_descs[i];
762
763	gent = gpt_get_generic_type(gent->generic_ptype);
764	if (gent == NULL)
765		return NULL;
766
767	/* this can not recurse deeper than once, we would not have found a
768	 * generic type a few lines above if it would. */
769	return gpt_find_native_type(gent);
770}
771
772static const struct gpt_ptype_desc *
773gpt_find_guid_type(const char *uid)
774{
775	if (gpt_ptype_cnt == 0)
776		gpt_init_ptypes();
777
778	if (uid == NULL || uid[0] == 0)
779		return NULL;
780
781	for (size_t i = 0; i < gpt_ptype_cnt; i++)
782		if (strcmp(gpt_ptype_descs[i].tid, uid) == 0)
783			return &gpt_ptype_descs[i];
784
785	return NULL;
786}
787
788static const struct part_type_desc *
789gpt_find_type(const char *desc)
790{
791	if (gpt_ptype_cnt == 0)
792		gpt_init_ptypes();
793
794	if (desc == NULL || desc[0] == 0)
795		return NULL;
796
797	for (size_t i = 0; i < gpt_ptype_cnt; i++)
798		if (strcmp(gpt_ptype_descs[i].gent.short_desc, desc) == 0)
799			return &gpt_ptype_descs[i].gent;
800
801	return NULL;
802}
803
804static const struct part_type_desc *
805gpt_get_fs_part_type(unsigned fstype, unsigned fs_sub_type)
806{
807	size_t i;
808
809	for (i = 0; i < __arraycount(gpt_fs_types); i++)
810		if (fstype == gpt_fs_types[i].fstype)
811			return gpt_find_type(gpt_fs_types[i].name);
812
813	return gpt_get_generic_type(PT_root);
814}
815
816static daddr_t
817gpt_get_part_alignment(const struct disk_partitions *parts)
818{
819
820	assert(parts->disk_size > 0);
821	if (parts->disk_size < 0)
822		return 1;
823
824	/* Use 1MB offset/alignemnt for large (>128GB) disks */
825	if (parts->disk_size > HUGE_DISK_SIZE)
826		return 2048;
827	else if (parts->disk_size > TINY_DISK_SIZE)
828		return 64;
829	else
830		return 4;
831}
832
833static bool
834gpt_can_add_partition(const struct disk_partitions *arg)
835{
836	const struct gpt_disk_partitions *parts =
837	    (const struct gpt_disk_partitions*)arg;
838	struct disk_part_free_space space;
839	daddr_t align;
840
841	if (parts->dp.num_part >= parts->max_num_parts)
842		return false;
843
844	align = gpt_get_part_alignment(arg);
845	if (parts->dp.free_space <= align)
846		return false;
847
848	if (gpt_get_free_spaces_internal(parts, &space, 1, align, align,
849	    0, -1) < 1)
850		return false;
851
852	return true;
853}
854
855static bool
856gpt_info_to_part(struct gpt_part_entry *p, const struct disk_part_info *info,
857    const char **err_msg)
858{
859	p->gp_type = gpt_find_native_type(info->nat_type);
860	p->gp_start = info->start;
861	p->gp_size = info->size;
862	if (info->last_mounted != NULL && info->last_mounted !=
863	    p->last_mounted) {
864		free(__UNCONST(p->last_mounted));
865		p->last_mounted = strdup(info->last_mounted);
866	}
867	p->fs_type = info->fs_type;
868	p->fs_sub_type = info->fs_sub_type;
869
870	return true;
871}
872
873static part_id
874gpt_add_part(struct disk_partitions *arg,
875    const struct disk_part_info *info, const char **err_msg)
876{
877	struct gpt_disk_partitions *parts =
878	    (struct gpt_disk_partitions*)arg;
879	struct disk_part_free_space space;
880	struct disk_part_info data = *info;
881	struct gpt_part_entry *p;
882	bool ok;
883
884	if (err_msg != NULL)
885		*err_msg = NULL;
886
887	if (gpt_get_free_spaces_internal(parts, &space, 1, 1, 1,
888	    info->start, -1) < 1) {
889		if (err_msg)
890			*err_msg = msg_string(MSG_No_free_space);
891		return NO_PART;
892	}
893	if (parts->dp.num_part >= parts->max_num_parts) {
894		if (err_msg)
895			*err_msg = msg_string(MSG_err_too_many_partitions);
896		return NO_PART;
897	}
898
899	if (data.size > space.size)
900		data.size = space.size;
901
902	p = calloc(1, sizeof(*p));
903	if (p == NULL) {
904		if (err_msg != NULL)
905			*err_msg = INTERNAL_ERROR;
906		return NO_PART;
907	}
908	if (!gpt_info_to_part(p, &data, err_msg)) {
909		free(p);
910		return NO_PART;
911	}
912	p->gp_flags |= GPEF_MODIFIED;
913	ok = gpt_insert_part_into_list(parts, &parts->partitions, p, err_msg);
914	if (ok) {
915		parts->dp.num_part++;
916		parts->dp.free_space -= p->gp_size;
917		return parts->dp.num_part-1;
918	} else {
919		free(p);
920		return NO_PART;
921	}
922}
923
924static bool
925gpt_delete_partition(struct disk_partitions *arg, part_id id,
926    const char **err_msg)
927{
928	struct gpt_disk_partitions *parts = (struct gpt_disk_partitions*)arg;
929	struct gpt_part_entry *p, *last = NULL;
930	part_id i;
931	bool res;
932
933	if (parts->dp.num_part == 0)
934		return false;
935
936	for (i = 0, p = parts->partitions;
937	    i != id && i < parts->dp.num_part && p != NULL;
938	    i++, p = p->gp_next)
939		last = p;
940
941	if (p == NULL) {
942		if (err_msg)
943			*err_msg = INTERNAL_ERROR;
944		return false;
945	}
946
947	if (last == NULL)
948		parts->partitions = p->gp_next;
949	else
950		last->gp_next = p->gp_next;
951
952	res = true;
953	if (p->gp_flags & GPEF_ON_DISK) {
954		if (!gpt_insert_part_into_list(parts, &parts->obsolete,
955		    p, err_msg))
956			res = false;
957	} else {
958		free(p);
959	}
960
961	if (res) {
962		parts->dp.num_part--;
963		parts->dp.free_space += p->gp_size;
964	}
965
966	return res;
967}
968
969static bool
970gpt_delete_all_partitions(struct disk_partitions *arg)
971{
972	struct gpt_disk_partitions *parts = (struct gpt_disk_partitions*)arg;
973
974	while (parts->dp.num_part > 0) {
975		if (!gpt_delete_partition(&parts->dp, 0, NULL))
976			return false;
977	}
978
979	return true;
980}
981
982static bool
983gpt_read_part(const char *disk, daddr_t start, struct gpt_part_entry *p)
984{
985	char *textbuf, *t, *tt;
986	static const char expected_hdr[] = "Details for index ";
987
988	/* run gpt show for this partition */
989	if (collect(T_OUTPUT, &textbuf,
990	    "gpt -r show -b %" PRIu64 " %s 2>/dev/null", start, disk) < 1)
991		return false;
992
993	/*
994	 * gpt show should respond with single partition details, but will
995	 * fall back to "show -a" output if something is wrong
996	 */
997	t = strtok(textbuf, "\n"); /* first line is special */
998	if (strncmp(t, expected_hdr, sizeof(expected_hdr)-1) != 0) {
999		free(textbuf);
1000		return false;
1001	}
1002
1003	/* parse output into "old" */
1004	while ((t = strtok(NULL, "\n")) != NULL) {
1005		tt = strsep(&t, " \t");
1006		if (strlen(tt) == 0)
1007			continue;
1008		gpt_add_info(p, tt, t, true);
1009	}
1010	free(textbuf);
1011
1012	return true;
1013}
1014
1015static bool
1016gpt_apply_attr(const char *disk, const char *cmd, off_t start, uint todo)
1017{
1018	size_t i;
1019	char attr_str[STRSIZE];
1020
1021	if (todo == 0)
1022		return true;
1023
1024	strcpy(attr_str, "-a ");
1025	for (i = 0; todo != 0; i++) {
1026		if (!(gpt_avail_attrs[i].flag & todo))
1027			continue;
1028		todo &= ~gpt_avail_attrs[i].flag;
1029		if (attr_str[0])
1030			strlcat(attr_str, ",",
1031			    sizeof(attr_str));
1032		strlcat(attr_str,
1033		    gpt_avail_attrs[i].name,
1034		    sizeof(attr_str));
1035	}
1036	if (run_program(RUN_SILENT,
1037	    "gpt %s %s -b %" PRIu64 " %s", cmd, attr_str, start, disk) != 0)
1038		return false;
1039	return true;
1040}
1041
1042/*
1043 * Modify an existing on-disk partition.
1044 * Start and size can not be changed here, caller needs to deal
1045 * with that kind of changes upfront.
1046 */
1047static bool
1048gpt_modify_part(const char *disk, struct gpt_part_entry *p)
1049{
1050	struct gpt_part_entry old;
1051	uint todo_set, todo_unset;
1052
1053	/*
1054	 * Query current on-disk state
1055	 */
1056	memset(&old, 0, sizeof old);
1057	if (!gpt_read_part(disk, p->gp_start, &old))
1058		return false;
1059
1060	/* Reject unsupported changes */
1061	if (old.gp_start != p->gp_start || old.gp_size != p->gp_size)
1062		return false;
1063
1064	/*
1065	 * GUID should never change, but the internal copy
1066	 * may not yet know it.
1067	 */
1068	strcpy(p->gp_id, old.gp_id);
1069
1070	/* Check type */
1071	if (p->gp_type != old.gp_type) {
1072		if (run_program(RUN_SILENT,
1073		    "gpt label -b %" PRIu64 " -T %s %s",
1074		    p->gp_start, p->gp_type->tid, disk) != 0)
1075			return false;
1076	}
1077
1078	/* Check label */
1079	if (strcmp(p->gp_label, old.gp_label) != 0) {
1080		if (run_program(RUN_SILENT,
1081		    "gpt label -b %" PRIu64 " -l %s %s",
1082		    p->gp_start, p->gp_label, disk) != 0)
1083			return false;
1084	}
1085
1086	/* Check attributes */
1087	if (p->gp_attr != old.gp_attr) {
1088		if (p->gp_attr == 0) {
1089			if (run_program(RUN_SILENT,
1090			    "gpt set -N -b %" PRIu64 " %s",
1091			    p->gp_start, disk) != 0)
1092				return false;
1093		} else {
1094			todo_set = (p->gp_attr ^ old.gp_attr) & p->gp_attr;
1095			todo_unset = (p->gp_attr ^ old.gp_attr) & old.gp_attr;
1096			if (!gpt_apply_attr(disk, "unset", p->gp_start,
1097			    todo_unset))
1098				return false;
1099			if (!gpt_apply_attr(disk, "set", p->gp_start,
1100			    todo_set))
1101				return false;
1102		}
1103	}
1104
1105	return true;
1106}
1107
1108/*
1109 * verbatim copy from sys/dev/dkwedge/dkwedge_bsdlabel.c:
1110 *  map FS_* to wedge strings
1111 */
1112static const char *
1113bsdlabel_fstype_to_str(uint8_t fstype)
1114{
1115	const char *str;
1116
1117	/*
1118	 * For each type known to FSTYPE_DEFN (from <sys/disklabel.h>),
1119	 * a suitable case branch will convert the type number to a string.
1120	 */
1121	switch (fstype) {
1122#define FSTYPE_TO_STR_CASE(tag, number, name, fsck, mount) \
1123	case __CONCAT(FS_,tag):	str = __CONCAT(DKW_PTYPE_,tag);			break;
1124	FSTYPE_DEFN(FSTYPE_TO_STR_CASE)
1125#undef FSTYPE_TO_STR_CASE
1126	default:		str = NULL;			break;
1127	}
1128
1129	return (str);
1130}
1131
1132static bool
1133gpt_add_wedge(const char *disk, struct gpt_part_entry *p)
1134{
1135	struct dkwedge_info dkw;
1136	const char *tname;
1137	char diskpath[MAXPATHLEN];
1138	int fd;
1139
1140	memset(&dkw, 0, sizeof(dkw));
1141	tname = bsdlabel_fstype_to_str(p->fs_type);
1142	if (tname)
1143		strlcpy(dkw.dkw_ptype, tname, sizeof(dkw.dkw_ptype));
1144
1145	strlcpy((char*)&dkw.dkw_wname, p->gp_id, sizeof(dkw.dkw_wname));
1146	dkw.dkw_offset = p->gp_start;
1147	dkw.dkw_size = p->gp_size;
1148
1149	fd = opendisk(disk, O_RDWR, diskpath, sizeof(diskpath), 0);
1150	if (fd < 0)
1151		return false;
1152	if (ioctl(fd, DIOCAWEDGE, &dkw) == -1) {
1153		close(fd);
1154		return false;
1155	}
1156	close(fd);
1157
1158	strlcpy(p->gp_dev_name, dkw.dkw_devname, sizeof(p->gp_dev_name));
1159	p->gp_flags |= GPEF_WEDGE;
1160	return true;
1161}
1162
1163static bool
1164gpt_get_part_device(const struct disk_partitions *arg,
1165    part_id id, char *devname, size_t max_devname_len, int *part,
1166    enum dev_name_usage usage, bool with_path)
1167{
1168	const struct gpt_disk_partitions *parts =
1169	    (const struct gpt_disk_partitions*)arg;
1170	struct  gpt_part_entry *p = parts->partitions;
1171	part_id no;
1172
1173
1174	for (no = 0; p != NULL && no < id; no++)
1175		p = p->gp_next;
1176
1177	if (no != id || p == NULL)
1178		return false;
1179
1180	if (part)
1181		*part = -1;
1182
1183	if (!(p->gp_flags & GPEF_WEDGE) &&
1184	    (usage == plain_name || usage == raw_dev_name))
1185		gpt_add_wedge(arg->disk, p);
1186
1187	switch (usage) {
1188	case logical_name:
1189		if (p->gp_label[0] != 0)
1190			snprintf(devname, max_devname_len,
1191			    "NAME=%s", p->gp_label);
1192		else
1193			snprintf(devname, max_devname_len,
1194			    "NAME=%s", p->gp_id);
1195		break;
1196	case plain_name:
1197		assert(p->gp_flags & GPEF_WEDGE);
1198		if (with_path)
1199			snprintf(devname, max_devname_len, _PATH_DEV "%s",
1200			    p->gp_dev_name);
1201		else
1202			strlcpy(devname, p->gp_dev_name, max_devname_len);
1203		break;
1204	case raw_dev_name:
1205		assert(p->gp_flags & GPEF_WEDGE);
1206		if (with_path)
1207			snprintf(devname, max_devname_len, _PATH_DEV "r%s",
1208			    p->gp_dev_name);
1209		else
1210			snprintf(devname, max_devname_len, "r%s",
1211			    p->gp_dev_name);
1212		break;
1213	default:
1214		return false;
1215	}
1216
1217	return true;
1218}
1219
1220static bool
1221gpt_write_to_disk(struct disk_partitions *arg)
1222{
1223	struct gpt_disk_partitions *parts = (struct gpt_disk_partitions*)arg;
1224	struct gpt_part_entry *p, *n;
1225	char label_arg[sizeof(p->gp_label) + 4];
1226	char diskpath[MAXPATHLEN];
1227	int fd, bits = 0;
1228	bool root_is_new = false, efi_is_new = false;
1229	part_id root_id = NO_PART, efi_id = NO_PART, pno;
1230
1231	/*
1232	 * Remove all wedges on this disk - they may become invalid and we
1233	 * have no easy way to associate them with the partitioning data.
1234	 * Instead we will explicitly request creation of wedges on demand
1235	 * later.
1236	 */
1237	fd = opendisk(arg->disk, O_RDWR, diskpath, sizeof(diskpath), 0);
1238	if (fd < 0)
1239		return false;
1240	if (ioctl(fd, DIOCRMWEDGES, &bits) == -1)
1241		return false;
1242	close(fd);
1243
1244	/*
1245	 * Mark all partitions as "have no wedge yet". While there,
1246	 * collect first root and efi partition (if available)
1247	 */
1248	for (pno = 0, p = parts->partitions; p != NULL; p = p->gp_next, pno++) {
1249		p->gp_flags &= ~GPEF_WEDGE;
1250		if (root_id == NO_PART) {
1251			if (p->gp_type->gent.generic_ptype == PT_root &&
1252			    p->gp_start == pm->ptstart) {
1253				root_id = pno;
1254				root_is_new = !(p->gp_flags & GPEF_ON_DISK);
1255			} else if (efi_id == NO_PART &&
1256			    p->gp_type->gent.generic_ptype == PT_EFI_SYSTEM) {
1257				efi_id = pno;
1258				efi_is_new = !(p->gp_flags & GPEF_ON_DISK);
1259			}
1260		}
1261	}
1262
1263	/*
1264	 * If no GPT on disk yet, create it.
1265	 */
1266	if (!parts->has_gpt) {
1267		char limit[30];
1268
1269		if (parts->max_num_parts > 0)
1270			sprintf(limit, "-p %zu", parts->max_num_parts);
1271		else
1272			limit[0] = 0;
1273		if (run_program(RUN_SILENT, "gpt create %s %s",
1274		    limit, parts->dp.disk))
1275			return false;
1276		parts->has_gpt = true;
1277	}
1278
1279	/*
1280	 * Delete all old partitions
1281	 */
1282	for (p = parts->obsolete; p != NULL; p = n) {
1283		run_program(RUN_SILENT, "gpt -n remove -b %" PRIu64 " %s",
1284		    p->gp_start, arg->disk);
1285		n = p->gp_next;
1286		free(p);
1287	}
1288	parts->obsolete = NULL;
1289
1290	/*
1291	 * Modify existing but changed partitions
1292	 */
1293	for (p = parts->partitions; p != NULL; p = p->gp_next) {
1294		if (!(p->gp_flags & GPEF_ON_DISK))
1295			continue;
1296
1297		if (p->gp_flags & GPEF_RESIZED) {
1298			run_program(RUN_SILENT,
1299			    "gpt -n resize -b %" PRIu64 " -s %" PRIu64 "s %s",
1300			    p->gp_start, p->gp_size, arg->disk);
1301			p->gp_flags &= ~GPEF_RESIZED;
1302		}
1303
1304		if (!(p->gp_flags & GPEF_MODIFIED))
1305			continue;
1306
1307		if (!gpt_modify_part(parts->dp.disk, p))
1308			return false;
1309	}
1310
1311	/*
1312	 * Add new partitions
1313	 */
1314	for (p = parts->partitions; p != NULL; p = p->gp_next) {
1315		if (p->gp_flags & GPEF_ON_DISK)
1316			continue;
1317		if (!(p->gp_flags & GPEF_MODIFIED))
1318			continue;
1319
1320		if (p->gp_label[0] == 0)
1321			label_arg[0] = 0;
1322		else
1323			sprintf(label_arg, "-l %s", p->gp_label);
1324
1325		run_program(RUN_SILENT,
1326		    "gpt -n add -b %" PRIu64 " -s %" PRIu64 "s -t %s %s %s",
1327		    p->gp_start, p->gp_size, p->gp_type->tid,
1328		    label_arg, arg->disk);
1329		gpt_apply_attr(arg->disk, "set", p->gp_start, p->gp_attr);
1330		gpt_read_part(arg->disk, p->gp_start, p);
1331		p->gp_flags |= GPEF_ON_DISK;
1332	}
1333
1334	/*
1335	 * Additional MD bootloader magic...
1336	 */
1337	if (!md_gpt_post_write(&parts->dp, root_id, root_is_new, efi_id,
1338	    efi_is_new))
1339		return false;
1340
1341	return true;
1342}
1343
1344bool
1345gpt_parts_check(void)
1346{
1347
1348	check_available_binaries();
1349
1350	return have_gpt && have_dk;
1351}
1352
1353static void
1354gpt_free(struct disk_partitions *arg)
1355{
1356	struct gpt_disk_partitions *parts = (struct gpt_disk_partitions*)arg;
1357	struct gpt_part_entry *p, *n;
1358
1359	assert(parts != NULL);
1360	for (p = parts->partitions; p != NULL; p = n) {
1361		free(__UNCONST(p->last_mounted));
1362		n = p->gp_next;
1363		free(p);
1364	}
1365	free(parts);
1366}
1367
1368static bool
1369gpt_custom_attribute_writable(const struct disk_partitions *arg,
1370    part_id ptn, size_t attr_no)
1371{
1372	const struct gpt_disk_partitions *parts =
1373	    (const struct gpt_disk_partitions*)arg;
1374	size_t i;
1375	struct gpt_part_entry *p;
1376
1377	if (attr_no >= arg->pscheme->custom_attribute_count)
1378		return false;
1379
1380	const msg label = arg->pscheme->custom_attributes[attr_no].label;
1381
1382	/* we can not edit the uuid attribute */
1383	if (label == MSG_ptn_uuid)
1384		return false;
1385
1386	/* the label is always editable */
1387	if (label == MSG_ptn_label)
1388		return true;
1389
1390	/* the GPT type is read only */
1391	if (label == MSG_ptn_gpt_type)
1392		return false;
1393
1394	/* BOOTME makes no sense on swap partitions */
1395	for (i = 0, p = parts->partitions; p != NULL; i++, p = p->gp_next)
1396		if (i == ptn)
1397			break;
1398
1399	if (p == NULL)
1400		return false;
1401
1402	if (p->fs_type == FS_SWAP || p->gp_type->gent.generic_ptype == PT_swap)
1403		return false;
1404
1405	return true;
1406}
1407
1408static bool
1409gpt_format_custom_attribute(const struct disk_partitions *arg,
1410    part_id ptn, size_t attr_no, const struct disk_part_info *info,
1411    char *out, size_t out_space)
1412{
1413	const struct gpt_disk_partitions *parts =
1414	    (const struct gpt_disk_partitions*)arg;
1415	size_t i;
1416	struct gpt_part_entry *p, data;
1417
1418	for (i = 0, p = parts->partitions; p != NULL; i++, p = p->gp_next)
1419		if (i == ptn)
1420			break;
1421
1422	if (p == NULL)
1423		return false;
1424
1425	if (attr_no >= parts->dp.pscheme->custom_attribute_count)
1426		return false;
1427
1428	const msg label = parts->dp.pscheme->custom_attributes[attr_no].label;
1429
1430	if (info != NULL) {
1431		data = *p;
1432		gpt_info_to_part(&data, info, NULL);
1433		p = &data;
1434	}
1435
1436	if (label == MSG_ptn_label)
1437		strlcpy(out, p->gp_label, out_space);
1438	else if (label == MSG_ptn_uuid)
1439		strlcpy(out, p->gp_id, out_space);
1440	else if (label == MSG_ptn_gpt_type)
1441		strlcpy(out, p->gp_type->gent.description, out_space);
1442	else if (label == MSG_ptn_boot)
1443		strlcpy(out, msg_string(p->gp_attr & GPT_ATTR_BOOT ?
1444		    MSG_Yes : MSG_No), out_space);
1445	else
1446		return false;
1447
1448	return true;
1449}
1450
1451static bool
1452gpt_custom_attribute_toggle(struct disk_partitions *arg,
1453    part_id ptn, size_t attr_no)
1454{
1455	const struct gpt_disk_partitions *parts =
1456	    (const struct gpt_disk_partitions*)arg;
1457	size_t i;
1458	struct gpt_part_entry *p;
1459
1460	for (i = 0, p = parts->partitions; p != NULL; i++, p = p->gp_next)
1461		if (i == ptn)
1462			break;
1463
1464	if (p == NULL)
1465		return false;
1466
1467	if (attr_no >= parts->dp.pscheme->custom_attribute_count)
1468		return false;
1469
1470	const msg label = parts->dp.pscheme->custom_attributes[attr_no].label;
1471	if (label != MSG_ptn_boot)
1472		return false;
1473
1474	if (p->gp_attr & GPT_ATTR_BOOT) {
1475		p->gp_attr &= ~GPT_ATTR_BOOT;
1476	} else {
1477		for (i = 0, p = parts->partitions; p != NULL;
1478		    i++, p = p->gp_next)
1479			if (i == ptn)
1480				p->gp_attr |= GPT_ATTR_BOOT;
1481			else
1482				p->gp_attr &= ~GPT_ATTR_BOOT;
1483	}
1484	return true;
1485}
1486
1487static bool
1488gpt_custom_attribute_set_str(struct disk_partitions *arg,
1489    part_id ptn, size_t attr_no, const char *new_val)
1490{
1491	const struct gpt_disk_partitions *parts =
1492	    (const struct gpt_disk_partitions*)arg;
1493	size_t i;
1494	struct gpt_part_entry *p;
1495
1496	for (i = 0, p = parts->partitions; p != NULL; i++, p = p->gp_next)
1497		if (i == ptn)
1498			break;
1499
1500	if (p == NULL)
1501		return false;
1502
1503	if (attr_no >= parts->dp.pscheme->custom_attribute_count)
1504		return false;
1505
1506	const msg label = parts->dp.pscheme->custom_attributes[attr_no].label;
1507
1508	if (label != MSG_ptn_label)
1509		return false;
1510
1511	strlcpy(p->gp_label, new_val, sizeof(p->gp_label));
1512	return true;
1513}
1514
1515static bool
1516gpt_have_boot_support(const char *disk)
1517{
1518#ifdef	HAVE_GPT_BOOT
1519	return true;
1520#else
1521	return false;
1522#endif
1523}
1524
1525const struct disk_part_custom_attribute gpt_custom_attrs[] = {
1526	{ .label = MSG_ptn_label,	.type = pet_str },
1527	{ .label = MSG_ptn_uuid,	.type = pet_str },
1528	{ .label = MSG_ptn_gpt_type,	.type = pet_str },
1529	{ .label = MSG_ptn_boot,	.type = pet_bool },
1530};
1531
1532const struct disk_partitioning_scheme
1533gpt_parts = {
1534	.name = MSG_parttype_gpt,
1535	.short_name = MSG_parttype_gpt_short,
1536	.part_flag_desc = MSG_gpt_flag_desc,
1537	.custom_attribute_count = __arraycount(gpt_custom_attrs),
1538	.custom_attributes = gpt_custom_attrs,
1539	.get_part_types_count = gpt_type_count,
1540	.get_part_type = gpt_get_ptype,
1541	.get_generic_part_type = gpt_get_generic_type,
1542	.get_fs_part_type = gpt_get_fs_part_type,
1543	.get_part_alignment = gpt_get_part_alignment,
1544	.read_from_disk = gpt_read_from_disk,
1545	.create_new_for_disk = gpt_create_new,
1546	.have_boot_support = gpt_have_boot_support,
1547	.can_add_partition = gpt_can_add_partition,
1548	.custom_attribute_writable = gpt_custom_attribute_writable,
1549	.format_custom_attribute = gpt_format_custom_attribute,
1550	.custom_attribute_toggle = gpt_custom_attribute_toggle,
1551	.custom_attribute_set_str = gpt_custom_attribute_set_str,
1552	.get_part_device = gpt_get_part_device,
1553	.max_free_space_at = gpt_max_free_space_at,
1554	.get_free_spaces = gpt_get_free_spaces,
1555	.adapt_foreign_part_info = gpt_adapt,
1556	.get_part_info = gpt_get_part_info,
1557	.get_part_attr_str = gpt_get_part_attr_str,
1558	.set_part_info = gpt_set_part_info,
1559	.add_partition = gpt_add_part,
1560	.delete_all_partitions = gpt_delete_all_partitions,
1561	.delete_partition = gpt_delete_partition,
1562	.write_to_disk = gpt_write_to_disk,
1563	.free = gpt_free,
1564};
1565