1213136Spjd/*-
2213136Spjd * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
3213136Spjd * All rights reserved.
4213136Spjd *
5213136Spjd * Redistribution and use in source and binary forms, with or without
6213136Spjd * modification, are permitted provided that the following conditions
7213136Spjd * are met:
8213136Spjd * 1. Redistributions of source code must retain the above copyright
9213136Spjd *    notice, this list of conditions and the following disclaimer.
10213136Spjd * 2. Redistributions in binary form must reproduce the above copyright
11213136Spjd *    notice, this list of conditions and the following disclaimer in the
12213136Spjd *    documentation and/or other materials provided with the distribution.
13213136Spjd *
14213136Spjd * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15213136Spjd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16213136Spjd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17213136Spjd * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18213136Spjd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19213136Spjd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20213136Spjd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21213136Spjd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22213136Spjd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23213136Spjd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24213136Spjd * SUCH DAMAGE.
25213136Spjd */
26213136Spjd
27213136Spjd#include <sys/cdefs.h>
28213136Spjd__FBSDID("$FreeBSD$");
29213136Spjd
30213136Spjd#include <sys/param.h>
31213136Spjd#include <sys/gpt.h>
32213136Spjd
33213136Spjd#ifndef LITTLE_ENDIAN
34213136Spjd#error gpt.c works only for little endian architectures
35213136Spjd#endif
36213136Spjd
37213136Spjd#include "crc32.h"
38213136Spjd#include "drv.h"
39213136Spjd#include "util.h"
40213136Spjd#include "gpt.h"
41213136Spjd
42213136Spjd#define	MAXTBLENTS	128
43213136Spjd
44213136Spjdstatic struct gpt_hdr hdr_primary, hdr_backup, *gpthdr;
45213136Spjdstatic uint64_t hdr_primary_lba, hdr_backup_lba;
46213136Spjdstatic struct gpt_ent table_primary[MAXTBLENTS], table_backup[MAXTBLENTS];
47213136Spjdstatic struct gpt_ent *gpttable;
48213136Spjdstatic int curent, bootonce;
49213136Spjd
50213136Spjd/*
51213136Spjd * Buffer below 64kB passed on gptread(), which can hold at least
52214114Spjd * one sector of data (512 bytes).
53213136Spjd */
54213136Spjdstatic char *secbuf;
55213136Spjd
56213136Spjdstatic void
57213136Spjdgptupdate(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
58213136Spjd    struct gpt_ent *table)
59213136Spjd{
60213136Spjd	int entries_per_sec, firstent;
61213136Spjd	daddr_t slba;
62213136Spjd
63213136Spjd	/*
64213136Spjd	 * We need to update the following for both primary and backup GPT:
65214114Spjd	 * 1. Sector on disk that contains current partition.
66213136Spjd	 * 2. Partition table checksum.
67213136Spjd	 * 3. Header checksum.
68213136Spjd	 * 4. Header on disk.
69213136Spjd	 */
70213136Spjd
71213136Spjd	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
72213136Spjd	slba = curent / entries_per_sec;
73213136Spjd	firstent = slba * entries_per_sec;
74219083Spjd	bcopy(&table[firstent], secbuf, DEV_BSIZE);
75213136Spjd	slba += hdr->hdr_lba_table;
76213136Spjd	if (drvwrite(dskp, secbuf, slba, 1)) {
77213136Spjd		printf("%s: unable to update %s GPT partition table\n",
78213136Spjd		    BOOTPROG, which);
79213136Spjd		return;
80213136Spjd	}
81213136Spjd	hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz);
82213136Spjd	hdr->hdr_crc_self = 0;
83213136Spjd	hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size);
84213136Spjd	bzero(secbuf, DEV_BSIZE);
85219083Spjd	bcopy(hdr, secbuf, hdr->hdr_size);
86213136Spjd	if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1)) {
87213136Spjd		printf("%s: unable to update %s GPT header\n", BOOTPROG, which);
88213136Spjd		return;
89213136Spjd	}
90213136Spjd}
91213136Spjd
92213136Spjdint
93213136Spjdgptfind(const uuid_t *uuid, struct dsk *dskp, int part)
94213136Spjd{
95213136Spjd	struct gpt_ent *ent;
96213136Spjd	int firsttry;
97213136Spjd
98213136Spjd	if (part >= 0) {
99213136Spjd		if (part == 0 || part > gpthdr->hdr_entries) {
100213136Spjd			printf("%s: invalid partition index\n", BOOTPROG);
101213136Spjd			return (-1);
102213136Spjd		}
103213136Spjd		ent = &gpttable[part - 1];
104213136Spjd		if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0) {
105213136Spjd			printf("%s: specified partition is not UFS\n",
106213136Spjd			    BOOTPROG);
107213136Spjd			return (-1);
108213136Spjd		}
109213136Spjd		curent = part - 1;
110213136Spjd		goto found;
111213136Spjd	}
112213136Spjd
113213136Spjd	firsttry = (curent == -1);
114213136Spjd	curent++;
115213136Spjd	if (curent >= gpthdr->hdr_entries) {
116213136Spjd		curent = gpthdr->hdr_entries;
117213136Spjd		return (-1);
118213136Spjd	}
119213136Spjd	if (bootonce) {
120213136Spjd		/*
121213136Spjd		 * First look for partition with both GPT_ENT_ATTR_BOOTME and
122213136Spjd		 * GPT_ENT_ATTR_BOOTONCE flags.
123213136Spjd		 */
124213136Spjd		for (; curent < gpthdr->hdr_entries; curent++) {
125213136Spjd			ent = &gpttable[curent];
126213136Spjd			if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
127213136Spjd				continue;
128213136Spjd			if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME))
129213136Spjd				continue;
130213136Spjd			if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTONCE))
131213136Spjd				continue;
132213136Spjd			/* Ok, found one. */
133213136Spjd			goto found;
134213136Spjd		}
135213136Spjd		bootonce = 0;
136213136Spjd		curent = 0;
137213136Spjd	}
138213136Spjd	for (; curent < gpthdr->hdr_entries; curent++) {
139213136Spjd		ent = &gpttable[curent];
140213136Spjd		if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
141213136Spjd			continue;
142213136Spjd		if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME))
143213136Spjd			continue;
144213136Spjd		if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE)
145213136Spjd			continue;
146213136Spjd		/* Ok, found one. */
147213136Spjd		goto found;
148213136Spjd	}
149213136Spjd	if (firsttry) {
150213136Spjd		/*
151213136Spjd		 * No partition with BOOTME flag was found, try to boot from
152213136Spjd		 * first UFS partition.
153213136Spjd		 */
154213136Spjd		for (curent = 0; curent < gpthdr->hdr_entries; curent++) {
155213136Spjd			ent = &gpttable[curent];
156213136Spjd			if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
157213136Spjd				continue;
158213136Spjd			/* Ok, found one. */
159213136Spjd			goto found;
160213136Spjd		}
161213136Spjd	}
162213136Spjd	return (-1);
163213136Spjdfound:
164213136Spjd	dskp->part = curent + 1;
165213136Spjd	ent = &gpttable[curent];
166213136Spjd	dskp->start = ent->ent_lba_start;
167213136Spjd	if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE) {
168213136Spjd		/*
169213136Spjd		 * Clear BOOTME, but leave BOOTONCE set before trying to
170213136Spjd		 * boot from this partition.
171213136Spjd		 */
172213136Spjd		if (hdr_primary_lba > 0) {
173213136Spjd			table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME;
174213136Spjd			gptupdate("primary", dskp, &hdr_primary, table_primary);
175213136Spjd		}
176213136Spjd		if (hdr_backup_lba > 0) {
177213136Spjd			table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME;
178213136Spjd			gptupdate("backup", dskp, &hdr_backup, table_backup);
179213136Spjd		}
180213136Spjd	}
181213136Spjd	return (0);
182213136Spjd}
183213136Spjd
184213136Spjdstatic int
185213136Spjdgptread_hdr(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
186213136Spjd    uint64_t hdrlba)
187213136Spjd{
188213136Spjd	uint32_t crc;
189213136Spjd
190213136Spjd	if (drvread(dskp, secbuf, hdrlba, 1)) {
191213136Spjd		printf("%s: unable to read %s GPT header\n", BOOTPROG, which);
192213136Spjd		return (-1);
193213136Spjd	}
194219083Spjd	bcopy(secbuf, hdr, sizeof(*hdr));
195213136Spjd	if (bcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof(hdr->hdr_sig)) != 0 ||
196213136Spjd	    hdr->hdr_lba_self != hdrlba || hdr->hdr_revision < 0x00010000 ||
197213136Spjd	    hdr->hdr_entsz < sizeof(struct gpt_ent) ||
198213136Spjd	    hdr->hdr_entries > MAXTBLENTS || DEV_BSIZE % hdr->hdr_entsz != 0) {
199213136Spjd		printf("%s: invalid %s GPT header\n", BOOTPROG, which);
200213136Spjd		return (-1);
201213136Spjd	}
202213136Spjd	crc = hdr->hdr_crc_self;
203213136Spjd	hdr->hdr_crc_self = 0;
204213136Spjd	if (crc32(hdr, hdr->hdr_size) != crc) {
205213136Spjd		printf("%s: %s GPT header checksum mismatch\n", BOOTPROG,
206213136Spjd		    which);
207213136Spjd		return (-1);
208213136Spjd	}
209213136Spjd	hdr->hdr_crc_self = crc;
210213136Spjd	return (0);
211213136Spjd}
212213136Spjd
213213136Spjdvoid
214213136Spjdgptbootfailed(struct dsk *dskp)
215213136Spjd{
216213136Spjd
217213136Spjd	if (!(gpttable[curent].ent_attr & GPT_ENT_ATTR_BOOTONCE))
218213136Spjd		return;
219213136Spjd
220213136Spjd	if (hdr_primary_lba > 0) {
221213136Spjd		table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
222213136Spjd		table_primary[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
223213136Spjd		gptupdate("primary", dskp, &hdr_primary, table_primary);
224213136Spjd	}
225213136Spjd	if (hdr_backup_lba > 0) {
226213136Spjd		table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
227213136Spjd		table_backup[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
228213136Spjd		gptupdate("backup", dskp, &hdr_backup, table_backup);
229213136Spjd	}
230213136Spjd}
231213136Spjd
232213136Spjdstatic void
233213136Spjdgptbootconv(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
234213136Spjd    struct gpt_ent *table)
235213136Spjd{
236213136Spjd	struct gpt_ent *ent;
237213136Spjd	daddr_t slba;
238213136Spjd	int table_updated, sector_updated;
239213136Spjd	int entries_per_sec, nent, part;
240213136Spjd
241213136Spjd	table_updated = 0;
242213136Spjd	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
243213136Spjd	for (nent = 0, slba = hdr->hdr_lba_table;
244213136Spjd	     slba < hdr->hdr_lba_table + hdr->hdr_entries / entries_per_sec;
245213136Spjd	     slba++, nent += entries_per_sec) {
246213136Spjd		sector_updated = 0;
247213136Spjd		for (part = 0; part < entries_per_sec; part++) {
248213136Spjd			ent = &table[nent + part];
249213136Spjd			if ((ent->ent_attr & (GPT_ENT_ATTR_BOOTME |
250213136Spjd			    GPT_ENT_ATTR_BOOTONCE |
251213136Spjd			    GPT_ENT_ATTR_BOOTFAILED)) !=
252213136Spjd			    GPT_ENT_ATTR_BOOTONCE) {
253213136Spjd				continue;
254213136Spjd			}
255213136Spjd			ent->ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
256213136Spjd			ent->ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
257213136Spjd			table_updated = 1;
258213136Spjd			sector_updated = 1;
259213136Spjd		}
260213136Spjd		if (!sector_updated)
261213136Spjd			continue;
262219083Spjd		bcopy(&table[nent], secbuf, DEV_BSIZE);
263213136Spjd		if (drvwrite(dskp, secbuf, slba, 1)) {
264213136Spjd			printf("%s: unable to update %s GPT partition table\n",
265213136Spjd			    BOOTPROG, which);
266213136Spjd		}
267213136Spjd	}
268213136Spjd	if (!table_updated)
269213136Spjd		return;
270213136Spjd	hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz);
271213136Spjd	hdr->hdr_crc_self = 0;
272213136Spjd	hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size);
273213136Spjd	bzero(secbuf, DEV_BSIZE);
274219083Spjd	bcopy(hdr, secbuf, hdr->hdr_size);
275213136Spjd	if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1))
276213136Spjd		printf("%s: unable to update %s GPT header\n", BOOTPROG, which);
277213136Spjd}
278213136Spjd
279213136Spjdstatic int
280213136Spjdgptread_table(const char *which, const uuid_t *uuid, struct dsk *dskp,
281213136Spjd    struct gpt_hdr *hdr, struct gpt_ent *table)
282213136Spjd{
283213136Spjd	struct gpt_ent *ent;
284213136Spjd	int entries_per_sec;
285213136Spjd	int part, nent;
286213136Spjd	daddr_t slba;
287213136Spjd
288213136Spjd	if (hdr->hdr_entries == 0)
289213136Spjd		return (0);
290213136Spjd
291213136Spjd	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
292213136Spjd	slba = hdr->hdr_lba_table;
293213136Spjd	nent = 0;
294213136Spjd	for (;;) {
295213136Spjd		if (drvread(dskp, secbuf, slba, 1)) {
296213136Spjd			printf("%s: unable to read %s GPT partition table\n",
297213136Spjd			    BOOTPROG, which);
298213136Spjd			return (-1);
299213136Spjd		}
300213136Spjd		ent = (struct gpt_ent *)secbuf;
301213136Spjd		for (part = 0; part < entries_per_sec; part++, ent++) {
302219083Spjd			bcopy(ent, &table[nent], sizeof(table[nent]));
303213136Spjd			if (++nent >= hdr->hdr_entries)
304213136Spjd				break;
305213136Spjd		}
306213136Spjd		if (nent >= hdr->hdr_entries)
307213136Spjd			break;
308213136Spjd		slba++;
309213136Spjd	}
310213136Spjd	if (crc32(table, nent * hdr->hdr_entsz) != hdr->hdr_crc_table) {
311213136Spjd		printf("%s: %s GPT table checksum mismatch\n", BOOTPROG, which);
312213136Spjd		return (-1);
313213136Spjd	}
314213136Spjd	return (0);
315213136Spjd}
316213136Spjd
317213136Spjdint
318213136Spjdgptread(const uuid_t *uuid, struct dsk *dskp, char *buf)
319213136Spjd{
320213136Spjd	uint64_t altlba;
321213136Spjd
322213136Spjd	/*
323213136Spjd	 * Read and verify both GPT headers: primary and backup.
324213136Spjd	 */
325213136Spjd
326213136Spjd	secbuf = buf;
327213136Spjd	hdr_primary_lba = hdr_backup_lba = 0;
328213136Spjd	curent = -1;
329213136Spjd	bootonce = 1;
330213136Spjd	dskp->start = 0;
331213136Spjd
332213136Spjd	if (gptread_hdr("primary", dskp, &hdr_primary, 1) == 0 &&
333213136Spjd	    gptread_table("primary", uuid, dskp, &hdr_primary,
334213136Spjd	    table_primary) == 0) {
335213136Spjd		hdr_primary_lba = hdr_primary.hdr_lba_self;
336213136Spjd		gpthdr = &hdr_primary;
337213136Spjd		gpttable = table_primary;
338213136Spjd	}
339213136Spjd
340234176Sae	if (hdr_primary_lba > 0) {
341213136Spjd		/*
342234176Sae		 * If primary header is valid, we can get backup
343234176Sae		 * header location from there.
344213136Spjd		 */
345213136Spjd		altlba = hdr_primary.hdr_lba_alt;
346234176Sae	} else {
347234176Sae		altlba = drvsize(dskp);
348234176Sae		if (altlba > 0)
349234176Sae			altlba--;
350213136Spjd	}
351213136Spjd	if (altlba == 0)
352213136Spjd		printf("%s: unable to locate backup GPT header\n", BOOTPROG);
353213136Spjd	else if (gptread_hdr("backup", dskp, &hdr_backup, altlba) == 0 &&
354213136Spjd	    gptread_table("backup", uuid, dskp, &hdr_backup,
355213136Spjd	    table_backup) == 0) {
356213136Spjd		hdr_backup_lba = hdr_backup.hdr_lba_self;
357213136Spjd		if (hdr_primary_lba == 0) {
358213136Spjd			gpthdr = &hdr_backup;
359213136Spjd			gpttable = table_backup;
360213136Spjd			printf("%s: using backup GPT\n", BOOTPROG);
361213136Spjd		}
362213136Spjd	}
363213136Spjd
364213136Spjd	/*
365213136Spjd	 * Convert all BOOTONCE without BOOTME flags into BOOTFAILED.
366213136Spjd	 * BOOTONCE without BOOTME means that we tried to boot from it,
367213136Spjd	 * but failed after leaving gptboot and machine was rebooted.
368213136Spjd	 * We don't want to leave partitions marked as BOOTONCE only,
369213136Spjd	 * because when we boot successfully start-up scripts should
370213136Spjd	 * find at most one partition with only BOOTONCE flag and this
371213136Spjd	 * will mean that we booted from that partition.
372213136Spjd	 */
373213136Spjd	if (hdr_primary_lba != 0)
374213136Spjd		gptbootconv("primary", dskp, &hdr_primary, table_primary);
375213136Spjd	if (hdr_backup_lba != 0)
376213136Spjd		gptbootconv("backup", dskp, &hdr_backup, table_backup);
377213136Spjd
378213136Spjd	if (hdr_primary_lba == 0 && hdr_backup_lba == 0)
379213136Spjd		return (-1);
380213136Spjd	return (0);
381213136Spjd}
382