xref: /openbsd/sbin/fdisk/gpt.c (revision 4cfece93)
1 /*	$OpenBSD: gpt.c,v 1.11 2016/03/28 16:55:09 mestre 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 int
46 GPT_get_header(off_t where)
47 {
48 	char *secbuf;
49 	uint64_t partlastlba;
50 	int partspersec;
51 	uint32_t orig_gh_csum, new_gh_csum;
52 
53 	secbuf = DISK_readsector(where);
54 	if (secbuf == 0)
55 		return (1);
56 
57 	memcpy(&gh, secbuf, sizeof(struct gpt_header));
58 	free(secbuf);
59 
60 	if (letoh64(gh.gh_sig) != GPTSIGNATURE) {
61 		DPRINTF("gpt signature: expected 0x%llx, got 0x%llx\n",
62 		    GPTSIGNATURE, letoh64(gh.gh_sig));
63 		return (1);
64 	}
65 
66 	if (letoh32(gh.gh_rev) != GPTREVISION) {
67 		DPRINTF("gpt revision: expected 0x%x, got 0x%x\n",
68 		    GPTREVISION, letoh32(gh.gh_rev));
69 		return (1);
70 	}
71 
72 	if (letoh64(gh.gh_lba_self) != where) {
73 		DPRINTF("gpt self lba: expected %lld, got %llu\n",
74 		    (long long)where, letoh64(gh.gh_lba_self));
75 		return (1);
76 	}
77 
78 	if (letoh32(gh.gh_size) != GPTMINHDRSIZE) {
79 		DPRINTF("gpt header size: expected %u, got %u\n",
80 		    GPTMINHDRSIZE, letoh32(gh.gh_size));
81 		return (1);
82 	}
83 
84 	if (letoh32(gh.gh_part_size) != GPTMINPARTSIZE) {
85 		DPRINTF("gpt partition size: expected %u, got %u\n",
86 		    GPTMINPARTSIZE, letoh32(gh.gh_part_size));
87 		return (1);
88 	}
89 
90 	if (letoh32(gh.gh_part_num) > NGPTPARTITIONS) {
91 		DPRINTF("gpt partition count: expected <= %u, got %u\n",
92 		    NGPTPARTITIONS, letoh32(gh.gh_part_num));
93 		return (1);
94 	}
95 
96 	orig_gh_csum = gh.gh_csum;
97 	gh.gh_csum = 0;
98 	new_gh_csum = crc32((unsigned char *)&gh, letoh32(gh.gh_size));
99 	gh.gh_csum = orig_gh_csum;
100 	if (letoh32(orig_gh_csum) != new_gh_csum) {
101 		DPRINTF("gpt header checksum: expected 0x%x, got 0x%x\n",
102 		    orig_gh_csum, new_gh_csum);
103 		return (1);
104 	}
105 
106 	if (letoh64(gh.gh_lba_end) >= DL_GETDSIZE(&dl)) {
107 		DPRINTF("gpt last usable LBA: expected < %lld, got %llu\n",
108 		    DL_GETDSIZE(&dl), letoh64(gh.gh_lba_end));
109 		return (1);
110 	}
111 
112 	if (letoh64(gh.gh_lba_start) >= letoh64(gh.gh_lba_end)) {
113 		DPRINTF("gpt first usable LBA: expected < %llu, got %llu\n",
114 		    letoh64(gh.gh_lba_start), letoh64(gh.gh_lba_start));
115 		return (1);
116 	}
117 
118 	if (letoh64(gh.gh_part_lba) <= letoh64(gh.gh_lba_end) &&
119 	    letoh64(gh.gh_part_lba) >= letoh64(gh.gh_lba_start)) {
120 		DPRINTF("gpt partition table start lba: expected < %llu or "
121 		    "> %llu, got %llu\n", letoh64(gh.gh_lba_start),
122 		    letoh64(gh.gh_lba_end), letoh64(gh.gh_part_lba));
123 		return (1);
124 	}
125 
126 	partspersec = dl.d_secsize / letoh32(gh.gh_part_size);
127 	partlastlba = letoh64(gh.gh_part_lba) +
128 	    ((letoh32(gh.gh_part_num) + partspersec - 1) / partspersec) - 1;
129 	if (partlastlba <= letoh64(gh.gh_lba_end) &&
130 	    partlastlba >= letoh64(gh.gh_lba_start)) {
131 		DPRINTF("gpt partition table last LBA: expected < %llu or "
132 		    "> %llu, got %llu\n", letoh64(gh.gh_lba_start),
133 		    letoh64(gh.gh_lba_end), partlastlba);
134 		return (1);
135 	}
136 
137 	/*
138 	 * Other possible paranoia checks:
139 	 *	1) partition table starts before primary gpt lba.
140 	 *	2) partition table extends into lowest partition.
141 	 *	3) alt partition table starts before gh_lba_end.
142 	 */
143 	return (0);
144 }
145 
146 int
147 GPT_get_partition_table(off_t where)
148 {
149 	ssize_t len;
150 	off_t off;
151 	int secs;
152 	uint32_t checksum, partspersec;
153 
154 	DPRINTF("gpt partition table being read from LBA %llu\n",
155 	    letoh64(gh.gh_part_lba));
156 
157 	partspersec = dl.d_secsize / letoh32(gh.gh_part_size);
158 	if (partspersec * letoh32(gh.gh_part_size) != dl.d_secsize) {
159 		DPRINTF("gpt partition table entry invalid size. %u\n",
160 		    letoh32(gh.gh_part_size));
161 		return (1);
162 	}
163 	secs = (letoh32(gh.gh_part_num) + partspersec - 1) / partspersec;
164 
165 	memset(&gp, 0, sizeof(gp));
166 
167 	where *= dl.d_secsize;
168 	off = lseek(disk.fd, where, SEEK_SET);
169 	if (off == -1) {
170 		DPRINTF("seek to gpt partition table @ sector %llu failed\n",
171 		    (unsigned long long)where / dl.d_secsize);
172 		return (1);
173 	}
174 	len = read(disk.fd, &gp, secs * dl.d_secsize);
175 	if (len == -1 || len != secs * dl.d_secsize) {
176 		DPRINTF("gpt partition table read failed.\n");
177 		return (1);
178 	}
179 
180 	checksum = crc32((unsigned char *)&gp, letoh32(gh.gh_part_num) *
181 	    letoh32(gh.gh_part_size));
182 	if (checksum != letoh32(gh.gh_part_csum)) {
183 		DPRINTF("gpt partition table checksum: expected %x, got %x\n",
184 		    checksum, letoh32(gh.gh_part_csum));
185 		return (1);
186 	}
187 
188 	return (0);
189 }
190 
191 void
192 GPT_get_gpt(int which)
193 {
194 	int privalid, altvalid;
195 
196 	/*
197 	 * primary header && primary partition table ||
198 	 * alt header && alt partition table
199 	 */
200 	privalid = GPT_get_header(GPTSECTOR);
201 	if (privalid == 0)
202 		privalid = GPT_get_partition_table(gh.gh_part_lba);
203 	if (which == 1 || (which == 0 && privalid == 0))
204 		return;
205 
206 	/* No valid GPT found. Zap any artifacts. */
207 	memset(&gh, 0, sizeof(gh));
208 	memset(&gp, 0, sizeof(gp));
209 
210 	altvalid = GPT_get_header(DL_GETDSIZE(&dl) - 1);
211 	if (altvalid == 0)
212 		altvalid = GPT_get_partition_table(gh.gh_part_lba);
213 	if (which == 2 || altvalid == 0)
214 		return;
215 
216 	/* No valid GPT found. Zap any artifacts. */
217 	memset(&gh, 0, sizeof(gh));
218 	memset(&gp, 0, sizeof(gp));
219 }
220 
221 void
222 GPT_print(char *units, int verbosity)
223 {
224 	const int secsize = unit_types[SECTORS].conversion;
225 	struct uuid guid;
226 	char *guidstr = NULL;
227 	double size;
228 	int i, u, status;
229 
230 	u = unit_lookup(units);
231 	size = ((double)DL_GETDSIZE(&dl) * secsize) / unit_types[u].conversion;
232 	printf("Disk: %s       Usable LBA: %llu to %llu [%.0f ",
233 	    disk.name, letoh64(gh.gh_lba_start), letoh64(gh.gh_lba_end), size);
234 
235 	if (u == SECTORS && secsize != DEV_BSIZE)
236 		printf("%d-byte ", secsize);
237 	printf("%s]\n", unit_types[u].lname);
238 
239 	if (verbosity) {
240 		printf("GUID: ");
241 		uuid_dec_le(&gh.gh_guid, &guid);
242 		uuid_to_string(&guid, &guidstr, &status);
243 		if (status == uuid_s_ok)
244 			printf("%s\n", guidstr);
245 		else
246 			printf("<invalid header GUID>\n");
247 		free(guidstr);
248 	}
249 
250 	GPT_print_parthdr(verbosity);
251 	for (i = 0; i < letoh32(gh.gh_part_num); i++) {
252 		if (uuid_is_nil(&gp[i].gp_type, NULL))
253 			continue;
254 		GPT_print_part(i, units, verbosity);
255 	}
256 
257 }
258 
259 void
260 GPT_print_parthdr(int verbosity)
261 {
262 	printf("   #: type                                "
263 	    " [       start:         size ]\n");
264 	if (verbosity)
265 		printf("      guid                                 name\n");
266 	printf("--------------------------------------------------------"
267 	    "----------------\n");
268 }
269 
270 void
271 GPT_print_part(int n, char *units, int verbosity)
272 {
273 	struct uuid guid;
274 	const int secsize = unit_types[SECTORS].conversion;
275 	struct gpt_partition *partn = &gp[n];
276 	char *guidstr = NULL;
277 	double size;
278 	int u, status;
279 
280 	uuid_dec_le(&partn->gp_type, &guid);
281 	u = unit_lookup(units);
282 	size = letoh64(partn->gp_lba_end) - letoh64(partn->gp_lba_start) + 1;
283 	size = (size * secsize) / unit_types[u].conversion;
284 	printf("%c%3d: %-36s [%12lld: %12.0f%s]\n",
285 	    (letoh64(partn->gp_attrs) & GPTDOSACTIVE)?'*':' ', n,
286 	    PRT_uuid_to_typename(&guid), letoh64(partn->gp_lba_start),
287 	    size, unit_types[u].abbr);
288 
289 	if (verbosity) {
290 		uuid_dec_le(&partn->gp_guid, &guid);
291 		uuid_to_string(&guid, &guidstr, &status);
292 		if (status != uuid_s_ok)
293 			printf("      <invalid partition guid>             ");
294 		else
295 			printf("      %-36s ", guidstr);
296 		printf("%-36s\n", utf16le_to_string(partn->gp_name));
297 		free(guidstr);
298 	}
299 }
300 
301 int
302 GPT_init(void)
303 {
304 	extern u_int32_t b_arg;
305 	const int secsize = unit_types[SECTORS].conversion;
306 	struct uuid guid;
307 	int needed;
308 	uint32_t status;
309 	const uint8_t gpt_uuid_efi_system[] = GPT_UUID_EFI_SYSTEM;
310 	const uint8_t gpt_uuid_openbsd[] = GPT_UUID_OPENBSD;
311 
312 	memset(&gh, 0, sizeof(gh));
313 	memset(&gp, 0, sizeof(gp));
314 
315 	needed = sizeof(gp) / secsize + 2;
316 	/* Start on 64 sector boundary */
317 	if (needed % 64)
318 		needed += (64 - (needed % 64));
319 
320 	gh.gh_sig = htole64(GPTSIGNATURE);
321 	gh.gh_rev = htole32(GPTREVISION);
322 	gh.gh_size = htole32(GPTMINHDRSIZE);
323 	gh.gh_csum = 0;
324 	gh.gh_rsvd = 0;
325 	gh.gh_lba_self = htole64(1);
326 	gh.gh_lba_alt = htole64(DL_GETDSIZE(&dl) - 1);
327 	gh.gh_lba_start = htole64(needed);
328 	gh.gh_lba_end = htole64(DL_GETDSIZE(&dl) - needed);
329 	gh.gh_part_lba = htole64(2);
330 	gh.gh_part_num = htole32(NGPTPARTITIONS);
331 	gh.gh_part_size = htole32(GPTMINPARTSIZE);
332 
333 	uuid_create(&guid, &status);
334 	if (status != uuid_s_ok)
335 		return (1);
336 	uuid_enc_le(&gh.gh_guid, &guid);
337 
338 #if defined(__i386__) || defined(__amd64__)
339 	if (b_arg > 0) {
340 		/* Add an EFI system partition on i386/amd64. */
341 		uuid_dec_be(gpt_uuid_efi_system, &guid);
342 		uuid_enc_le(&gp[1].gp_type, &guid);
343 		uuid_create(&guid, &status);
344 		if (status != uuid_s_ok)
345 			return (1);
346 		uuid_enc_le(&gp[1].gp_guid, &guid);
347 		gp[1].gp_lba_start = gh.gh_lba_start;
348 		gp[1].gp_lba_end = htole64(letoh64(gh.gh_lba_start)+b_arg - 1);
349 		memcpy(gp[1].gp_name, string_to_utf16le("EFI System Area"),
350 		    sizeof(gp[1].gp_name));
351 	}
352 #endif
353 	uuid_dec_be(gpt_uuid_openbsd, &guid);
354 	uuid_enc_le(&gp[3].gp_type, &guid);
355 	uuid_create(&guid, &status);
356 	if (status != uuid_s_ok)
357 		return (1);
358 	uuid_enc_le(&gp[3].gp_guid, &guid);
359 	gp[3].gp_lba_start = gh.gh_lba_start;
360 #if defined(__i386__) || defined(__amd64__)
361 	if (b_arg > 0) {
362 		gp[3].gp_lba_start = htole64(letoh64(gp[3].gp_lba_start) +
363 		    b_arg);
364 		if (letoh64(gp[3].gp_lba_start) % 64)
365 			gp[3].gp_lba_start =
366 			    htole64(letoh64(gp[3].gp_lba_start) +
367 			    (64 - letoh64(gp[3].gp_lba_start) % 64));
368 	}
369 #endif
370 	gp[3].gp_lba_end = gh.gh_lba_end;
371 	memcpy(gp[3].gp_name, string_to_utf16le("OpenBSD Area"),
372 	    sizeof(gp[3].gp_name));
373 
374 	gh.gh_part_csum = crc32((unsigned char *)&gp, sizeof(gp));
375 	gh.gh_csum = crc32((unsigned char *)&gh, sizeof(gh));
376 
377 	return 0;
378 }
379 
380 int
381 GPT_write(void)
382 {
383 	char *secbuf;
384 	const int secsize = unit_types[SECTORS].conversion;
385 	ssize_t len;
386 	off_t off;
387 	u_int64_t altgh, altgp;
388 
389 	/* Assume we always write full-size partition table. XXX */
390 	altgh = DL_GETDSIZE(&dl) - 1;
391 	altgp = DL_GETDSIZE(&dl) - 1 - (sizeof(gp) / secsize);
392 
393 	/*
394 	 * Place the new GPT header at the start of sectors 1 and
395 	 * DL_GETDSIZE(lp)-1 and write the sectors back.
396 	 */
397 	gh.gh_lba_self = htole64(1);
398 	gh.gh_lba_alt = htole64(altgh);
399 	gh.gh_part_lba = htole64(2);
400 	gh.gh_part_csum = crc32((unsigned char *)&gp, sizeof(gp));
401 	gh.gh_csum = 0;
402 	gh.gh_csum = crc32((unsigned char *)&gh, letoh32(gh.gh_size));
403 
404 	secbuf = DISK_readsector(1);
405 	if (secbuf == NULL)
406 		return (-1);
407 
408 	memcpy(secbuf, &gh, sizeof(gh));
409 	DISK_writesector(secbuf, 1);
410 	free(secbuf);
411 
412 	gh.gh_lba_self = htole64(altgh);
413 	gh.gh_lba_alt = htole64(1);
414 	gh.gh_part_lba = htole64(altgp);
415 	gh.gh_csum = 0;
416 	gh.gh_csum = crc32((unsigned char *)&gh, letoh32(gh.gh_size));
417 
418 	secbuf = DISK_readsector(altgh);
419 	if (secbuf == NULL)
420 		return (-1);
421 
422 	memcpy(secbuf, &gh, sizeof(gh));
423 	DISK_writesector(secbuf, altgh);
424 	free(secbuf);
425 
426 	/*
427 	 * Write partition table after primary header
428 	 * (i.e. at sector 1) and before alt header
429 	 * (i.e. ending in sector before alt header.
430 	 * XXX ALWAYS NGPTPARTITIONS!
431 	 * XXX ASSUME gp is multiple of sector size!
432 	 */
433 	off = lseek(disk.fd, secsize * 2, SEEK_SET);
434 	if (off == secsize * 2)
435 		len = write(disk.fd, &gp, sizeof(gp));
436 	else
437 		len = -1;
438 	if (len == -1 || len != sizeof(gp)) {
439 		errno = EIO;
440 		return (-1);
441 	}
442 
443 	off = lseek(disk.fd, secsize * altgp, SEEK_SET);
444 	if (off == secsize * altgp)
445 		len = write(disk.fd, &gp, sizeof(gp));
446 	else
447 		len = -1;
448 
449 	if (len == -1 || len != sizeof(gp)) {
450 		errno = EIO;
451 		return (-1);
452 	}
453 
454 	/* Refresh in-kernel disklabel from the updated disk information. */
455 	ioctl(disk.fd, DIOCRLDINFO, 0);
456 
457 	return (0);
458 }
459