/* * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012 Milan Jurik. All rights reserved. * Copyright 2016 Toomas Soome * Copyright 2016 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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. */ 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; /* Installing the bootblock is the default operation. */ static boolean_t do_install = B_TRUE; /* 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 int init_device(ig_device_t *, char *path); 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_disk_fd(ig_device_t *device); static int get_raw_partition_fd(ig_device_t *); static char *get_raw_partition_path(ig_device_t *); static int propagate_bootblock(ig_data_t *, ig_data_t *, char *); static int find_x86_bootpar(struct mboot *, int *, uint32_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 *); static void add_stage2_einfo(ig_stage2_t *, char *updt_str); static boolean_t is_update_necessary(ig_data_t *, char *); extern int read_stage2_blocklist(int, unsigned int *); int main(int argc, char *argv[]) { int opt; int params = 3; int ret; char **handle_args; char *progname; (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); } /* * retro-compatibility: installing the bootblock is the default * and there is no switch for it. */ do_install = B_TRUE; while ((opt = getopt(argc, argv, "dVMFfmneiu:")) != EOF) { switch (opt) { case 'm': write_mbr = B_TRUE; break; case 'n': nowrite = B_TRUE; break; case 'f': force_mbr = B_TRUE; break; case 'i': do_getinfo = B_TRUE; do_install = B_FALSE; params = 1; break; case 'V': verbose_dump = B_TRUE; break; case 'd': boot_debug = B_TRUE; break; case 'F': force_update = B_TRUE; break; case 'e': strip = B_TRUE; break; case 'M': do_mirror_bblk = B_TRUE; do_install = B_FALSE; params = 2; break; case 'u': do_version = B_TRUE; update_str = malloc(strlen(optarg) + 1); if (update_str == NULL) { (void) fprintf(stderr, gettext("Unable to " "allocate memory\n")); exit(BC_ERROR); } (void) strlcpy(update_str, optarg, strlen(optarg) + 1); break; default: /* fall through to process non-optional args */ break; } } /* check arguments */ if (argc != optind + params) { usage(argv[0]); exit(BC_ERROR); } /* * clean up options (and bail out if an unrecoverable combination is * requested. */ progname = argv[0]; check_options(progname); handle_args = argv + optind; if (nowrite) (void) fprintf(stdout, DRY_RUN); 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; } } } /* * Install a new stage1/stage2 pair on the specified device. handle_install() * 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 handle_install(char *progname, char **argv) { ig_data_t install_data; char *stage1_path = NULL; char *stage2_path = NULL; char *device_path = NULL; int ret = BC_ERROR; stage1_path = strdup(argv[0]); stage2_path = strdup(argv[1]); device_path = strdup(argv[2]); bzero(&install_data, sizeof (ig_data_t)); if (!stage1_path || !stage2_path || !device_path) { (void) fprintf(stderr, gettext("Missing parameter")); usage(progname); goto out; } BOOT_DEBUG("stage1 path: %s, stage2 path: %s, device: %s\n", stage1_path, stage2_path, device_path); if (init_device(&install_data.device, device_path) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Unable to gather device " "information for %s\n"), device_path); goto out; } /* read in stage1 and stage2. */ if (read_stage1_from_file(stage1_path, &install_data) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error opening %s\n"), stage1_path); goto out_dev; } if (read_stage2_from_file(stage2_path, &install_data) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error opening %s\n"), stage2_path); goto out_dev; } /* We do not support versioning on PCFS. */ if (is_bootpar(install_data.device.type) && do_version) do_version = B_FALSE; /* * 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("GRUB 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; } /* * 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"); ret = commit_to_disk(&install_data, update_str); out_dev: cleanup_device(&install_data.device); out: free(stage1_path); free(stage2_path); 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) { ig_data_t data; ig_stage2_t *stage2 = &data.stage2; ig_device_t *device = &data.device; bblk_einfo_t *einfo; uint8_t flags = 0; uint32_t size; char *device_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 (ig_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 for %s\n"), device_path); goto out_dev; } if (is_bootpar(device->type)) { (void) fprintf(stderr, gettext("Versioning not supported on " "PCFS\n")); goto out_dev; } ret = read_stage2_from_disk(device->part_fd, stage2, device->type); if (ret == BC_ERROR) { (void) fprintf(stderr, gettext("Error reading stage2 from " "%s\n"), device_path); goto out_dev; } if (ret == BC_NOEXTRA) { (void) fprintf(stdout, gettext("No multiboot header found on " "%s, unable to locate extra information area\n"), device_path); retval = BC_NOEINFO; goto out_dev; } einfo = find_einfo(stage2->extra, stage2->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; size = stage2->buf_size - P2ROUNDUP(stage2->file_size, 8); print_einfo(flags, einfo, size); retval = BC_SUCCESS; out_dev: cleanup_device(&data.device); out: free(device_path); 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 handle_mirror(char *progname, char **argv) { ig_data_t curr_data; ig_data_t attach_data; ig_device_t *curr_device = &curr_data.device; ig_device_t *attach_device = &attach_data.device; ig_stage2_t *stage2_curr = &curr_data.stage2; ig_stage2_t *stage2_attach = &attach_data.stage2; bblk_einfo_t *einfo_curr = NULL; char *curr_device_path; char *attach_device_path; char *updt_str = NULL; 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 (ig_data_t)); bzero(&attach_data, sizeof (ig_data_t)); if (init_device(curr_device, curr_device_path) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Unable to gather device " "information for %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 for %s (attaching device)\n"), attach_device_path); goto out_devs; } if (is_bootpar(curr_device->type) || is_bootpar(attach_device->type)) { (void) fprintf(stderr, gettext("boot block mirroring is not " "supported on PCFS\n")); goto out_devs; } ret = read_stage2_from_disk(curr_device->part_fd, stage2_curr, curr_device->type); if (ret == BC_ERROR) { BOOT_DEBUG("Error reading first stage2 blocks from %s\n", curr_device->path); retval = BC_ERROR; goto out_devs; } if (ret == BC_NOEXTRA) { BOOT_DEBUG("No multiboot header found on %s, unable to grab " "stage2\n", curr_device->path); retval = BC_NOEXTRA; goto out_devs; } einfo_curr = find_einfo(stage2_curr->extra, stage2_curr->extra_size); if (einfo_curr != NULL) updt_str = einfo_get_string(einfo_curr); write_mbr = B_TRUE; force_mbr = B_TRUE; retval = propagate_bootblock(&curr_data, &attach_data, updt_str); cleanup_stage2(stage2_curr); cleanup_stage2(stage2_attach); out_devs: cleanup_device(attach_device); out_currdev: cleanup_device(curr_device); out: free(curr_device_path); free(attach_device_path); return (retval); } static int commit_to_disk(ig_data_t *install, char *updt_str) { assert(install != NULL); /* * 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. */ if (prepare_stage2(install, updt_str) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error building stage2\n")); return (BC_ERROR); } if (prepare_stage1(install) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error building stage1\n")); return (BC_ERROR); } /* Write stage2 out to disk. */ if (write_stage2(install) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error writing stage2 to " "disk\n")); return (BC_ERROR); } /* Write stage1 to disk and, if requested, to the MBR. */ if (write_stage1(install) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error writing stage1 to " "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 propagate_bootblock(ig_data_t *source, ig_data_t *target, char *updt_str) { ig_device_t *src_device = &source->device; ig_device_t *dest_device = &target->device; ig_stage2_t *src_stage2 = &source->stage2; ig_stage2_t *dest_stage2 = &target->stage2; uint32_t buf_size; int retval; assert(source != NULL); assert(target != NULL); /* read in stage1 from the source disk. */ if (read_stage1_from_disk(src_device->part_fd, target->stage1_buf) != BC_SUCCESS) return (BC_ERROR); /* Prepare target stage2 for commit_to_disk. */ cleanup_stage2(dest_stage2); if (updt_str != NULL) do_version = B_TRUE; else do_version = B_FALSE; buf_size = src_stage2->file_size + SECTOR_SIZE; dest_stage2->buf_size = P2ROUNDUP(buf_size, SECTOR_SIZE); dest_stage2->buf = malloc(dest_stage2->buf_size); if (dest_stage2->buf == NULL) { perror(gettext("Memory allocation failed")); return (BC_ERROR); } dest_stage2->file = dest_stage2->buf; dest_stage2->file_size = src_stage2->file_size; memcpy(dest_stage2->file, src_stage2->file, dest_stage2->file_size); dest_stage2->extra = dest_stage2->buf + P2ROUNDUP(dest_stage2->file_size, 8); /* If we get down here we do have a mboot structure. */ assert(src_stage2->mboot); dest_stage2->mboot_off = src_stage2->mboot_off; dest_stage2->mboot = (multiboot_header_t *)(dest_stage2->buf + dest_stage2->mboot_off); (void) fprintf(stdout, gettext("Propagating %s stage1/stage2 to %s\n"), src_device->path, dest_device->path); retval = commit_to_disk(target, updt_str); return (retval); } /* * open the device and fill the various members of ig_device_t. */ static int init_device(ig_device_t *device, char *path) { struct dk_gpt *vtoc; fstyp_handle_t fhdl; const char *fident; bzero(device, sizeof (*device)); device->part_fd = -1; device->disk_fd = -1; device->path_p0 = NULL; device->path = strdup(path); if (device->path == NULL) { perror(gettext("Memory allocation failed")); return (BC_ERROR); } if (strstr(device->path, "diskette")) { (void) fprintf(stderr, gettext("installing GRUB to a floppy " "disk is no longer supported\n")); return (BC_ERROR); } /* Detect if the target device is a pcfs partition. */ if (strstr(device->path, "p0:boot")) device->type = IG_DEV_X86BOOTPAR; if (get_disk_fd(device) != BC_SUCCESS) return (BC_ERROR); /* read in the device boot sector. */ if (read(device->disk_fd, device->boot_sector, SECTOR_SIZE) != SECTOR_SIZE) { (void) fprintf(stderr, gettext("Error reading boot sector\n")); perror("read"); return (BC_ERROR); } if (efi_alloc_and_read(device->disk_fd, &vtoc) >= 0) { device->type = IG_DEV_EFI; efi_free(vtoc); } if (get_raw_partition_fd(device) != BC_SUCCESS) return (BC_ERROR); if (is_efi(device->type)) { if (fstyp_init(device->part_fd, 0, NULL, &fhdl) != 0) return (BC_ERROR); if (fstyp_ident(fhdl, "zfs", &fident) != 0) { fstyp_fini(fhdl); (void) fprintf(stderr, gettext("Booting of EFI labeled " "disks is only supported with ZFS\n")); return (BC_ERROR); } fstyp_fini(fhdl); } if (get_start_sector(device) != BC_SUCCESS) return (BC_ERROR); return (BC_SUCCESS); } static void cleanup_device(ig_device_t *device) { if (device->path) free(device->path); if (device->path_p0) free(device->path_p0); if (device->part_fd != -1) (void) close(device->part_fd); if (device->disk_fd != -1) (void) close(device->disk_fd); bzero(device, sizeof (ig_device_t)); device->part_fd = -1; device->disk_fd = -1; } static void cleanup_stage2(ig_stage2_t *stage2) { if (stage2->buf) free(stage2->buf); bzero(stage2, sizeof (ig_stage2_t)); } static int get_start_sector(ig_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 (is_efi(device->type)) { struct dk_gpt *vtoc; if (efi_alloc_and_read(device->disk_fd, &vtoc) < 0) return (BC_ERROR); device->start_sector = vtoc->efi_parts[device->slice].p_start; /* GPT doesn't use traditional slice letters */ device->partition = device->slice; device->slice = 0xff; efi_free(vtoc); goto found_part; } mboot = (struct mboot *)device->boot_sector; if (is_bootpar(device->type)) { if (find_x86_bootpar(mboot, &pno, &secnum) != BC_SUCCESS) { (void) fprintf(stderr, NOBOOTPAR); return (BC_ERROR); } else { device->start_sector = secnum; device->partition = pno; 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->part_fd, DKIOCEXTPARTINFO, &edkpi) < 0) { if (ioctl(device->part_fd, DKIOCPARTINFO, &dkpi) < 0) { (void) fprintf(stderr, PART_FAIL); return (BC_ERROR); } else { edkpi.p_start = dkpi.p_start; } } for (i = 0; i < FD_NUMPART; i++) { part = (struct ipart *)mboot->parts + i; if (part->relsect == 0) { (void) fprintf(stderr, BAD_PART, i); 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, NOSOLPAR); 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->start_sector = part->relsect; device->partition = i; goto found_part; } /* * Solaris in a logical partition. Find that partition in the * extended part. */ if ((rval = libfdisk_init(&epp, device->path_p0, 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, NOSOLPAR); return (BC_ERROR); case FDISK_ENOVGEOM: (void) fprintf(stderr, NO_VIRT_GEOM); return (BC_ERROR); case FDISK_ENOPGEOM: (void) fprintf(stderr, NO_PHYS_GEOM); return (BC_ERROR); case FDISK_ENOLGEOM: (void) fprintf(stderr, NO_LABEL_GEOM); return (BC_ERROR); default: (void) fprintf(stderr, LIBFDISK_INIT_FAIL); 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, NOSOLPAR); return (BC_ERROR); } device->start_sector = secnum; device->partition = pno - 1; log_part = 1; found_part: /* get confirmation for -m */ if (write_mbr && !force_mbr) { (void) fprintf(stdout, MBOOT_PROMPT); if (!yes()) { write_mbr = 0; (void) fprintf(stdout, MBOOT_NOT_UPDATED); return (BC_ERROR); } } /* * Currently if Solaris is in an extended partition we need to * write GRUB to the MBR. Check for this. */ if (log_part && !write_mbr) { (void) fprintf(stdout, gettext("Installing Solaris on an " "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 */ if (part != NULL) { if (!log_part && part->bootid != 128 && !write_mbr) { (void) fprintf(stdout, SOLPAR_INACTIVE, device->partition + 1); } } return (BC_SUCCESS); } static int get_disk_fd(ig_device_t *device) { int i = 0; char save[2] = { '\0', '\0' }; char *end = NULL; assert(device != NULL); assert(device->path != NULL); if (is_bootpar(device->type)) { end = strstr(device->path, "p0:boot"); /* tested at the start of init_device() */ assert(end != NULL); /* chop off :boot */ save[0] = end[2]; end[2] = '\0'; } else { i = strlen(device->path); save[0] = device->path[i - 2]; save[1] = device->path[i - 1]; device->path[i - 2] = 'p'; device->path[i - 1] = '0'; } if (nowrite) device->disk_fd = open(device->path, O_RDONLY); else device->disk_fd = open(device->path, O_RDWR); device->path_p0 = strdup(device->path); if (device->path_p0 == NULL) { perror("strdup"); return (BC_ERROR); } if (is_bootpar(device->type)) { end[2] = save[0]; } else { device->path[i - 2] = save[0]; device->path[i - 1] = save[1]; } if (device->disk_fd == -1) { perror("open"); return (BC_ERROR); } return (BC_SUCCESS); } static void prepare_fake_multiboot(ig_stage2_t *stage2) { multiboot_header_t *mboot; assert(stage2 != NULL); assert(stage2->mboot != NULL); assert(stage2->buf != NULL); mboot = stage2->mboot; /* * Currently we expect find_multiboot() to have located a multiboot * header with the AOUT kludge flag set. */ assert(mboot->flags & BB_MBOOT_AOUT_FLAG); /* Insert the information necessary to locate stage2. */ mboot->header_addr = stage2->mboot_off; mboot->load_addr = 0; mboot->load_end_addr = stage2->file_size; } static void add_stage2_einfo(ig_stage2_t *stage2, char *updt_str) { bblk_hs_t hs; uint32_t avail_space; assert(stage2 != NULL); /* Fill bootblock hashing source information. */ hs.src_buf = (unsigned char *)stage2->file; hs.src_size = stage2->file_size; /* How much space for the extended information structure? */ avail_space = stage2->buf_size - P2ROUNDUP(stage2->file_size, 8); add_einfo(stage2->extra, updt_str, &hs, avail_space); } static int write_stage2(ig_data_t *install) { ig_device_t *device = &install->device; ig_stage2_t *stage2 = &install->stage2; off_t offset; assert(install != NULL); if (is_bootpar(device->type)) { /* * stage2 is already on the filesystem, we only need to update * the first two blocks (that we have modified during * prepare_stage2()) */ if (write_out(device->part_fd, stage2->file, SECTOR_SIZE, stage2->pcfs_first_sectors[0] * SECTOR_SIZE) != BC_SUCCESS || write_out(device->part_fd, stage2->file + SECTOR_SIZE, SECTOR_SIZE, stage2->pcfs_first_sectors[1] * SECTOR_SIZE) != BC_SUCCESS) { (void) fprintf(stderr, WRITE_FAIL_STAGE2); return (BC_ERROR); } (void) fprintf(stdout, WRITE_STAGE2_PCFS); 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. */ if (is_efi(device->type) && stage2->buf_size > STAGE2_MAXSIZE) { (void) fprintf(stderr, WRITE_FAIL_STAGE2); return (BC_ERROR); } offset = STAGE2_BLKOFF(device->type) * SECTOR_SIZE; if (write_out(device->part_fd, stage2->buf, stage2->buf_size, offset) != BC_SUCCESS) { perror("write"); return (BC_ERROR); } /* Simulate the "old" installgrub output. */ (void) fprintf(stdout, WRITE_STAGE2_DISK, device->partition, (stage2->buf_size / SECTOR_SIZE) + 1, STAGE2_BLKOFF(device->type), stage2->first_sector); return (BC_SUCCESS); } static int write_stage1(ig_data_t *install) { ig_device_t *device = &install->device; assert(install != NULL); if (write_out(device->part_fd, install->stage1_buf, sizeof (install->stage1_buf), 0) != BC_SUCCESS) { (void) fprintf(stdout, WRITE_FAIL_PBOOT); perror("write"); return (BC_ERROR); } /* Simulate "old" installgrub output. */ (void) fprintf(stdout, WRITE_PBOOT, device->partition, device->start_sector); if (write_mbr) { if (write_out(device->disk_fd, install->stage1_buf, sizeof (install->stage1_buf), 0) != BC_SUCCESS) { (void) fprintf(stdout, WRITE_FAIL_BOOTSEC); perror("write"); return (BC_ERROR); } /* Simulate "old" installgrub output. */ (void) fprintf(stdout, WRITE_MBOOT); } 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" \ #define CANON_USAGE_STR gettext(USAGE_STRING) static void usage(char *progname) { (void) fprintf(stdout, CANON_USAGE_STR, progname, progname, progname); } static int read_stage1_from_file(char *path, ig_data_t *dest) { int fd; assert(dest); /* read the stage1 file from filesystem */ fd = open(path, O_RDONLY); if (fd == -1 || read(fd, dest->stage1_buf, SECTOR_SIZE) != SECTOR_SIZE) { (void) fprintf(stderr, READ_FAIL_STAGE1, path); return (BC_ERROR); } (void) close(fd); return (BC_SUCCESS); } static int read_stage2_from_file(char *path, ig_data_t *dest) { int fd; struct stat sb; ig_stage2_t *stage2 = &dest->stage2; ig_device_t *device = &dest->device; uint32_t buf_size; assert(dest); assert(stage2->buf == NULL); fd = open(path, O_RDONLY); if (fstat(fd, &sb) == -1) { perror("fstat"); goto out; } stage2->file_size = sb.st_size; if (!is_bootpar(device->type)) { /* * 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). */ buf_size = stage2->file_size + SECTOR_SIZE; } else { /* In the PCFS case we only need to read in stage2. */ buf_size = stage2->file_size; } stage2->buf_size = P2ROUNDUP(buf_size, SECTOR_SIZE); BOOT_DEBUG("stage2 buffer size = %d (%d sectors)\n", stage2->buf_size, stage2->buf_size / SECTOR_SIZE); stage2->buf = malloc(stage2->buf_size); if (stage2->buf == NULL) { perror(gettext("Memory allocation failed")); goto out_fd; } stage2->file = stage2->buf; /* * Extra information (e.g. the versioning structure) is placed at the * end of stage2, aligned on a 8-byte boundary. */ if (!(is_bootpar(device->type))) stage2->extra = stage2->file + P2ROUNDUP(stage2->file_size, 8); if (lseek(fd, 0, SEEK_SET) == -1) { perror("lseek"); goto out_alloc; } if (read(fd, stage2->file, stage2->file_size) < 0) { perror(gettext("unable to read stage2")); goto out_alloc; } (void) close(fd); return (BC_SUCCESS); out_alloc: free(stage2->buf); stage2->buf = NULL; out_fd: (void) close(fd); out: return (BC_ERROR); } static int prepare_stage1(ig_data_t *install) { ig_device_t *device = &install->device; assert(install != NULL); /* If PCFS add the BIOS Parameter Block. */ if (is_bootpar(device->type)) { char bpb_sect[SECTOR_SIZE]; if (pread(device->part_fd, bpb_sect, SECTOR_SIZE, 0) != SECTOR_SIZE) { (void) fprintf(stderr, READ_FAIL_BPB); return (BC_ERROR); } bcopy(bpb_sect + STAGE1_BPB_OFFSET, install->stage1_buf + STAGE1_BPB_OFFSET, STAGE1_BPB_SIZE); } /* copy MBR to stage1 in case of overwriting MBR sector. */ bcopy(device->boot_sector + BOOTSZ, install->stage1_buf + BOOTSZ, SECTOR_SIZE - BOOTSZ); /* modify default stage1 file generated by GRUB. */ *((unsigned char *)(install->stage1_buf + STAGE1_FORCE_LBA)) = 1; *((ulong_t *)(install->stage1_buf + STAGE1_STAGE2_SECTOR)) = install->stage2.first_sector; *((ushort_t *)(install->stage1_buf + STAGE1_STAGE2_ADDRESS)) = STAGE2_MEMADDR; *((ushort_t *)(install->stage1_buf + STAGE1_STAGE2_SEGMENT)) = STAGE2_MEMADDR >> 4; return (BC_SUCCESS); } /* * Grab stage1 from the specified device file descriptor. */ static int read_stage1_from_disk(int dev_fd, char *stage1_buf) { assert(stage1_buf != NULL); if (read_in(dev_fd, stage1_buf, SECTOR_SIZE, 0) != BC_SUCCESS) { perror(gettext("Unable to read stage1 from disk")); return (BC_ERROR); } return (BC_SUCCESS); } static int read_stage2_from_disk(int dev_fd, ig_stage2_t *stage2, int type) { uint32_t size; uint32_t buf_size; uint32_t mboot_off; multiboot_header_t *mboot; assert(stage2 != NULL); assert(dev_fd != -1); if (read_in(dev_fd, mboot_scan, sizeof (mboot_scan), STAGE2_BLKOFF(type) * SECTOR_SIZE) != BC_SUCCESS) { perror(gettext("Error reading stage2 sectors")); return (BC_ERROR); } /* No multiboot means no chance of knowing stage2 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); /* * 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 || 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); stage2->buf = malloc(buf_size); if (stage2->buf == NULL) { perror(gettext("Memory allocation failed")); return (BC_ERROR); } stage2->buf_size = buf_size; if (read_in(dev_fd, stage2->buf, buf_size, STAGE2_BLKOFF(type) * SECTOR_SIZE) != BC_SUCCESS) { perror("read"); free(stage2->buf); return (BC_ERROR); } /* Update pointers. */ stage2->file = stage2->buf; stage2->file_size = size; stage2->mboot_off = mboot_off; stage2->mboot = (multiboot_header_t *)(stage2->buf + stage2->mboot_off); stage2->extra = stage2->buf + P2ROUNDUP(stage2->file_size, 8); stage2->extra_size = stage2->buf_size - P2ROUNDUP(stage2->file_size, 8); return (BC_SUCCESS); } static boolean_t is_update_necessary(ig_data_t *data, char *updt_str) { bblk_einfo_t *einfo; bblk_hs_t stage2_hs; ig_stage2_t stage2_disk; ig_stage2_t *stage2_file = &data->stage2; ig_device_t *device = &data->device; int dev_fd = device->part_fd; assert(data != NULL); assert(device->part_fd != -1); bzero(&stage2_disk, sizeof (ig_stage2_t)); /* Gather stage2 (if present) from the target device. */ if (read_stage2_from_disk(dev_fd, &stage2_disk, device->type) != BC_SUCCESS) { BOOT_DEBUG("Unable to read stage2 from %s\n", device->path); BOOT_DEBUG("No multiboot wrapped stage2 on %s\n", device->path); return (B_TRUE); } /* * Look for the extended information structure in the extra payload * area. */ einfo = find_einfo(stage2_disk.extra, stage2_disk.extra_size); if (einfo == NULL) { BOOT_DEBUG("No extended information available\n"); return (B_TRUE); } if (!do_version || updt_str == NULL) { (void) fprintf(stdout, "WARNING: target device %s has a " "versioned stage2 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); } /* Compare the two extended information structures. */ stage2_hs.src_buf = (unsigned char *)stage2_file->file; stage2_hs.src_size = stage2_file->file_size; return (einfo_should_update(einfo, &stage2_hs, updt_str)); } #define START_BLOCK(pos) (*(ulong_t *)(pos)) #define NUM_BLOCK(pos) (*(ushort_t *)((pos) + 4)) #define START_SEG(pos) (*(ushort_t *)((pos) + 6)) static int prepare_stage2(ig_data_t *install, char *updt_str) { ig_device_t *device = &install->device; ig_stage2_t *stage2 = &install->stage2; uint32_t mboot_off = 0; assert(install != NULL); assert(stage2->file != NULL); /* New stage2 files come with an embedded stage2. */ if (find_multiboot(stage2->file, stage2->file_size, &mboot_off) != BC_SUCCESS) { BOOT_DEBUG("WARNING: no multiboot structure found in stage2, " "are you using an old GRUB stage2?\n"); if (do_version == B_TRUE) { (void) fprintf(stderr, gettext("Versioning requested " "but stage2 does not support it.. skipping.\n")); do_version = B_FALSE; } } else { /* Keep track of where the multiboot header is. */ stage2->mboot_off = mboot_off; stage2->mboot = (multiboot_header_t *)(stage2->file + mboot_off); 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. */ add_stage2_einfo(stage2, updt_str); } /* * Fill multiboot information. We add them even without * versioning to support as much as possible mirroring. */ prepare_fake_multiboot(stage2); } if (is_bootpar(device->type)) { uint32_t blocklist[SECTOR_SIZE / sizeof (uint32_t)]; uint32_t install_addr = STAGE2_MEMADDR + SECTOR_SIZE; int i = 0; uchar_t *pos; bzero(blocklist, sizeof (blocklist)); if (read_stage2_blocklist(device->part_fd, blocklist) != 0) { (void) fprintf(stderr, gettext("Error reading pcfs " "stage2 blocklist\n")); return (BC_ERROR); } pos = (uchar_t *)stage2->file + STAGE2_BLOCKLIST; stage2->first_sector = device->start_sector + blocklist[0]; stage2->pcfs_first_sectors[0] = blocklist[0]; BOOT_DEBUG("stage2 first sector: %d\n", stage2->first_sector); if (blocklist[1] > 1) { blocklist[0]++; blocklist[1]--; } else { i += 2; } stage2->pcfs_first_sectors[1] = blocklist[i]; while (blocklist[i]) { if (START_BLOCK(pos - 8) != 0 && START_BLOCK(pos - 8) != blocklist[i + 2]) { (void) fprintf(stderr, PCFS_FRAGMENTED); return (BC_ERROR); } START_BLOCK(pos) = blocklist[i] + device->start_sector; START_SEG(pos) = (ushort_t)(install_addr >> 4); NUM_BLOCK(pos) = blocklist[i + 1]; install_addr += blocklist[i + 1] * SECTOR_SIZE; pos -= 8; i += 2; } } else { /* Solaris VTOC & EFI */ if (device->start_sector > UINT32_MAX - STAGE2_BLKOFF(device->type)) { fprintf(stderr, gettext("Error: partition start sector " "must be less than %lld\n"), (uint64_t)UINT32_MAX - STAGE2_BLKOFF(device->type)); return (BC_ERROR); } stage2->first_sector = device->start_sector + STAGE2_BLKOFF(device->type); BOOT_DEBUG("stage2 first sector: %d\n", stage2->first_sector); /* * In a solaris partition, stage2 is written to contiguous * blocks. So we update the starting block only. */ *((ulong_t *)(stage2->file + STAGE2_BLOCKLIST)) = stage2->first_sector + 1; } /* force lba and set disk partition */ *((unsigned char *) (stage2->file + STAGE2_FORCE_LBA)) = 1; *((long *)(stage2->file + STAGE2_INSTALLPART)) = (device->partition << 16) | (device->slice << 8) | 0xff; return (BC_SUCCESS); } static int find_x86_bootpar(struct mboot *mboot, int *part_num, uint32_t *start_sect) { int i; for (i = 0; i < FD_NUMPART; i++) { struct ipart *part; part = (struct ipart *)mboot->parts + i; if (part->systid == 0xbe) { if (start_sect) *start_sect = part->relsect; if (part_num) *part_num = i; /* solaris boot part */ return (BC_SUCCESS); } } return (BC_ERROR); } static char * get_raw_partition_path(ig_device_t *device) { char *raw; int len; if (is_bootpar(device->type)) { int part; struct mboot *mboot; mboot = (struct mboot *)device->boot_sector; if (find_x86_bootpar(mboot, &part, NULL) != BC_SUCCESS) { (void) fprintf(stderr, BOOTPAR_NOTFOUND, device->path_p0); return (NULL); } raw = strdup(device->path_p0); if (raw == NULL) { perror(gettext("Memory allocation failed")); return (NULL); } raw[strlen(raw) - 2] = '1' + part; return (raw); } /* For disk, remember slice and return whole fdisk partition */ raw = strdup(device->path); if (raw == NULL) { perror(gettext("Memory allocation failed")); return (NULL); } len = strlen(raw); if (!is_efi(device->type) && (raw[len - 2] != 's' || raw[len - 1] == '2')) { (void) fprintf(stderr, NOT_ROOT_SLICE); free(raw); return (NULL); } device->slice = atoi(&raw[len - 1]); if (!is_efi(device->type)) { raw[len - 2] = 's'; raw[len - 1] = '2'; } return (raw); } static int get_raw_partition_fd(ig_device_t *device) { struct stat stat = {0}; char *raw; raw = get_raw_partition_path(device); if (raw == NULL) return (BC_ERROR); if (nowrite) device->part_fd = open(raw, O_RDONLY); else device->part_fd = open(raw, O_RDWR); if (device->part_fd < 0 || fstat(device->part_fd, &stat) != 0) { (void) fprintf(stderr, OPEN_FAIL, raw); free(raw); return (BC_ERROR); } if (S_ISCHR(stat.st_mode) == 0) { (void) fprintf(stderr, NOT_RAW_DEVICE, raw); (void) close(device->part_fd); device->part_fd = -1; free(raw); return (BC_ERROR); } free(raw); return (BC_SUCCESS); }