diff options
author | jhb <jhb@FreeBSD.org> | 2007-10-24 21:33:00 +0000 |
---|---|---|
committer | jhb <jhb@FreeBSD.org> | 2007-10-24 21:33:00 +0000 |
commit | 2f8a906c36edd256f39a8d2ef209fef1a548d79e (patch) | |
tree | 2e0ad7e3920a2c896b216cf32db1db922c1f0e29 /sbin/gpt/boot.c | |
parent | 81c7dc737f4505dc976b462a40fdd5832e63a765 (diff) | |
download | FreeBSD-src-2f8a906c36edd256f39a8d2ef209fef1a548d79e.zip FreeBSD-src-2f8a906c36edd256f39a8d2ef209fef1a548d79e.tar.gz |
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)
Diffstat (limited to 'sbin/gpt/boot.c')
-rw-r--r-- | sbin/gpt/boot.c | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/sbin/gpt/boot.c b/sbin/gpt/boot.c new file mode 100644 index 0000000..ff9d4a0 --- /dev/null +++ b/sbin/gpt/boot.c @@ -0,0 +1,266 @@ +/*- + * 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. */ + buf = malloc(sb.st_size); + 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, sb.st_size); + if (nbytes < 0) { + warn("unable to write GPT boot loader"); + return; + } + if (nbytes != sb.st_size) { + 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); +} |