gpt.c revision 219083
150276Speter/*-
2184989Srafan * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
350276Speter * All rights reserved.
450276Speter *
550276Speter * Redistribution and use in source and binary forms, with or without
650276Speter * modification, are permitted provided that the following conditions
750276Speter * are met:
850276Speter * 1. Redistributions of source code must retain the above copyright
950276Speter *    notice, this list of conditions and the following disclaimer.
1050276Speter * 2. Redistributions in binary form must reproduce the above copyright
1150276Speter *    notice, this list of conditions and the following disclaimer in the
1250276Speter *    documentation and/or other materials provided with the distribution.
1350276Speter *
1450276Speter * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
1550276Speter * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1650276Speter * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1750276Speter * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
1850276Speter * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1950276Speter * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2050276Speter * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2150276Speter * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2250276Speter * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2350276Speter * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2450276Speter * SUCH DAMAGE.
2550276Speter */
2650276Speter
2750276Speter#include <sys/cdefs.h>
2850276Speter__FBSDID("$FreeBSD: head/sys/boot/common/gpt.c 219083 2011-02-27 12:25:47Z pjd $");
2950276Speter
3050276Speter#include <sys/param.h>
3150276Speter#include <sys/gpt.h>
32166124Srafan
3350276Speter#ifndef LITTLE_ENDIAN
3450276Speter#error gpt.c works only for little endian architectures
3550276Speter#endif
3650276Speter
3750276Speter#include "crc32.h"
3850276Speter#include "drv.h"
3950276Speter#include "util.h"
4050276Speter#include "gpt.h"
4150276Speter
4250276Speter#define	MAXTBLENTS	128
4350276Speter
4450276Speterstatic struct gpt_hdr hdr_primary, hdr_backup, *gpthdr;
4550276Speterstatic uint64_t hdr_primary_lba, hdr_backup_lba;
4650276Speterstatic struct gpt_ent table_primary[MAXTBLENTS], table_backup[MAXTBLENTS];
4750276Speterstatic struct gpt_ent *gpttable;
4850276Speterstatic int curent, bootonce;
4950276Speter
5050276Speter/*
5150276Speter * Buffer below 64kB passed on gptread(), which can hold at least
5250276Speter * one sector of data (512 bytes).
5350276Speter */
5450276Speterstatic char *secbuf;
5550276Speter
5650276Speterstatic void
5750276Spetergptupdate(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
5850276Speter    struct gpt_ent *table)
5950276Speter{
6050276Speter	int entries_per_sec, firstent;
6150276Speter	daddr_t slba;
6250276Speter
6350276Speter	/*
6450276Speter	 * We need to update the following for both primary and backup GPT:
6550276Speter	 * 1. Sector on disk that contains current partition.
6650276Speter	 * 2. Partition table checksum.
6750276Speter	 * 3. Header checksum.
6850276Speter	 * 4. Header on disk.
6950276Speter	 */
7050276Speter
7150276Speter	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
7250276Speter	slba = curent / entries_per_sec;
7350276Speter	firstent = slba * entries_per_sec;
7450276Speter	bcopy(&table[firstent], secbuf, DEV_BSIZE);
7550276Speter	slba += hdr->hdr_lba_table;
76174993Srafan	if (drvwrite(dskp, secbuf, slba, 1)) {
7750276Speter		printf("%s: unable to update %s GPT partition table\n",
7850276Speter		    BOOTPROG, which);
7950276Speter		return;
8050276Speter	}
8150276Speter	hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz);
8250276Speter	hdr->hdr_crc_self = 0;
8350276Speter	hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size);
8450276Speter	bzero(secbuf, DEV_BSIZE);
8550276Speter	bcopy(hdr, secbuf, hdr->hdr_size);
8650276Speter	if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1)) {
8750276Speter		printf("%s: unable to update %s GPT header\n", BOOTPROG, which);
8850276Speter		return;
8950276Speter	}
9050276Speter}
9150276Speter
92184989Srafanint
93184989Srafangptfind(const uuid_t *uuid, struct dsk *dskp, int part)
94184989Srafan{
95184989Srafan	struct gpt_ent *ent;
96184989Srafan	int firsttry;
97184989Srafan
9850276Speter	if (part >= 0) {
9950276Speter		if (part == 0 || part > gpthdr->hdr_entries) {
10050276Speter			printf("%s: invalid partition index\n", BOOTPROG);
10150276Speter			return (-1);
10250276Speter		}
10397049Speter		ent = &gpttable[part - 1];
10497049Speter		if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0) {
10550276Speter			printf("%s: specified partition is not UFS\n",
10650276Speter			    BOOTPROG);
10750276Speter			return (-1);
10866963Speter		}
10950276Speter		curent = part - 1;
110184989Srafan		goto found;
11150276Speter	}
112184989Srafan
113184989Srafan	firsttry = (curent == -1);
114184989Srafan	curent++;
115184989Srafan	if (curent >= gpthdr->hdr_entries) {
116184989Srafan		curent = gpthdr->hdr_entries;
117184989Srafan		return (-1);
118184989Srafan	}
119184989Srafan	if (bootonce) {
120184989Srafan		/*
121184989Srafan		 * First look for partition with both GPT_ENT_ATTR_BOOTME and
122184989Srafan		 * GPT_ENT_ATTR_BOOTONCE flags.
123184989Srafan		 */
124184989Srafan		for (; curent < gpthdr->hdr_entries; curent++) {
125184989Srafan			ent = &gpttable[curent];
126184989Srafan			if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
127184989Srafan				continue;
128184989Srafan			if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME))
129184989Srafan				continue;
130184989Srafan			if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTONCE))
131184989Srafan				continue;
13250276Speter			/* Ok, found one. */
13350276Speter			goto found;
13450276Speter		}
13550276Speter		bootonce = 0;
13650276Speter		curent = 0;
13750276Speter	}
13850276Speter	for (; curent < gpthdr->hdr_entries; curent++) {
13997049Speter		ent = &gpttable[curent];
14050276Speter		if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
141166124Srafan			continue;
142166124Srafan		if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME))
143166124Srafan			continue;
14497049Speter		if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE)
14566963Speter			continue;
14662449Speter		/* Ok, found one. */
14762449Speter		goto found;
14862449Speter	}
14962449Speter	if (firsttry) {
15050276Speter		/*
15197049Speter		 * No partition with BOOTME flag was found, try to boot from
15250276Speter		 * first UFS partition.
15350276Speter		 */
15462449Speter		for (curent = 0; curent < gpthdr->hdr_entries; curent++) {
15562449Speter			ent = &gpttable[curent];
15662449Speter			if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
15762449Speter				continue;
15862449Speter			/* Ok, found one. */
15962449Speter			goto found;
16062449Speter		}
16162449Speter	}
16262449Speter	return (-1);
16350276Speterfound:
16450276Speter	dskp->part = curent + 1;
16550276Speter	ent = &gpttable[curent];
16697049Speter	dskp->start = ent->ent_lba_start;
16797049Speter	if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE) {
16897049Speter		/*
16997049Speter		 * Clear BOOTME, but leave BOOTONCE set before trying to
17097049Speter		 * boot from this partition.
17197049Speter		 */
172166124Srafan		if (hdr_primary_lba > 0) {
17397049Speter			table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME;
17497049Speter			gptupdate("primary", dskp, &hdr_primary, table_primary);
17597049Speter		}
17697049Speter		if (hdr_backup_lba > 0) {
17762449Speter			table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME;
17850276Speter			gptupdate("backup", dskp, &hdr_backup, table_backup);
17962449Speter		}
18062449Speter	}
181166124Srafan	return (0);
18262449Speter}
18362449Speter
18497049Speterstatic int
18562449Spetergptread_hdr(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
18650276Speter    uint64_t hdrlba)
18750276Speter{
18850276Speter	uint32_t crc;
18950276Speter
19050276Speter	if (drvread(dskp, secbuf, hdrlba, 1)) {
19162449Speter		printf("%s: unable to read %s GPT header\n", BOOTPROG, which);
192166124Srafan		return (-1);
193166124Srafan	}
194184989Srafan	bcopy(secbuf, hdr, sizeof(*hdr));
195166124Srafan	if (bcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof(hdr->hdr_sig)) != 0 ||
196166124Srafan	    hdr->hdr_lba_self != hdrlba || hdr->hdr_revision < 0x00010000 ||
197166124Srafan	    hdr->hdr_entsz < sizeof(struct gpt_ent) ||
198166124Srafan	    hdr->hdr_entries > MAXTBLENTS || DEV_BSIZE % hdr->hdr_entsz != 0) {
199166124Srafan		printf("%s: invalid %s GPT header\n", BOOTPROG, which);
200166124Srafan		return (-1);
20197049Speter	}
20262449Speter	crc = hdr->hdr_crc_self;
20350276Speter	hdr->hdr_crc_self = 0;
20450276Speter	if (crc32(hdr, hdr->hdr_size) != crc) {
20550276Speter		printf("%s: %s GPT header checksum mismatch\n", BOOTPROG,
20650276Speter		    which);
20750276Speter		return (-1);
20897049Speter	}
20997049Speter	hdr->hdr_crc_self = crc;
21062449Speter	return (0);
21150276Speter}
21297049Speter
21362449Spetervoid
21450276Spetergptbootfailed(struct dsk *dskp)
21597049Speter{
21697049Speter
21797049Speter	if (!(gpttable[curent].ent_attr & GPT_ENT_ATTR_BOOTONCE))
21897049Speter		return;
21950276Speter
22050276Speter	if (hdr_primary_lba > 0) {
22150276Speter		table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
22250276Speter		table_primary[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
22350276Speter		gptupdate("primary", dskp, &hdr_primary, table_primary);
22462449Speter	}
22550276Speter	if (hdr_backup_lba > 0) {
22650276Speter		table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
22750276Speter		table_backup[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
22850276Speter		gptupdate("backup", dskp, &hdr_backup, table_backup);
22950276Speter	}
23050276Speter}
23162449Speter
23262449Speterstatic void
23350276Spetergptbootconv(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
23462449Speter    struct gpt_ent *table)
235166124Srafan{
23662449Speter	struct gpt_ent *ent;
23762449Speter	daddr_t slba;
23897049Speter	int table_updated, sector_updated;
23997049Speter	int entries_per_sec, nent, part;
24062449Speter
24162449Speter	table_updated = 0;
24262449Speter	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
24362449Speter	for (nent = 0, slba = hdr->hdr_lba_table;
24462449Speter	     slba < hdr->hdr_lba_table + hdr->hdr_entries / entries_per_sec;
24562449Speter	     slba++, nent += entries_per_sec) {
24662449Speter		sector_updated = 0;
24762449Speter		for (part = 0; part < entries_per_sec; part++) {
24862449Speter			ent = &table[nent + part];
24962449Speter			if ((ent->ent_attr & (GPT_ENT_ATTR_BOOTME |
25097049Speter			    GPT_ENT_ATTR_BOOTONCE |
25197049Speter			    GPT_ENT_ATTR_BOOTFAILED)) !=
25262449Speter			    GPT_ENT_ATTR_BOOTONCE) {
25362449Speter				continue;
25450276Speter			}
25550276Speter			ent->ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
25662449Speter			ent->ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
25762449Speter			table_updated = 1;
25862449Speter			sector_updated = 1;
25962449Speter		}
26062449Speter		if (!sector_updated)
26162449Speter			continue;
26262449Speter		bcopy(&table[nent], secbuf, DEV_BSIZE);
26350276Speter		if (drvwrite(dskp, secbuf, slba, 1)) {
26450276Speter			printf("%s: unable to update %s GPT partition table\n",
26550276Speter			    BOOTPROG, which);
26650276Speter		}
26750276Speter	}
26850276Speter	if (!table_updated)
26950276Speter		return;
27050276Speter	hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz);
27150276Speter	hdr->hdr_crc_self = 0;
27250276Speter	hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size);
27350276Speter	bzero(secbuf, DEV_BSIZE);
27450276Speter	bcopy(hdr, secbuf, hdr->hdr_size);
27550276Speter	if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1))
27650276Speter		printf("%s: unable to update %s GPT header\n", BOOTPROG, which);
27750276Speter}
27850276Speter
27950276Speterstatic int
28062449Spetergptread_table(const char *which, const uuid_t *uuid, struct dsk *dskp,
28162449Speter    struct gpt_hdr *hdr, struct gpt_ent *table)
28262449Speter{
28362449Speter	struct gpt_ent *ent;
28466963Speter	int entries_per_sec;
28550276Speter	int part, nent;
28650276Speter	daddr_t slba;
28750276Speter
28850276Speter	if (hdr->hdr_entries == 0)
28950276Speter		return (0);
29062449Speter
29162449Speter	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
29250276Speter	slba = hdr->hdr_lba_table;
29350276Speter	nent = 0;
29462449Speter	for (;;) {
29562449Speter		if (drvread(dskp, secbuf, slba, 1)) {
29662449Speter			printf("%s: unable to read %s GPT partition table\n",
29762449Speter			    BOOTPROG, which);
29862449Speter			return (-1);
29962449Speter		}
30062449Speter		ent = (struct gpt_ent *)secbuf;
30162449Speter		for (part = 0; part < entries_per_sec; part++, ent++) {
30262449Speter			bcopy(ent, &table[nent], sizeof(table[nent]));
30362449Speter			if (++nent >= hdr->hdr_entries)
30462449Speter				break;
30562449Speter		}
30662449Speter		if (nent >= hdr->hdr_entries)
30762449Speter			break;
30862449Speter		slba++;
30962449Speter	}
31062449Speter	if (crc32(table, nent * hdr->hdr_entsz) != hdr->hdr_crc_table) {
31166963Speter		printf("%s: %s GPT table checksum mismatch\n", BOOTPROG, which);
31266963Speter		return (-1);
31362449Speter	}
31466963Speter	return (0);
31566963Speter}
31662449Speter
31766963Speterint
31866963Spetergptread(const uuid_t *uuid, struct dsk *dskp, char *buf)
31962449Speter{
32066963Speter	uint64_t altlba;
32166963Speter
32262449Speter	/*
32366963Speter	 * Read and verify both GPT headers: primary and backup.
32450276Speter	 */
32562449Speter
32650276Speter	secbuf = buf;
32750276Speter	hdr_primary_lba = hdr_backup_lba = 0;
32862449Speter	curent = -1;
32950276Speter	bootonce = 1;
33050276Speter	dskp->start = 0;
33150276Speter
33262449Speter	if (gptread_hdr("primary", dskp, &hdr_primary, 1) == 0 &&
33350276Speter	    gptread_table("primary", uuid, dskp, &hdr_primary,
33450276Speter	    table_primary) == 0) {
33562449Speter		hdr_primary_lba = hdr_primary.hdr_lba_self;
33650276Speter		gpthdr = &hdr_primary;
33750276Speter		gpttable = table_primary;
33850276Speter	}
33962449Speter
34050276Speter	altlba = drvsize(dskp);
34150276Speter	if (altlba > 0)
34262449Speter		altlba--;
34350276Speter	else if (hdr_primary_lba > 0) {
34450276Speter		/*
34562449Speter		 * If we cannot obtain disk size, but primary header
34650276Speter		 * is valid, we can get backup header location from
34750276Speter		 * there.
34862449Speter		 */
34950276Speter		altlba = hdr_primary.hdr_lba_alt;
35062449Speter	}
35150276Speter	if (altlba == 0)
35250276Speter		printf("%s: unable to locate backup GPT header\n", BOOTPROG);
35350276Speter	else if (gptread_hdr("backup", dskp, &hdr_backup, altlba) == 0 &&
35450276Speter	    gptread_table("backup", uuid, dskp, &hdr_backup,
35550276Speter	    table_backup) == 0) {
35662449Speter		hdr_backup_lba = hdr_backup.hdr_lba_self;
35762449Speter		if (hdr_primary_lba == 0) {
35850276Speter			gpthdr = &hdr_backup;
35962449Speter			gpttable = table_backup;
36062449Speter			printf("%s: using backup GPT\n", BOOTPROG);
36162449Speter		}
36250276Speter	}
36362449Speter
36462449Speter	/*
36562449Speter	 * Convert all BOOTONCE without BOOTME flags into BOOTFAILED.
36662449Speter	 * BOOTONCE without BOOTME means that we tried to boot from it,
36750276Speter	 * but failed after leaving gptboot and machine was rebooted.
36862449Speter	 * We don't want to leave partitions marked as BOOTONCE only,
36962449Speter	 * because when we boot successfully start-up scripts should
37062449Speter	 * find at most one partition with only BOOTONCE flag and this
37162449Speter	 * will mean that we booted from that partition.
37250276Speter	 */
37350276Speter	if (hdr_primary_lba != 0)
37450276Speter		gptbootconv("primary", dskp, &hdr_primary, table_primary);
37550276Speter	if (hdr_backup_lba != 0)
37650276Speter		gptbootconv("backup", dskp, &hdr_backup, table_backup);
37750276Speter
37850276Speter	if (hdr_primary_lba == 0 && hdr_backup_lba == 0)
37950276Speter		return (-1);
38050276Speter	return (0);
38150276Speter}
38262449Speter