diff options
author | Toomas Soome <tsoome@me.com> | 2015-06-24 21:59:53 +0300 |
---|---|---|
committer | Robert Mustacchi <rm@joyent.com> | 2016-09-22 11:43:39 -0700 |
commit | 0c946d80993858b7b1314e0b31773e48500e03fb (patch) | |
tree | 1cef0a0ed0424f3ceaf5e520e4dc19ad29857f49 /usr/src | |
parent | 1386b601c0c7f5c89a9325b8a1e34037304e8119 (diff) | |
download | illumos-gate-0c946d80993858b7b1314e0b31773e48500e03fb.tar.gz |
6701 add installboot to i386 platform (loader project)
Reviewed by: Richard Lowe <richlowe@richlowe.net>
Approved by: Robert Mustacchi <rm@joyent.com>
Diffstat (limited to 'usr/src')
-rw-r--r-- | usr/src/cmd/boot/Makefile | 20 | ||||
-rw-r--r-- | usr/src/cmd/boot/installboot/Makefile | 55 | ||||
-rw-r--r-- | usr/src/cmd/boot/installboot/Makefile.com | 66 | ||||
-rw-r--r-- | usr/src/cmd/boot/installboot/i386/Makefile | 35 | ||||
-rw-r--r-- | usr/src/cmd/boot/installboot/i386/installboot.c | 1597 | ||||
-rw-r--r-- | usr/src/cmd/boot/installboot/i386/installboot.h | 112 | ||||
-rw-r--r-- | usr/src/cmd/boot/installboot/sparc/Makefile | 18 | ||||
-rw-r--r-- | usr/src/cmd/boot/installboot/sparc/installboot.c (renamed from usr/src/cmd/boot/installboot/installboot.c) | 6 | ||||
-rw-r--r-- | usr/src/cmd/boot/installboot/sparc/installboot.h (renamed from usr/src/cmd/boot/installboot/installboot.h) | 0 | ||||
-rw-r--r-- | usr/src/man/man1m/Makefile | 1 | ||||
-rw-r--r-- | usr/src/man/man1m/installboot.1m | 209 | ||||
-rw-r--r-- | usr/src/pkg/manifests/SUNWcs.man1m.inc | 2 | ||||
-rw-r--r-- | usr/src/pkg/manifests/SUNWcs.mf | 1 |
13 files changed, 2070 insertions, 52 deletions
diff --git a/usr/src/cmd/boot/Makefile b/usr/src/cmd/boot/Makefile index c923fe599b..32f8c778d6 100644 --- a/usr/src/cmd/boot/Makefile +++ b/usr/src/cmd/boot/Makefile @@ -18,7 +18,7 @@ # # CDDL HEADER END # -# +# Copyright 2016 Toomas Soome <tsoome@me.com> # Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. # @@ -30,36 +30,36 @@ COMMON_SUBDIRS= \ filelist \ fiocompress \ scripts \ - bootadm + bootadm \ + installboot i386_SUBDIRS= \ installgrub \ mbr \ symdef -sparc_SUBDIRS= \ - installboot +sparc_SUBDIRS= COMMON_LINTSUBDIRS= \ - bootadm + bootadm \ + installboot i386_LINTSUBDIRS= \ fiocompress \ installgrub \ symdef -sparc_LINTSUBDIRS= \ - installboot +sparc_LINTSUBDIRS= COMMON_MSGSUBDIRS= \ bootadm \ - fiocompress + fiocompress \ + installboot i386_MSGSUBDIRS= \ installgrub -sparc_MSGSUBDIRS= \ - installboot +sparc_MSGSUBDIRS= all:= TARGET= all diff --git a/usr/src/cmd/boot/installboot/Makefile b/usr/src/cmd/boot/installboot/Makefile index 024e277115..83ff8e242d 100644 --- a/usr/src/cmd/boot/installboot/Makefile +++ b/usr/src/cmd/boot/installboot/Makefile @@ -18,50 +18,27 @@ # # CDDL HEADER END # -# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. -# - -PROG= installboot - -EINFO_SRC= ./../common/bblk_einfo.c -UTILS_SRC =./../common/boot_utils.c -EXTRA_SRC =./../common/mboot_extra.c - -OBJS= installboot.o bblk_einfo.o boot_utils.o mboot_extra.o -SRCS= installboot.c $(UTILS_SRC) $(EINFO_SRC) $(EXTRA_SRC) - -include ../Makefile.com - -CPPFLAGS += -I$(SRC)/uts/common - -LDLIBS += -lmd5 +# Copyright 2016 Toomas Soome <tsoome@me.com> +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# -C99MODE= $(C99_ENABLE) +SUBDIRS = $(MACH) -LINTFLAGS += -erroff=E_BAD_PTR_CAST_ALIGN +all := TARGET= all +install := TARGET= install +clean := TARGET= clean +clobber := TARGET= clobber +_msg := TARGET= _msg +lint := TARGET= lint .KEEP_STATE: -all: $(PROG) - -$(PROG): $(OBJS) - $(LINK.c) -o $@ $(OBJS) $(LDLIBS) - $(POST_PROCESS) - -boot_utils.o: $(UTILS_SRC) - $(COMPILE.c) -o $@ $(UTILS_SRC) - -mboot_extra.o: $(EXTRA_SRC) - $(COMPILE.c) -o $@ $(EXTRA_SRC) - -bblk_einfo.o: $(EINFO_SRC) - $(COMPILE.c) -o $@ $(EINFO_SRC) - -install: all $(ROOTUSRSBINPROG) +install: $(SUBDIRS) -clean: - $(RM) $(OBJS) +all clobber clean lint _msg: $(SUBDIRS) -lint: lint_SRCS +FRC: -include ../Makefile.targ +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) diff --git a/usr/src/cmd/boot/installboot/Makefile.com b/usr/src/cmd/boot/installboot/Makefile.com new file mode 100644 index 0000000000..566e48c42a --- /dev/null +++ b/usr/src/cmd/boot/installboot/Makefile.com @@ -0,0 +1,66 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. +# Copyright 2016 Toomas Soome <tsoome@me.com> +# + +PROG= installboot + +EINFO_SRC =../../common/bblk_einfo.c +UTILS_SRC =../../common/boot_utils.c +EXTRA_SRC =../../common/mboot_extra.c + +OBJS= installboot.o bblk_einfo.o boot_utils.o mboot_extra.o +SRCS= installboot.c $(UTILS_SRC) $(EINFO_SRC) $(EXTRA_SRC) + +include ../../Makefile.com + +CPPFLAGS += -I$(SRC)/uts/common + +LDLIBS += -lmd5 + +C99MODE= $(C99_ENABLE) + +LINTFLAGS += -erroff=E_BAD_PTR_CAST_ALIGN + +.KEEP_STATE: + +all: $(PROG) + +$(PROG): $(OBJS) + $(LINK.c) -o $@ $(OBJS) $(LDLIBS) + $(POST_PROCESS) + +boot_utils.o: $(UTILS_SRC) + $(COMPILE.c) -o $@ $(UTILS_SRC) + +mboot_extra.o: $(EXTRA_SRC) + $(COMPILE.c) -o $@ $(EXTRA_SRC) + +bblk_einfo.o: $(EINFO_SRC) + $(COMPILE.c) -o $@ $(EINFO_SRC) + +install: all $(ROOTUSRSBINPROG) + +clean: + $(RM) $(OBJS) + +lint: lint_SRCS diff --git a/usr/src/cmd/boot/installboot/i386/Makefile b/usr/src/cmd/boot/installboot/i386/Makefile new file mode 100644 index 0000000000..5b473a6570 --- /dev/null +++ b/usr/src/cmd/boot/installboot/i386/Makefile @@ -0,0 +1,35 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# Copyright 2016 Toomas Soome <tsoome@me.com> +# + +include ../Makefile.com + +OBJS += getresponse.o +SRCS += $(SRC)/common/util/getresponse.c + +CPPFLAGS += -I$(SRC)/common/util +LDLIBS += -lfdisk -lefi -lfstyp -luuid + +# getresponse.c and libfdisk.c need this +LINTFLAGS += \ + -xerroff=E_NAME_DEF_NOT_USED2 + +.KEEP_STATE: + +all: + +$(PROG): $(OBJS) + +%.o: $(SRC)/common/util/%.c + $(COMPILE.c) $(OUTPUT_OPTION) $< + +include $(SRC)/cmd/boot/Makefile.targ diff --git a/usr/src/cmd/boot/installboot/i386/installboot.c b/usr/src/cmd/boot/installboot/i386/installboot.c new file mode 100644 index 0000000000..a822b46b71 --- /dev/null +++ b/usr/src/cmd/boot/installboot/i386/installboot.c @@ -0,0 +1,1597 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright 2012 Nexenta Systems, Inc. All rights reserved. + * Copyright 2016 Toomas Soome <tsoome@me.com> + */ + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <assert.h> +#include <locale.h> +#include <strings.h> +#include <libfdisk.h> + +#include <sys/dktp/fdisk.h> +#include <sys/dkio.h> +#include <sys/vtoc.h> +#include <sys/multiboot.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <sys/efi_partition.h> +#include <libfstyp.h> +#include <uuid/uuid.h> + +#include "installboot.h" +#include "../../common/bblk_einfo.h" +#include "../../common/boot_utils.h" +#include "../../common/mboot_extra.h" +#include "getresponse.h" + +#ifndef TEXT_DOMAIN +#define TEXT_DOMAIN "SUNW_OST_OSCMD" +#endif + +/* + * BIOS bootblock installation: + * + * 1. MBR is first sector of the disk. If the file system on target is + * ufs or zfs, the same MBR code is installed on first sector of the + * partition as well; this will allow to have real MBR sector to be + * replaced by some other boot loader and have illumos chainloaded. + * + * installboot will record the start LBA and size of stage2 code in MBR code. + * On boot, the MBR code will read the stage2 code and executes it. + * + * 2. Stage2 location depends on file system type; + * In case of zfs, installboot will store stage2 to zfs bootblk area, + * which is 512k bytes from partition start and size is 3.5MB. + * + * In case of ufs, the stage2 location is 50 512B sectors from + * Solaris2 MBR partition start, within boot slice, boot slice size is + * one cylinder. + * + * In case of pcfs, the stage2 location is 50 512B sectors from beginning + * of the disk, filling the space between MBR and first partition. + * This location assumes no other bootloader and the space is one cylinder, + * as first partition is starting from cylinder 1. + * + * In case of GPT partitioning and if file system is not zfs, the boot + * support is only possible with dedicated boot partition. For GPT, + * the current implementation is using BOOT partition, which must exist. + * BOOT partition does only contain raw boot blocks, without any file system. + * + * Loader stage2 is created with embedded version, by using fake multiboot (MB) + * header within first 32k and EINFO block is at the end of the actual + * boot block. MB header load_addr is set to 0 and load_end_addr is set to + * actual block end, so the EINFO size is (file size - load_end_addr). + * installboot does also store the illumos boot partition LBA to MB space, + * starting from bss_end_addr structure member location; stage2 will + * detect the partition and file system based on this value. + * + * Stored location values in MBR/stage2 also mean the bootblocks must be + * reinstalled in case the partition content is relocated. + */ + +static boolean_t write_mbr = B_FALSE; +static boolean_t force_mbr = B_FALSE; +static boolean_t force_update = B_FALSE; +static boolean_t do_getinfo = B_FALSE; +static boolean_t do_version = B_FALSE; +static boolean_t do_mirror_bblk = B_FALSE; +static boolean_t strip = B_FALSE; +static boolean_t verbose_dump = B_FALSE; + +/* Versioning string, if present. */ +static char *update_str; + +/* + * Temporary buffer to store the first 32K of data looking for a multiboot + * signature. + */ +char mboot_scan[MBOOT_SCAN_SIZE]; + +/* Function prototypes. */ +static void check_options(char *); +static int get_start_sector(ib_device_t *); + +static int read_stage1_from_file(char *, ib_data_t *data); +static int read_bootblock_from_file(char *, ib_data_t *data); +static int read_bootblock_from_disk(ib_device_t *device, ib_bootblock_t *, + char **); +static void add_bootblock_einfo(ib_bootblock_t *, char *); +static int prepare_stage1(ib_data_t *); +static int prepare_bootblock(ib_data_t *, char *); +static int write_stage1(ib_data_t *); +static int write_bootblock(ib_data_t *); +static int init_device(ib_device_t *, char *); +static void cleanup_device(ib_device_t *); +static int commit_to_disk(ib_data_t *, char *); +static int handle_install(char *, char **); +static int handle_getinfo(char *, char **); +static int handle_mirror(char *, char **); +static boolean_t is_update_necessary(ib_data_t *, char *); +static int propagate_bootblock(ib_data_t *, ib_data_t *, char *); +static void usage(char *); + +static int +read_stage1_from_file(char *path, ib_data_t *dest) +{ + int fd; + + assert(dest != NULL); + + /* read the stage1 file from filesystem */ + fd = open(path, O_RDONLY); + if (fd == -1 || + read(fd, dest->stage1, SECTOR_SIZE) != SECTOR_SIZE) { + (void) fprintf(stderr, gettext("cannot read stage1 file %s\n"), + path); + return (BC_ERROR); + } + (void) close(fd); + return (BC_SUCCESS); +} + +static int +read_bootblock_from_file(char *file, ib_data_t *data) +{ + ib_bootblock_t *bblock = &data->bootblock; + struct stat sb; + uint32_t buf_size; + uint32_t mboot_off; + int fd = -1; + int retval = BC_ERROR; + + assert(data != NULL); + assert(file != NULL); + + fd = open(file, O_RDONLY); + if (fd == -1) { + BOOT_DEBUG("Error opening %s\n", file); + perror("open"); + goto out; + } + + if (fstat(fd, &sb) == -1) { + BOOT_DEBUG("Error getting information (stat) about %s", file); + perror("stat"); + goto outfd; + } + + /* loader bootblock has version built in */ + buf_size = sb.st_size; + + bblock->buf_size = buf_size; + BOOT_DEBUG("bootblock in-memory buffer size is %d\n", + bblock->buf_size); + + bblock->buf = malloc(buf_size); + if (bblock->buf == NULL) { + perror(gettext("Memory allocation failure")); + goto outbuf; + } + bblock->file = bblock->buf; + + if (read(fd, bblock->file, bblock->buf_size) != bblock->buf_size) { + BOOT_DEBUG("Read from %s failed\n", file); + perror("read"); + goto outfd; + } + + if (find_multiboot(bblock->file, MBOOT_SCAN_SIZE, &mboot_off) + != BC_SUCCESS) { + (void) fprintf(stderr, + gettext("Unable to find multiboot header\n")); + goto outfd; + } + + bblock->mboot = (multiboot_header_t *)(bblock->file + mboot_off); + bblock->mboot_off = mboot_off; + + bblock->file_size = + bblock->mboot->load_end_addr - bblock->mboot->load_addr; + BOOT_DEBUG("bootblock file size is %d\n", bblock->file_size); + + bblock->extra = bblock->buf + P2ROUNDUP(bblock->file_size, 8); + bblock->extra_size = bblock->buf_size - P2ROUNDUP(bblock->file_size, 8); + + BOOT_DEBUG("mboot at %p offset %d, extra at %p size %d, buf=%p " + "(size=%d)\n", bblock->mboot, bblock->mboot_off, bblock->extra, + bblock->extra_size, bblock->buf, bblock->buf_size); + + (void) close(fd); + return (BC_SUCCESS); + +outbuf: + (void) free(bblock->buf); + bblock->buf = NULL; +outfd: + (void) close(fd); +out: + return (retval); +} + +static int +read_bootblock_from_disk(ib_device_t *device, ib_bootblock_t *bblock, + char **path) +{ + int dev_fd; + uint32_t size, offset; + uint32_t buf_size; + uint32_t mboot_off; + multiboot_header_t *mboot; + + assert(device != NULL); + assert(bblock != NULL); + + if (device->target.fstype == IG_FS_ZFS) { + dev_fd = device->target.fd; + offset = BBLK_ZFS_BLK_OFF * SECTOR_SIZE; + *path = device->target.path; + } else { + dev_fd = device->stage.fd; + offset = device->stage.offset * SECTOR_SIZE; + *path = device->stage.path; + } + + if (read_in(dev_fd, mboot_scan, sizeof (mboot_scan), offset) + != BC_SUCCESS) { + BOOT_DEBUG("Error reading bootblock area\n"); + perror("read"); + return (BC_ERROR); + } + + /* No multiboot means no chance of knowing bootblock size */ + if (find_multiboot(mboot_scan, sizeof (mboot_scan), &mboot_off) + != BC_SUCCESS) { + BOOT_DEBUG("Unable to find multiboot header\n"); + return (BC_NOEXTRA); + } + mboot = (multiboot_header_t *)(mboot_scan + mboot_off); + + /* + * make sure mboot has sane values + */ + if (mboot->load_end_addr == 0 || + mboot->load_end_addr < mboot->load_addr) + return (BC_NOEXTRA); + + /* + * Currently, the amount of space reserved for extra information + * is "fixed". We may have to scan for the terminating extra payload + * in the future. + */ + size = mboot->load_end_addr - mboot->load_addr; + buf_size = P2ROUNDUP(size + SECTOR_SIZE, SECTOR_SIZE); + bblock->file_size = size; + + bblock->buf = malloc(buf_size); + if (bblock->buf == NULL) { + BOOT_DEBUG("Unable to allocate enough memory to read" + " the extra bootblock from the disk\n"); + perror(gettext("Memory allocation failure")); + return (BC_ERROR); + } + bblock->buf_size = buf_size; + + if (read_in(dev_fd, bblock->buf, buf_size, offset) != BC_SUCCESS) { + BOOT_DEBUG("Error reading the bootblock\n"); + (void) free(bblock->buf); + bblock->buf = NULL; + return (BC_ERROR); + } + + /* Update pointers. */ + bblock->file = bblock->buf; + bblock->mboot_off = mboot_off; + bblock->mboot = (multiboot_header_t *)(bblock->buf + bblock->mboot_off); + bblock->extra = bblock->buf + P2ROUNDUP(bblock->file_size, 8); + bblock->extra_size = bblock->buf_size - P2ROUNDUP(bblock->file_size, 8); + + BOOT_DEBUG("mboot at %p offset %d, extra at %p size %d, buf=%p " + "(size=%d)\n", bblock->mboot, bblock->mboot_off, bblock->extra, + bblock->extra_size, bblock->buf, bblock->buf_size); + + return (BC_SUCCESS); +} + +static boolean_t +is_update_necessary(ib_data_t *data, char *updt_str) +{ + bblk_einfo_t *einfo; + bblk_einfo_t *einfo_file; + bblk_hs_t bblock_hs; + ib_bootblock_t bblock_disk; + ib_bootblock_t *bblock_file = &data->bootblock; + ib_device_t *device = &data->device; + int ret; + char *path; + + assert(data != NULL); + + bzero(&bblock_disk, sizeof (ib_bootblock_t)); + + ret = read_bootblock_from_disk(device, &bblock_disk, &path); + if (ret != BC_SUCCESS) { + BOOT_DEBUG("Unable to read bootblock from %s\n", path); + return (B_TRUE); + } + + einfo = find_einfo(bblock_disk.extra, bblock_disk.extra_size); + if (einfo == NULL) { + BOOT_DEBUG("No extended information available on disk\n"); + return (B_TRUE); + } + + einfo_file = find_einfo(bblock_file->extra, bblock_file->extra_size); + if (einfo_file == NULL) { + /* + * loader bootblock is versioned. missing version means + * probably incompatible block. installboot can not install + * grub, for example. + */ + (void) fprintf(stderr, + gettext("ERROR: non versioned bootblock in file\n")); + return (B_FALSE); + } else { + if (updt_str == NULL) { + updt_str = einfo_get_string(einfo_file); + do_version = B_TRUE; + } + } + + if (!do_version || updt_str == NULL) { + (void) fprintf(stderr, + gettext("WARNING: target device %s has a " + "versioned bootblock that is going to be overwritten by a " + "non versioned one\n"), device->path); + return (B_TRUE); + } + + if (force_update) { + BOOT_DEBUG("Forcing update of %s bootblock\n", device->path); + return (B_TRUE); + } + + BOOT_DEBUG("Ready to check installed version vs %s\n", updt_str); + + bblock_hs.src_buf = (unsigned char *)bblock_file->file; + bblock_hs.src_size = bblock_file->file_size; + + return (einfo_should_update(einfo, &bblock_hs, updt_str)); +} + +static void +add_bootblock_einfo(ib_bootblock_t *bblock, char *updt_str) +{ + bblk_hs_t hs; + uint32_t avail_space; + + assert(bblock != NULL); + + if (updt_str == NULL) { + BOOT_DEBUG("WARNING: no update string passed to " + "add_bootblock_einfo()\n"); + return; + } + + /* Fill bootblock hashing source information. */ + hs.src_buf = (unsigned char *)bblock->file; + hs.src_size = bblock->file_size; + /* How much space for the extended information structure? */ + avail_space = bblock->buf_size - P2ROUNDUP(bblock->file_size, 8); + /* Place the extended information structure. */ + add_einfo(bblock->extra, updt_str, &hs, avail_space); +} + +/* + * set up data for case stage1 is installed as MBR + * set up location and size of bootblock + * set disk guid to provide unique information for biosdev command + */ +static int +prepare_stage1(ib_data_t *data) +{ + ib_device_t *device; + + assert(data != NULL); + device = &data->device; + + /* copy BPB */ + bcopy(device->mbr + STAGE1_BPB_OFFSET, + data->stage1 + STAGE1_BPB_OFFSET, STAGE1_BPB_SIZE); + + + /* copy MBR, note STAGE1_SIG == BOOTSZ */ + bcopy(device->mbr + STAGE1_SIG, data->stage1 + STAGE1_SIG, + SECTOR_SIZE - STAGE1_SIG); + + /* set stage2 size */ + *((uint16_t *)(data->stage1 + STAGE1_STAGE2_SIZE)) = + (uint16_t)(data->bootblock.buf_size / SECTOR_SIZE); + + /* + * set stage2 location. + * for zfs always use zfs embedding, for ufs/pcfs use partition_start + * as base for stage2 location, for ufs/pcfs in MBR partition, use + * free space after MBR record. + */ + if (device->target.fstype == IG_FS_ZFS) + *((uint64_t *)(data->stage1 + STAGE1_STAGE2_LBA)) = + device->target.start + device->target.offset; + else { + *((uint64_t *)(data->stage1 + STAGE1_STAGE2_LBA)) = + device->stage.start + device->stage.offset; + } + + /* + * set disk uuid. we only need reasonable amount of uniqueness + * to allow biosdev to identify disk based on mbr differences. + */ + uuid_generate(data->stage1 + STAGE1_STAGE2_UUID); + + return (BC_SUCCESS); +} + +static int +prepare_bootblock(ib_data_t *data, char *updt_str) +{ + ib_bootblock_t *bblock; + ib_device_t *device; + uint64_t *ptr; + + assert(data != NULL); + + bblock = &data->bootblock; + device = &data->device; + + ptr = (uint64_t *)(&bblock->mboot->bss_end_addr); + *ptr = device->target.start; + + /* + * the loader bootblock has built in version, if custom + * version was provided, update it. + */ + if (do_version) + add_bootblock_einfo(bblock, updt_str); + + return (BC_SUCCESS); +} + +static int +write_bootblock(ib_data_t *data) +{ + ib_device_t *device = &data->device; + ib_bootblock_t *bblock = &data->bootblock; + uint64_t abs; + int dev_fd, ret; + off_t offset; + char *path; + + assert(data != NULL); + + /* + * ZFS bootblock area is 3.5MB, make sure we can fit. + * buf_size is size of bootblk+EINFO. + */ + if (bblock->buf_size > BBLK_ZFS_BLK_SIZE) { + (void) fprintf(stderr, gettext("bootblock is too large\n")); + return (BC_ERROR); + } + + if (device->target.fstype == IG_FS_ZFS) { + dev_fd = device->target.fd; + abs = device->target.start + device->target.offset; + offset = BBLK_ZFS_BLK_OFF * SECTOR_SIZE; + path = device->target.path; + } else { + dev_fd = device->stage.fd; + abs = device->stage.start + device->stage.offset; + offset = device->stage.offset * SECTOR_SIZE; + path = device->stage.path; + if (bblock->buf_size > + (device->stage.size - device->stage.offset) * SECTOR_SIZE) { + (void) fprintf(stderr, gettext("Device %s is " + "too small to fit the stage2\n"), path); + return (BC_ERROR); + } + } + ret = write_out(dev_fd, bblock->buf, bblock->buf_size, offset); + if (ret != BC_SUCCESS) { + BOOT_DEBUG("Error writing the ZFS bootblock " + "to %s at offset %d\n", path, offset); + return (BC_ERROR); + } + + (void) fprintf(stdout, gettext("bootblock written for %s," + " %d sectors starting at %d (abs %lld)\n"), path, + (bblock->buf_size / SECTOR_SIZE) + 1, offset / SECTOR_SIZE, abs); + + return (BC_SUCCESS); +} + +static int +write_stage1(ib_data_t *data) +{ + ib_device_t *device = &data->device; + + assert(data != NULL); + + /* + * Partition boot block or volume boot record. + * This is essentially copy of MBR (1 sector) and we store it + * to support multi boot setups. + * + * Not all combinations are supported; as pcfs does not leave + * space, we will not write to pcfs target. + * In addition, in VTOC setup, we will only write VBR to slice 2. + */ + if (device->stage.start != 0 && + strcmp(device->target.path, device->stage.path)) { + /* we got separate stage area, use it */ + if (write_out(device->stage.fd, data->stage1, + sizeof (data->stage1), 0) != BC_SUCCESS) { + (void) fprintf(stdout, gettext("cannot write " + "partition boot sector\n")); + perror("write"); + return (BC_ERROR); + } + + (void) fprintf(stdout, gettext("stage1 written to " + "%s %d sector 0 (abs %d)\n"), + device->devtype == IG_DEV_MBR? "partition":"slice", + device->stage.id, device->stage.start); + } + + /* + * both ufs and zfs have initial 8k reserved for VTOC/boot + * so its safe to use this area. however, only + * write to target if we have MBR/GPT. + */ + if (device->devtype != IG_DEV_VTOC && + device->target.fstype != IG_FS_PCFS) { + if (write_out(device->target.fd, data->stage1, + sizeof (data->stage1), 0) != BC_SUCCESS) { + (void) fprintf(stdout, gettext("cannot write " + "partition boot sector\n")); + perror("write"); + return (BC_ERROR); + } + + (void) fprintf(stdout, gettext("stage1 written to " + "%s %d sector 0 (abs %d)\n"), + device->devtype == IG_DEV_MBR? "partition":"slice", + device->target.id, device->target.start); + } + + if (write_mbr) { + if (write_out(device->fd, data->stage1, + sizeof (data->stage1), 0) != BC_SUCCESS) { + (void) fprintf(stdout, + gettext("cannot write master boot sector\n")); + perror("write"); + return (BC_ERROR); + } + (void) fprintf(stdout, + gettext("stage1 written to master boot sector\n")); + } + + return (BC_SUCCESS); +} + +/* + * find partition/slice start sector. will be recorded in stage2 and used + * by stage2 to identify partition with boot file system. + */ +static int +get_start_sector(ib_device_t *device) +{ + uint32_t secnum = 0, numsec = 0; + int i, pno, rval, log_part = 0; + struct mboot *mboot; + struct ipart *part = NULL; + ext_part_t *epp; + struct part_info dkpi; + struct extpart_info edkpi; + + if (device->devtype == IG_DEV_EFI) { + struct dk_gpt *vtoc; + + if (efi_alloc_and_read(device->fd, &vtoc) < 0) + return (BC_ERROR); + + if (device->stage.start == 0) { + /* zero size means the fstype must be zfs */ + assert(device->target.fstype == IG_FS_ZFS); + + device->stage.start = + vtoc->efi_parts[device->stage.id].p_start; + device->stage.size = + vtoc->efi_parts[device->stage.id].p_size; + device->stage.offset = BBLK_ZFS_BLK_OFF; + device->target.offset = BBLK_ZFS_BLK_OFF; + } + + device->target.start = + vtoc->efi_parts[device->target.id].p_start; + device->target.size = + vtoc->efi_parts[device->target.id].p_size; + + /* with pcfs we always write MBR */ + if (device->target.fstype == IG_FS_PCFS) { + force_mbr = 1; + write_mbr = 1; + } + + efi_free(vtoc); + goto found_part; + } + + mboot = (struct mboot *)device->mbr; + + /* For MBR we have device->stage filled already. */ + if (device->devtype == IG_DEV_MBR) { + /* MBR partition starts from 0 */ + pno = device->target.id - 1; + part = (struct ipart *)mboot->parts + pno; + + if (part->relsect == 0) { + (void) fprintf(stderr, gettext("Partition %d of the " + "disk has an incorrect offset\n"), + device->target.id); + return (BC_ERROR); + } + device->target.start = part->relsect; + device->target.size = part->numsect; + + /* with pcfs we always write MBR */ + if (device->target.fstype == IG_FS_PCFS) { + force_mbr = 1; + write_mbr = 1; + } + if (device->target.fstype == IG_FS_ZFS) + device->target.offset = BBLK_ZFS_BLK_OFF; + + goto found_part; + } + + /* + * Search for Solaris fdisk partition + * Get the solaris partition information from the device + * and compare the offset of S2 with offset of solaris partition + * from fdisk partition table. + */ + if (ioctl(device->target.fd, DKIOCEXTPARTINFO, &edkpi) < 0) { + if (ioctl(device->target.fd, DKIOCPARTINFO, &dkpi) < 0) { + (void) fprintf(stderr, gettext("cannot get the " + "slice information of the disk\n")); + return (BC_ERROR); + } else { + edkpi.p_start = dkpi.p_start; + edkpi.p_length = dkpi.p_length; + } + } + + device->target.start = edkpi.p_start; + device->target.size = edkpi.p_length; + if (device->target.fstype == IG_FS_ZFS) + device->target.offset = BBLK_ZFS_BLK_OFF; + + for (i = 0; i < FD_NUMPART; i++) { + part = (struct ipart *)mboot->parts + i; + + if (part->relsect == 0) { + (void) fprintf(stderr, gettext("Partition %d of the " + "disk has an incorrect offset\n"), i+1); + return (BC_ERROR); + } + + if (edkpi.p_start >= part->relsect && + edkpi.p_start < (part->relsect + part->numsect)) { + /* Found the partition */ + break; + } + } + + if (i == FD_NUMPART) { + /* No solaris fdisk partitions (primary or logical) */ + (void) fprintf(stderr, gettext("Solaris partition not found. " + "Aborting operation.\n")); + return (BC_ERROR); + } + + /* + * We have found a Solaris fdisk partition (primary or extended) + * Handle the simple case first: Solaris in a primary partition + */ + if (!fdisk_is_dos_extended(part->systid)) { + device->stage.start = part->relsect; + device->stage.size = part->numsect; + if (device->target.fstype == IG_FS_ZFS) + device->stage.offset = BBLK_ZFS_BLK_OFF; + else + device->stage.offset = BBLK_BLKLIST_OFF; + device->stage.id = i + 1; + goto found_part; + } + + /* + * Solaris in a logical partition. Find that partition in the + * extended part. + */ + + if ((rval = libfdisk_init(&epp, device->path, NULL, FDISK_READ_DISK)) + != FDISK_SUCCESS) { + switch (rval) { + /* + * The first 3 cases are not an error per-se, just that + * there is no Solaris logical partition + */ + case FDISK_EBADLOGDRIVE: + case FDISK_ENOLOGDRIVE: + case FDISK_EBADMAGIC: + (void) fprintf(stderr, gettext("Solaris " + "partition not found. " + "Aborting operation.\n")); + return (BC_ERROR); + case FDISK_ENOVGEOM: + (void) fprintf(stderr, gettext("Could not get " + "virtual geometry\n")); + return (BC_ERROR); + case FDISK_ENOPGEOM: + (void) fprintf(stderr, gettext("Could not get " + "physical geometry\n")); + return (BC_ERROR); + case FDISK_ENOLGEOM: + (void) fprintf(stderr, gettext("Could not get " + "label geometry\n")); + return (BC_ERROR); + default: + (void) fprintf(stderr, gettext("Failed to " + "initialize libfdisk.\n")); + return (BC_ERROR); + } + } + + rval = fdisk_get_solaris_part(epp, &pno, &secnum, &numsec); + libfdisk_fini(&epp); + if (rval != FDISK_SUCCESS) { + /* No solaris logical partition */ + (void) fprintf(stderr, gettext("Solaris partition not found. " + "Aborting operation.\n")); + return (BC_ERROR); + } + + device->stage.start = secnum; + device->stage.size = numsec; + device->stage.id = pno; + log_part = 1; + +found_part: + /* get confirmation for -m */ + if (write_mbr && !force_mbr) { + (void) fprintf(stdout, gettext("Updating master boot sector " + "destroys existing boot managers (if any).\n" + "continue (y/n)? ")); + if (!yes()) { + write_mbr = 0; + (void) fprintf(stdout, gettext("master boot sector " + "not updated\n")); + return (BC_ERROR); + } + } + + /* + * warn, if illumos in primary partition and loader not in MBR and + * partition is not active + */ + if (device->devtype != IG_DEV_EFI) { + if (!log_part && part->bootid != 128 && !write_mbr) { + (void) fprintf(stdout, gettext("Solaris fdisk " + "partition is inactive.\n"), device->stage.id); + } + } + + return (BC_SUCCESS); +} + +static int +open_device(char *path) +{ + struct stat statbuf = {0}; + int fd = -1; + + if (nowrite) + fd = open(path, O_RDONLY); + else + fd = open(path, O_RDWR); + + if (fd == -1) { + BOOT_DEBUG("Unable to open %s\n", path); + perror("open"); + return (-1); + } + + if (fstat(fd, &statbuf) != 0) { + BOOT_DEBUG("Unable to stat %s\n", path); + perror("stat"); + (void) close(fd); + return (-1); + } + + if (S_ISCHR(statbuf.st_mode) == 0) { + (void) fprintf(stderr, gettext("%s: Not a character device\n"), + path); + (void) close(fd); + return (-1); + } + + return (fd); +} + +static int +get_boot_partition(ib_device_t *device, struct mboot *mbr) +{ + struct ipart *part; + char *path, *ptr; + int i; + + part = (struct ipart *)mbr->parts; + for (i = 0; i < FD_NUMPART; i++) { + if (part[i].systid == X86BOOT) + break; + } + + /* no X86BOOT, try to use space between MBR and first partition */ + if (i == FD_NUMPART) { + device->stage.path = strdup(device->path); + if (device->stage.path == NULL) { + perror(gettext("Memory allocation failure")); + return (BC_ERROR); + } + device->stage.fd = dup(device->fd); + device->stage.id = 0; + device->stage.devtype = IG_DEV_MBR; + device->stage.fstype = IG_FS_NONE; + device->stage.start = 0; + device->stage.size = part[0].relsect; + device->stage.offset = BBLK_BLKLIST_OFF; + return (BC_SUCCESS); + } + + if ((path = strdup(device->path)) == NULL) { + perror(gettext("Memory allocation failure")); + return (BC_ERROR); + } + + ptr = strrchr(path, 'p'); + ptr++; + *ptr = '\0'; + (void) asprintf(&ptr, "%s%d", path, i+1); /* partitions are p1..p4 */ + free(path); + if (ptr == NULL) { + perror(gettext("Memory allocation failure")); + return (BC_ERROR); + } + device->stage.path = ptr; + device->stage.fd = open_device(ptr); + device->stage.id = i + 1; + device->stage.devtype = IG_DEV_MBR; + device->stage.fstype = IG_FS_NONE; + device->stage.start = part[i].relsect; + device->stage.size = part[i].numsect; + device->stage.offset = 1; /* leave sector 0 for VBR */ + return (BC_SUCCESS); +} + +static int +get_boot_slice(ib_device_t *device, struct dk_gpt *vtoc) +{ + uint_t i; + char *path, *ptr; + + for (i = 0; i < vtoc->efi_nparts; i++) { + if (vtoc->efi_parts[i].p_tag == V_BOOT) { + if ((path = strdup(device->target.path)) == NULL) { + perror(gettext("Memory allocation failure")); + return (BC_ERROR); + } + ptr = strrchr(path, 's'); + ptr++; + *ptr = '\0'; + (void) asprintf(&ptr, "%s%d", path, i); + free(path); + if (ptr == NULL) { + perror(gettext("Memory allocation failure")); + return (BC_ERROR); + } + device->stage.path = ptr; + device->stage.fd = open_device(ptr); + device->stage.id = i; + device->stage.devtype = IG_DEV_EFI; + device->stage.fstype = IG_FS_NONE; + device->stage.start = vtoc->efi_parts[i].p_start; + device->stage.size = vtoc->efi_parts[i].p_size; + device->stage.offset = 1; /* leave sector 0 for VBR */ + return (BC_SUCCESS); + } + } + return (BC_SUCCESS); +} + +static int +init_device(ib_device_t *device, char *path) +{ + struct dk_gpt *vtoc; + fstyp_handle_t fhdl; + const char *fident; + char *p; + int pathlen = strlen(path); + int ret; + + bzero(device, sizeof (*device)); + device->fd = -1; /* whole disk fd */ + device->stage.fd = -1; /* bootblock partition fd */ + device->target.fd = -1; /* target fs partition fd */ + + /* basic check, whole disk is not allowed */ + if ((p = strrchr(path, '/')) == NULL) + p = path; + if ((strrchr(p, 'p') == NULL && strrchr(p, 's') == NULL) || + (path[pathlen-2] == 'p' && path[pathlen-1] == '0')) { + (void) fprintf(stderr, gettext("installing loader to " + "whole disk device is not supported\n")); + } + + device->target.path = strdup(path); + if (device->target.path == NULL) { + perror(gettext("Memory allocation failure")); + return (BC_ERROR); + } + device->path = strdup(path); + if (device->path == NULL) { + perror(gettext("Memory allocation failure")); + return (BC_ERROR); + } + + /* change device name to p0 */ + device->path[pathlen - 2] = 'p'; + device->path[pathlen - 1] = '0'; + + if (strstr(device->target.path, "diskette")) { + (void) fprintf(stderr, gettext("installing loader to a floppy " + "disk is not supported\n")); + return (BC_ERROR); + } + + /* Detect if the target device is a pcfs partition. */ + if (strstr(device->target.path, "p0:boot")) { + (void) fprintf(stderr, gettext("installing loader to x86 boot " + "partition is not supported\n")); + return (BC_ERROR); + } + + if ((device->fd = open_device(device->path)) == -1) + return (BC_ERROR); + + /* read in the device boot sector. */ + if (read(device->fd, device->mbr, SECTOR_SIZE) != SECTOR_SIZE) { + (void) fprintf(stderr, gettext("Error reading boot sector\n")); + perror("read"); + return (BC_ERROR); + } + + device->devtype = IG_DEV_VTOC; + if (efi_alloc_and_read(device->fd, &vtoc) >= 0) { + ret = get_boot_slice(device, vtoc); + device->devtype = IG_DEV_EFI; + efi_free(vtoc); + if (ret == BC_ERROR) + return (BC_ERROR); + } else if (device->target.path[pathlen - 2] == 'p') { + device->devtype = IG_DEV_MBR; + ret = get_boot_partition(device, (struct mboot *)device->mbr); + if (ret == BC_ERROR) + return (BC_ERROR); + } else if (device->target.path[pathlen - 1] == '2') { + /* + * NOTE: we could relax there and allow zfs boot on + * slice 2 for instance, but lets keep traditional limits. + */ + (void) fprintf(stderr, + gettext("raw device must be a root slice (not s2)\n")); + return (BC_ERROR); + } + + /* fill stage partition for case there is no boot partition */ + if (device->stage.path == NULL) { + if ((device->stage.path = strdup(path)) == NULL) { + perror(gettext("Memory allocation failure")); + return (BC_ERROR); + } + if (device->devtype == IG_DEV_VTOC) { + /* use slice 2 */ + device->stage.path[pathlen - 2] = 's'; + device->stage.path[pathlen - 1] = '2'; + device->stage.id = 2; + } else { + p = strrchr(device->stage.path, 'p'); + if (p == NULL) + p = strrchr(device->stage.path, 's'); + device->stage.id = atoi(++p); + } + device->stage.devtype = device->devtype; + device->stage.fd = open_device(device->stage.path); + } + + p = strrchr(device->target.path, 'p'); + if (p == NULL) + p = strrchr(device->target.path, 's'); + device->target.id = atoi(++p); + + if (strcmp(device->stage.path, device->target.path) == 0) + device->target.fd = dup(device->stage.fd); + else + device->target.fd = open_device(device->target.path); + + if (fstyp_init(device->target.fd, 0, NULL, &fhdl) != 0) + return (BC_ERROR); + + if (fstyp_ident(fhdl, NULL, &fident) != 0) { + fstyp_fini(fhdl); + (void) fprintf(stderr, gettext("Failed to detect file " + "system type\n")); + return (BC_ERROR); + } + + /* at this moment non-boot partition has no size set, use this fact */ + if (device->devtype == IG_DEV_EFI && strcmp(fident, "zfs") && + device->stage.size == 0) { + fstyp_fini(fhdl); + (void) fprintf(stderr, gettext("Booting %s of EFI labeled " + "disks requires the boot partition.\n"), fident); + return (BC_ERROR); + } + if (strcmp(fident, "zfs") == 0) + device->target.fstype = IG_FS_ZFS; + else if (strcmp(fident, "ufs") == 0) { + device->target.fstype = IG_FS_UFS; + } else if (strcmp(fident, "pcfs") == 0) { + device->target.fstype = IG_FS_PCFS; + } else { + (void) fprintf(stderr, gettext("File system %s is not " + "supported by loader\n"), fident); + fstyp_fini(fhdl); + return (BC_ERROR); + } + fstyp_fini(fhdl); + + /* check for boot partition content */ + if (device->stage.size) { + if (fstyp_init(device->stage.fd, 0, NULL, &fhdl) != 0) + return (BC_ERROR); + + if (fstyp_ident(fhdl, NULL, &fident) == 0) { + (void) fprintf(stderr, gettext("Unexpected %s file " + "system on boot partition\n"), fident); + fstyp_fini(fhdl); + return (BC_ERROR); + } + fstyp_fini(fhdl); + } + return (get_start_sector(device)); +} + +static void +cleanup_device(ib_device_t *device) +{ + if (device->path) + free(device->path); + if (device->stage.path) + free(device->stage.path); + if (device->target.path) + free(device->target.path); + + if (device->fd != -1) + (void) close(device->fd); + if (device->stage.fd != -1) + (void) close(device->stage.fd); + if (device->target.fd != -1) + (void) close(device->target.fd); + bzero(device, sizeof (*device)); +} + +static void +cleanup_bootblock(ib_bootblock_t *bblock) +{ + free(bblock->buf); + bzero(bblock, sizeof (ib_bootblock_t)); +} + +/* + * Propagate the bootblock on the source disk to the destination disk and + * version it with 'updt_str' in the process. Since we cannot trust any data + * on the attaching disk, we do not perform any specific check on a potential + * target extended information structure and we just blindly update. + */ +static int +propagate_bootblock(ib_data_t *src, ib_data_t *dest, char *updt_str) +{ + ib_bootblock_t *src_bblock = &src->bootblock; + ib_bootblock_t *dest_bblock = &dest->bootblock; + + assert(src != NULL); + assert(dest != NULL); + + /* read the stage1 file from source disk */ + if (read(src->device.fd, dest->stage1, SECTOR_SIZE) != SECTOR_SIZE) { + (void) fprintf(stderr, gettext("cannot read stage1 from %s\n"), + src->device.path); + return (BC_ERROR); + } + + cleanup_bootblock(dest_bblock); + + dest_bblock->buf_size = src_bblock->buf_size; + dest_bblock->buf = malloc(dest_bblock->buf_size); + if (dest_bblock->buf == NULL) { + perror(gettext("Memory Allocation Failure")); + return (BC_ERROR); + } + dest_bblock->file = dest_bblock->buf; + dest_bblock->file_size = src_bblock->file_size; + (void) memcpy(dest_bblock->buf, src_bblock->buf, + dest_bblock->buf_size); + + dest_bblock->mboot = (multiboot_header_t *)(dest_bblock->file + + src_bblock->mboot_off); + dest_bblock->mboot_off = src_bblock->mboot_off; + dest_bblock->extra = (char *)dest_bblock->file + + P2ROUNDUP(dest_bblock->file_size, 8); + dest_bblock->extra_size = src_bblock->extra_size; + + (void) fprintf(stdout, gettext("Propagating %s bootblock to %s\n"), + src->device.path, dest->device.path); + + return (commit_to_disk(dest, updt_str)); +} + +static int +commit_to_disk(ib_data_t *data, char *update_str) +{ + assert(data != NULL); + + if (prepare_bootblock(data, update_str) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Error updating the bootblock " + "image\n")); + return (BC_ERROR); + } + + if (prepare_stage1(data) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Error updating the stage1 " + "image\n")); + return (BC_ERROR); + } + + if (write_bootblock(data) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Error writing bootblock to " + "disk\n")); + return (BC_ERROR); + } + + return (write_stage1(data)); +} + +/* + * Install a new bootblock on the given device. handle_install() expects argv + * to contain 3 parameters (the target device path and the path to the + * bootblock. + * + * Returns: BC_SUCCESS - if the installation is successful + * BC_ERROR - if the installation failed + * BC_NOUPDT - if no installation was performed because the + * version currently installed is more recent than the + * supplied one. + * + */ +static int +handle_install(char *progname, char **argv) +{ + ib_data_t install_data; + char *stage1 = NULL; + char *bootblock = NULL; + char *device_path = NULL; + int ret = BC_ERROR; + + stage1 = strdup(argv[0]); + bootblock = strdup(argv[1]); + device_path = strdup(argv[2]); + + if (!device_path || !bootblock || !stage1) { + (void) fprintf(stderr, gettext("Missing parameter")); + usage(progname); + goto out; + } + + BOOT_DEBUG("device path: %s, stage1 path: %s bootblock path: %s\n", + device_path, stage1, bootblock); + bzero(&install_data, sizeof (ib_data_t)); + + if (init_device(&install_data.device, device_path) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Unable to open device %s\n"), + device_path); + goto out; + } + + if (read_stage1_from_file(stage1, &install_data) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Error opening %s\n"), stage1); + goto out_dev; + } + + if (read_bootblock_from_file(bootblock, &install_data) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Error reading %s\n"), + bootblock); + goto out_dev; + } + + /* + * is_update_necessary() will take care of checking if versioning and/or + * forcing the update have been specified. It will also emit a warning + * if a non-versioned update is attempted over a versioned bootblock. + */ + if (!is_update_necessary(&install_data, update_str)) { + (void) fprintf(stderr, gettext("bootblock version installed " + "on %s is more recent or identical\n" + "Use -F to override or install without the -u option\n"), + device_path); + ret = BC_NOUPDT; + goto out_dev; + } + + BOOT_DEBUG("Ready to commit to disk\n"); + ret = commit_to_disk(&install_data, update_str); + +out_dev: + cleanup_device(&install_data.device); +out: + free(stage1); + free(bootblock); + free(device_path); + return (ret); +} + +/* + * Retrieves from a device the extended information (einfo) associated to the + * installed stage2. + * Expects one parameter, the device path, in the form: /dev/rdsk/c?[t?]d?s0. + * Returns: + * - BC_SUCCESS (and prints out einfo contents depending on 'flags') + * - BC_ERROR (on error) + * - BC_NOEINFO (no extended information available) + */ +static int +handle_getinfo(char *progname, char **argv) +{ + + ib_data_t data; + ib_bootblock_t *bblock = &data.bootblock; + ib_device_t *device = &data.device; + bblk_einfo_t *einfo; + uint8_t flags = 0; + char *device_path, *path; + int retval = BC_ERROR; + int ret; + + device_path = strdup(argv[0]); + if (!device_path) { + (void) fprintf(stderr, gettext("Missing parameter")); + usage(progname); + goto out; + } + + bzero(&data, sizeof (ib_data_t)); + BOOT_DEBUG("device path: %s\n", device_path); + + if (init_device(device, device_path) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Unable to gather device " + "information from %s\n"), device_path); + goto out_dev; + } + + ret = read_bootblock_from_disk(device, bblock, &path); + if (ret == BC_ERROR) { + (void) fprintf(stderr, gettext("Error reading bootblock from " + "%s\n"), path); + goto out_dev; + } + + if (ret == BC_NOEXTRA) { + BOOT_DEBUG("No multiboot header found on %s, unable " + "to locate extra information area (old/non versioned " + "bootblock?) \n", device_path); + (void) fprintf(stderr, gettext("No extended information " + "found\n")); + retval = BC_NOEINFO; + goto out_dev; + } + + einfo = find_einfo(bblock->extra, bblock->extra_size); + if (einfo == NULL) { + retval = BC_NOEINFO; + (void) fprintf(stderr, gettext("No extended information " + "found\n")); + goto out_dev; + } + + /* Print the extended information. */ + if (strip) + flags |= EINFO_EASY_PARSE; + if (verbose_dump) + flags |= EINFO_PRINT_HEADER; + + print_einfo(flags, einfo, bblock->extra_size); + retval = BC_SUCCESS; + +out_dev: + cleanup_device(&data.device); +out: + free(device_path); + return (retval); +} + +/* + * Attempt to mirror (propagate) the current bootblock over the attaching disk. + * + * Returns: + * - BC_SUCCESS (a successful propagation happened) + * - BC_ERROR (an error occurred) + * - BC_NOEXTRA (it is not possible to dump the current bootblock since + * there is no multiboot information) + */ +static int +handle_mirror(char *progname, char **argv) +{ + ib_data_t curr_data; + ib_data_t attach_data; + ib_device_t *curr_device = &curr_data.device; + ib_device_t *attach_device = &attach_data.device; + ib_bootblock_t *bblock_curr = &curr_data.bootblock; + ib_bootblock_t *bblock_attach = &attach_data.bootblock; + bblk_einfo_t *einfo_curr = NULL; + char *curr_device_path; + char *attach_device_path; + char *updt_str = NULL; + char *path; + int retval = BC_ERROR; + int ret; + + curr_device_path = strdup(argv[0]); + attach_device_path = strdup(argv[1]); + + if (!curr_device_path || !attach_device_path) { + (void) fprintf(stderr, gettext("Missing parameter")); + usage(progname); + goto out; + } + BOOT_DEBUG("Current device path is: %s, attaching device path is: " + " %s\n", curr_device_path, attach_device_path); + + bzero(&curr_data, sizeof (ib_data_t)); + bzero(&attach_data, sizeof (ib_data_t)); + + if (init_device(curr_device, curr_device_path) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Unable to gather device " + "information from %s (current device)\n"), + curr_device_path); + goto out_currdev; + } + + if (init_device(attach_device, attach_device_path) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Unable to gather device " + "information from %s (attaching device)\n"), + attach_device_path); + goto out_devs; + } + + ret = read_bootblock_from_disk(curr_device, bblock_curr, &path); + if (ret == BC_ERROR) { + BOOT_DEBUG("Error reading bootblock from %s\n", path); + retval = BC_ERROR; + goto out_devs; + } + + if (ret == BC_NOEXTRA) { + BOOT_DEBUG("No multiboot header found on %s, unable to retrieve" + " the bootblock\n", path); + retval = BC_NOEXTRA; + goto out_devs; + } + + write_mbr = B_TRUE; + force_mbr = B_TRUE; + einfo_curr = find_einfo(bblock_curr->extra, bblock_curr->extra_size); + if (einfo_curr != NULL) + updt_str = einfo_get_string(einfo_curr); + + retval = propagate_bootblock(&curr_data, &attach_data, updt_str); + cleanup_bootblock(bblock_curr); + cleanup_bootblock(bblock_attach); +out_devs: + cleanup_device(attach_device); +out_currdev: + cleanup_device(curr_device); +out: + free(curr_device_path); + free(attach_device_path); + return (retval); +} + +#define USAGE_STRING "Usage:\t%s [-h|-m|-f|-n|-F|-u verstr] stage1 stage2 " \ + "raw-device\n" \ + "\t%s -M [-n] raw-device attach-raw-device\n" \ + "\t%s [-e|-V] -i raw-device\n" + +#define CANON_USAGE_STR gettext(USAGE_STRING) + +static void +usage(char *progname) +{ + (void) fprintf(stdout, CANON_USAGE_STR, progname, progname, progname); +} + +int +main(int argc, char **argv) +{ + int opt; + int params = 3; + int ret; + char *progname; + char **handle_args; + + (void) setlocale(LC_ALL, ""); + (void) textdomain(TEXT_DOMAIN); + if (init_yes() < 0) { + (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES), + strerror(errno)); + exit(BC_ERROR); + } + + while ((opt = getopt(argc, argv, "deFfhiMmnu:V")) != EOF) { + switch (opt) { + case 'd': + boot_debug = B_TRUE; + break; + case 'e': + strip = B_TRUE; + break; + case 'F': + force_update = B_TRUE; + break; + case 'f': + force_mbr = B_TRUE; + break; + case 'h': + usage(argv[0]); + exit(BC_SUCCESS); + break; + case 'i': + do_getinfo = B_TRUE; + params = 1; + break; + case 'M': + do_mirror_bblk = B_TRUE; + params = 2; + break; + case 'm': + write_mbr = B_TRUE; + break; + case 'n': + nowrite = B_TRUE; + break; + case 'u': + do_version = B_TRUE; + + update_str = malloc(strlen(optarg) + 1); + if (update_str == NULL) { + perror(gettext("Memory allocation failure")); + exit(BC_ERROR); + } + (void) strlcpy(update_str, optarg, strlen(optarg) + 1); + break; + case 'V': + verbose_dump = B_TRUE; + break; + default: + /* fall through to process non-optional args */ + break; + } + } + + /* check arguments */ + if (argc != optind + params) { + usage(argv[0]); + exit(BC_ERROR); + } + progname = argv[0]; + check_options(progname); + handle_args = argv + optind; + + if (nowrite) + (void) fprintf(stdout, gettext("Dry run requested. Nothing will" + " be written to disk.\n")); + + if (do_getinfo) { + ret = handle_getinfo(progname, handle_args); + } else if (do_mirror_bblk) { + ret = handle_mirror(progname, handle_args); + } else { + ret = handle_install(progname, handle_args); + } + return (ret); +} + +#define MEANINGLESS_OPT gettext("%s specified but meaningless, ignoring\n") +static void +check_options(char *progname) +{ + if (do_getinfo && do_mirror_bblk) { + (void) fprintf(stderr, gettext("Only one of -M and -i can be " + "specified at the same time\n")); + usage(progname); + exit(BC_ERROR); + } + + if (do_mirror_bblk) { + /* + * -u and -F may actually reflect a user intent that is not + * correct with this command (mirror can be interpreted + * "similar" to install. Emit a message and continue. + * -e and -V have no meaning, be quiet here and only report the + * incongruence if a debug output is requested. + */ + if (do_version) { + (void) fprintf(stderr, MEANINGLESS_OPT, "-u"); + do_version = B_FALSE; + } + if (force_update) { + (void) fprintf(stderr, MEANINGLESS_OPT, "-F"); + force_update = B_FALSE; + } + if (strip || verbose_dump) { + BOOT_DEBUG(MEANINGLESS_OPT, "-e|-V"); + strip = B_FALSE; + verbose_dump = B_FALSE; + } + } + + if (do_getinfo) { + if (write_mbr || force_mbr || do_version || force_update) { + BOOT_DEBUG(MEANINGLESS_OPT, "-m|-f|-u|-F"); + write_mbr = force_mbr = do_version = B_FALSE; + force_update = B_FALSE; + } + } +} diff --git a/usr/src/cmd/boot/installboot/i386/installboot.h b/usr/src/cmd/boot/installboot/i386/installboot.h new file mode 100644 index 0000000000..741f1cb682 --- /dev/null +++ b/usr/src/cmd/boot/installboot/i386/installboot.h @@ -0,0 +1,112 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright 2012 Nexenta Systems, Inc. All rights reserved. + * Copyright 2016 Toomas Soome <tsoome@me.com> + */ + +#ifndef _INSTALLBOOT_H +#define _INSTALLBOOT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/multiboot.h> +#include <sys/types.h> + +#define SECTOR_SIZE (512) + +/* partitioning type for device */ +enum ig_devtype_t { + IG_DEV_VTOC = 0, + IG_DEV_MBR, + IG_DEV_EFI +}; + +/* file system type */ +enum ig_fstype_t { + IG_FS_NONE = 0, + IG_FS_ZFS, + IG_FS_UFS, + IG_FS_PCFS +}; + +/* partition info for boot block location. */ +struct stage_part { + char *path; /* device name */ + int fd; /* open file descriptor */ + int id; /* partition/slice number */ + enum ig_devtype_t devtype; /* partitioning type */ + enum ig_fstype_t fstype; /* none or pcfs */ + uint64_t start; /* partition LBA */ + uint64_t size; /* partition size */ + uint64_t offset; /* block offset */ +}; + +/* boot device data */ +typedef struct _ib_device { + char *path; /* whole disk */ + int fd; /* whole disk fd */ + enum ig_devtype_t devtype; + struct stage_part stage; /* location of boot block */ + struct stage_part target; /* target file system */ + char mbr[SECTOR_SIZE]; +} ib_device_t; + +/* stage 2 location */ +typedef struct _ib_bootblock { + char *buf; + char *file; + char *extra; + multiboot_header_t *mboot; + uint32_t mboot_off; + uint32_t file_size; + uint32_t buf_size; + uint32_t extra_size; +} ib_bootblock_t; + +typedef struct _ib_data { + unsigned char stage1[SECTOR_SIZE]; /* partition boot block */ + ib_device_t device; /* boot device */ + ib_bootblock_t bootblock; /* stage 2 */ +} ib_data_t; + +#define BBLK_BLKLIST_OFF 50 /* vtoc/disk boot offset */ +#define BBLK_ZFS_BLK_OFF 1024 /* vdev boot offset */ +#define BBLK_ZFS_BLK_SIZE (7ULL << 19) /* vdev max boot size */ + +/* locations of MBR parts, must be reviewd if mbr code is changed */ +#define STAGE1_BPB_OFFSET (0x3) /* technically BPB starts at 0xb */ +#define STAGE1_BPB_SIZE (0x3b) +#define STAGE1_MBR_VERSION (0xfa) /* 2 bytes, not used */ +#define STAGE1_STAGE2_SIZE (0xfc) /* 16bits */ +#define STAGE1_STAGE2_LBA (0xfe) /* 64bits */ +#define STAGE1_STAGE2_UUID (0x106) /* 128bits */ +#define STAGE1_SIG (0x1b8) /* 4+2 bytes */ +#define STAGE1_PARTTBL (0x1be) /* MBR partition table */ +#define STAGE1_MAGIC (0x1fe) /* 0xAA55 */ +#ifdef __cplusplus +} +#endif + +#endif /* _INSTALLBOOT_H */ diff --git a/usr/src/cmd/boot/installboot/sparc/Makefile b/usr/src/cmd/boot/installboot/sparc/Makefile new file mode 100644 index 0000000000..cbf9632fba --- /dev/null +++ b/usr/src/cmd/boot/installboot/sparc/Makefile @@ -0,0 +1,18 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +include ../Makefile.com + +.KEEP_STATE: + +all: + +include $(SRC)/cmd/boot/Makefile.targ diff --git a/usr/src/cmd/boot/installboot/installboot.c b/usr/src/cmd/boot/installboot/sparc/installboot.c index 2a4f48fd18..36b0bd3d15 100644 --- a/usr/src/cmd/boot/installboot/installboot.c +++ b/usr/src/cmd/boot/installboot/sparc/installboot.c @@ -36,9 +36,9 @@ #include <sys/sysmacros.h> #include "installboot.h" -#include "./../common/bblk_einfo.h" -#include "./../common/boot_utils.h" -#include "./../common/mboot_extra.h" +#include "../../common/bblk_einfo.h" +#include "../../common/boot_utils.h" +#include "../../common/mboot_extra.h" #ifndef TEXT_DOMAIN #define TEXT_DOMAIN "SUNW_OST_OSCMD" diff --git a/usr/src/cmd/boot/installboot/installboot.h b/usr/src/cmd/boot/installboot/sparc/installboot.h index 2fa7ad2561..2fa7ad2561 100644 --- a/usr/src/cmd/boot/installboot/installboot.h +++ b/usr/src/cmd/boot/installboot/sparc/installboot.h diff --git a/usr/src/man/man1m/Makefile b/usr/src/man/man1m/Makefile index bdf284a437..709e14f5a4 100644 --- a/usr/src/man/man1m/Makefile +++ b/usr/src/man/man1m/Makefile @@ -213,6 +213,7 @@ _MANFILES= 6to4relay.1m \ init.1m \ inityp2l.1m \ install.1m \ + installboot.1m \ installf.1m \ installgrub.1m \ intrd.1m \ diff --git a/usr/src/man/man1m/installboot.1m b/usr/src/man/man1m/installboot.1m new file mode 100644 index 0000000000..92ff24a277 --- /dev/null +++ b/usr/src/man/man1m/installboot.1m @@ -0,0 +1,209 @@ +.\" +.\" This file and its contents are supplied under the terms of the +.\" Common Development and Distribution License ("CDDL"), version 1.0. +.\" You may only use this file in accordance with the terms of version +.\" 1.0 of the CDDL. +.\" +.\" A full copy of the text of the CDDL should have accompanied this +.\" source. A copy of the CDDL is also available via the Internet at +.\" http://www.illumos.org/license/CDDL. +.\" +.\" +.\" Copyright 2016 Toomas Soome <tsoome@me.com> +.\" +.Dd February 10, 2016 +.Dt INSTALLBOOT 1M +.Os +.Sh NAME +.Nm installboot +.Nd install bootloader in a disk partition +.Sh SYNOPSIS +.Ss SPARC +.Nm +.Op Fl fn +.Op Fl F Sy zfs Ns | Ns Sy ufs Ns | Ns Sy hsfs +.Op Fl u Ar verstr +.Ar bootblk raw-device +.Nm +.Op Fl enV +.Fl F Sy zfs +.Fl i +.Ar raw-device +.Nm +.Op Fl n +.Fl F Sy zfs +.Fl M +.Ar raw-device attach-raw-device +.Ss x86 +.Nm +.Op Fl fFmn +.Op Fl u Ar verstr +.Ar stage1 stage2 raw-device +.Nm +.Op Fl enV +.Fl i +.Ar raw-device +.Nm +.Op Fl n +.Fl M +.Ar raw-device attach-raw-device +.Sh DESCRIPTION +The +.Xr boot 1M +boot program is loaded from disk and is responsible of loading kernel and its +support files from specific file system. +.Pp +The SPARC systems have one boot loader program file to be installed on the boot +area of a disk slice. As the SPARC zfs boot loader is too large to fit into +boot area at the start of the disk slice, +.Nm +command will split the zfs boot loader between disk slice boot area, and zfs +pool boot area. +.Pp +The x86 systems have boot loader implemented as three stages: +.Bl -tag -width Ds +.It Sy stage1 +.Pa /boot/pmbr +is used as master boot record +.Pq MBR +and partition boot program. +.It Sy stage2 +.Pa /boot/gptzfsboot +is responsible for loading files from file system. The +.Sy stage2 +on x86 systems is always installed to zfs pool boot area, and therefore only zfs +boot is supported. +.Nm +command will record the location of +.Sy stage2 +to +.Sy stage1 , +which is always installed at least on partition +.Pq MBR or GPT +boot area, making it possible to boot via chainload from other boot loaders. +.Pp +When +.Nm +command is used with the +.Fl m +option, +.Nm +installs the stage1 file on the master boot sector of the disk as well. +.It Sy stage3 +.Pa /boot/zfsloader +is read from file system and executed by +.Sy stage2 +and will provide boot loader user environment and is responsible of loading +and starting the operating system kernel. +.Pp +In case of GPT partitioning scheme, if the file system to boot from is either +UFS or PCFS, there must be +.Sy boot +partition defined to store stage2 boot program. This is needed because UFS and +PCFS do not have sufficient space reserved to store boot programs. +.Pp +The boot partition must use following GPT UUID: +.Bd -literal -offset indent +6a82cb45-1dd2-11b2-99a6-080020736631 +.Ed +.Pp +which is provided by +.Qq boot +tag in +.Xr format 1M +partition menu. +.El +.Ss Options +The +.Nm +command accepts the following options: +.Bl -tag -width Ds +.It Fl h +Prints short usage message. +.It Fl m +Installs +.Sy stage1 +on the master boot sector interactively. You must use this option if OS is +installed on an extended FDISK or an EFI/GPT partition. +.It Fl f +Suppresses interaction when overwriting the master boot sector on x86. +Force update on SPARC. +.It Fl n +Dry run session. Will not write to disk. +.It Fl F +On SPARC, specify file system type. On x86, inhibit version check and enforce +boot loader update. +.It Fl u Ar verstr +Specify custom version string. Can be used to add version on non-versioned +boot loader or change built in version string. +.It Fl i +Print version string from installed boot loader. +.It Fl e +Print version string from installed boot loader without description. +.It Fl V +Print version string from installed boot loader with full description. +.It Fl M +Mirror boot loader from installed disk partition. +.El +.Ss Operands +The +.Nm +command accepts the following operands: +.Bl -tag -width Ds +.It Ar bootblk +The name of the SPARC boot loader code. +.It Ar stage1 +The name of the loader stage 1 file. +.It Ar stage2 +The name of the loader stage 2 file. +.It Ar raw-device +The name of the device onto which bootloader code is to be installed. It must be +a character device that is readable and writable and part of boot pool. +.El +.Sh FILES +.Bl -tag -width Ds +.It Pa /boot +Directory where x86 loader files reside. +.It Pa /usr/platform/platform name/lib/fs +Directory where SPARC boot loader files reside. +.El +.Sh EXAMPLES +.Bl -tag -width Ds +.It Sy Example 1 No Installing zfs boot loader on SPARC disk slice +The following command installs zfs boot loader on SPARC system: +.Bd -literal +# installboot -F zfs /usr/platform/`uname -i`/lib/fs/zfs/bootblk \e + /dev/rdsk/c0t0d0s0 +.Ed +.It Sy Example 2 No Installing boot loader on x86 system +The following command installs loader stage files and master boot record: +.Bd -literal +# installboot -m /boot/pmbr /boot/gptzfsboot /dev/rdsk/c0t0d0s0 +.Ed +.El +.Sh INTERFACE STABILITY +.Sy Uncommitted +.Sh SEE ALSO +.Xr boot 1M , +.Xr bootadm 1M , +.Xr fdisk 1M , +.Xr fmthard 1M , +.Xr format 1M , +.Xr kernel 1M , +.Xr attributes 5 +.Sh WARNINGS +Installing +.Sy stage1 +on the master boot sector +.Po +.Fl m +option +.Pc +overrides any boot loader currently installed on the machine. The system will +always boot the current OS partition regardless of which fdisk partition is +active. +.Pp +If version string indicates the source boot loader might be more recent, +.Nm +will also verify md5 checksums to determine if update is really necessary. If +checksums match, the install will not be performed. diff --git a/usr/src/pkg/manifests/SUNWcs.man1m.inc b/usr/src/pkg/manifests/SUNWcs.man1m.inc index c8d0a1c3fb..32bdce2476 100644 --- a/usr/src/pkg/manifests/SUNWcs.man1m.inc +++ b/usr/src/pkg/manifests/SUNWcs.man1m.inc @@ -12,6 +12,7 @@ # # Copyright 2011, Richard Lowe # Copyright 2015 Nexenta Systems, Inc. All rights reserved. +# Copyright 2016 Toomas Soome <tsoome@me.com> # file path=usr/share/man/man1m/6to4relay.1m @@ -121,6 +122,7 @@ file path=usr/share/man/man1m/inetd.1m file path=usr/share/man/man1m/infocmp.1m file path=usr/share/man/man1m/init.1m file path=usr/share/man/man1m/install.1m +file path=usr/share/man/man1m/installboot.1m file path=usr/share/man/man1m/installgrub.1m file path=usr/share/man/man1m/intrd.1m file path=usr/share/man/man1m/iostat.1m diff --git a/usr/src/pkg/manifests/SUNWcs.mf b/usr/src/pkg/manifests/SUNWcs.mf index 6b08d1dbc2..5f06b3b040 100644 --- a/usr/src/pkg/manifests/SUNWcs.mf +++ b/usr/src/pkg/manifests/SUNWcs.mf @@ -1336,6 +1336,7 @@ file path=usr/sbin/ikecert mode=0555 file path=usr/sbin/inetadm mode=0555 file path=usr/sbin/inetconv mode=0555 file path=usr/sbin/install mode=0555 +file path=usr/sbin/installboot group=sys mode=0555 file path=usr/sbin/ipaddrsel mode=0555 file path=usr/sbin/ipsecalgs mode=0555 file path=usr/sbin/ipsecconf mode=0555 |