1/*-
2 * Copyright (c) 2007 Yahoo!, Inc.
3 * All rights reserved.
4 * Written by: John Baldwin <jhb@FreeBSD.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the author nor the names of any co-contributors
15 *    may be used to endorse or promote products derived from this software
16 *    without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: src/sbin/gpt/boot.c,v 1.2.2.1.6.1 2010/02/10 00:26:20 kensmith Exp $");
33
34#include <sys/types.h>
35#include <sys/stat.h>
36
37#include <assert.h>
38#include <err.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <unistd.h>
42#include <stdio.h>
43#include <stdlib.h>
44
45#include "map.h"
46#include "gpt.h"
47
48static uuid_t boot_uuid = GPT_ENT_TYPE_FREEBSD_BOOT;
49static const char *pmbr_path = "/boot/pmbr";
50static const char *gptboot_path = "/boot/gptboot";
51static u_long boot_size;
52
53static void
54usage_boot(void)
55{
56
57	fprintf(stderr,
58	    "usage: %s [-b pmbr] [-g gptboot] [-s count] device ...\n",
59	    getprogname());
60	exit(1);
61}
62
63static int
64gpt_find(uuid_t *type, map_t **mapp)
65{
66	map_t *gpt, *tbl, *map;
67	struct gpt_hdr *hdr;
68	struct gpt_ent *ent;
69	unsigned int i;
70
71	/* Find a GPT partition with the requested UUID type. */
72	gpt = map_find(MAP_TYPE_PRI_GPT_HDR);
73	if (gpt == NULL) {
74		warnx("%s: error: no primary GPT header", device_name);
75		return (ENXIO);
76	}
77
78	tbl = map_find(MAP_TYPE_PRI_GPT_TBL);
79	if (tbl == NULL) {
80		warnx("%s: error: no primary partition table", device_name);
81		return (ENXIO);
82	}
83
84	hdr = gpt->map_data;
85	for (i = 0; i < le32toh(hdr->hdr_entries); i++) {
86		ent = (void *)((char *)tbl->map_data + i *
87		    le32toh(hdr->hdr_entsz));
88		if (uuid_equal(&ent->ent_type, type, NULL))
89			break;
90	}
91	if (i == le32toh(hdr->hdr_entries)) {
92		*mapp = NULL;
93		return (0);
94	}
95
96	/* Lookup the map corresponding to this partition. */
97	for (map = map_find(MAP_TYPE_GPT_PART); map != NULL;
98	     map = map->map_next) {
99		if (map->map_type != MAP_TYPE_GPT_PART)
100			continue;
101		if (map->map_start == (off_t)le64toh(ent->ent_lba_start)) {
102			assert(map->map_start + map->map_size - 1LL ==
103			    (off_t)le64toh(ent->ent_lba_end));
104			*mapp = map;
105			return (0);
106		}
107	}
108
109	/* Hmm, the map list is not in sync with the GPT table. */
110	errx(1, "internal map list is corrupted");
111}
112
113static void
114boot(int fd)
115{
116	struct stat sb;
117	off_t bsize, ofs;
118	map_t *pmbr, *gptboot;
119	struct mbr *mbr;
120	char *buf;
121	ssize_t nbytes;
122	unsigned int entry;
123	int bfd;
124
125	/* First step: verify boot partition size. */
126	if (boot_size == 0)
127		/* Default to 64k. */
128		bsize = 65536 / secsz;
129	else {
130		if (boot_size * secsz < 16384) {
131			warnx("invalid boot partition size %lu", boot_size);
132			return;
133		}
134		bsize = boot_size;
135	}
136
137	/* Second step: write the PMBR boot loader into the PMBR. */
138	pmbr = map_find(MAP_TYPE_PMBR);
139	if (pmbr == NULL) {
140		warnx("%s: error: PMBR not found", device_name);
141		return;
142	}
143	bfd = open(pmbr_path, O_RDONLY);
144	if (bfd < 0 || fstat(bfd, &sb) < 0) {
145		warn("unable to open PMBR boot loader");
146		return;
147	}
148	if (sb.st_size != secsz) {
149		warnx("invalid PMBR boot loader");
150		return;
151	}
152	mbr = pmbr->map_data;
153	nbytes = read(bfd, mbr->mbr_code, sizeof(mbr->mbr_code));
154	if (nbytes < 0) {
155		warn("unable to read PMBR boot loader");
156		return;
157	}
158	if (nbytes != sizeof(mbr->mbr_code)) {
159		warnx("short read of PMBR boot loader");
160		return;
161	}
162	close(bfd);
163	gpt_write(fd, pmbr);
164
165	/* Third step: open gptboot and obtain its size. */
166	bfd = open(gptboot_path, O_RDONLY);
167	if (bfd < 0 || fstat(bfd, &sb) < 0) {
168		warn("unable to open GPT boot loader");
169		return;
170	}
171
172	/* Fourth step: find an existing boot partition or create one. */
173	if (gpt_find(&boot_uuid, &gptboot) != 0)
174		return;
175	if (gptboot != NULL) {
176		if (gptboot->map_size * secsz < sb.st_size) {
177			warnx("%s: error: boot partition is too small",
178			    device_name);
179			return;
180		}
181	} else if (bsize * secsz < sb.st_size) {
182		warnx(
183		    "%s: error: proposed size for boot partition is too small",
184		    device_name);
185		return;
186	} else {
187		entry = 0;
188		gptboot = gpt_add_part(fd, &boot_uuid, 0, bsize, &entry);
189		if (gptboot == NULL)
190			return;
191	}
192
193	/*
194	 * Fourth step, write out the gptboot binary to the boot partition.
195	 * When writing to a disk device, the write must be sector aligned
196	 * and not write to any partial sectors, so round up the buffer size
197	 * to the next sector and zero it.
198	 */
199	bsize = (sb.st_size + secsz - 1) / secsz * secsz;
200	buf = calloc(1, bsize);
201	nbytes = read(bfd, buf, sb.st_size);
202	if (nbytes < 0) {
203		warn("unable to read GPT boot loader");
204		return;
205	}
206	if (nbytes != sb.st_size) {
207		warnx("short read of GPT boot loader");
208		return;
209	}
210	close(bfd);
211	ofs = gptboot->map_start * secsz;
212	if (lseek(fd, ofs, SEEK_SET) != ofs) {
213		warn("%s: error: unable to seek to boot partition",
214		    device_name);
215		return;
216	}
217	nbytes = write(fd, buf, bsize);
218	if (nbytes < 0) {
219		warn("unable to write GPT boot loader");
220		return;
221	}
222	if (nbytes != bsize) {
223		warnx("short write of GPT boot loader");
224		return;
225	}
226	free(buf);
227}
228
229int
230cmd_boot(int argc, char *argv[])
231{
232	char *p;
233	int ch, fd;
234
235	while ((ch = getopt(argc, argv, "b:g:s:")) != -1) {
236		switch (ch) {
237		case 'b':
238			pmbr_path = optarg;
239			break;
240		case 'g':
241			gptboot_path = optarg;
242			break;
243		case 's':
244			if (boot_size > 0)
245				usage_boot();
246			boot_size = strtol(optarg, &p, 10);
247			if (*p != '\0' || boot_size < 1)
248				usage_boot();
249			break;
250		default:
251			usage_boot();
252		}
253	}
254
255	if (argc == optind)
256		usage_boot();
257
258	while (optind < argc) {
259		fd = gpt_open(argv[optind++]);
260		if (fd == -1) {
261			warn("unable to open device '%s'", device_name);
262			return (1);
263		}
264
265		boot(fd);
266
267		gpt_close(fd);
268	}
269
270	return (0);
271}
272