installgrub.c revision 3bbf88b3546192f29c18986b9fb8a19ff364a4ea
/*
* 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 Milan Jurik. All rights reserved.
* Copyright 2016 Toomas Soome <tsoome@me.com>
* Copyright 2016 Nexenta Systems, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <malloc.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <libintl.h>
#include <locale.h>
#include <errno.h>
#include <libfdisk.h>
#include <stdarg.h>
#include <assert.h>
#include <sys/multiboot.h>
#include <sys/sysmacros.h>
#include <sys/efi_partition.h>
#include <libnvpair.h>
#include <libfstyp.h>
#include "message.h"
#include "installgrub.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
/*
* Variables to track installgrub desired mode of operation.
* 'nowrite' and 'boot_debug' come from boot_common.h.
*/
/* Installing the bootblock is the default operation. */
/* 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 handle_install(char *, char **);
static int handle_mirror(char *, char **);
static int handle_getinfo(char *, char **);
static int commit_to_disk(ig_data_t *, char *);
static void cleanup_device(ig_device_t *);
static void cleanup_stage2(ig_stage2_t *);
static int get_start_sector(ig_device_t *);
static int get_raw_partition_fd(ig_device_t *);
static char *get_raw_partition_path(ig_device_t *);
static int write_stage2(ig_data_t *);
static int write_stage1(ig_data_t *);
static void usage(char *);
static int read_stage1_from_file(char *, ig_data_t *);
static int read_stage2_from_file(char *, ig_data_t *);
static int read_stage1_from_disk(int, char *);
static int read_stage2_from_disk(int, ig_stage2_t *, int);
static int prepare_stage1(ig_data_t *);
static int prepare_stage2(ig_data_t *, char *);
static void prepare_fake_multiboot(ig_stage2_t *);
extern int read_stage2_blocklist(int, unsigned int *);
int
{
int opt;
int params = 3;
int ret;
char **handle_args;
char *progname;
(void) textdomain(TEXT_DOMAIN);
if (init_yes() < 0) {
}
/*
* retro-compatibility: installing the bootblock is the default
* and there is no switch for it.
*/
do_install = B_TRUE;
switch (opt) {
case 'm':
break;
case 'n':
break;
case 'f':
break;
case 'i':
do_getinfo = B_TRUE;
params = 1;
break;
case 'V':
break;
case 'd':
boot_debug = B_TRUE;
break;
case 'F':
break;
case 'e':
break;
case 'M':
params = 2;
break;
case 'u':
do_version = B_TRUE;
if (update_str == NULL) {
"allocate memory\n"));
}
break;
default:
/* fall through to process non-optional args */
break;
}
}
/* check arguments */
}
/*
* clean up options (and bail out if an unrecoverable combination is
* requested.
*/
if (nowrite)
if (do_getinfo) {
} else if (do_mirror_bblk) {
} else {
}
return (ret);
}
static void
check_options(char *progname)
{
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) {
}
}
}
/*
* expects argv to contain 3 parameters (the path to stage1, the path to stage2,
* the target device).
*
* Returns: BC_SUCCESS - if the installation is successful
* BC_ERROR - if the installation failed
* BC_NOUPDT - if no installation was performed because the GRUB
* version currently installed is more recent than the
* supplied one.
*
*/
static int
{
char *stage1_path = NULL;
char *stage2_path = NULL;
char *device_path = NULL;
goto out;
}
BOOT_DEBUG("stage1 path: %s, stage2 path: %s, device: %s\n",
"information for %s\n"), device_path);
goto out;
}
/* read in stage1 and stage2. */
goto out_dev;
}
goto out_dev;
}
/* We do not support versioning on PCFS. */
/*
* 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;
}
/*
* We get here if:
* - the installed GRUB version is older than the one about to be
* installed.
* - no versioning string has been passed through the command line.
* - a forced update is requested (-F).
*/
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
{
char *device_path;
int ret;
if (!device_path) {
goto out;
}
"information for %s\n"), device_path);
goto out_dev;
}
"PCFS\n"));
goto out_dev;
}
"%s\n"), device_path);
goto out_dev;
}
if (ret == BC_NOEXTRA) {
"%s, unable to locate extra information area\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 stage2 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;
int ret;
if (!curr_device_path || !attach_device_path) {
goto out;
}
BOOT_DEBUG("Current device path is: %s, attaching device path is: "
"information for %s (current device)\n"), curr_device_path);
goto out_currdev;
}
"information for %s (attaching device)\n"),
goto out_devs;
}
"supported on PCFS\n"));
goto out_devs;
}
curr_device->type);
BOOT_DEBUG("Error reading first stage2 blocks from %s\n",
curr_device->path);
goto out_devs;
}
if (ret == BC_NOEXTRA) {
BOOT_DEBUG("No multiboot header found on %s, unable to grab "
retval = BC_NOEXTRA;
goto out_devs;
}
if (einfo_curr != NULL)
out:
return (retval);
}
static int
{
/*
* vanilla stage1 and stage2 need to be updated at runtime.
* Update stage2 before stage1 because stage1 needs to know the first
* sector stage2 will be written to.
*/
return (BC_ERROR);
}
return (BC_ERROR);
}
/* Write stage2 out to disk. */
"disk\n"));
return (BC_ERROR);
}
/* Write stage1 to disk and, if requested, to the MBR. */
"disk\n"));
return (BC_ERROR);
}
return (BC_SUCCESS);
}
/*
* 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
{
int retval;
/* read in stage1 from the source disk. */
!= BC_SUCCESS)
return (BC_ERROR);
/* Prepare target stage2 for commit_to_disk. */
do_version = B_TRUE;
else
return (BC_ERROR);
}
/* If we get down here we do have a mboot structure. */
return (retval);
}
/*
* open the device and fill the various members of ig_device_t.
*/
static int
{
const char *fident;
return (BC_ERROR);
}
"disk is no longer supported\n"));
return (BC_ERROR);
}
/* Detect if the target device is a pcfs partition. */
return (BC_ERROR);
/* read in the device boot sector. */
!= SECTOR_SIZE) {
perror("read");
return (BC_ERROR);
}
}
return (BC_ERROR);
return (BC_ERROR);
"disks is only supported with ZFS\n"));
return (BC_ERROR);
}
}
return (BC_ERROR);
return (BC_SUCCESS);
}
static void
{
}
static void
{
}
static int
{
struct extpart_info edkpi;
return (BC_ERROR);
/* GPT doesn't use traditional slice letters */
goto found_part;
}
return (BC_ERROR);
} else {
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.
*/
return (BC_ERROR);
} else {
}
}
for (i = 0; i < FD_NUMPART; i++) {
return (BC_ERROR);
}
/* Found the partition */
break;
}
}
if (i == FD_NUMPART) {
/* No solaris fdisk partitions (primary or logical) */
return (BC_ERROR);
}
/*
* We have found a Solaris fdisk partition (primary or extended)
* Handle the simple case first: Solaris in a primary partition
*/
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:
return (BC_ERROR);
case FDISK_ENOVGEOM:
return (BC_ERROR);
case FDISK_ENOPGEOM:
return (BC_ERROR);
case FDISK_ENOLGEOM:
return (BC_ERROR);
default:
return (BC_ERROR);
}
}
libfdisk_fini(&epp);
if (rval != FDISK_SUCCESS) {
/* No solaris logical partition */
return (BC_ERROR);
}
log_part = 1;
/* get confirmation for -m */
if (!yes()) {
write_mbr = 0;
return (BC_ERROR);
}
}
/*
* Currently if Solaris is in an extended partition we need to
* write GRUB to the MBR. Check for this.
*/
"extended partition... forcing MBR update\n"));
write_mbr = 1;
}
/*
* warn, if Solaris in primary partition and GRUB not in MBR and
* partition is not active
*/
}
return (BC_SUCCESS);
}
static int
{
int i = 0;
/* tested at the start of init_device() */
/* chop off :boot */
} else {
}
if (nowrite)
else
perror("strdup");
return (BC_ERROR);
}
} else {
}
perror("open");
return (BC_ERROR);
}
return (BC_SUCCESS);
}
static void
{
/*
* Currently we expect find_multiboot() to have located a multiboot
* header with the AOUT kludge flag set.
*/
/* Insert the information necessary to locate stage2. */
}
static void
{
/* Fill bootblock hashing source information. */
/* How much space for the extended information structure? */
}
static int
{
/*
* stage2 is already on the filesystem, we only need to update
* the first two blocks (that we have modified during
* prepare_stage2())
*/
!= BC_SUCCESS ||
!= BC_SUCCESS) {
return (BC_ERROR);
}
return (BC_SUCCESS);
}
/*
* For disk, write stage2 starting at STAGE2_BLKOFF sector.
* Note that we use stage2->buf rather than stage2->file, because we
* may have extended information after the latter.
*
* If we're writing to an EFI-labeled disk where stage2 lives in the
* 3.5MB boot loader gap following the ZFS vdev labels, make sure the
* size of the buffer doesn't exceed the size of the gap.
*/
return (BC_ERROR);
}
offset) != BC_SUCCESS) {
perror("write");
return (BC_ERROR);
}
/* Simulate the "old" installgrub output. */
return (BC_SUCCESS);
}
static int
{
perror("write");
return (BC_ERROR);
}
/* Simulate "old" installgrub output. */
if (write_mbr) {
perror("write");
return (BC_ERROR);
}
/* Simulate "old" installgrub output. */
}
return (BC_SUCCESS);
}
#define USAGE_STRING "%s [-m|-f|-n|-F|-u verstr] stage1 stage2 device\n" \
"%s -M [-n] device1 device2\n" \
"%s [-V|-e] -i device\n" \
static void
{
}
static int
{
int fd;
/* read the stage1 file from filesystem */
if (fd == -1 ||
return (BC_ERROR);
}
return (BC_SUCCESS);
}
static int
{
int fd;
perror("fstat");
goto out;
}
/*
* buffer size needs to account for stage2 plus the extra
* versioning information at the end of it. We reserve one
* extra sector (plus we round up to the next sector boundary).
*/
} else {
/* In the PCFS case we only need to read in stage2. */
}
goto out_fd;
}
/*
* Extra information (e.g. the versioning structure) is placed at the
* end of stage2, aligned on a 8-byte boundary.
*/
perror("lseek");
goto out_alloc;
}
goto out_alloc;
}
return (BC_SUCCESS);
out:
return (BC_ERROR);
}
static int
{
/* If PCFS add the BIOS Parameter Block. */
char bpb_sect[SECTOR_SIZE];
!= SECTOR_SIZE) {
return (BC_ERROR);
}
}
/* copy MBR to stage1 in case of overwriting MBR sector. */
SECTOR_SIZE - BOOTSZ);
/* modify default stage1 file generated by GRUB. */
= STAGE2_MEMADDR >> 4;
return (BC_SUCCESS);
}
/*
* Grab stage1 from the specified device file descriptor.
*/
static int
{
return (BC_ERROR);
}
return (BC_SUCCESS);
}
static int
{
return (BC_ERROR);
}
/* No multiboot means no chance of knowing stage2 size */
!= BC_SUCCESS) {
BOOT_DEBUG("Unable to find multiboot header\n");
return (BC_NOEXTRA);
}
/*
* Unfilled mboot values mean an older version of installgrub installed
* the stage2. Again we have no chance of knowing stage2 size.
*/
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.
*/
return (BC_ERROR);
}
SECTOR_SIZE) != BC_SUCCESS) {
perror("read");
return (BC_ERROR);
}
/* Update pointers. */
return (BC_SUCCESS);
}
static boolean_t
{
/* Gather stage2 (if present) from the target device. */
!= BC_SUCCESS) {
return (B_TRUE);
}
/*
* Look for the extended information structure in the extra payload
* area.
*/
BOOT_DEBUG("No extended information available\n");
return (B_TRUE);
}
"versioned stage2 that is going to be overwritten by a non "
return (B_TRUE);
}
if (force_update) {
return (B_TRUE);
}
/* Compare the two extended information structures. */
}
static int
{
/* New stage2 files come with an embedded stage2. */
!= BC_SUCCESS) {
BOOT_DEBUG("WARNING: no multiboot structure found in stage2, "
"are you using an old GRUB stage2?\n");
if (do_version == B_TRUE) {
"but stage2 does not support it.. skipping.\n"));
}
} else {
/* Keep track of where the multiboot header is. */
if (do_version) {
/*
* Adding stage2 information needs to happen before
* we modify the copy of stage2 we have in memory, so
* that the hashing reflects the one of the file.
* An error here is not fatal.
*/
}
/*
* Fill multiboot information. We add them even without
* versioning to support as much as possible mirroring.
*/
}
int i = 0;
"stage2 blocklist\n"));
return (BC_ERROR);
}
blocklist[0]++;
blocklist[1]--;
} else {
i += 2;
}
while (blocklist[i]) {
return (BC_ERROR);
}
pos -= 8;
i += 2;
}
} else {
/* Solaris VTOC & EFI */
if (device->start_sector >
"must be less than %lld\n"),
return (BC_ERROR);
}
/*
* In a solaris partition, stage2 is written to contiguous
* blocks. So we update the starting block only.
*/
}
/* force lba and set disk partition */
return (BC_SUCCESS);
}
static int
{
int i;
for (i = 0; i < FD_NUMPART; i++) {
if (start_sect)
if (part_num)
*part_num = i;
/* solaris boot part */
return (BC_SUCCESS);
}
}
return (BC_ERROR);
}
static char *
{
char *raw;
int len;
int part;
return (NULL);
}
return (NULL);
}
return (raw);
}
/* For disk, remember slice and return whole fdisk partition */
return (NULL);
}
return (NULL);
}
}
return (raw);
}
static int
{
char *raw;
return (BC_ERROR);
if (nowrite)
else
return (BC_ERROR);
}
return (BC_ERROR);
}
return (BC_SUCCESS);
}