1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-18 10:35:55 +00:00
freebsd/sbin/gpt/boot.c

272 lines
6.7 KiB
C
Raw Normal View History

First cut at support for booting a GPT labeled disk via the BIOS bootstrap on i386 and amd64 machines. The overall process is that /boot/pmbr lives in the PMBR (similar to /boot/mbr for MBR disks) and is responsible for locating and loading /boot/gptboot. /boot/gptboot is similar to /boot/boot except that it groks GPT rather than MBR + bsdlabel. Unlike /boot/boot, /boot/gptboot lives in its own dedicated GPT partition with a new "FreeBSD boot" type. This partition does not have a fixed size in that /boot/pmbr will load the entire partition into the lower 640k. However, it is limited in that it can only be 545k. That's still a lot better than the current 7.5k limit for boot2 on MBR. gptboot mostly acts just like boot2 in that it reads /boot.config and loads up /boot/loader. Some more details: - Include uuid_equal() and uuid_is_nil() in libstand. - Add a new 'boot' command to gpt(8) which makes a GPT disk bootable using /boot/pmbr and /boot/gptboot. Note that the disk must have some free space for the boot partition. - This required exposing the backend of the 'add' function as a gpt_add_part() function to the rest of gpt(8). 'boot' uses this to create a boot partition if needed. - Don't cripple cgbase() in the UFS boot code for /boot/gptboot so that it can handle a filesystem > 1.5 TB. - /boot/gptboot has a simple loader (gptldr) that doesn't do any I/O unlike boot1 since /boot/pmbr loads all of gptboot up front. The C portion of gptboot (gptboot.c) has been repocopied from boot2.c. The primary changes are to parse the GPT to find a root filesystem and to use 64-bit disk addresses. Currently gptboot assumes that the first UFS partition on the disk is the / filesystem, but this algorithm will likely be improved in the future. - Teach the biosdisk driver in /boot/loader to understand GPT tables. GPT partitions are identified as 'disk0pX:' (e.g. disk0p2:) which is similar to the /dev names the kernel uses (e.g. /dev/ad0p2). - Add a new "freebsd-boot" alias to g_part() for the new boot UUID. MFC after: 1 month Discussed with: marcel (some things might still change, but am committing what I have so far)
2007-10-24 21:33:00 +00:00
/*-
* Copyright (c) 2007 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "map.h"
#include "gpt.h"
static uuid_t boot_uuid = GPT_ENT_TYPE_FREEBSD_BOOT;
static const char *pmbr_path = "/boot/pmbr";
static const char *gptboot_path = "/boot/gptboot";
static u_long boot_size;
static void
usage_boot(void)
{
fprintf(stderr,
"usage: %s [-b pmbr] [-g gptboot] [-s count] device ...\n",
getprogname());
exit(1);
}
static int
gpt_find(uuid_t *type, map_t **mapp)
{
map_t *gpt, *tbl, *map;
struct gpt_hdr *hdr;
struct gpt_ent *ent;
unsigned int i;
/* Find a GPT partition with the requested UUID type. */
gpt = map_find(MAP_TYPE_PRI_GPT_HDR);
if (gpt == NULL) {
warnx("%s: error: no primary GPT header", device_name);
return (ENXIO);
}
tbl = map_find(MAP_TYPE_PRI_GPT_TBL);
if (tbl == NULL) {
warnx("%s: error: no primary partition table", device_name);
return (ENXIO);
}
hdr = gpt->map_data;
for (i = 0; i < le32toh(hdr->hdr_entries); i++) {
ent = (void *)((char *)tbl->map_data + i *
le32toh(hdr->hdr_entsz));
if (uuid_equal(&ent->ent_type, type, NULL))
break;
}
if (i == le32toh(hdr->hdr_entries)) {
*mapp = NULL;
return (0);
}
/* Lookup the map corresponding to this partition. */
for (map = map_find(MAP_TYPE_GPT_PART); map != NULL;
map = map->map_next) {
if (map->map_type != MAP_TYPE_GPT_PART)
continue;
if (map->map_start == (off_t)le64toh(ent->ent_lba_start)) {
assert(map->map_start + map->map_size - 1LL ==
(off_t)le64toh(ent->ent_lba_end));
*mapp = map;
return (0);
}
}
/* Hmm, the map list is not in sync with the GPT table. */
errx(1, "internal map list is corrupted");
}
static void
boot(int fd)
{
struct stat sb;
off_t bsize, ofs;
map_t *pmbr, *gptboot;
struct mbr *mbr;
char *buf;
ssize_t nbytes;
unsigned int entry;
int bfd;
/* First step: verify boot partition size. */
if (boot_size == 0)
/* Default to 64k. */
bsize = 65536 / secsz;
else {
if (boot_size * secsz < 16384) {
warnx("invalid boot partition size %lu", boot_size);
return;
}
bsize = boot_size;
}
/* Second step: write the PMBR boot loader into the PMBR. */
pmbr = map_find(MAP_TYPE_PMBR);
if (pmbr == NULL) {
warnx("%s: error: PMBR not found", device_name);
return;
}
bfd = open(pmbr_path, O_RDONLY);
if (bfd < 0 || fstat(bfd, &sb) < 0) {
warn("unable to open PMBR boot loader");
return;
}
if (sb.st_size != secsz) {
warnx("invalid PMBR boot loader");
return;
}
mbr = pmbr->map_data;
nbytes = read(bfd, mbr->mbr_code, sizeof(mbr->mbr_code));
if (nbytes < 0) {
warn("unable to read PMBR boot loader");
return;
}
if (nbytes != sizeof(mbr->mbr_code)) {
warnx("short read of PMBR boot loader");
return;
}
close(bfd);
gpt_write(fd, pmbr);
/* Third step: open gptboot and obtain its size. */
bfd = open(gptboot_path, O_RDONLY);
if (bfd < 0 || fstat(bfd, &sb) < 0) {
warn("unable to open GPT boot loader");
return;
}
/* Fourth step: find an existing boot partition or create one. */
if (gpt_find(&boot_uuid, &gptboot) != 0)
return;
if (gptboot != NULL) {
if (gptboot->map_size * secsz < sb.st_size) {
warnx("%s: error: boot partition is too small",
device_name);
return;
}
} else if (bsize * secsz < sb.st_size) {
warnx(
"%s: error: proposed size for boot partition is too small",
device_name);
return;
} else {
entry = 0;
gptboot = gpt_add_part(fd, boot_uuid, 0, bsize, &entry);
if (gptboot == NULL)
return;
}
/*
* Fourth step, write out the gptboot binary to the boot partition.
* When writing to a disk device, the write must be sector aligned
* and not write to any partial sectors, so round up the buffer size
* to the next sector and zero it.
*/
bsize = (sb.st_size + secsz - 1) / secsz * secsz;
buf = calloc(1, bsize);
First cut at support for booting a GPT labeled disk via the BIOS bootstrap on i386 and amd64 machines. The overall process is that /boot/pmbr lives in the PMBR (similar to /boot/mbr for MBR disks) and is responsible for locating and loading /boot/gptboot. /boot/gptboot is similar to /boot/boot except that it groks GPT rather than MBR + bsdlabel. Unlike /boot/boot, /boot/gptboot lives in its own dedicated GPT partition with a new "FreeBSD boot" type. This partition does not have a fixed size in that /boot/pmbr will load the entire partition into the lower 640k. However, it is limited in that it can only be 545k. That's still a lot better than the current 7.5k limit for boot2 on MBR. gptboot mostly acts just like boot2 in that it reads /boot.config and loads up /boot/loader. Some more details: - Include uuid_equal() and uuid_is_nil() in libstand. - Add a new 'boot' command to gpt(8) which makes a GPT disk bootable using /boot/pmbr and /boot/gptboot. Note that the disk must have some free space for the boot partition. - This required exposing the backend of the 'add' function as a gpt_add_part() function to the rest of gpt(8). 'boot' uses this to create a boot partition if needed. - Don't cripple cgbase() in the UFS boot code for /boot/gptboot so that it can handle a filesystem > 1.5 TB. - /boot/gptboot has a simple loader (gptldr) that doesn't do any I/O unlike boot1 since /boot/pmbr loads all of gptboot up front. The C portion of gptboot (gptboot.c) has been repocopied from boot2.c. The primary changes are to parse the GPT to find a root filesystem and to use 64-bit disk addresses. Currently gptboot assumes that the first UFS partition on the disk is the / filesystem, but this algorithm will likely be improved in the future. - Teach the biosdisk driver in /boot/loader to understand GPT tables. GPT partitions are identified as 'disk0pX:' (e.g. disk0p2:) which is similar to the /dev names the kernel uses (e.g. /dev/ad0p2). - Add a new "freebsd-boot" alias to g_part() for the new boot UUID. MFC after: 1 month Discussed with: marcel (some things might still change, but am committing what I have so far)
2007-10-24 21:33:00 +00:00
nbytes = read(bfd, buf, sb.st_size);
if (nbytes < 0) {
warn("unable to read GPT boot loader");
return;
}
if (nbytes != sb.st_size) {
warnx("short read of GPT boot loader");
return;
}
close(bfd);
ofs = gptboot->map_start * secsz;
if (lseek(fd, ofs, SEEK_SET) != ofs) {
warn("%s: error: unable to seek to boot partition",
device_name);
return;
}
nbytes = write(fd, buf, bsize);
First cut at support for booting a GPT labeled disk via the BIOS bootstrap on i386 and amd64 machines. The overall process is that /boot/pmbr lives in the PMBR (similar to /boot/mbr for MBR disks) and is responsible for locating and loading /boot/gptboot. /boot/gptboot is similar to /boot/boot except that it groks GPT rather than MBR + bsdlabel. Unlike /boot/boot, /boot/gptboot lives in its own dedicated GPT partition with a new "FreeBSD boot" type. This partition does not have a fixed size in that /boot/pmbr will load the entire partition into the lower 640k. However, it is limited in that it can only be 545k. That's still a lot better than the current 7.5k limit for boot2 on MBR. gptboot mostly acts just like boot2 in that it reads /boot.config and loads up /boot/loader. Some more details: - Include uuid_equal() and uuid_is_nil() in libstand. - Add a new 'boot' command to gpt(8) which makes a GPT disk bootable using /boot/pmbr and /boot/gptboot. Note that the disk must have some free space for the boot partition. - This required exposing the backend of the 'add' function as a gpt_add_part() function to the rest of gpt(8). 'boot' uses this to create a boot partition if needed. - Don't cripple cgbase() in the UFS boot code for /boot/gptboot so that it can handle a filesystem > 1.5 TB. - /boot/gptboot has a simple loader (gptldr) that doesn't do any I/O unlike boot1 since /boot/pmbr loads all of gptboot up front. The C portion of gptboot (gptboot.c) has been repocopied from boot2.c. The primary changes are to parse the GPT to find a root filesystem and to use 64-bit disk addresses. Currently gptboot assumes that the first UFS partition on the disk is the / filesystem, but this algorithm will likely be improved in the future. - Teach the biosdisk driver in /boot/loader to understand GPT tables. GPT partitions are identified as 'disk0pX:' (e.g. disk0p2:) which is similar to the /dev names the kernel uses (e.g. /dev/ad0p2). - Add a new "freebsd-boot" alias to g_part() for the new boot UUID. MFC after: 1 month Discussed with: marcel (some things might still change, but am committing what I have so far)
2007-10-24 21:33:00 +00:00
if (nbytes < 0) {
warn("unable to write GPT boot loader");
return;
}
if (nbytes != bsize) {
First cut at support for booting a GPT labeled disk via the BIOS bootstrap on i386 and amd64 machines. The overall process is that /boot/pmbr lives in the PMBR (similar to /boot/mbr for MBR disks) and is responsible for locating and loading /boot/gptboot. /boot/gptboot is similar to /boot/boot except that it groks GPT rather than MBR + bsdlabel. Unlike /boot/boot, /boot/gptboot lives in its own dedicated GPT partition with a new "FreeBSD boot" type. This partition does not have a fixed size in that /boot/pmbr will load the entire partition into the lower 640k. However, it is limited in that it can only be 545k. That's still a lot better than the current 7.5k limit for boot2 on MBR. gptboot mostly acts just like boot2 in that it reads /boot.config and loads up /boot/loader. Some more details: - Include uuid_equal() and uuid_is_nil() in libstand. - Add a new 'boot' command to gpt(8) which makes a GPT disk bootable using /boot/pmbr and /boot/gptboot. Note that the disk must have some free space for the boot partition. - This required exposing the backend of the 'add' function as a gpt_add_part() function to the rest of gpt(8). 'boot' uses this to create a boot partition if needed. - Don't cripple cgbase() in the UFS boot code for /boot/gptboot so that it can handle a filesystem > 1.5 TB. - /boot/gptboot has a simple loader (gptldr) that doesn't do any I/O unlike boot1 since /boot/pmbr loads all of gptboot up front. The C portion of gptboot (gptboot.c) has been repocopied from boot2.c. The primary changes are to parse the GPT to find a root filesystem and to use 64-bit disk addresses. Currently gptboot assumes that the first UFS partition on the disk is the / filesystem, but this algorithm will likely be improved in the future. - Teach the biosdisk driver in /boot/loader to understand GPT tables. GPT partitions are identified as 'disk0pX:' (e.g. disk0p2:) which is similar to the /dev names the kernel uses (e.g. /dev/ad0p2). - Add a new "freebsd-boot" alias to g_part() for the new boot UUID. MFC after: 1 month Discussed with: marcel (some things might still change, but am committing what I have so far)
2007-10-24 21:33:00 +00:00
warnx("short write of GPT boot loader");
return;
}
free(buf);
}
int
cmd_boot(int argc, char *argv[])
{
char *p;
int ch, fd;
while ((ch = getopt(argc, argv, "b:g:s:")) != -1) {
switch (ch) {
case 'b':
pmbr_path = optarg;
break;
case 'g':
gptboot_path = optarg;
break;
case 's':
if (boot_size > 0)
usage_boot();
boot_size = strtol(optarg, &p, 10);
if (*p != '\0' || boot_size < 1)
usage_boot();
break;
default:
usage_boot();
}
}
if (argc == optind)
usage_boot();
while (optind < argc) {
fd = gpt_open(argv[optind++]);
if (fd < 0) {
warn("unable to open device '%s'", device_name);
continue;
}
boot(fd);
gpt_close(fd);
}
return (0);
}