xref: /openbsd/sbin/fdisk/gpt.c (revision 097a140d)
1 /*	$OpenBSD: gpt.c,v 1.12 2021/01/30 18:16:36 krw Exp $	*/
2 /*
3  * Copyright (c) 2015 Markus Muller <mmu@grummel.net>
4  * Copyright (c) 2015 Kenneth R Westerback <krw@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>	/* DEV_BSIZE */
20 #include <sys/disklabel.h>
21 #include <sys/dkio.h>
22 #include <sys/ioctl.h>
23 
24 #include <errno.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <uuid.h>
30 
31 #include "disk.h"
32 #include "misc.h"
33 #include "part.h"
34 #include "gpt.h"
35 
36 #ifdef DEBUG
37 #define DPRINTF(x...)	printf(x)
38 #else
39 #define DPRINTF(x...)
40 #endif
41 
42 struct gpt_header gh;
43 struct gpt_partition gp[NGPTPARTITIONS];
44 
45 struct gpt_partition	**sort_gpt(void);
46 int			  lba_start_cmp(const void *e1, const void *e2);
47 
48 int
49 GPT_get_header(off_t where)
50 {
51 	char *secbuf;
52 	uint64_t partlastlba;
53 	int partspersec;
54 	uint32_t orig_gh_csum, new_gh_csum;
55 
56 	secbuf = DISK_readsector(where);
57 	if (secbuf == 0)
58 		return (1);
59 
60 	memcpy(&gh, secbuf, sizeof(struct gpt_header));
61 	free(secbuf);
62 
63 	if (letoh64(gh.gh_sig) != GPTSIGNATURE) {
64 		DPRINTF("gpt signature: expected 0x%llx, got 0x%llx\n",
65 		    GPTSIGNATURE, letoh64(gh.gh_sig));
66 		return (1);
67 	}
68 
69 	if (letoh32(gh.gh_rev) != GPTREVISION) {
70 		DPRINTF("gpt revision: expected 0x%x, got 0x%x\n",
71 		    GPTREVISION, letoh32(gh.gh_rev));
72 		return (1);
73 	}
74 
75 	if (letoh64(gh.gh_lba_self) != where) {
76 		DPRINTF("gpt self lba: expected %lld, got %llu\n",
77 		    (long long)where, letoh64(gh.gh_lba_self));
78 		return (1);
79 	}
80 
81 	if (letoh32(gh.gh_size) != GPTMINHDRSIZE) {
82 		DPRINTF("gpt header size: expected %u, got %u\n",
83 		    GPTMINHDRSIZE, letoh32(gh.gh_size));
84 		return (1);
85 	}
86 
87 	if (letoh32(gh.gh_part_size) != GPTMINPARTSIZE) {
88 		DPRINTF("gpt partition size: expected %u, got %u\n",
89 		    GPTMINPARTSIZE, letoh32(gh.gh_part_size));
90 		return (1);
91 	}
92 
93 	if (letoh32(gh.gh_part_num) > NGPTPARTITIONS) {
94 		DPRINTF("gpt partition count: expected <= %u, got %u\n",
95 		    NGPTPARTITIONS, letoh32(gh.gh_part_num));
96 		return (1);
97 	}
98 
99 	orig_gh_csum = gh.gh_csum;
100 	gh.gh_csum = 0;
101 	new_gh_csum = crc32((unsigned char *)&gh, letoh32(gh.gh_size));
102 	gh.gh_csum = orig_gh_csum;
103 	if (letoh32(orig_gh_csum) != new_gh_csum) {
104 		DPRINTF("gpt header checksum: expected 0x%x, got 0x%x\n",
105 		    orig_gh_csum, new_gh_csum);
106 		return (1);
107 	}
108 
109 	if (letoh64(gh.gh_lba_end) >= DL_GETDSIZE(&dl)) {
110 		DPRINTF("gpt last usable LBA: expected < %lld, got %llu\n",
111 		    DL_GETDSIZE(&dl), letoh64(gh.gh_lba_end));
112 		return (1);
113 	}
114 
115 	if (letoh64(gh.gh_lba_start) >= letoh64(gh.gh_lba_end)) {
116 		DPRINTF("gpt first usable LBA: expected < %llu, got %llu\n",
117 		    letoh64(gh.gh_lba_start), letoh64(gh.gh_lba_start));
118 		return (1);
119 	}
120 
121 	if (letoh64(gh.gh_part_lba) <= letoh64(gh.gh_lba_end) &&
122 	    letoh64(gh.gh_part_lba) >= letoh64(gh.gh_lba_start)) {
123 		DPRINTF("gpt partition table start lba: expected < %llu or "
124 		    "> %llu, got %llu\n", letoh64(gh.gh_lba_start),
125 		    letoh64(gh.gh_lba_end), letoh64(gh.gh_part_lba));
126 		return (1);
127 	}
128 
129 	partspersec = dl.d_secsize / letoh32(gh.gh_part_size);
130 	partlastlba = letoh64(gh.gh_part_lba) +
131 	    ((letoh32(gh.gh_part_num) + partspersec - 1) / partspersec) - 1;
132 	if (partlastlba <= letoh64(gh.gh_lba_end) &&
133 	    partlastlba >= letoh64(gh.gh_lba_start)) {
134 		DPRINTF("gpt partition table last LBA: expected < %llu or "
135 		    "> %llu, got %llu\n", letoh64(gh.gh_lba_start),
136 		    letoh64(gh.gh_lba_end), partlastlba);
137 		return (1);
138 	}
139 
140 	/*
141 	 * Other possible paranoia checks:
142 	 *	1) partition table starts before primary gpt lba.
143 	 *	2) partition table extends into lowest partition.
144 	 *	3) alt partition table starts before gh_lba_end.
145 	 */
146 	return (0);
147 }
148 
149 int
150 GPT_get_partition_table(off_t where)
151 {
152 	ssize_t len;
153 	off_t off;
154 	int secs;
155 	uint32_t checksum, partspersec;
156 
157 	DPRINTF("gpt partition table being read from LBA %llu\n",
158 	    letoh64(gh.gh_part_lba));
159 
160 	partspersec = dl.d_secsize / letoh32(gh.gh_part_size);
161 	if (partspersec * letoh32(gh.gh_part_size) != dl.d_secsize) {
162 		DPRINTF("gpt partition table entry invalid size. %u\n",
163 		    letoh32(gh.gh_part_size));
164 		return (1);
165 	}
166 	secs = (letoh32(gh.gh_part_num) + partspersec - 1) / partspersec;
167 
168 	memset(&gp, 0, sizeof(gp));
169 
170 	where *= dl.d_secsize;
171 	off = lseek(disk.fd, where, SEEK_SET);
172 	if (off == -1) {
173 		DPRINTF("seek to gpt partition table @ sector %llu failed\n",
174 		    (unsigned long long)where / dl.d_secsize);
175 		return (1);
176 	}
177 	len = read(disk.fd, &gp, secs * dl.d_secsize);
178 	if (len == -1 || len != secs * dl.d_secsize) {
179 		DPRINTF("gpt partition table read failed.\n");
180 		return (1);
181 	}
182 
183 	checksum = crc32((unsigned char *)&gp, letoh32(gh.gh_part_num) *
184 	    letoh32(gh.gh_part_size));
185 	if (checksum != letoh32(gh.gh_part_csum)) {
186 		DPRINTF("gpt partition table checksum: expected %x, got %x\n",
187 		    checksum, letoh32(gh.gh_part_csum));
188 		return (1);
189 	}
190 
191 	return (0);
192 }
193 
194 void
195 GPT_get_gpt(int which)
196 {
197 	int privalid, altvalid;
198 
199 	/*
200 	 * primary header && primary partition table ||
201 	 * alt header && alt partition table
202 	 */
203 	privalid = GPT_get_header(GPTSECTOR);
204 	if (privalid == 0)
205 		privalid = GPT_get_partition_table(gh.gh_part_lba);
206 	if (which == 1 || (which == 0 && privalid == 0))
207 		return;
208 
209 	/* No valid GPT found. Zap any artifacts. */
210 	memset(&gh, 0, sizeof(gh));
211 	memset(&gp, 0, sizeof(gp));
212 
213 	altvalid = GPT_get_header(DL_GETDSIZE(&dl) - 1);
214 	if (altvalid == 0)
215 		altvalid = GPT_get_partition_table(gh.gh_part_lba);
216 	if (which == 2 || altvalid == 0)
217 		return;
218 
219 	/* No valid GPT found. Zap any artifacts. */
220 	memset(&gh, 0, sizeof(gh));
221 	memset(&gp, 0, sizeof(gp));
222 }
223 
224 void
225 GPT_print(char *units, int verbosity)
226 {
227 	const int secsize = unit_types[SECTORS].conversion;
228 	struct uuid guid;
229 	char *guidstr = NULL;
230 	double size;
231 	int i, u, status;
232 
233 	u = unit_lookup(units);
234 	size = ((double)DL_GETDSIZE(&dl) * secsize) / unit_types[u].conversion;
235 	printf("Disk: %s       Usable LBA: %llu to %llu [%.0f ",
236 	    disk.name, letoh64(gh.gh_lba_start), letoh64(gh.gh_lba_end), size);
237 
238 	if (u == SECTORS && secsize != DEV_BSIZE)
239 		printf("%d-byte ", secsize);
240 	printf("%s]\n", unit_types[u].lname);
241 
242 	if (verbosity) {
243 		printf("GUID: ");
244 		uuid_dec_le(&gh.gh_guid, &guid);
245 		uuid_to_string(&guid, &guidstr, &status);
246 		if (status == uuid_s_ok)
247 			printf("%s\n", guidstr);
248 		else
249 			printf("<invalid header GUID>\n");
250 		free(guidstr);
251 	}
252 
253 	GPT_print_parthdr(verbosity);
254 	for (i = 0; i < letoh32(gh.gh_part_num); i++) {
255 		if (uuid_is_nil(&gp[i].gp_type, NULL))
256 			continue;
257 		GPT_print_part(i, units, verbosity);
258 	}
259 
260 }
261 
262 void
263 GPT_print_parthdr(int verbosity)
264 {
265 	printf("   #: type                                "
266 	    " [       start:         size ]\n");
267 	if (verbosity)
268 		printf("      guid                                 name\n");
269 	printf("--------------------------------------------------------"
270 	    "----------------\n");
271 }
272 
273 void
274 GPT_print_part(int n, char *units, int verbosity)
275 {
276 	struct uuid guid;
277 	const int secsize = unit_types[SECTORS].conversion;
278 	struct gpt_partition *partn = &gp[n];
279 	char *guidstr = NULL;
280 	double size;
281 	int u, status;
282 
283 	uuid_dec_le(&partn->gp_type, &guid);
284 	u = unit_lookup(units);
285 	size = letoh64(partn->gp_lba_end) - letoh64(partn->gp_lba_start) + 1;
286 	size = (size * secsize) / unit_types[u].conversion;
287 	printf("%c%3d: %-36s [%12lld: %12.0f%s]\n",
288 	    (letoh64(partn->gp_attrs) & GPTDOSACTIVE)?'*':' ', n,
289 	    PRT_uuid_to_typename(&guid), letoh64(partn->gp_lba_start),
290 	    size, unit_types[u].abbr);
291 
292 	if (verbosity) {
293 		uuid_dec_le(&partn->gp_guid, &guid);
294 		uuid_to_string(&guid, &guidstr, &status);
295 		if (status != uuid_s_ok)
296 			printf("      <invalid partition guid>             ");
297 		else
298 			printf("      %-36s ", guidstr);
299 		printf("%-36s\n", utf16le_to_string(partn->gp_name));
300 		free(guidstr);
301 	}
302 }
303 
304 int
305 GPT_init(void)
306 {
307 	extern u_int32_t b_arg;
308 	const int secsize = unit_types[SECTORS].conversion;
309 	struct uuid guid;
310 	int needed;
311 	uint32_t status;
312 	const uint8_t gpt_uuid_efi_system[] = GPT_UUID_EFI_SYSTEM;
313 	const uint8_t gpt_uuid_openbsd[] = GPT_UUID_OPENBSD;
314 
315 	memset(&gh, 0, sizeof(gh));
316 	memset(&gp, 0, sizeof(gp));
317 
318 	needed = sizeof(gp) / secsize + 2;
319 	/* Start on 64 sector boundary */
320 	if (needed % 64)
321 		needed += (64 - (needed % 64));
322 
323 	gh.gh_sig = htole64(GPTSIGNATURE);
324 	gh.gh_rev = htole32(GPTREVISION);
325 	gh.gh_size = htole32(GPTMINHDRSIZE);
326 	gh.gh_csum = 0;
327 	gh.gh_rsvd = 0;
328 	gh.gh_lba_self = htole64(1);
329 	gh.gh_lba_alt = htole64(DL_GETDSIZE(&dl) - 1);
330 	gh.gh_lba_start = htole64(needed);
331 	gh.gh_lba_end = htole64(DL_GETDSIZE(&dl) - needed);
332 	gh.gh_part_lba = htole64(2);
333 	gh.gh_part_num = htole32(NGPTPARTITIONS);
334 	gh.gh_part_size = htole32(GPTMINPARTSIZE);
335 
336 	uuid_create(&guid, &status);
337 	if (status != uuid_s_ok)
338 		return (1);
339 	uuid_enc_le(&gh.gh_guid, &guid);
340 
341 #if defined(__i386__) || defined(__amd64__)
342 	if (b_arg > 0) {
343 		/* Add an EFI system partition on i386/amd64. */
344 		uuid_dec_be(gpt_uuid_efi_system, &guid);
345 		uuid_enc_le(&gp[1].gp_type, &guid);
346 		uuid_create(&guid, &status);
347 		if (status != uuid_s_ok)
348 			return (1);
349 		uuid_enc_le(&gp[1].gp_guid, &guid);
350 		gp[1].gp_lba_start = gh.gh_lba_start;
351 		gp[1].gp_lba_end = htole64(letoh64(gh.gh_lba_start)+b_arg - 1);
352 		memcpy(gp[1].gp_name, string_to_utf16le("EFI System Area"),
353 		    sizeof(gp[1].gp_name));
354 	}
355 #endif
356 	uuid_dec_be(gpt_uuid_openbsd, &guid);
357 	uuid_enc_le(&gp[3].gp_type, &guid);
358 	uuid_create(&guid, &status);
359 	if (status != uuid_s_ok)
360 		return (1);
361 	uuid_enc_le(&gp[3].gp_guid, &guid);
362 	gp[3].gp_lba_start = gh.gh_lba_start;
363 #if defined(__i386__) || defined(__amd64__)
364 	if (b_arg > 0) {
365 		gp[3].gp_lba_start = htole64(letoh64(gp[3].gp_lba_start) +
366 		    b_arg);
367 		if (letoh64(gp[3].gp_lba_start) % 64)
368 			gp[3].gp_lba_start =
369 			    htole64(letoh64(gp[3].gp_lba_start) +
370 			    (64 - letoh64(gp[3].gp_lba_start) % 64));
371 	}
372 #endif
373 	gp[3].gp_lba_end = gh.gh_lba_end;
374 	memcpy(gp[3].gp_name, string_to_utf16le("OpenBSD Area"),
375 	    sizeof(gp[3].gp_name));
376 
377 	gh.gh_part_csum = crc32((unsigned char *)&gp, sizeof(gp));
378 	gh.gh_csum = crc32((unsigned char *)&gh, sizeof(gh));
379 
380 	return 0;
381 }
382 
383 int
384 GPT_write(void)
385 {
386 	char *secbuf;
387 	const int secsize = unit_types[SECTORS].conversion;
388 	ssize_t len;
389 	off_t off;
390 	u_int64_t altgh, altgp;
391 
392 	/* Assume we always write full-size partition table. XXX */
393 	altgh = DL_GETDSIZE(&dl) - 1;
394 	altgp = DL_GETDSIZE(&dl) - 1 - (sizeof(gp) / secsize);
395 
396 	/*
397 	 * Place the new GPT header at the start of sectors 1 and
398 	 * DL_GETDSIZE(lp)-1 and write the sectors back.
399 	 */
400 	gh.gh_lba_self = htole64(1);
401 	gh.gh_lba_alt = htole64(altgh);
402 	gh.gh_part_lba = htole64(2);
403 	gh.gh_part_csum = crc32((unsigned char *)&gp, sizeof(gp));
404 	gh.gh_csum = 0;
405 	gh.gh_csum = crc32((unsigned char *)&gh, letoh32(gh.gh_size));
406 
407 	secbuf = DISK_readsector(1);
408 	if (secbuf == NULL)
409 		return (-1);
410 
411 	memcpy(secbuf, &gh, sizeof(gh));
412 	DISK_writesector(secbuf, 1);
413 	free(secbuf);
414 
415 	gh.gh_lba_self = htole64(altgh);
416 	gh.gh_lba_alt = htole64(1);
417 	gh.gh_part_lba = htole64(altgp);
418 	gh.gh_csum = 0;
419 	gh.gh_csum = crc32((unsigned char *)&gh, letoh32(gh.gh_size));
420 
421 	secbuf = DISK_readsector(altgh);
422 	if (secbuf == NULL)
423 		return (-1);
424 
425 	memcpy(secbuf, &gh, sizeof(gh));
426 	DISK_writesector(secbuf, altgh);
427 	free(secbuf);
428 
429 	/*
430 	 * Write partition table after primary header
431 	 * (i.e. at sector 1) and before alt header
432 	 * (i.e. ending in sector before alt header.
433 	 * XXX ALWAYS NGPTPARTITIONS!
434 	 * XXX ASSUME gp is multiple of sector size!
435 	 */
436 	off = lseek(disk.fd, secsize * 2, SEEK_SET);
437 	if (off == secsize * 2)
438 		len = write(disk.fd, &gp, sizeof(gp));
439 	else
440 		len = -1;
441 	if (len == -1 || len != sizeof(gp)) {
442 		errno = EIO;
443 		return (-1);
444 	}
445 
446 	off = lseek(disk.fd, secsize * altgp, SEEK_SET);
447 	if (off == secsize * altgp)
448 		len = write(disk.fd, &gp, sizeof(gp));
449 	else
450 		len = -1;
451 
452 	if (len == -1 || len != sizeof(gp)) {
453 		errno = EIO;
454 		return (-1);
455 	}
456 
457 	/* Refresh in-kernel disklabel from the updated disk information. */
458 	ioctl(disk.fd, DIOCRLDINFO, 0);
459 
460 	return (0);
461 }
462 
463 int
464 gp_lba_start_cmp(const void *e1, const void *e2)
465 {
466 	struct gpt_partition *p1 = *(struct gpt_partition **)e1;
467 	struct gpt_partition *p2 = *(struct gpt_partition **)e2;
468 	u_int64_t o1;
469 	u_int64_t o2;
470 
471 	o1 = letoh64(p1->gp_lba_start);
472 	o2 = letoh64(p2->gp_lba_start);
473 
474 	if (o1 < o2)
475 		return -1;
476 	else if (o1 > o2)
477 		return 1;
478 	else
479 		return 0;
480 }
481 
482 struct gpt_partition **
483 sort_gpt(void)
484 {
485 	static struct gpt_partition *sgp[NGPTPARTITIONS+2];
486 	unsigned int i, j;
487 
488 	memset(sgp, 0, sizeof(sgp));
489 
490 	j = 0;
491 	for (i = 0; i < letoh32(gh.gh_part_num); i++) {
492 		if (letoh64(gp[i].gp_lba_start) >= letoh64(gh.gh_lba_start))
493 			sgp[j++] = &gp[i];
494 	}
495 
496 	if (j > 1) {
497 		if (mergesort(sgp, j, sizeof(sgp[0]), gp_lba_start_cmp) == -1) {
498 			printf("unable to sort gpt by lba start\n");
499 			return NULL;
500 		}
501 	}
502 
503 	return (sgp);
504 }
505 
506 int
507 GPT_get_lba_start(unsigned int pn)
508 {
509 	struct gpt_partition	**sgp;
510 	uint64_t		  bs, bigbs, nextbs, ns;
511 	unsigned int		  i;
512 
513 	bs = letoh64(gh.gh_lba_start);
514 
515 	if (letoh64(gp[pn].gp_lba_start) >= bs) {
516 		bs = letoh64(gp[pn].gp_lba_start);
517 	} else {
518 		sgp = sort_gpt();
519 		if (sgp == NULL)
520 			return -1;
521 		if (sgp[0] != NULL) {
522 			bigbs = bs;
523 			ns = 0;
524 			for (i = 0; sgp[i] != NULL; i++) {
525 				nextbs = letoh64(sgp[i]->gp_lba_start);
526 				if (bs < nextbs && ns < nextbs - bs) {
527 					ns = nextbs - bs;
528 					bigbs = bs;
529 				}
530 				bs = letoh64(sgp[i]->gp_lba_end) + 1;
531 			}
532 			nextbs = letoh64(gh.gh_lba_end) + 1;
533 			if (bs < nextbs && ns < nextbs - bs) {
534 				ns = nextbs - bs;
535 				bigbs = bs;
536 			}
537 			if (ns == 0) {
538 				printf("no space for partition %u\n", pn);
539 				return -1;
540 			}
541 			bs = bigbs;
542 		}
543 	}
544 
545 	bs = getuint64("Partition offset", bs, letoh64(gh.gh_lba_start),
546 	    letoh64(gh.gh_lba_end));
547 
548 	for (i = 0; i < letoh32(gh.gh_part_num); i++) {
549 		if (i == pn)
550 			continue;
551 		if (bs >= letoh64(gp[i].gp_lba_start) &&
552 		    bs <= letoh64(gp[i].gp_lba_end)) {
553 			printf("partition %u can't start inside partition %u\n",
554 			    pn, i);
555 			return -1;
556 		}
557 	}
558 
559 	gp[pn].gp_lba_start = htole64(bs);
560 
561 	return 0;
562 }
563 
564 int
565 GPT_get_lba_end(unsigned int pn)
566 {
567 	struct gpt_partition	**sgp;
568 	uint64_t		  bs, nextbs, ns;
569 	unsigned int		  i;
570 
571 	sgp = sort_gpt();
572 	if (sgp == NULL)
573 		return -1;
574 
575 	bs = letoh64(gp[pn].gp_lba_start);
576 	ns = letoh64(gh.gh_lba_end) - bs + 1;
577 	for (i = 0; sgp[i] != NULL; i++) {
578 		nextbs = letoh64(sgp[i]->gp_lba_start);
579 		if (nextbs > bs) {
580 			ns = nextbs - bs;
581 			break;
582 		}
583 	}
584 	ns = getuint64("Partition size", ns, 1, ns);
585 
586 	gp[pn].gp_lba_end = htole64(bs + ns - 1);
587 
588 	return 0;
589 }
590