gpt.c revision 1.6
1/*	$NetBSD: gpt.c,v 1.6 2019/07/28 16:30:36 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 != NULL) {
358
359			if (p->gp_type->fsflags != 0) {
360				const char *lm = get_last_mounted(fd,
361				    p->gp_start, &p->fs_type,
362				    &p->fs_sub_type, p->gp_type->fsflags);
363				if (lm != NULL && *lm != 0) {
364					char *path = strdup(lm);
365					canonicalize_last_mounted(path);
366					p->last_mounted = path;
367				} else {
368					p->fs_type = p->gp_type->
369					    default_fs_type;
370#ifdef DEFAULT_UFS2
371					fs_is_default = true;
372#endif
373				}
374			} else {
375				p->fs_type = p->gp_type->default_fs_type;
376#ifdef DEFAULT_UFS2
377				fs_is_default = true;
378#endif
379			}
380#ifdef DEFAULT_UFS2
381			if (fs_is_default && p->fs_type == FS_BSDFFS)
382				p->fs_sub_type = 2;
383#endif
384		}
385
386		parts->dp.free_space -= p->gp_size;
387	}
388	close(fd);
389
390	return &parts->dp;
391}
392
393static struct disk_partitions *
394gpt_create_new(const char *disk, daddr_t start, daddr_t len, daddr_t total,
395    bool is_boot_drive)
396{
397	struct gpt_disk_partitions *parts;
398
399	if (start != 0) {
400		assert(0);
401		return NULL;
402	}
403
404	parts = calloc(1, sizeof(*parts));
405	if (!parts)
406		return NULL;
407
408	parts->dp.pscheme = &gpt_parts;
409	parts->dp.disk = disk;
410
411	gpt_md_init(is_boot_drive, &parts->max_num_parts, &parts->prologue,
412	    &parts->epilogue);
413
414	parts->dp.disk_start = start;
415	parts->dp.disk_size = len;
416	parts->dp.free_space = len - start - parts->prologue - parts->epilogue;
417	parts->has_gpt = false;
418
419	return &parts->dp;
420}
421
422static bool
423gpt_get_part_info(const struct disk_partitions *arg, part_id id,
424    struct disk_part_info *info)
425{
426	static const struct part_type_desc gpt_unknown_type =
427		{ .generic_ptype = PT_undef,
428		  .short_desc = "<unknown>" };
429	const struct gpt_disk_partitions *parts =
430	    (const struct gpt_disk_partitions*)arg;
431	const struct gpt_part_entry *p = parts->partitions;
432	part_id no;
433
434	for (no = 0; p != NULL && no < id; no++)
435		p = p->gp_next;
436
437	if (no != id || p == NULL)
438		return false;
439
440	memset(info, 0, sizeof(*info));
441	info->start = p->gp_start;
442	info->size = p->gp_size;
443	if (p->gp_type)
444		info->nat_type = &p->gp_type->gent;
445	else
446		info->nat_type = &gpt_unknown_type;
447	info->last_mounted = p->last_mounted;
448	info->fs_type = p->fs_type;
449	info->fs_sub_type = p->fs_sub_type;
450
451	return true;
452}
453
454static bool
455gpt_get_part_attr_str(const struct disk_partitions *arg, part_id id,
456    char *str, size_t avail_space)
457{
458	const struct gpt_disk_partitions *parts =
459	    (const struct gpt_disk_partitions*)arg;
460	const struct gpt_part_entry *p = parts->partitions;
461	part_id no;
462	static const char *flags = NULL;
463
464	for (no = 0; p != NULL && no < id; no++)
465		p = p->gp_next;
466
467	if (no != id || p == NULL)
468		return false;
469
470	if (flags == NULL)
471		flags = msg_string(MSG_gpt_flags);
472
473	if (avail_space < 2)
474		return false;
475
476	if (p->gp_attr & GPT_ATTR_BOOT)
477		*str++ = flags[0];
478	*str = 0;
479
480	return true;
481}
482
483/*
484 * Find insert position and check for duplicates.
485 * If all goes well, insert the new "entry" in the "list".
486 * If there are collisions, report "no free space".
487 * We keep all lists sorted by start sector number,
488 */
489static bool
490gpt_insert_part_into_list(struct gpt_disk_partitions *parts,
491    struct gpt_part_entry **list,
492    struct gpt_part_entry *entry, const char **err_msg)
493{
494	struct gpt_part_entry *p, *last;
495
496	/* find the first entry past the new one (if any) */
497	for (last = NULL, p = *list; p != NULL; last = p, p = p->gp_next) {
498		if (p->gp_start > entry->gp_start)
499			break;
500	}
501
502	/* check if last partition overlaps with new one */
503	if (last) {
504		if (last->gp_start + last->gp_size > entry->gp_start) {
505			if (err_msg)
506				*err_msg = msg_string(MSG_No_free_space);
507			return false;
508		}
509	}
510
511	if (p == NULL) {
512		entry->gp_next = NULL;
513		if (last != NULL) {
514			last->gp_next = entry;
515		}
516	} else {
517		/* check if new entry overlaps with next */
518		if (entry->gp_start + entry->gp_size > p->gp_start) {
519			if (err_msg)
520				*err_msg = msg_string(MSG_No_free_space);
521			return false;
522		}
523
524		entry->gp_next = p;
525		if (last != NULL)
526			last->gp_next = entry;
527		else
528			*list = entry;
529	}
530	if (*list == NULL)
531		*list = entry;
532
533	return true;
534}
535
536static bool
537gpt_set_part_info(struct disk_partitions *arg, part_id id,
538    const struct disk_part_info *info, const char **err_msg)
539{
540	struct gpt_disk_partitions *parts =
541	    (struct gpt_disk_partitions*)arg;
542	struct gpt_part_entry *p = parts->partitions, *n;
543	part_id no;
544	daddr_t lendiff;
545
546	for (no = 0; p != NULL && no < id; no++)
547		p = p->gp_next;
548
549	if (no != id || p == NULL)
550		return false;
551
552	if ((p->gp_flags & GPEF_ON_DISK)) {
553		if (info->start != p->gp_start) {
554			/* partition moved, we need to delete and re-add */
555			n = calloc(1, sizeof(*n));
556			if (n == NULL) {
557				if (err_msg)
558					*err_msg = err_outofmem;
559				return false;
560			}
561			*n = *p;
562			p->gp_flags &= ~GPEF_ON_DISK;
563			if (!gpt_insert_part_into_list(parts, &parts->obsolete,
564			    n, err_msg))
565				return false;
566		} else if (info->size != p->gp_size) {
567			p->gp_flags |= GPEF_RESIZED;
568		}
569	}
570
571	p->gp_flags |= GPEF_MODIFIED;
572
573	lendiff = info->size - p->gp_size;
574	parts->dp.free_space -= lendiff;
575	return gpt_info_to_part(p, info, err_msg);
576}
577
578static size_t
579gpt_get_free_spaces_internal(const struct gpt_disk_partitions *parts,
580    struct disk_part_free_space *result, size_t max_num_result,
581    daddr_t min_space_size, daddr_t align, daddr_t start, daddr_t ignore)
582{
583	size_t cnt = 0;
584	daddr_t s, e, from, size, end_of_disk;
585	struct gpt_part_entry *p;
586
587	if (align > 1)
588		start = max(roundup(start, align), align);
589	if (start < 0 || start < (daddr_t)parts->prologue)
590		start = parts->prologue;
591	if (parts->dp.disk_start != 0 && parts->dp.disk_start > start)
592		start = parts->dp.disk_start;
593	if (min_space_size < 1)
594		min_space_size = 1;
595	end_of_disk = parts->dp.disk_start + parts->dp.disk_size
596	    - parts->epilogue;
597	from = start;
598	while (from < end_of_disk && cnt < max_num_result) {
599again:
600		size = parts->dp.disk_start + parts->dp.disk_size - from;
601		start = from;
602		if (start + size > end_of_disk)
603			size = end_of_disk - start;
604		for (p = parts->partitions; p != NULL; p = p->gp_next) {
605			s = p->gp_start;
606			e = p->gp_size + s;
607			if (s == ignore)
608				continue;
609			if (e < from)
610				continue;
611			if (s <= from && e > from) {
612				if (e - 1 >= end_of_disk)
613					return cnt;
614				from = e + 1;
615				if (align > 1) {
616					from = max(roundup(from, align), align);
617					if (from >= end_of_disk) {
618						size = 0;
619						break;
620					}
621				}
622				goto again;
623			}
624			if (s > from && s - from < size) {
625				size = s - from;
626			}
627		}
628		if (size >= min_space_size) {
629			result->start = start;
630			result->size = size;
631			result++;
632			cnt++;
633		}
634		from += size + 1;
635		if (align > 1)
636			from = max(roundup(from, align), align);
637	}
638
639	return cnt;
640}
641
642static daddr_t
643gpt_max_free_space_at(const struct disk_partitions *arg, daddr_t start)
644{
645	const struct gpt_disk_partitions *parts =
646	    (const struct gpt_disk_partitions*)arg;
647	struct disk_part_free_space space;
648
649	if (gpt_get_free_spaces_internal(parts, &space, 1, 1, 0,
650	    start, start) == 1)
651		return space.size;
652
653	return 0;
654}
655
656static size_t
657gpt_get_free_spaces(const struct disk_partitions *arg,
658    struct disk_part_free_space *result, size_t max_num_result,
659    daddr_t min_space_size, daddr_t align, daddr_t start,
660    daddr_t ignore)
661{
662	const struct gpt_disk_partitions *parts =
663	    (const struct gpt_disk_partitions*)arg;
664
665	return gpt_get_free_spaces_internal(parts, result,
666	    max_num_result, min_space_size, align, start, ignore);
667}
668
669
670static bool
671gpt_adapt(const struct disk_partitions *arg,
672    const struct disk_part_info *src, struct disk_part_info *dest)
673{
674	/* slightly simplistic, enhance when needed */
675	memcpy(dest, src, sizeof(*dest));
676
677	if (src->nat_type == NULL)
678		return false;
679
680	dest->nat_type = arg->pscheme->get_generic_part_type(
681	    src->nat_type->generic_ptype);
682	if (dest->nat_type == NULL)
683		dest->nat_type = arg->pscheme->get_generic_part_type(
684		    PT_unknown);
685
686	return true;
687}
688
689static void
690gpt_match_ptype(const char *name, struct gpt_ptype_desc *t)
691{
692	size_t i;
693
694	for (i = 0; i < __arraycount(gpt_fs_types); i++) {
695		if (strcmp(name, gpt_fs_types[i].name) == 0) {
696			t->gent.generic_ptype = gpt_fs_types[i].ptype;
697			t->fsflags = gpt_fs_types[i].fsflags;
698			t->default_fs_type = gpt_fs_types[i].fstype;
699			return;
700		}
701	}
702
703	t->gent.generic_ptype = PT_unknown;
704	t->fsflags = 0;
705	t->default_fs_type = FS_BSDFFS;
706}
707
708static void
709gpt_internal_add_ptype(const char *uid, const char *name, const char *desc)
710{
711	strlcpy(gpt_ptype_descs[gpt_ptype_cnt].tid, uid,
712	    sizeof(gpt_ptype_descs[gpt_ptype_cnt].tid));
713	gpt_ptype_descs[gpt_ptype_cnt].gent.short_desc = name;
714	gpt_ptype_descs[gpt_ptype_cnt].gent.description = desc;
715	gpt_match_ptype(name, &gpt_ptype_descs[gpt_ptype_cnt]);
716	gpt_ptype_cnt++;
717}
718
719static void
720gpt_init_ptypes(void)
721{
722	if (gpt_ptype_cnt == 0)
723		gpt_uuid_query(gpt_internal_add_ptype);
724}
725
726static size_t
727gpt_type_count(void)
728{
729	if (gpt_ptype_cnt == 0)
730		gpt_init_ptypes();
731
732	return gpt_ptype_cnt;
733}
734
735static const struct part_type_desc *
736gpt_get_ptype(size_t ndx)
737{
738	if (gpt_ptype_cnt == 0)
739		gpt_init_ptypes();
740
741	if (ndx >= gpt_ptype_cnt)
742		return NULL;
743
744	return &gpt_ptype_descs[ndx].gent;
745}
746
747static const struct part_type_desc *
748gpt_get_generic_type(enum part_type gent)
749{
750	if (gpt_ptype_cnt == 0)
751		gpt_init_ptypes();
752
753	for (size_t i = 0; i < gpt_ptype_cnt; i++)
754		if (gpt_ptype_descs[i].gent.generic_ptype == gent)
755			return &gpt_ptype_descs[i].gent;
756
757	return NULL;
758}
759
760static const struct gpt_ptype_desc *
761gpt_find_native_type(const struct part_type_desc *gent)
762{
763	if (gpt_ptype_cnt == 0)
764		gpt_init_ptypes();
765
766	if (gent == NULL)
767		return NULL;
768
769	for (size_t i = 0; i < gpt_ptype_cnt; i++)
770		if (gent == &gpt_ptype_descs[i].gent)
771			return &gpt_ptype_descs[i];
772
773	gent = gpt_get_generic_type(gent->generic_ptype);
774	if (gent == NULL)
775		return NULL;
776
777	/* this can not recurse deeper than once, we would not have found a
778	 * generic type a few lines above if it would. */
779	return gpt_find_native_type(gent);
780}
781
782static const struct gpt_ptype_desc *
783gpt_find_guid_type(const char *uid)
784{
785	if (gpt_ptype_cnt == 0)
786		gpt_init_ptypes();
787
788	if (uid == NULL || uid[0] == 0)
789		return NULL;
790
791	for (size_t i = 0; i < gpt_ptype_cnt; i++)
792		if (strcmp(gpt_ptype_descs[i].tid, uid) == 0)
793			return &gpt_ptype_descs[i];
794
795	return NULL;
796}
797
798static const struct part_type_desc *
799gpt_find_type(const char *desc)
800{
801	if (gpt_ptype_cnt == 0)
802		gpt_init_ptypes();
803
804	if (desc == NULL || desc[0] == 0)
805		return NULL;
806
807	for (size_t i = 0; i < gpt_ptype_cnt; i++)
808		if (strcmp(gpt_ptype_descs[i].gent.short_desc, desc) == 0)
809			return &gpt_ptype_descs[i].gent;
810
811	return NULL;
812}
813
814static const struct part_type_desc *
815gpt_get_fs_part_type(unsigned fstype, unsigned fs_sub_type)
816{
817	size_t i;
818
819	for (i = 0; i < __arraycount(gpt_fs_types); i++)
820		if (fstype == gpt_fs_types[i].fstype)
821			return gpt_find_type(gpt_fs_types[i].name);
822
823	return gpt_get_generic_type(PT_root);
824}
825
826static daddr_t
827gpt_get_part_alignment(const struct disk_partitions *parts)
828{
829
830	assert(parts->disk_size > 0);
831	if (parts->disk_size < 0)
832		return 1;
833
834	/* Use 1MB offset/alignemnt for large (>128GB) disks */
835	if (parts->disk_size > HUGE_DISK_SIZE)
836		return 2048;
837	else if (parts->disk_size > TINY_DISK_SIZE)
838		return 64;
839	else
840		return 4;
841}
842
843static bool
844gpt_can_add_partition(const struct disk_partitions *arg)
845{
846	const struct gpt_disk_partitions *parts =
847	    (const struct gpt_disk_partitions*)arg;
848	struct disk_part_free_space space;
849	daddr_t align;
850
851	if (parts->dp.num_part >= parts->max_num_parts)
852		return false;
853
854	align = gpt_get_part_alignment(arg);
855	if (parts->dp.free_space <= align)
856		return false;
857
858	if (gpt_get_free_spaces_internal(parts, &space, 1, align, align,
859	    0, -1) < 1)
860		return false;
861
862	return true;
863}
864
865static bool
866gpt_info_to_part(struct gpt_part_entry *p, const struct disk_part_info *info,
867    const char **err_msg)
868{
869	p->gp_type = gpt_find_native_type(info->nat_type);
870	p->gp_start = info->start;
871	p->gp_size = info->size;
872	if (info->last_mounted != NULL && info->last_mounted !=
873	    p->last_mounted) {
874		free(__UNCONST(p->last_mounted));
875		p->last_mounted = strdup(info->last_mounted);
876	}
877	p->fs_type = info->fs_type;
878	p->fs_sub_type = info->fs_sub_type;
879
880	return true;
881}
882
883static part_id
884gpt_add_part(struct disk_partitions *arg,
885    const struct disk_part_info *info, const char **err_msg)
886{
887	struct gpt_disk_partitions *parts =
888	    (struct gpt_disk_partitions*)arg;
889	struct disk_part_free_space space;
890	struct disk_part_info data = *info;
891	struct gpt_part_entry *p;
892	bool ok;
893
894	if (err_msg != NULL)
895		*err_msg = NULL;
896
897	if (gpt_get_free_spaces_internal(parts, &space, 1, 1, 1,
898	    info->start, -1) < 1) {
899		if (err_msg)
900			*err_msg = msg_string(MSG_No_free_space);
901		return NO_PART;
902	}
903	if (parts->dp.num_part >= parts->max_num_parts) {
904		if (err_msg)
905			*err_msg = msg_string(MSG_err_too_many_partitions);
906		return NO_PART;
907	}
908
909	if (data.size > space.size)
910		data.size = space.size;
911
912	p = calloc(1, sizeof(*p));
913	if (p == NULL) {
914		if (err_msg != NULL)
915			*err_msg = INTERNAL_ERROR;
916		return NO_PART;
917	}
918	if (!gpt_info_to_part(p, &data, err_msg)) {
919		free(p);
920		return NO_PART;
921	}
922	p->gp_flags |= GPEF_MODIFIED;
923	ok = gpt_insert_part_into_list(parts, &parts->partitions, p, err_msg);
924	if (ok) {
925		parts->dp.num_part++;
926		parts->dp.free_space -= p->gp_size;
927		return parts->dp.num_part-1;
928	} else {
929		free(p);
930		return NO_PART;
931	}
932}
933
934static bool
935gpt_delete_partition(struct disk_partitions *arg, part_id id,
936    const char **err_msg)
937{
938	struct gpt_disk_partitions *parts = (struct gpt_disk_partitions*)arg;
939	struct gpt_part_entry *p, *last = NULL;
940	part_id i;
941	bool res;
942
943	if (parts->dp.num_part == 0)
944		return false;
945
946	for (i = 0, p = parts->partitions;
947	    i != id && i < parts->dp.num_part && p != NULL;
948	    i++, p = p->gp_next)
949		last = p;
950
951	if (p == NULL) {
952		if (err_msg)
953			*err_msg = INTERNAL_ERROR;
954		return false;
955	}
956
957	if (last == NULL)
958		parts->partitions = p->gp_next;
959	else
960		last->gp_next = p->gp_next;
961
962	res = true;
963	if (p->gp_flags & GPEF_ON_DISK) {
964		if (!gpt_insert_part_into_list(parts, &parts->obsolete,
965		    p, err_msg))
966			res = false;
967	} else {
968		free(p);
969	}
970
971	if (res) {
972		parts->dp.num_part--;
973		parts->dp.free_space += p->gp_size;
974	}
975
976	return res;
977}
978
979static bool
980gpt_delete_all_partitions(struct disk_partitions *arg)
981{
982	struct gpt_disk_partitions *parts = (struct gpt_disk_partitions*)arg;
983
984	while (parts->dp.num_part > 0) {
985		if (!gpt_delete_partition(&parts->dp, 0, NULL))
986			return false;
987	}
988
989	return true;
990}
991
992static bool
993gpt_read_part(const char *disk, daddr_t start, struct gpt_part_entry *p)
994{
995	char *textbuf, *t, *tt;
996	static const char expected_hdr[] = "Details for index ";
997
998	/* run gpt show for this partition */
999	if (collect(T_OUTPUT, &textbuf,
1000	    "gpt -r show -b %" PRIu64 " %s 2>/dev/null", start, disk) < 1)
1001		return false;
1002
1003	/*
1004	 * gpt show should respond with single partition details, but will
1005	 * fall back to "show -a" output if something is wrong
1006	 */
1007	t = strtok(textbuf, "\n"); /* first line is special */
1008	if (strncmp(t, expected_hdr, sizeof(expected_hdr)-1) != 0) {
1009		free(textbuf);
1010		return false;
1011	}
1012
1013	/* parse output into "old" */
1014	while ((t = strtok(NULL, "\n")) != NULL) {
1015		tt = strsep(&t, " \t");
1016		if (strlen(tt) == 0)
1017			continue;
1018		gpt_add_info(p, tt, t, true);
1019	}
1020	free(textbuf);
1021
1022	return true;
1023}
1024
1025static bool
1026gpt_apply_attr(const char *disk, const char *cmd, off_t start, uint todo)
1027{
1028	size_t i;
1029	char attr_str[STRSIZE];
1030
1031	if (todo == 0)
1032		return true;
1033
1034	strcpy(attr_str, "-a ");
1035	for (i = 0; todo != 0; i++) {
1036		if (!(gpt_avail_attrs[i].flag & todo))
1037			continue;
1038		todo &= ~gpt_avail_attrs[i].flag;
1039		if (attr_str[0])
1040			strlcat(attr_str, ",",
1041			    sizeof(attr_str));
1042		strlcat(attr_str,
1043		    gpt_avail_attrs[i].name,
1044		    sizeof(attr_str));
1045	}
1046	if (run_program(RUN_SILENT,
1047	    "gpt %s %s -b %" PRIu64 " %s", cmd, attr_str, start, disk) != 0)
1048		return false;
1049	return true;
1050}
1051
1052/*
1053 * Modify an existing on-disk partition.
1054 * Start and size can not be changed here, caller needs to deal
1055 * with that kind of changes upfront.
1056 */
1057static bool
1058gpt_modify_part(const char *disk, struct gpt_part_entry *p)
1059{
1060	struct gpt_part_entry old;
1061	uint todo_set, todo_unset;
1062
1063	/*
1064	 * Query current on-disk state
1065	 */
1066	memset(&old, 0, sizeof old);
1067	if (!gpt_read_part(disk, p->gp_start, &old))
1068		return false;
1069
1070	/* Reject unsupported changes */
1071	if (old.gp_start != p->gp_start || old.gp_size != p->gp_size)
1072		return false;
1073
1074	/*
1075	 * GUID should never change, but the internal copy
1076	 * may not yet know it.
1077	 */
1078	strcpy(p->gp_id, old.gp_id);
1079
1080	/* Check type */
1081	if (p->gp_type != old.gp_type) {
1082		if (run_program(RUN_SILENT,
1083		    "gpt label -b %" PRIu64 " -T %s %s",
1084		    p->gp_start, p->gp_type->tid, disk) != 0)
1085			return false;
1086	}
1087
1088	/* Check label */
1089	if (strcmp(p->gp_label, old.gp_label) != 0) {
1090		if (run_program(RUN_SILENT,
1091		    "gpt label -b %" PRIu64 " -l %s %s",
1092		    p->gp_start, p->gp_label, disk) != 0)
1093			return false;
1094	}
1095
1096	/* Check attributes */
1097	if (p->gp_attr != old.gp_attr) {
1098		if (p->gp_attr == 0) {
1099			if (run_program(RUN_SILENT,
1100			    "gpt set -N -b %" PRIu64 " %s",
1101			    p->gp_start, disk) != 0)
1102				return false;
1103		} else {
1104			todo_set = (p->gp_attr ^ old.gp_attr) & p->gp_attr;
1105			todo_unset = (p->gp_attr ^ old.gp_attr) & old.gp_attr;
1106			if (!gpt_apply_attr(disk, "unset", p->gp_start,
1107			    todo_unset))
1108				return false;
1109			if (!gpt_apply_attr(disk, "set", p->gp_start,
1110			    todo_set))
1111				return false;
1112		}
1113	}
1114
1115	return true;
1116}
1117
1118/*
1119 * verbatim copy from sys/dev/dkwedge/dkwedge_bsdlabel.c:
1120 *  map FS_* to wedge strings
1121 */
1122static const char *
1123bsdlabel_fstype_to_str(uint8_t fstype)
1124{
1125	const char *str;
1126
1127	/*
1128	 * For each type known to FSTYPE_DEFN (from <sys/disklabel.h>),
1129	 * a suitable case branch will convert the type number to a string.
1130	 */
1131	switch (fstype) {
1132#define FSTYPE_TO_STR_CASE(tag, number, name, fsck, mount) \
1133	case __CONCAT(FS_,tag):	str = __CONCAT(DKW_PTYPE_,tag);			break;
1134	FSTYPE_DEFN(FSTYPE_TO_STR_CASE)
1135#undef FSTYPE_TO_STR_CASE
1136	default:		str = NULL;			break;
1137	}
1138
1139	return (str);
1140}
1141
1142static bool
1143gpt_add_wedge(const char *disk, struct gpt_part_entry *p)
1144{
1145	struct dkwedge_info dkw;
1146	const char *tname;
1147	char diskpath[MAXPATHLEN];
1148	int fd;
1149
1150	memset(&dkw, 0, sizeof(dkw));
1151	tname = bsdlabel_fstype_to_str(p->fs_type);
1152	if (tname)
1153		strlcpy(dkw.dkw_ptype, tname, sizeof(dkw.dkw_ptype));
1154
1155	strlcpy((char*)&dkw.dkw_wname, p->gp_id, sizeof(dkw.dkw_wname));
1156	dkw.dkw_offset = p->gp_start;
1157	dkw.dkw_size = p->gp_size;
1158
1159	fd = opendisk(disk, O_RDWR, diskpath, sizeof(diskpath), 0);
1160	if (fd < 0)
1161		return false;
1162	if (ioctl(fd, DIOCAWEDGE, &dkw) == -1) {
1163		close(fd);
1164		return false;
1165	}
1166	close(fd);
1167
1168	strlcpy(p->gp_dev_name, dkw.dkw_devname, sizeof(p->gp_dev_name));
1169	p->gp_flags |= GPEF_WEDGE;
1170	return true;
1171}
1172
1173static bool
1174gpt_get_part_device(const struct disk_partitions *arg,
1175    part_id id, char *devname, size_t max_devname_len, int *part,
1176    enum dev_name_usage usage, bool with_path)
1177{
1178	const struct gpt_disk_partitions *parts =
1179	    (const struct gpt_disk_partitions*)arg;
1180	struct  gpt_part_entry *p = parts->partitions;
1181	part_id no;
1182
1183
1184	for (no = 0; p != NULL && no < id; no++)
1185		p = p->gp_next;
1186
1187	if (no != id || p == NULL)
1188		return false;
1189
1190	if (part)
1191		*part = -1;
1192
1193	if (!(p->gp_flags & GPEF_WEDGE) &&
1194	    (usage == plain_name || usage == raw_dev_name))
1195		gpt_add_wedge(arg->disk, p);
1196
1197	switch (usage) {
1198	case logical_name:
1199		if (p->gp_label[0] != 0)
1200			snprintf(devname, max_devname_len,
1201			    "NAME=%s", p->gp_label);
1202		else
1203			snprintf(devname, max_devname_len,
1204			    "NAME=%s", p->gp_id);
1205		break;
1206	case plain_name:
1207		assert(p->gp_flags & GPEF_WEDGE);
1208		if (with_path)
1209			snprintf(devname, max_devname_len, _PATH_DEV "%s",
1210			    p->gp_dev_name);
1211		else
1212			strlcpy(devname, p->gp_dev_name, max_devname_len);
1213		break;
1214	case raw_dev_name:
1215		assert(p->gp_flags & GPEF_WEDGE);
1216		if (with_path)
1217			snprintf(devname, max_devname_len, _PATH_DEV "r%s",
1218			    p->gp_dev_name);
1219		else
1220			snprintf(devname, max_devname_len, "r%s",
1221			    p->gp_dev_name);
1222		break;
1223	default:
1224		return false;
1225	}
1226
1227	return true;
1228}
1229
1230static bool
1231gpt_write_to_disk(struct disk_partitions *arg)
1232{
1233	struct gpt_disk_partitions *parts = (struct gpt_disk_partitions*)arg;
1234	struct gpt_part_entry *p, *n;
1235	char label_arg[sizeof(p->gp_label) + 4];
1236	char diskpath[MAXPATHLEN];
1237	int fd, bits = 0;
1238	bool root_is_new = false, efi_is_new = false;
1239	part_id root_id = NO_PART, efi_id = NO_PART, pno;
1240
1241	/*
1242	 * Remove all wedges on this disk - they may become invalid and we
1243	 * have no easy way to associate them with the partitioning data.
1244	 * Instead we will explicitly request creation of wedges on demand
1245	 * later.
1246	 */
1247	fd = opendisk(arg->disk, O_RDWR, diskpath, sizeof(diskpath), 0);
1248	if (fd < 0)
1249		return false;
1250	if (ioctl(fd, DIOCRMWEDGES, &bits) == -1)
1251		return false;
1252	close(fd);
1253
1254	/*
1255	 * Mark all partitions as "have no wedge yet". While there,
1256	 * collect first root and efi partition (if available)
1257	 */
1258	for (pno = 0, p = parts->partitions; p != NULL; p = p->gp_next, pno++) {
1259		p->gp_flags &= ~GPEF_WEDGE;
1260		if (root_id == NO_PART && p->gp_type != NULL) {
1261			if (p->gp_type->gent.generic_ptype == PT_root &&
1262			    p->gp_start == pm->ptstart) {
1263				root_id = pno;
1264				root_is_new = !(p->gp_flags & GPEF_ON_DISK);
1265			} else if (efi_id == NO_PART &&
1266			    p->gp_type->gent.generic_ptype == PT_EFI_SYSTEM) {
1267				efi_id = pno;
1268				efi_is_new = !(p->gp_flags & GPEF_ON_DISK);
1269			}
1270		}
1271	}
1272
1273	/*
1274	 * If no GPT on disk yet, create it.
1275	 */
1276	if (!parts->has_gpt) {
1277		char limit[30];
1278
1279		if (parts->max_num_parts > 0)
1280			sprintf(limit, "-p %zu", parts->max_num_parts);
1281		else
1282			limit[0] = 0;
1283		if (run_program(RUN_SILENT, "gpt create %s %s",
1284		    limit, parts->dp.disk))
1285			return false;
1286		parts->has_gpt = true;
1287	}
1288
1289	/*
1290	 * Delete all old partitions
1291	 */
1292	for (p = parts->obsolete; p != NULL; p = n) {
1293		run_program(RUN_SILENT, "gpt -n remove -b %" PRIu64 " %s",
1294		    p->gp_start, arg->disk);
1295		n = p->gp_next;
1296		free(p);
1297	}
1298	parts->obsolete = NULL;
1299
1300	/*
1301	 * Modify existing but changed partitions
1302	 */
1303	for (p = parts->partitions; p != NULL; p = p->gp_next) {
1304		if (!(p->gp_flags & GPEF_ON_DISK))
1305			continue;
1306
1307		if (p->gp_flags & GPEF_RESIZED) {
1308			run_program(RUN_SILENT,
1309			    "gpt -n resize -b %" PRIu64 " -s %" PRIu64 "s %s",
1310			    p->gp_start, p->gp_size, arg->disk);
1311			p->gp_flags &= ~GPEF_RESIZED;
1312		}
1313
1314		if (!(p->gp_flags & GPEF_MODIFIED))
1315			continue;
1316
1317		if (!gpt_modify_part(parts->dp.disk, p))
1318			return false;
1319	}
1320
1321	/*
1322	 * Add new partitions
1323	 */
1324	for (p = parts->partitions; p != NULL; p = p->gp_next) {
1325		if (p->gp_flags & GPEF_ON_DISK)
1326			continue;
1327		if (!(p->gp_flags & GPEF_MODIFIED))
1328			continue;
1329
1330		if (p->gp_label[0] == 0)
1331			label_arg[0] = 0;
1332		else
1333			sprintf(label_arg, "-l %s", p->gp_label);
1334
1335		if (p->gp_type != NULL)
1336			run_program(RUN_SILENT,
1337			    "gpt -n add -b %" PRIu64 " -s %" PRIu64
1338			    "s -t %s %s %s",
1339			    p->gp_start, p->gp_size, p->gp_type->tid,
1340			    label_arg, arg->disk);
1341		else
1342			run_program(RUN_SILENT,
1343			    "gpt -n add -b %" PRIu64 " -s %" PRIu64
1344			    "s %s %s",
1345			    p->gp_start, p->gp_size, label_arg, arg->disk);
1346		gpt_apply_attr(arg->disk, "set", p->gp_start, p->gp_attr);
1347		gpt_read_part(arg->disk, p->gp_start, p);
1348		p->gp_flags |= GPEF_ON_DISK;
1349	}
1350
1351	/*
1352	 * Additional MD bootloader magic...
1353	 */
1354	if (!md_gpt_post_write(&parts->dp, root_id, root_is_new, efi_id,
1355	    efi_is_new))
1356		return false;
1357
1358	return true;
1359}
1360
1361bool
1362gpt_parts_check(void)
1363{
1364
1365	check_available_binaries();
1366
1367	return have_gpt && have_dk;
1368}
1369
1370static void
1371gpt_free(struct disk_partitions *arg)
1372{
1373	struct gpt_disk_partitions *parts = (struct gpt_disk_partitions*)arg;
1374	struct gpt_part_entry *p, *n;
1375
1376	assert(parts != NULL);
1377	for (p = parts->partitions; p != NULL; p = n) {
1378		free(__UNCONST(p->last_mounted));
1379		n = p->gp_next;
1380		free(p);
1381	}
1382	free(parts);
1383}
1384
1385static bool
1386gpt_custom_attribute_writable(const struct disk_partitions *arg,
1387    part_id ptn, size_t attr_no)
1388{
1389	const struct gpt_disk_partitions *parts =
1390	    (const struct gpt_disk_partitions*)arg;
1391	size_t i;
1392	struct gpt_part_entry *p;
1393
1394	if (attr_no >= arg->pscheme->custom_attribute_count)
1395		return false;
1396
1397	const msg label = arg->pscheme->custom_attributes[attr_no].label;
1398
1399	/* we can not edit the uuid attribute */
1400	if (label == MSG_ptn_uuid)
1401		return false;
1402
1403	/* the label is always editable */
1404	if (label == MSG_ptn_label)
1405		return true;
1406
1407	/* the GPT type is read only */
1408	if (label == MSG_ptn_gpt_type)
1409		return false;
1410
1411	/* BOOTME makes no sense on swap partitions */
1412	for (i = 0, p = parts->partitions; p != NULL; i++, p = p->gp_next)
1413		if (i == ptn)
1414			break;
1415
1416	if (p == NULL)
1417		return false;
1418
1419	if (p->fs_type == FS_SWAP ||
1420	    (p->gp_type != NULL && p->gp_type->gent.generic_ptype == PT_swap))
1421		return false;
1422
1423	return true;
1424}
1425
1426static const char *
1427gpt_get_label_str(const struct disk_partitions *arg, part_id ptn)
1428{
1429	const struct gpt_disk_partitions *parts =
1430	    (const struct gpt_disk_partitions*)arg;
1431	size_t i;
1432	struct gpt_part_entry *p;
1433
1434	for (i = 0, p = parts->partitions; p != NULL; i++, p = p->gp_next)
1435		if (i == ptn)
1436			break;
1437
1438	if (p == NULL)
1439		return NULL;
1440
1441	if (p->gp_label[0] != 0)
1442		return p->gp_label;
1443	return p->gp_id;
1444}
1445
1446static bool
1447gpt_format_custom_attribute(const struct disk_partitions *arg,
1448    part_id ptn, size_t attr_no, const struct disk_part_info *info,
1449    char *out, size_t out_space)
1450{
1451	const struct gpt_disk_partitions *parts =
1452	    (const struct gpt_disk_partitions*)arg;
1453	size_t i;
1454	struct gpt_part_entry *p, data;
1455
1456	for (i = 0, p = parts->partitions; p != NULL; i++, p = p->gp_next)
1457		if (i == ptn)
1458			break;
1459
1460	if (p == NULL)
1461		return false;
1462
1463	if (attr_no >= parts->dp.pscheme->custom_attribute_count)
1464		return false;
1465
1466	const msg label = parts->dp.pscheme->custom_attributes[attr_no].label;
1467
1468	if (info != NULL) {
1469		data = *p;
1470		gpt_info_to_part(&data, info, NULL);
1471		p = &data;
1472	}
1473
1474	if (label == MSG_ptn_label)
1475		strlcpy(out, p->gp_label, out_space);
1476	else if (label == MSG_ptn_uuid)
1477		strlcpy(out, p->gp_id, out_space);
1478	else if (label == MSG_ptn_gpt_type) {
1479		if (p->gp_type != NULL)
1480			strlcpy(out, p->gp_type->gent.description, out_space);
1481		else if (out_space > 1)
1482			out[0] = 0;
1483	} else if (label == MSG_ptn_boot)
1484		strlcpy(out, msg_string(p->gp_attr & GPT_ATTR_BOOT ?
1485		    MSG_Yes : MSG_No), out_space);
1486	else
1487		return false;
1488
1489	return true;
1490}
1491
1492static bool
1493gpt_custom_attribute_toggle(struct disk_partitions *arg,
1494    part_id ptn, size_t attr_no)
1495{
1496	const struct gpt_disk_partitions *parts =
1497	    (const struct gpt_disk_partitions*)arg;
1498	size_t i;
1499	struct gpt_part_entry *p;
1500
1501	for (i = 0, p = parts->partitions; p != NULL; i++, p = p->gp_next)
1502		if (i == ptn)
1503			break;
1504
1505	if (p == NULL)
1506		return false;
1507
1508	if (attr_no >= parts->dp.pscheme->custom_attribute_count)
1509		return false;
1510
1511	const msg label = parts->dp.pscheme->custom_attributes[attr_no].label;
1512	if (label != MSG_ptn_boot)
1513		return false;
1514
1515	if (p->gp_attr & GPT_ATTR_BOOT) {
1516		p->gp_attr &= ~GPT_ATTR_BOOT;
1517	} else {
1518		for (i = 0, p = parts->partitions; p != NULL;
1519		    i++, p = p->gp_next)
1520			if (i == ptn)
1521				p->gp_attr |= GPT_ATTR_BOOT;
1522			else
1523				p->gp_attr &= ~GPT_ATTR_BOOT;
1524	}
1525	return true;
1526}
1527
1528static bool
1529gpt_custom_attribute_set_str(struct disk_partitions *arg,
1530    part_id ptn, size_t attr_no, const char *new_val)
1531{
1532	const struct gpt_disk_partitions *parts =
1533	    (const struct gpt_disk_partitions*)arg;
1534	size_t i;
1535	struct gpt_part_entry *p;
1536
1537	for (i = 0, p = parts->partitions; p != NULL; i++, p = p->gp_next)
1538		if (i == ptn)
1539			break;
1540
1541	if (p == NULL)
1542		return false;
1543
1544	if (attr_no >= parts->dp.pscheme->custom_attribute_count)
1545		return false;
1546
1547	const msg label = parts->dp.pscheme->custom_attributes[attr_no].label;
1548
1549	if (label != MSG_ptn_label)
1550		return false;
1551
1552	strlcpy(p->gp_label, new_val, sizeof(p->gp_label));
1553	return true;
1554}
1555
1556static bool
1557gpt_have_boot_support(const char *disk)
1558{
1559#ifdef	HAVE_GPT_BOOT
1560	return true;
1561#else
1562	return false;
1563#endif
1564}
1565
1566const struct disk_part_custom_attribute gpt_custom_attrs[] = {
1567	{ .label = MSG_ptn_label,	.type = pet_str },
1568	{ .label = MSG_ptn_uuid,	.type = pet_str },
1569	{ .label = MSG_ptn_gpt_type,	.type = pet_str },
1570	{ .label = MSG_ptn_boot,	.type = pet_bool },
1571};
1572
1573const struct disk_partitioning_scheme
1574gpt_parts = {
1575	.name = MSG_parttype_gpt,
1576	.short_name = MSG_parttype_gpt_short,
1577	.part_flag_desc = MSG_gpt_flag_desc,
1578	.custom_attribute_count = __arraycount(gpt_custom_attrs),
1579	.custom_attributes = gpt_custom_attrs,
1580	.get_part_types_count = gpt_type_count,
1581	.get_part_type = gpt_get_ptype,
1582	.get_generic_part_type = gpt_get_generic_type,
1583	.get_fs_part_type = gpt_get_fs_part_type,
1584	.get_part_alignment = gpt_get_part_alignment,
1585	.read_from_disk = gpt_read_from_disk,
1586	.create_new_for_disk = gpt_create_new,
1587	.have_boot_support = gpt_have_boot_support,
1588	.can_add_partition = gpt_can_add_partition,
1589	.custom_attribute_writable = gpt_custom_attribute_writable,
1590	.format_custom_attribute = gpt_format_custom_attribute,
1591	.custom_attribute_toggle = gpt_custom_attribute_toggle,
1592	.custom_attribute_set_str = gpt_custom_attribute_set_str,
1593	.other_partition_identifier = gpt_get_label_str,
1594	.get_part_device = gpt_get_part_device,
1595	.max_free_space_at = gpt_max_free_space_at,
1596	.get_free_spaces = gpt_get_free_spaces,
1597	.adapt_foreign_part_info = gpt_adapt,
1598	.get_part_info = gpt_get_part_info,
1599	.get_part_attr_str = gpt_get_part_attr_str,
1600	.set_part_info = gpt_set_part_info,
1601	.add_partition = gpt_add_part,
1602	.delete_all_partitions = gpt_delete_all_partitions,
1603	.delete_partition = gpt_delete_partition,
1604	.write_to_disk = gpt_write_to_disk,
1605	.free = gpt_free,
1606};
1607