1/*-
2 * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: stable/11/stand/libsa/gpt.c 329175 2018-02-12 17:44:35Z kevans $");
29
30#include <sys/param.h>
31#include <sys/gpt.h>
32
33#ifndef LITTLE_ENDIAN
34#error gpt.c works only for little endian architectures
35#endif
36
37#include "stand.h"
38#include "crc32.h"
39#include "drv.h"
40#include "gpt.h"
41
42static struct gpt_hdr hdr_primary, hdr_backup, *gpthdr;
43static uint64_t hdr_primary_lba, hdr_backup_lba;
44static struct gpt_ent table_primary[MAXTBLENTS], table_backup[MAXTBLENTS];
45static struct gpt_ent *gpttable;
46static int curent, bootonce;
47
48/*
49 * Buffer below 64kB passed on gptread(), which can hold at least
50 * one sector of data (512 bytes).
51 */
52static char *secbuf;
53
54static void
55gptupdate(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
56    struct gpt_ent *table)
57{
58	int entries_per_sec, firstent;
59	daddr_t slba;
60
61	/*
62	 * We need to update the following for both primary and backup GPT:
63	 * 1. Sector on disk that contains current partition.
64	 * 2. Partition table checksum.
65	 * 3. Header checksum.
66	 * 4. Header on disk.
67	 */
68
69	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
70	slba = curent / entries_per_sec;
71	firstent = slba * entries_per_sec;
72	bcopy(&table[firstent], secbuf, DEV_BSIZE);
73	slba += hdr->hdr_lba_table;
74	if (drvwrite(dskp, secbuf, slba, 1)) {
75		printf("%s: unable to update %s GPT partition table\n",
76		    BOOTPROG, which);
77		return;
78	}
79	hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz);
80	hdr->hdr_crc_self = 0;
81	hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size);
82	bzero(secbuf, DEV_BSIZE);
83	bcopy(hdr, secbuf, hdr->hdr_size);
84	if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1)) {
85		printf("%s: unable to update %s GPT header\n", BOOTPROG, which);
86		return;
87	}
88}
89
90int
91gptfind(const uuid_t *uuid, struct dsk *dskp, int part)
92{
93	struct gpt_ent *ent;
94	int firsttry;
95
96	if (part >= 0) {
97		if (part == 0 || part > gpthdr->hdr_entries) {
98			printf("%s: invalid partition index\n", BOOTPROG);
99			return (-1);
100		}
101		ent = &gpttable[part - 1];
102		if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0) {
103			printf("%s: specified partition is not UFS\n",
104			    BOOTPROG);
105			return (-1);
106		}
107		curent = part - 1;
108		goto found;
109	}
110
111	firsttry = (curent == -1);
112	curent++;
113	if (curent >= gpthdr->hdr_entries) {
114		curent = gpthdr->hdr_entries;
115		return (-1);
116	}
117	if (bootonce) {
118		/*
119		 * First look for partition with both GPT_ENT_ATTR_BOOTME and
120		 * GPT_ENT_ATTR_BOOTONCE flags.
121		 */
122		for (; curent < gpthdr->hdr_entries; curent++) {
123			ent = &gpttable[curent];
124			if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
125				continue;
126			if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME))
127				continue;
128			if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTONCE))
129				continue;
130			/* Ok, found one. */
131			goto found;
132		}
133		bootonce = 0;
134		curent = 0;
135	}
136	for (; curent < gpthdr->hdr_entries; curent++) {
137		ent = &gpttable[curent];
138		if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
139			continue;
140		if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME))
141			continue;
142		if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE)
143			continue;
144		/* Ok, found one. */
145		goto found;
146	}
147	if (firsttry) {
148		/*
149		 * No partition with BOOTME flag was found, try to boot from
150		 * first UFS partition.
151		 */
152		for (curent = 0; curent < gpthdr->hdr_entries; curent++) {
153			ent = &gpttable[curent];
154			if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0)
155				continue;
156			/* Ok, found one. */
157			goto found;
158		}
159	}
160	return (-1);
161found:
162	dskp->part = curent + 1;
163	ent = &gpttable[curent];
164	dskp->start = ent->ent_lba_start;
165	if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE) {
166		/*
167		 * Clear BOOTME, but leave BOOTONCE set before trying to
168		 * boot from this partition.
169		 */
170		if (hdr_primary_lba > 0) {
171			table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME;
172			gptupdate("primary", dskp, &hdr_primary, table_primary);
173		}
174		if (hdr_backup_lba > 0) {
175			table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME;
176			gptupdate("backup", dskp, &hdr_backup, table_backup);
177		}
178	}
179	return (0);
180}
181
182static int
183gptread_hdr(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
184    uint64_t hdrlba)
185{
186	uint32_t crc;
187
188	if (drvread(dskp, secbuf, hdrlba, 1)) {
189		printf("%s: unable to read %s GPT header\n", BOOTPROG, which);
190		return (-1);
191	}
192	bcopy(secbuf, hdr, sizeof(*hdr));
193	if (bcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof(hdr->hdr_sig)) != 0 ||
194	    hdr->hdr_lba_self != hdrlba || hdr->hdr_revision < 0x00010000 ||
195	    hdr->hdr_entsz < sizeof(struct gpt_ent) ||
196	    hdr->hdr_entries > MAXTBLENTS || DEV_BSIZE % hdr->hdr_entsz != 0) {
197		printf("%s: invalid %s GPT header\n", BOOTPROG, which);
198		return (-1);
199	}
200	crc = hdr->hdr_crc_self;
201	hdr->hdr_crc_self = 0;
202	if (crc32(hdr, hdr->hdr_size) != crc) {
203		printf("%s: %s GPT header checksum mismatch\n", BOOTPROG,
204		    which);
205		return (-1);
206	}
207	hdr->hdr_crc_self = crc;
208	return (0);
209}
210
211void
212gptbootfailed(struct dsk *dskp)
213{
214
215	if (!(gpttable[curent].ent_attr & GPT_ENT_ATTR_BOOTONCE))
216		return;
217
218	if (hdr_primary_lba > 0) {
219		table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
220		table_primary[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
221		gptupdate("primary", dskp, &hdr_primary, table_primary);
222	}
223	if (hdr_backup_lba > 0) {
224		table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
225		table_backup[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
226		gptupdate("backup", dskp, &hdr_backup, table_backup);
227	}
228}
229
230static void
231gptbootconv(const char *which, struct dsk *dskp, struct gpt_hdr *hdr,
232    struct gpt_ent *table)
233{
234	struct gpt_ent *ent;
235	daddr_t slba;
236	int table_updated, sector_updated;
237	int entries_per_sec, nent, part;
238
239	table_updated = 0;
240	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
241	for (nent = 0, slba = hdr->hdr_lba_table;
242	     slba < hdr->hdr_lba_table + hdr->hdr_entries / entries_per_sec;
243	     slba++, nent += entries_per_sec) {
244		sector_updated = 0;
245		for (part = 0; part < entries_per_sec; part++) {
246			ent = &table[nent + part];
247			if ((ent->ent_attr & (GPT_ENT_ATTR_BOOTME |
248			    GPT_ENT_ATTR_BOOTONCE |
249			    GPT_ENT_ATTR_BOOTFAILED)) !=
250			    GPT_ENT_ATTR_BOOTONCE) {
251				continue;
252			}
253			ent->ent_attr &= ~GPT_ENT_ATTR_BOOTONCE;
254			ent->ent_attr |= GPT_ENT_ATTR_BOOTFAILED;
255			table_updated = 1;
256			sector_updated = 1;
257		}
258		if (!sector_updated)
259			continue;
260		bcopy(&table[nent], secbuf, DEV_BSIZE);
261		if (drvwrite(dskp, secbuf, slba, 1)) {
262			printf("%s: unable to update %s GPT partition table\n",
263			    BOOTPROG, which);
264		}
265	}
266	if (!table_updated)
267		return;
268	hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz);
269	hdr->hdr_crc_self = 0;
270	hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size);
271	bzero(secbuf, DEV_BSIZE);
272	bcopy(hdr, secbuf, hdr->hdr_size);
273	if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1))
274		printf("%s: unable to update %s GPT header\n", BOOTPROG, which);
275}
276
277static int
278gptread_table(const char *which, const uuid_t *uuid, struct dsk *dskp,
279    struct gpt_hdr *hdr, struct gpt_ent *table)
280{
281	struct gpt_ent *ent;
282	int entries_per_sec;
283	int part, nent;
284	daddr_t slba;
285
286	if (hdr->hdr_entries == 0)
287		return (0);
288
289	entries_per_sec = DEV_BSIZE / hdr->hdr_entsz;
290	slba = hdr->hdr_lba_table;
291	nent = 0;
292	for (;;) {
293		if (drvread(dskp, secbuf, slba, 1)) {
294			printf("%s: unable to read %s GPT partition table\n",
295			    BOOTPROG, which);
296			return (-1);
297		}
298		ent = (struct gpt_ent *)secbuf;
299		for (part = 0; part < entries_per_sec; part++, ent++) {
300			bcopy(ent, &table[nent], sizeof(table[nent]));
301			if (++nent >= hdr->hdr_entries)
302				break;
303		}
304		if (nent >= hdr->hdr_entries)
305			break;
306		slba++;
307	}
308	if (crc32(table, nent * hdr->hdr_entsz) != hdr->hdr_crc_table) {
309		printf("%s: %s GPT table checksum mismatch\n", BOOTPROG, which);
310		return (-1);
311	}
312	return (0);
313}
314
315int
316gptread(const uuid_t *uuid, struct dsk *dskp, char *buf)
317{
318	uint64_t altlba;
319
320	/*
321	 * Read and verify both GPT headers: primary and backup.
322	 */
323
324	secbuf = buf;
325	hdr_primary_lba = hdr_backup_lba = 0;
326	curent = -1;
327	bootonce = 1;
328	dskp->start = 0;
329
330	if (gptread_hdr("primary", dskp, &hdr_primary, 1) == 0 &&
331	    gptread_table("primary", uuid, dskp, &hdr_primary,
332	    table_primary) == 0) {
333		hdr_primary_lba = hdr_primary.hdr_lba_self;
334		gpthdr = &hdr_primary;
335		gpttable = table_primary;
336	}
337
338	if (hdr_primary_lba > 0) {
339		/*
340		 * If primary header is valid, we can get backup
341		 * header location from there.
342		 */
343		altlba = hdr_primary.hdr_lba_alt;
344	} else {
345		altlba = drvsize(dskp);
346		if (altlba > 0)
347			altlba--;
348	}
349	if (altlba == 0)
350		printf("%s: unable to locate backup GPT header\n", BOOTPROG);
351	else if (gptread_hdr("backup", dskp, &hdr_backup, altlba) == 0 &&
352	    gptread_table("backup", uuid, dskp, &hdr_backup,
353	    table_backup) == 0) {
354		hdr_backup_lba = hdr_backup.hdr_lba_self;
355		if (hdr_primary_lba == 0) {
356			gpthdr = &hdr_backup;
357			gpttable = table_backup;
358			printf("%s: using backup GPT\n", BOOTPROG);
359		}
360	}
361
362	/*
363	 * Convert all BOOTONCE without BOOTME flags into BOOTFAILED.
364	 * BOOTONCE without BOOTME means that we tried to boot from it,
365	 * but failed after leaving gptboot and machine was rebooted.
366	 * We don't want to leave partitions marked as BOOTONCE only,
367	 * because when we boot successfully start-up scripts should
368	 * find at most one partition with only BOOTONCE flag and this
369	 * will mean that we booted from that partition.
370	 */
371	if (hdr_primary_lba != 0)
372		gptbootconv("primary", dskp, &hdr_primary, table_primary);
373	if (hdr_backup_lba != 0)
374		gptbootconv("backup", dskp, &hdr_backup, table_backup);
375
376	if (hdr_primary_lba == 0 && hdr_backup_lba == 0)
377		return (-1);
378	return (0);
379}
380