1/*	$NetBSD: biosboot.c,v 1.32 2019/06/20 10:56:38 martin Exp $ */
2
3/*
4 * Copyright (c) 2009 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to the NetBSD Foundation
8 * by Mike M. Volokhov. Development of this software was supported by the
9 * Google Summer of Code program.
10 * The GSoC project was mentored by Allen Briggs and Joerg Sonnenberger.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#if HAVE_NBTOOL_CONFIG_H
35#include "nbtool_config.h"
36#endif
37
38#include <sys/cdefs.h>
39#ifdef __RCSID
40__RCSID("$NetBSD: biosboot.c,v 1.32 2019/06/20 10:56:38 martin Exp $");
41#endif
42
43#include <sys/stat.h>
44#include <sys/types.h>
45#include <sys/ioctl.h>
46#include <sys/param.h>
47#include <sys/bootblock.h>
48
49#if defined(DIOCGWEDGEINFO) && !defined(HAVE_NBTOOL_CONFIG_H)
50#define USE_WEDGES
51#endif
52#ifdef USE_WEDGES
53#include <sys/disk.h>
54#endif
55
56#include <err.h>
57#include <fcntl.h>
58#include <paths.h>
59#include <stddef.h>
60#include <stdio.h>
61#include <stdlib.h>
62#include <string.h>
63#include <unistd.h>
64
65#include "map.h"
66#include "gpt.h"
67#include "gpt_private.h"
68
69#define DEFAULT_BOOTDIR		"/usr/mdec"
70#define DEFAULT_BOOTCODE	"gptmbr.bin"
71
72static int cmd_biosboot(gpt_t, int, char *[]);
73
74static const char *biosboothelp[] = {
75	"[-A] [-c bootcode] [-i index] [-L label] [-b startsec]",
76#if notyet
77	"[-a alignment] [-b blocknr] [-i index] [-l label]",
78	"[-s size] [-t type]",
79#endif
80};
81
82struct gpt_cmd c_biosboot = {
83	"biosboot",
84	cmd_biosboot,
85	biosboothelp, __arraycount(biosboothelp),
86	0,
87};
88
89#define usage() gpt_usage(NULL, &c_biosboot)
90
91static struct mbr*
92read_boot(gpt_t gpt, const char *bootpath)
93{
94	int bfd, ret = -1;
95	struct mbr *buf;
96	struct stat st;
97	char *bp;
98
99	buf = NULL;
100	bfd = -1;
101
102	if (bootpath == NULL)
103		bp = strdup(DEFAULT_BOOTDIR "/" DEFAULT_BOOTCODE);
104	else if (*bootpath == '/')
105		bp = strdup(bootpath);
106	else {
107		if (asprintf(&bp, "%s/%s", DEFAULT_BOOTDIR, bootpath) < 0)
108			bp = NULL;
109	}
110
111	if (bp == NULL) {
112		gpt_warn(gpt, "Can't allocate memory for bootpath");
113		goto fail;
114	}
115
116	if ((buf = malloc((size_t)gpt->secsz)) == NULL) {
117		gpt_warn(gpt, "Can't allocate memory for sector");
118		goto fail;
119	}
120
121
122	if ((bfd = open(bp, O_RDONLY)) < 0 || fstat(bfd, &st) == -1) {
123		gpt_warn(gpt, "Can't open `%s'", bp);
124		goto fail;
125	}
126
127	if (st.st_size != MBR_DSN_OFFSET) {
128		gpt_warnx(gpt, "The bootcode in `%s' does not match the"
129		    " expected size %u", bp, MBR_DSN_OFFSET);
130		goto fail;
131	}
132
133	if (read(bfd, buf, (size_t)st.st_size) != (ssize_t)st.st_size) {
134		gpt_warn(gpt, "Error reading from `%s'", bp);
135		goto fail;
136	}
137
138	ret = 0;
139fail:
140	if (bfd != -1)
141		close(bfd);
142	if (ret == -1) {
143		free(buf);
144		buf = NULL;
145	}
146	free(bp);
147	return buf;
148}
149
150static int
151set_bootable(gpt_t gpt, map_t map, map_t tbl, unsigned int i)
152{
153	unsigned int j;
154	struct gpt_hdr *hdr = map->map_data;
155	struct gpt_ent *ent;
156	unsigned int ne = le32toh(hdr->hdr_entries);
157
158	for (j = 0; j < ne; j++) {
159		ent = gpt_ent(map, tbl, j);
160		ent->ent_attr &= ~GPT_ENT_ATTR_LEGACY_BIOS_BOOTABLE;
161	}
162
163	ent = gpt_ent(map, tbl, i);
164	ent->ent_attr |= GPT_ENT_ATTR_LEGACY_BIOS_BOOTABLE;
165
166	return gpt_write_crc(gpt, map, tbl);
167}
168
169static int
170biosboot(gpt_t gpt, daddr_t start, uint64_t size, u_int entry, uint8_t *label,
171    const char *bootpath, int active)
172{
173	map_t mbrmap, m;
174	struct mbr *mbr, *bootcode;
175	unsigned int i;
176	struct gpt_ent *ent;
177	uint8_t utfbuf[__arraycount(ent->ent_name) * 3 + 1];
178
179	/*
180	 * Parse and validate partition maps
181	 */
182	if (gpt_hdr(gpt) == NULL)
183		return -1;
184
185	mbrmap = map_find(gpt, MAP_TYPE_PMBR);
186	if (mbrmap == NULL || mbrmap->map_start != 0) {
187		gpt_warnx(gpt, "No valid Protective MBR found");
188		return -1;
189	}
190
191	mbr = mbrmap->map_data;
192
193	/*
194	 * Update the boot code
195	 */
196	if ((bootcode = read_boot(gpt, bootpath)) == NULL) {
197		gpt_warnx(gpt, "Error reading bootcode");
198		return -1;
199	}
200	(void)memcpy(&mbr->mbr_code, &bootcode->mbr_code,
201		sizeof(mbr->mbr_code));
202	free(bootcode);
203
204	for (i = 0; i < __arraycount(mbr->mbr_part); i++)
205		if (mbr->mbr_part[i].part_typ == MBR_PTYPE_PMBR)
206			mbr->mbr_part[i].part_flag = active ? 0x80 : 0;
207
208	/*
209	 * Walk through the GPT and see where we can boot from
210	 */
211	for (m = map_first(gpt); m != NULL; m = m->map_next) {
212		if (m->map_type != MAP_TYPE_GPT_PART || m->map_index < 1)
213			continue;
214
215		ent = m->map_data;
216
217		/* first, prefer user selection */
218		if (entry > 0 && m->map_index == entry)
219			break;
220
221		if (label != NULL) {
222			utf16_to_utf8(ent->ent_name,
223			    __arraycount(ent->ent_name), utfbuf,
224			    __arraycount(utfbuf));
225			if (strcmp((char *)label, (char *)utfbuf) == 0)
226				break;
227		}
228
229		/* next, partition as could be specified by wedge */
230		if (entry < 1 && label == NULL && size > 0 &&
231		    m->map_start == start && m->map_size == (off_t)size)
232			break;
233		/* next could be start sector specified by -b option */
234		if (entry < 1 && label == NULL && size == 0 &&
235		    m->map_start == start)
236			break;
237	}
238
239	if (m == NULL) {
240		gpt_warnx(gpt, "No bootable partition");
241		return -1;
242	}
243
244	i = m->map_index - 1;
245
246
247	if (set_bootable(gpt, gpt->gpt, gpt->tbl, i) == -1)
248		return -1;
249
250	if (set_bootable(gpt, gpt->tpg, gpt->lbt, i) == -1)
251		return -1;
252
253	if (gpt_write(gpt, mbrmap) == -1) {
254		gpt_warnx(gpt, "Cannot update Protective MBR");
255		return -1;
256	}
257
258	gpt_msg(gpt, "Partition %d marked as bootable", i + 1);
259	return 0;
260}
261
262static int
263cmd_biosboot(gpt_t gpt, int argc, char *argv[])
264{
265#ifdef USE_WEDGES
266	struct dkwedge_info dkw;
267#endif
268	int ch;
269	gpt_t ngpt = gpt;
270	daddr_t start = 0;
271	uint64_t size = 0;
272	int active = 0;
273	unsigned int entry = 0;
274	uint8_t *label = NULL;
275	char *bootpath = NULL;
276
277	while ((ch = getopt(argc, argv, "Ac:i:L:b:")) != -1) {
278		switch(ch) {
279		case 'A':
280			active = 1;
281			break;
282		case 'c':
283			if (gpt_name_get(gpt, &bootpath) == -1)
284				goto usage;
285			break;
286		case 'i':
287			if (gpt_uint_get(gpt, &entry) == -1)
288				goto usage;
289			break;
290		case 'L':
291			if (gpt_name_get(gpt, &label) == -1)
292				goto usage;
293			break;
294		case 'b':
295			if (gpt_human_get(gpt, &start) == -1)
296				goto usage;
297			break;
298		default:
299			goto usage;
300		}
301	}
302
303	if (argc != optind)
304		return usage();
305
306#ifdef USE_WEDGES
307	if ((gpt->sb.st_mode & S_IFMT) != S_IFREG &&
308	    ioctl(gpt->fd, DIOCGWEDGEINFO, &dkw) != -1) {
309		if (entry > 0)
310			/* wedges and indexes are mutually exclusive */
311			goto usage;
312		start = dkw.dkw_offset;
313		size = dkw.dkw_size;
314		ngpt = gpt_open(dkw.dkw_parent, gpt->flags, gpt->verbose,
315		    0, 0, 0);
316		if (ngpt == NULL)
317			goto cleanup;
318	}
319#endif
320	if (biosboot(ngpt, start, size, entry, label, bootpath, active) == -1)
321		goto cleanup;
322	if (ngpt != gpt)
323		gpt_close(ngpt);
324	return 0;
325usage:
326	usage();
327cleanup:
328	if (ngpt != gpt)
329		gpt_close(ngpt);
330	free(bootpath);
331	free(label);
332	return -1;
333}
334