/*
* 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
* 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 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/multiboot.h>
#include <sys/sysmacros.h>
#include <sys/efi_partition.h>
#include <libfstyp.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
#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.
*
* reinstalled in case the partition content is relocated.
*/
/* Versioning string, if present. */
static char *update_str;
/*
* Temporary buffer to store the first 32K of data looking for a multiboot
* signature.
*/
/* Function prototypes. */
static void check_options(char *);
static int get_start_sector(ib_device_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 void usage(char *);
static int
{
int fd;
/* read the stage1 file from filesystem */
if (fd == -1 ||
path);
return (BC_ERROR);
}
return (BC_SUCCESS);
}
static int
{
if (fd == -1) {
perror("open");
goto out;
}
perror("stat");
goto outfd;
}
/* loader bootblock has version built in */
BOOT_DEBUG("bootblock in-memory buffer size is %d\n",
goto outbuf;
}
perror("read");
goto outfd;
}
!= BC_SUCCESS) {
gettext("Unable to find multiboot header\n"));
goto outfd;
}
BOOT_DEBUG("mboot at %p offset %d, extra at %p size %d, buf=%p "
return (BC_SUCCESS);
out:
return (retval);
}
static int
char **path)
{
int dev_fd;
} else {
}
!= BC_SUCCESS) {
BOOT_DEBUG("Error reading bootblock area\n");
perror("read");
return (BC_ERROR);
}
/* No multiboot means no chance of knowing bootblock size */
!= BC_SUCCESS) {
BOOT_DEBUG("Unable to find multiboot header\n");
return (BC_NOEXTRA);
}
/*
* make sure mboot has sane values
*/
if (mboot->load_end_addr == 0 ||
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.
*/
BOOT_DEBUG("Unable to allocate enough memory to read"
" the extra bootblock from the disk\n");
return (BC_ERROR);
}
BOOT_DEBUG("Error reading the bootblock\n");
return (BC_ERROR);
}
/* Update pointers. */
BOOT_DEBUG("mboot at %p offset %d, extra at %p size %d, buf=%p "
return (BC_SUCCESS);
}
static boolean_t
{
int ret;
char *path;
if (ret != BC_SUCCESS) {
return (B_TRUE);
}
BOOT_DEBUG("No extended information available on disk\n");
return (B_TRUE);
}
if (einfo_file == NULL) {
/*
* loader bootblock is versioned. missing version means
* probably incompatible block. installboot can not install
* grub, for example.
*/
gettext("ERROR: non versioned bootblock in file\n"));
return (B_FALSE);
} else {
do_version = B_TRUE;
}
}
gettext("WARNING: target device %s has a "
"versioned bootblock that is going to be overwritten by a "
return (B_TRUE);
}
if (force_update) {
return (B_TRUE);
}
}
static void
{
BOOT_DEBUG("WARNING: no update string passed to "
"add_bootblock_einfo()\n");
return;
}
/* Fill bootblock hashing source information. */
/* How much space for the extended information structure? */
/* Place the extended information structure. */
}
/*
* 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
{
/* copy BPB */
/* copy MBR, note STAGE1_SIG == BOOTSZ */
/* set stage2 size */
/*
* set stage2 location.
* free space after MBR record.
*/
else {
}
/*
* set disk uuid. we only need reasonable amount of uniqueness
* to allow biosdev to identify disk based on mbr differences.
*/
return (BC_SUCCESS);
}
static int
{
/*
* the loader bootblock has built in version, if custom
* version was provided, update it.
*/
if (do_version)
return (BC_SUCCESS);
}
static int
{
char *path;
/*
* ZFS bootblock area is 3.5MB, make sure we can fit.
* buf_size is size of bootblk+EINFO.
*/
return (BC_ERROR);
}
} else {
"too small to fit the stage2\n"), path);
return (BC_ERROR);
}
}
if (ret != BC_SUCCESS) {
BOOT_DEBUG("Error writing the ZFS bootblock "
return (BC_ERROR);
}
" %d sectors starting at %d (abs %lld)\n"), path,
return (BC_SUCCESS);
}
static int
{
/*
* 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.
*/
/* we got separate stage area, use it */
"partition boot sector\n"));
perror("write");
return (BC_ERROR);
}
"%s %d sector 0 (abs %d)\n"),
}
/*
* so its safe to use this area. however, only
*/
"partition boot sector\n"));
perror("write");
return (BC_ERROR);
}
"%s %d sector 0 (abs %d)\n"),
}
if (write_mbr) {
gettext("cannot write master boot sector\n"));
perror("write");
return (BC_ERROR);
}
gettext("stage1 written to master boot sector\n"));
}
return (BC_SUCCESS);
}
/*
* by stage2 to identify partition with boot file system.
*/
static int
{
return (BC_ERROR);
/* zero size means the fstype must be zfs */
}
/* with pcfs we always write MBR */
force_mbr = 1;
write_mbr = 1;
}
goto found_part;
}
/* For MBR we have device->stage filled already. */
/* MBR partition starts from 0 */
"disk has an incorrect offset\n"),
return (BC_ERROR);
}
/* with pcfs we always write MBR */
force_mbr = 1;
write_mbr = 1;
}
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.
*/
"slice information of the disk\n"));
return (BC_ERROR);
} else {
}
}
for (i = 0; i < FD_NUMPART; i++) {
"disk has an incorrect offset\n"), i+1);
return (BC_ERROR);
}
/* Found the partition */
break;
}
}
if (i == FD_NUMPART) {
/* No solaris fdisk partitions (primary or logical) */
"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
*/
else
goto found_part;
}
/*
* Solaris in a logical partition. Find that partition in the
* extended part.
*/
!= 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:
"partition not found. "
"Aborting operation.\n"));
return (BC_ERROR);
case FDISK_ENOVGEOM:
"virtual geometry\n"));
return (BC_ERROR);
case FDISK_ENOPGEOM:
"physical geometry\n"));
return (BC_ERROR);
case FDISK_ENOLGEOM:
"label geometry\n"));
return (BC_ERROR);
default:
"initialize libfdisk.\n"));
return (BC_ERROR);
}
}
libfdisk_fini(&epp);
if (rval != FDISK_SUCCESS) {
/* No solaris logical partition */
"Aborting operation.\n"));
return (BC_ERROR);
}
log_part = 1;
/* get confirmation for -m */
"destroys existing boot managers (if any).\n"
"continue (y/n)? "));
if (!yes()) {
write_mbr = 0;
"not updated\n"));
return (BC_ERROR);
}
}
/*
* warn, if illumos in primary partition and loader not in MBR and
* partition is not active
*/
}
}
return (BC_SUCCESS);
}
static int
{
if (nowrite)
else
if (fd == -1) {
perror("open");
return (-1);
}
perror("stat");
return (-1);
}
path);
return (-1);
}
return (fd);
}
static int
{
int i;
for (i = 0; i < FD_NUMPART; i++) {
break;
}
/* no X86BOOT, try to use space between MBR and first partition */
if (i == FD_NUMPART) {
return (BC_ERROR);
}
return (BC_SUCCESS);
}
return (BC_ERROR);
}
ptr++;
*ptr = '\0';
return (BC_ERROR);
}
return (BC_SUCCESS);
}
static int
{
uint_t i;
for (i = 0; i < vtoc->efi_nparts; i++) {
return (BC_ERROR);
}
ptr++;
*ptr = '\0';
return (BC_ERROR);
}
return (BC_SUCCESS);
}
}
return (BC_SUCCESS);
}
static int
{
const char *fident;
char *p;
int ret;
/* basic check, whole disk is not allowed */
p = path;
"whole disk device is not supported\n"));
}
return (BC_ERROR);
}
return (BC_ERROR);
}
/* change device name to p0 */
"disk is not supported\n"));
return (BC_ERROR);
}
/* Detect if the target device is a pcfs partition. */
"partition is not supported\n"));
return (BC_ERROR);
}
return (BC_ERROR);
/* read in the device boot sector. */
perror("read");
return (BC_ERROR);
}
return (BC_ERROR);
return (BC_ERROR);
/*
* NOTE: we could relax there and allow zfs boot on
* slice 2 for instance, but lets keep traditional limits.
*/
gettext("raw device must be a root slice (not s2)\n"));
return (BC_ERROR);
}
/* fill stage partition for case there is no boot partition */
return (BC_ERROR);
}
/* use slice 2 */
} else {
if (p == NULL)
}
}
if (p == NULL)
else
return (BC_ERROR);
"system type\n"));
return (BC_ERROR);
}
/* at this moment non-boot partition has no size set, use this fact */
"disks requires the boot partition.\n"), fident);
return (BC_ERROR);
}
} else {
"supported by loader\n"), fident);
return (BC_ERROR);
}
/* check for boot partition content */
return (BC_ERROR);
"system on boot partition\n"), fident);
return (BC_ERROR);
}
}
return (get_start_sector(device));
}
static void
{
}
static void
{
}
/*
* 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
{
/* read the stage1 file from source disk */
return (BC_ERROR);
}
return (BC_ERROR);
}
}
static int
{
"image\n"));
return (BC_ERROR);
}
"image\n"));
return (BC_ERROR);
}
"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
{
goto out;
}
BOOT_DEBUG("device path: %s, stage1 path: %s bootblock path: %s\n",
goto out;
}
goto out_dev;
}
goto out_dev;
}
/*
* forcing the update have been specified. It will also emit a warning
* if a non-versioned update is attempted over a versioned bootblock.
*/
"on %s is more recent or identical\n"
"Use -F to override or install without the -u option\n"),
goto out_dev;
}
BOOT_DEBUG("Ready to commit to disk\n");
out:
return (ret);
}
/*
* Retrieves from a device the extended information (einfo) associated to the
* installed stage2.
* Returns:
* - BC_SUCCESS (and prints out einfo contents depending on 'flags')
* - BC_ERROR (on error)
* - BC_NOEINFO (no extended information available)
*/
static int
{
int ret;
if (!device_path) {
goto out;
}
"information from %s\n"), device_path);
goto out_dev;
}
"%s\n"), path);
goto out_dev;
}
if (ret == BC_NOEXTRA) {
BOOT_DEBUG("No multiboot header found on %s, unable "
"bootblock?) \n", device_path);
"found\n"));
retval = BC_NOEINFO;
goto out_dev;
}
retval = BC_NOEINFO;
"found\n"));
goto out_dev;
}
/* Print the extended information. */
if (strip)
if (verbose_dump)
retval = BC_SUCCESS;
out:
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
{
char *curr_device_path;
char *attach_device_path;
char *path;
int ret;
if (!curr_device_path || !attach_device_path) {
goto out;
}
BOOT_DEBUG("Current device path is: %s, attaching device path is: "
"information from %s (current device)\n"),
goto out_currdev;
}
"information from %s (attaching device)\n"),
goto out_devs;
}
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;
}
if (einfo_curr != NULL)
out:
return (retval);
}
"raw-device\n" \
"\t%s -M [-n] raw-device attach-raw-device\n" \
"\t%s [-e|-V] -i raw-device\n"
static void
{
}
int
{
int opt;
int ret;
char *progname;
char **handle_args;
(void) textdomain(TEXT_DOMAIN);
if (init_yes() < 0) {
}
switch (opt) {
case 'd':
boot_debug = B_TRUE;
break;
case 'e':
break;
case 'F':
break;
case 'f':
break;
case 'h':
break;
case 'i':
do_getinfo = B_TRUE;
params = 1;
break;
case 'M':
params = 2;
break;
case 'm':
break;
case 'n':
break;
case 'u':
do_version = B_TRUE;
if (update_str == NULL) {
}
break;
case 'V':
break;
default:
/* fall through to process non-optional args */
break;
}
}
/* check arguments */
}
if (nowrite)
" be written to disk.\n"));
if (do_getinfo) {
} else if (do_mirror_bblk) {
} else {
}
return (ret);
}
static void
{
if (do_getinfo && do_mirror_bblk) {
"specified at the same time\n"));
}
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) {
}
if (force_update) {
}
if (strip || verbose_dump) {
}
}
if (do_getinfo) {
}
}
}