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: stable/11/stand/libsa/gpt.c 329175 2018-02-12 17:44:35Z kevans $");
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
37329175Skevans#include "stand.h"
38213136Spjd#include "crc32.h"
39213136Spjd#include "drv.h"
40213136Spjd#include "gpt.h"
41213136Spjd
42213136Spjdstatic struct gpt_hdr hdr_primary, hdr_backup, *gpthdr;
43213136Spjdstatic uint64_t hdr_primary_lba, hdr_backup_lba;
44213136Spjdstatic struct gpt_ent table_primary[MAXTBLENTS], table_backup[MAXTBLENTS];
45213136Spjdstatic struct gpt_ent *gpttable;
46213136Spjdstatic int curent, bootonce;
47213136Spjd
48213136Spjd/*
49213136Spjd * Buffer below 64kB passed on gptread(), which can hold at least
50214114Spjd * one sector of data (512 bytes).
51213136Spjd */
52213136Spjdstatic char *secbuf;
53213136Spjd
54213136Spjdstatic void
55213136Spjdgptupdate(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
56213136Spjd    struct gpt_ent *table)
57213136Spjd{
58213136Spjd	int entries_per_sec, firstent;
59213136Spjd	daddr_t slba;
60213136Spjd
61213136Spjd	/*
62213136Spjd	 * We need to update the following for both primary and backup GPT:
63214114Spjd	 * 1. Sector on disk that contains current partition.
64213136Spjd	 * 2. Partition table checksum.
65213136Spjd	 * 3. Header checksum.
66213136Spjd	 * 4. Header on disk.
67213136Spjd	 */
68213136Spjd
69213136Spjd	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
70213136Spjd	slba = curent / entries_per_sec;
71213136Spjd	firstent = slba * entries_per_sec;
72219083Spjd	bcopy(&table[firstent], secbuf, DEV_BSIZE);
73213136Spjd	slba += hdr->hdr_lba_table;
74213136Spjd	if (drvwrite(dskp, secbuf, slba, 1)) {
75213136Spjd		printf("%s: unable to update %s GPT partition table\n",
76213136Spjd		    BOOTPROG, which);
77213136Spjd		return;
78213136Spjd	}
79213136Spjd	hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz);
80213136Spjd	hdr->hdr_crc_self = 0;
81213136Spjd	hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size);
82213136Spjd	bzero(secbuf, DEV_BSIZE);
83219083Spjd	bcopy(hdr, secbuf, hdr->hdr_size);
84213136Spjd	if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1)) {
85213136Spjd		printf("%s: unable to update %s GPT header\n", BOOTPROG, which);
86213136Spjd		return;
87213136Spjd	}
88213136Spjd}
89213136Spjd
90213136Spjdint
91213136Spjdgptfind(const uuid_t *uuid, struct dsk *dskp, int part)
92213136Spjd{
93213136Spjd	struct gpt_ent *ent;
94213136Spjd	int firsttry;
95213136Spjd
96213136Spjd	if (part >= 0) {
97213136Spjd		if (part == 0 || part > gpthdr->hdr_entries) {
98213136Spjd			printf("%s: invalid partition index\n", BOOTPROG);
99213136Spjd			return (-1);
100213136Spjd		}
101213136Spjd		ent = &gpttable[part - 1];
102213136Spjd		if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0) {
103213136Spjd			printf("%s: specified partition is not UFS\n",
104213136Spjd			    BOOTPROG);
105213136Spjd			return (-1);
106213136Spjd		}
107213136Spjd		curent = part - 1;
108213136Spjd		goto found;
109213136Spjd	}
110213136Spjd
111213136Spjd	firsttry = (curent == -1);
112213136Spjd	curent++;
113213136Spjd	if (curent >= gpthdr->hdr_entries) {
114213136Spjd		curent = gpthdr->hdr_entries;
115213136Spjd		return (-1);
116213136Spjd	}
117213136Spjd	if (bootonce) {
118213136Spjd		/*
119213136Spjd		 * First look for partition with both GPT_ENT_ATTR_BOOTME and
120213136Spjd		 * GPT_ENT_ATTR_BOOTONCE flags.
121213136Spjd		 */
122213136Spjd		for (; curent < gpthdr->hdr_entries; curent++) {
123213136Spjd			ent = &gpttable[curent];
124213136Spjd			if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
125213136Spjd				continue;
126213136Spjd			if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME))
127213136Spjd				continue;
128213136Spjd			if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTONCE))
129213136Spjd				continue;
130213136Spjd			/* Ok, found one. */
131213136Spjd			goto found;
132213136Spjd		}
133213136Spjd		bootonce = 0;
134213136Spjd		curent = 0;
135213136Spjd	}
136213136Spjd	for (; curent < gpthdr->hdr_entries; curent++) {
137213136Spjd		ent = &gpttable[curent];
138213136Spjd		if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
139213136Spjd			continue;
140213136Spjd		if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME))
141213136Spjd			continue;
142213136Spjd		if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE)
143213136Spjd			continue;
144213136Spjd		/* Ok, found one. */
145213136Spjd		goto found;
146213136Spjd	}
147213136Spjd	if (firsttry) {
148213136Spjd		/*
149213136Spjd		 * No partition with BOOTME flag was found, try to boot from
150213136Spjd		 * first UFS partition.
151213136Spjd		 */
152213136Spjd		for (curent = 0; curent < gpthdr->hdr_entries; curent++) {
153213136Spjd			ent = &gpttable[curent];
154213136Spjd			if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
155213136Spjd				continue;
156213136Spjd			/* Ok, found one. */
157213136Spjd			goto found;
158213136Spjd		}
159213136Spjd	}
160213136Spjd	return (-1);
161213136Spjdfound:
162213136Spjd	dskp->part = curent + 1;
163213136Spjd	ent = &gpttable[curent];
164213136Spjd	dskp->start = ent->ent_lba_start;
165213136Spjd	if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE) {
166213136Spjd		/*
167213136Spjd		 * Clear BOOTME, but leave BOOTONCE set before trying to
168213136Spjd		 * boot from this partition.
169213136Spjd		 */
170213136Spjd		if (hdr_primary_lba > 0) {
171213136Spjd			table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME;
172213136Spjd			gptupdate("primary", dskp, &hdr_primary, table_primary);
173213136Spjd		}
174213136Spjd		if (hdr_backup_lba > 0) {
175213136Spjd			table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME;
176213136Spjd			gptupdate("backup", dskp, &hdr_backup, table_backup);
177213136Spjd		}
178213136Spjd	}
179213136Spjd	return (0);
180213136Spjd}
181213136Spjd
182213136Spjdstatic int
183213136Spjdgptread_hdr(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
184213136Spjd    uint64_t hdrlba)
185213136Spjd{
186213136Spjd	uint32_t crc;
187213136Spjd
188213136Spjd	if (drvread(dskp, secbuf, hdrlba, 1)) {
189213136Spjd		printf("%s: unable to read %s GPT header\n", BOOTPROG, which);
190213136Spjd		return (-1);
191213136Spjd	}
192219083Spjd	bcopy(secbuf, hdr, sizeof(*hdr));
193213136Spjd	if (bcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof(hdr->hdr_sig)) != 0 ||
194213136Spjd	    hdr->hdr_lba_self != hdrlba || hdr->hdr_revision < 0x00010000 ||
195213136Spjd	    hdr->hdr_entsz < sizeof(struct gpt_ent) ||
196213136Spjd	    hdr->hdr_entries > MAXTBLENTS || DEV_BSIZE % hdr->hdr_entsz != 0) {
197213136Spjd		printf("%s: invalid %s GPT header\n", BOOTPROG, which);
198213136Spjd		return (-1);
199213136Spjd	}
200213136Spjd	crc = hdr->hdr_crc_self;
201213136Spjd	hdr->hdr_crc_self = 0;
202213136Spjd	if (crc32(hdr, hdr->hdr_size) != crc) {
203213136Spjd		printf("%s: %s GPT header checksum mismatch\n", BOOTPROG,
204213136Spjd		    which);
205213136Spjd		return (-1);
206213136Spjd	}
207213136Spjd	hdr->hdr_crc_self = crc;
208213136Spjd	return (0);
209213136Spjd}
210213136Spjd
211213136Spjdvoid
212213136Spjdgptbootfailed(struct dsk *dskp)
213213136Spjd{
214213136Spjd
215213136Spjd	if (!(gpttable[curent].ent_attr & GPT_ENT_ATTR_BOOTONCE))
216213136Spjd		return;
217213136Spjd
218213136Spjd	if (hdr_primary_lba > 0) {
219213136Spjd		table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
220213136Spjd		table_primary[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
221213136Spjd		gptupdate("primary", dskp, &hdr_primary, table_primary);
222213136Spjd	}
223213136Spjd	if (hdr_backup_lba > 0) {
224213136Spjd		table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
225213136Spjd		table_backup[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
226213136Spjd		gptupdate("backup", dskp, &hdr_backup, table_backup);
227213136Spjd	}
228213136Spjd}
229213136Spjd
230213136Spjdstatic void
231213136Spjdgptbootconv(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
232213136Spjd    struct gpt_ent *table)
233213136Spjd{
234213136Spjd	struct gpt_ent *ent;
235213136Spjd	daddr_t slba;
236213136Spjd	int table_updated, sector_updated;
237213136Spjd	int entries_per_sec, nent, part;
238213136Spjd
239213136Spjd	table_updated = 0;
240213136Spjd	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
241213136Spjd	for (nent = 0, slba = hdr->hdr_lba_table;
242213136Spjd	     slba < hdr->hdr_lba_table + hdr->hdr_entries / entries_per_sec;
243213136Spjd	     slba++, nent += entries_per_sec) {
244213136Spjd		sector_updated = 0;
245213136Spjd		for (part = 0; part < entries_per_sec; part++) {
246213136Spjd			ent = &table[nent + part];
247213136Spjd			if ((ent->ent_attr & (GPT_ENT_ATTR_BOOTME |
248213136Spjd			    GPT_ENT_ATTR_BOOTONCE |
249213136Spjd			    GPT_ENT_ATTR_BOOTFAILED)) !=
250213136Spjd			    GPT_ENT_ATTR_BOOTONCE) {
251213136Spjd				continue;
252213136Spjd			}
253213136Spjd			ent->ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
254213136Spjd			ent->ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
255213136Spjd			table_updated = 1;
256213136Spjd			sector_updated = 1;
257213136Spjd		}
258213136Spjd		if (!sector_updated)
259213136Spjd			continue;
260219083Spjd		bcopy(&table[nent], secbuf, DEV_BSIZE);
261213136Spjd		if (drvwrite(dskp, secbuf, slba, 1)) {
262213136Spjd			printf("%s: unable to update %s GPT partition table\n",
263213136Spjd			    BOOTPROG, which);
264213136Spjd		}
265213136Spjd	}
266213136Spjd	if (!table_updated)
267213136Spjd		return;
268213136Spjd	hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz);
269213136Spjd	hdr->hdr_crc_self = 0;
270213136Spjd	hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size);
271213136Spjd	bzero(secbuf, DEV_BSIZE);
272219083Spjd	bcopy(hdr, secbuf, hdr->hdr_size);
273213136Spjd	if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1))
274213136Spjd		printf("%s: unable to update %s GPT header\n", BOOTPROG, which);
275213136Spjd}
276213136Spjd
277213136Spjdstatic int
278213136Spjdgptread_table(const char *which, const uuid_t *uuid, struct dsk *dskp,
279213136Spjd    struct gpt_hdr *hdr, struct gpt_ent *table)
280213136Spjd{
281213136Spjd	struct gpt_ent *ent;
282213136Spjd	int entries_per_sec;
283213136Spjd	int part, nent;
284213136Spjd	daddr_t slba;
285213136Spjd
286213136Spjd	if (hdr->hdr_entries == 0)
287213136Spjd		return (0);
288213136Spjd
289213136Spjd	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
290213136Spjd	slba = hdr->hdr_lba_table;
291213136Spjd	nent = 0;
292213136Spjd	for (;;) {
293213136Spjd		if (drvread(dskp, secbuf, slba, 1)) {
294213136Spjd			printf("%s: unable to read %s GPT partition table\n",
295213136Spjd			    BOOTPROG, which);
296213136Spjd			return (-1);
297213136Spjd		}
298213136Spjd		ent = (struct gpt_ent *)secbuf;
299213136Spjd		for (part = 0; part < entries_per_sec; part++, ent++) {
300219083Spjd			bcopy(ent, &table[nent], sizeof(table[nent]));
301213136Spjd			if (++nent >= hdr->hdr_entries)
302213136Spjd				break;
303213136Spjd		}
304213136Spjd		if (nent >= hdr->hdr_entries)
305213136Spjd			break;
306213136Spjd		slba++;
307213136Spjd	}
308213136Spjd	if (crc32(table, nent * hdr->hdr_entsz) != hdr->hdr_crc_table) {
309213136Spjd		printf("%s: %s GPT table checksum mismatch\n", BOOTPROG, which);
310213136Spjd		return (-1);
311213136Spjd	}
312213136Spjd	return (0);
313213136Spjd}
314213136Spjd
315213136Spjdint
316213136Spjdgptread(const uuid_t *uuid, struct dsk *dskp, char *buf)
317213136Spjd{
318213136Spjd	uint64_t altlba;
319213136Spjd
320213136Spjd	/*
321213136Spjd	 * Read and verify both GPT headers: primary and backup.
322213136Spjd	 */
323213136Spjd
324213136Spjd	secbuf = buf;
325213136Spjd	hdr_primary_lba = hdr_backup_lba = 0;
326213136Spjd	curent = -1;
327213136Spjd	bootonce = 1;
328213136Spjd	dskp->start = 0;
329213136Spjd
330213136Spjd	if (gptread_hdr("primary", dskp, &hdr_primary, 1) == 0 &&
331213136Spjd	    gptread_table("primary", uuid, dskp, &hdr_primary,
332213136Spjd	    table_primary) == 0) {
333213136Spjd		hdr_primary_lba = hdr_primary.hdr_lba_self;
334213136Spjd		gpthdr = &hdr_primary;
335213136Spjd		gpttable = table_primary;
336213136Spjd	}
337213136Spjd
338234176Sae	if (hdr_primary_lba > 0) {
339213136Spjd		/*
340234176Sae		 * If primary header is valid, we can get backup
341234176Sae		 * header location from there.
342213136Spjd		 */
343213136Spjd		altlba = hdr_primary.hdr_lba_alt;
344234176Sae	} else {
345234176Sae		altlba = drvsize(dskp);
346234176Sae		if (altlba > 0)
347234176Sae			altlba--;
348213136Spjd	}
349213136Spjd	if (altlba == 0)
350213136Spjd		printf("%s: unable to locate backup GPT header\n", BOOTPROG);
351213136Spjd	else if (gptread_hdr("backup", dskp, &hdr_backup, altlba) == 0 &&
352213136Spjd	    gptread_table("backup", uuid, dskp, &hdr_backup,
353213136Spjd	    table_backup) == 0) {
354213136Spjd		hdr_backup_lba = hdr_backup.hdr_lba_self;
355213136Spjd		if (hdr_primary_lba == 0) {
356213136Spjd			gpthdr = &hdr_backup;
357213136Spjd			gpttable = table_backup;
358213136Spjd			printf("%s: using backup GPT\n", BOOTPROG);
359213136Spjd		}
360213136Spjd	}
361213136Spjd
362213136Spjd	/*
363213136Spjd	 * Convert all BOOTONCE without BOOTME flags into BOOTFAILED.
364213136Spjd	 * BOOTONCE without BOOTME means that we tried to boot from it,
365213136Spjd	 * but failed after leaving gptboot and machine was rebooted.
366213136Spjd	 * We don't want to leave partitions marked as BOOTONCE only,
367213136Spjd	 * because when we boot successfully start-up scripts should
368213136Spjd	 * find at most one partition with only BOOTONCE flag and this
369213136Spjd	 * will mean that we booted from that partition.
370213136Spjd	 */
371213136Spjd	if (hdr_primary_lba != 0)
372213136Spjd		gptbootconv("primary", dskp, &hdr_primary, table_primary);
373213136Spjd	if (hdr_backup_lba != 0)
374213136Spjd		gptbootconv("backup", dskp, &hdr_backup, table_backup);
375213136Spjd
376213136Spjd	if (hdr_primary_lba == 0 && hdr_backup_lba == 0)
377213136Spjd		return (-1);
378213136Spjd	return (0);
379213136Spjd}
380