diff options
Diffstat (limited to 'usr/src/cmd/boot/installgrub/installgrub.c')
-rw-r--r-- | usr/src/cmd/boot/installgrub/installgrub.c | 1742 |
1 files changed, 1178 insertions, 564 deletions
diff --git a/usr/src/cmd/boot/installgrub/installgrub.c b/usr/src/cmd/boot/installgrub/installgrub.c index f1beaa473b..452a188d9c 100644 --- a/usr/src/cmd/boot/installgrub/installgrub.c +++ b/usr/src/cmd/boot/installgrub/installgrub.c @@ -19,8 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2010 Sun Microsystems, Inc. All rights reserved. - * Use is subject to license terms. + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. */ #include <stdio.h> @@ -28,122 +27,154 @@ #include <libgen.h> #include <malloc.h> #include <string.h> -#include <sys/types.h> -#include <sys/stat.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/mount.h> #include <sys/mnttab.h> #include <sys/dktp/fdisk.h> #include <sys/dkio.h> #include <sys/vtoc.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/multiboot.h> +#include <sys/sysmacros.h> -#include <libintl.h> -#include <locale.h> #include "message.h" -#include <errno.h> -#include <libfdisk.h> -#include <md5.h> +#include "installgrub.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" #endif -#define SECTOR_SIZE 0x200 -#define HASH_SIZE 0x10 -#define VERSION_SIZE 0x50 -#define STAGE2_MEMADDR 0x8000 /* loading addr of stage2 */ - -#define STAGE1_BPB_OFFSET 0x3 -#define STAGE1_BPB_SIZE 0x3B -#define STAGE1_BOOT_DRIVE 0x40 -#define STAGE1_FORCE_LBA 0x41 -#define STAGE1_STAGE2_ADDRESS 0x42 -#define STAGE1_STAGE2_SECTOR 0x44 -#define STAGE1_STAGE2_SEGMENT 0x48 - -#define STAGE2_BLOCKLIST (SECTOR_SIZE - 0x8) -#define STAGE2_INSTALLPART (SECTOR_SIZE + 0x8) -#define STAGE2_FORCE_LBA (SECTOR_SIZE + 0x11) -#define STAGE2_VER_STRING (SECTOR_SIZE + 0x12) -#define STAGE2_SIGN_OFFSET (SECTOR_SIZE + 0x60) -#define STAGE2_PKG_VERSION (SECTOR_SIZE + 0x70) -#define STAGE2_BLKOFF 50 /* offset from start of fdisk part */ - -static char extended_sig[] = "\xCC\xCC\xCC\xCC\xAA\xAA\xAA\xAA\xBB\xBB\xBB\xBB" -"\xBB\xBB\xBB\xBB"; - -static int nowrite = 0; -static int write_mboot = 0; -static int force_mboot = 0; -static int getinfo = 0; -static int do_version = 0; -static int is_floppy = 0; -static int is_bootpar = 0; -static int strip = 0; -static int stage2_fd; -static int partition, slice = 0xff; -static char *device_p0; -static uint32_t stage2_first_sector, stage2_second_sector; - - -static char bpb_sect[SECTOR_SIZE]; -static char boot_sect[SECTOR_SIZE]; -static char stage1_buffer[SECTOR_SIZE]; -static char stage2_buffer[2 * SECTOR_SIZE]; -static char signature[HASH_SIZE]; -static char verstring[VERSION_SIZE]; -static unsigned int blocklist[SECTOR_SIZE / sizeof (unsigned int)]; - -static int open_device(char *); -static void read_bpb_sect(int); -static void read_boot_sect(char *); -static void write_boot_sect(char *); -static void read_stage1_stage2(char *, char *); -static void modify_and_write_stage1(int); -static void modify_and_write_stage2(int); -static unsigned int get_start_sector(int); -static void copy_stage2(int, char *); -static char *get_raw_partition(char *); +/* + * 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 boolean_t gather_stage2_from_dev(ig_data_t *); +static int propagate_bootblock(ig_data_t *, ig_data_t *, char *); +static int find_x86_bootpar(struct mboot *, int *, uint32_t *); +static int copy_stage2_to_pcfs(ig_data_t *); +static int write_stage2(ig_data_t *); +static int write_stage1(ig_data_t *); static void usage(char *); -static void print_info(); -static int read_stage2_info(int); -static void check_extended_support(); +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 *); +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 dev_fd, opt, params = 3; - char *stage1, *stage2, *device; + int opt; + int params = 3; + int ret; + char **handle_args; + char *progname; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); - while ((opt = getopt(argc, argv, "fmneis:")) != EOF) { + /* + * 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_mboot = 1; + write_mbr = B_TRUE; break; case 'n': - nowrite = 1; + nowrite = B_TRUE; break; case 'f': - force_mboot = 1; + force_mbr = B_TRUE; break; case 'i': - getinfo = 1; + 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 = 1; + strip = B_TRUE; + break; + case 'M': + do_mirror_bblk = B_TRUE; + do_install = B_FALSE; + params = 2; break; - case 's': - do_version = 1; - (void) snprintf(verstring, sizeof (verstring), "%s", - optarg); + 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 */ @@ -154,121 +185,543 @@ main(int argc, char *argv[]) /* check arguments */ if (argc != optind + params) { usage(argv[0]); + exit(BC_ERROR); } - if (nowrite) { + /* + * 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 (params == 1) { - device = strdup(argv[optind]); - if (!device) { - usage(argv[0]); + 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; } - } else if (params == 3) { - stage1 = strdup(argv[optind]); - stage2 = strdup(argv[optind + 1]); - device = strdup(argv[optind + 2]); + if (strip || verbose_dump) { + BOOT_DEBUG(MEANINGLESS_OPT, "-e|-V"); + strip = B_FALSE; + verbose_dump = B_FALSE; + } + } - if (!stage1 || !stage2 || !device) { - usage(argv[0]); + 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; } } +} - /* open and check device type */ - dev_fd = open_device(device); +/* + * 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; + } - if (getinfo) { - if (read_stage2_info(dev_fd) != 0) { - fprintf(stderr, "Unable to read extended information" - " from %s\n", device); - exit(1); - } - print_info(); - (void) free(device); - (void) close(dev_fd); - return (0); + 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; } - /* read in stage1 and stage2 into buffer */ - read_stage1_stage2(stage1, stage2); + if (read_stage2_from_file(stage2_path, &install_data) != BC_SUCCESS) { + (void) fprintf(stderr, gettext("Error opening %s\n"), + stage2_path); + goto out_dev; + } - /* check if stage2 supports extended versioning */ - if (do_version) - check_extended_support(stage2); + /* We do not support versioning on PCFS. */ + if (is_bootpar(install_data.device.type) && do_version) + do_version = B_FALSE; - /* In the pcfs case, write a fresh stage2 */ - if (is_floppy || is_bootpar) { - copy_stage2(dev_fd, device); - read_bpb_sect(dev_fd); + /* + * 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); +} - /* read in boot sector */ - if (!is_floppy) - read_boot_sect(device); +/* + * 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); - /* modify stage1 based on grub needs */ - modify_and_write_stage1(dev_fd); + 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; + } - /* modify stage2 and write to media */ - modify_and_write_stage2(dev_fd); + if (is_bootpar(device->type)) { + (void) fprintf(stderr, gettext("Versioning not supported on " + "PCFS\n")); + goto out_dev; + } - if (!is_floppy && write_mboot) - write_boot_sect(device); + ret = read_stage2_from_disk(device->part_fd, stage2); + if (ret == BC_ERROR) { + (void) fprintf(stderr, gettext("Error reading stage2 from " + "%s\n"), device_path); + goto out_dev; + } - (void) close(dev_fd); - free(device); - free(stage1); - free(stage2); + 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; + } - return (0); + einfo = find_einfo(stage2->extra); + 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); } -static unsigned int -get_start_sector(int fd) +/* + * 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) { - static unsigned int start_sect = 0; - uint32_t secnum = 0, numsec = 0; - int i, pno, rval, log_part = 0; - struct mboot *mboot; - struct ipart *part; - ext_part_t *epp; - struct part_info dkpi; - struct extpart_info edkpi; - - if (start_sect) - return (start_sect); - - mboot = (struct mboot *)boot_sect; - for (i = 0; i < FD_NUMPART; i++) { - part = (struct ipart *)mboot->parts + i; - if (is_bootpar) { - if (part->systid == 0xbe) { - start_sect = part->relsect; - partition = i; - goto found_part; - } - } + 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); + 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); + 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); /* - * We will not support x86 boot partition on extended partitions + * 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 (is_bootpar) { - (void) fprintf(stderr, NOBOOTPAR); - exit(-1); + 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) +{ + 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 (get_raw_partition_fd(device) != BC_SUCCESS) + return (BC_ERROR); + + 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; + ext_part_t *epp; + struct part_info dkpi; + struct extpart_info edkpi; + + 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; + } } /* - * Not an x86 boot partition. Search for Solaris fdisk partition + * 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(fd, DKIOCEXTPARTINFO, &edkpi) < 0) { - if (ioctl(fd, DKIOCPARTINFO, &dkpi) < 0) { + if (ioctl(device->part_fd, DKIOCEXTPARTINFO, &edkpi) < 0) { + if (ioctl(device->part_fd, DKIOCPARTINFO, &dkpi) < 0) { (void) fprintf(stderr, PART_FAIL); - exit(-1); + return (BC_ERROR); } else { edkpi.p_start = dkpi.p_start; } @@ -279,7 +732,7 @@ get_start_sector(int fd) if (part->relsect == 0) { (void) fprintf(stderr, BAD_PART, i); - exit(-1); + return (BC_ERROR); } if (edkpi.p_start >= part->relsect && @@ -292,7 +745,7 @@ get_start_sector(int fd) if (i == FD_NUMPART) { /* No solaris fdisk partitions (primary or logical) */ (void) fprintf(stderr, NOSOLPAR); - exit(-1); + return (BC_ERROR); } /* @@ -300,8 +753,8 @@ get_start_sector(int fd) * Handle the simple case first: Solaris in a primary partition */ if (!fdisk_is_dos_extended(part->systid)) { - start_sect = part->relsect; - partition = i; + device->start_sector = part->relsect; + device->partition = i; goto found_part; } @@ -309,7 +762,7 @@ get_start_sector(int fd) * Solaris in a logical partition. Find that partition in the * extended part. */ - if ((rval = libfdisk_init(&epp, device_p0, NULL, FDISK_READ_DISK)) + if ((rval = libfdisk_init(&epp, device->path_p0, NULL, FDISK_READ_DISK)) != FDISK_SUCCESS) { switch (rval) { /* @@ -320,23 +773,19 @@ get_start_sector(int fd) case FDISK_ENOLOGDRIVE: case FDISK_EBADMAGIC: (void) fprintf(stderr, NOSOLPAR); - exit(-1); - /*NOTREACHED*/ + return (BC_ERROR); case FDISK_ENOVGEOM: (void) fprintf(stderr, NO_VIRT_GEOM); - exit(1); - break; + return (BC_ERROR); case FDISK_ENOPGEOM: (void) fprintf(stderr, NO_PHYS_GEOM); - exit(1); - break; + return (BC_ERROR); case FDISK_ENOLGEOM: (void) fprintf(stderr, NO_LABEL_GEOM); - exit(1); - break; + return (BC_ERROR); default: (void) fprintf(stderr, LIBFDISK_INIT_FAIL); - exit(1); + return (BC_ERROR); break; } } @@ -346,21 +795,21 @@ get_start_sector(int fd) if (rval != FDISK_SUCCESS) { /* No solaris logical partition */ (void) fprintf(stderr, NOSOLPAR); - exit(-1); + return (BC_ERROR); } - start_sect = secnum; - partition = pno - 1; + device->start_sector = secnum; + device->partition = pno - 1; log_part = 1; found_part: /* get confirmation for -m */ - if (write_mboot && !force_mboot) { + if (write_mbr && !force_mbr) { (void) fprintf(stdout, MBOOT_PROMPT); if (getchar() != 'y') { - write_mboot = 0; + write_mbr = 0; (void) fprintf(stdout, MBOOT_NOT_UPDATED); - exit(-1); + return (BC_ERROR); } } @@ -368,318 +817,463 @@ found_part: * Currently if Solaris is in an extended partition we need to * write GRUB to the MBR. Check for this. */ - if (log_part && !write_mboot) { - write_mboot = 1; + 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 (!log_part && part->bootid != 128 && !write_mboot) { - (void) fprintf(stdout, SOLPAR_INACTIVE, partition + 1); + if (!log_part && part->bootid != 128 && !write_mbr) { + (void) fprintf(stdout, SOLPAR_INACTIVE, device->partition + 1); } - return (start_sect); -} - -static void -usage(char *progname) -{ - (void) fprintf(stderr, USAGE, basename(progname)); - exit(-1); + return (BC_SUCCESS); } static int -open_device(char *device) +get_disk_fd(ig_device_t *device) { - int dev_fd; - struct stat stat; - char *raw_part; - - is_floppy = strncmp(device, "/dev/rdsk", strlen("/dev/rdsk")) && - strncmp(device, "/dev/dsk", strlen("/dev/dsk")); - - /* handle boot partition specification */ - if (!is_floppy && strstr(device, "p0:boot")) { - is_bootpar = 1; + int i; + char save[2]; + 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'; } - raw_part = get_raw_partition(device); - if (nowrite) - dev_fd = open(raw_part, O_RDONLY); + device->disk_fd = open(device->path, O_RDONLY); else - dev_fd = open(raw_part, O_RDWR); + device->disk_fd = open(device->path, O_RDWR); - if (dev_fd == -1 || fstat(dev_fd, &stat) != 0) { - (void) fprintf(stderr, OPEN_FAIL, raw_part); - exit(-1); + device->path_p0 = strdup(device->path); + if (device->path_p0 == NULL) { + perror("strdup"); + return (BC_ERROR); } - if (S_ISCHR(stat.st_mode) == 0) { - (void) fprintf(stderr, NOT_RAW_DEVICE, raw_part); - exit(-1); + + 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 (dev_fd); + return (BC_SUCCESS); } static void -read_stage1_stage2(char *stage1, char *stage2) +prepare_fake_multiboot(ig_stage2_t *stage2) { - int fd; + multiboot_header_t *mboot; - /* read the stage1 file from filesystem */ - fd = open(stage1, O_RDONLY); - if (fd == -1 || read(fd, stage1_buffer, SECTOR_SIZE) != SECTOR_SIZE) { - (void) fprintf(stderr, READ_FAIL_STAGE1, stage1); - exit(-1); - } - (void) close(fd); + assert(stage2 != NULL); + assert(stage2->mboot != NULL); + assert(stage2->buf != NULL); - /* read first two blocks of stage 2 from filesystem */ - stage2_fd = open(stage2, O_RDONLY); - if (stage2_fd == -1 || - read(stage2_fd, stage2_buffer, 2 * SECTOR_SIZE) - != 2 * SECTOR_SIZE) { - (void) fprintf(stderr, READ_FAIL_STAGE2, stage2); - exit(-1); - } - /* leave the stage2 file open for later */ + 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 -read_bpb_sect(int dev_fd) +add_stage2_einfo(ig_stage2_t *stage2, char *updt_str) { - if (pread(dev_fd, bpb_sect, SECTOR_SIZE, 0) != SECTOR_SIZE) { - (void) fprintf(stderr, READ_FAIL_BPB); - exit(-1); + 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. + */ + offset = STAGE2_BLKOFF * 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, + stage2->first_sector); + + return (BC_SUCCESS); } -static void -read_boot_sect(char *device) +static int +write_stage1(ig_data_t *install) { - static int read_mbr = 0; - int i, fd; - char save[2]; - - if (read_mbr) - return; - read_mbr = 1; - - /* get the whole disk (p0) */ - i = strlen(device); - save[0] = device[i - 2]; - save[1] = device[i - 1]; - device[i - 2] = 'p'; - device[i - 1] = '0'; - - device_p0 = strdup(device); - fd = open(device, O_RDONLY); - if (fd == -1 || read(fd, boot_sect, SECTOR_SIZE) != SECTOR_SIZE) { - (void) fprintf(stderr, READ_FAIL_MBR, device); - if (fd == -1) - perror("open"); - else - perror("read"); - exit(-1); + 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); } - (void) close(fd); - device[i - 2] = save[0]; - device[i - 1] = save[1]; + + /* 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 -write_boot_sect(char *device) +usage(char *progname) { - int fd, len; - char *raw, *end; - struct stat stat; + (void) fprintf(stdout, CANON_USAGE_STR, progname, progname, progname); +} - /* make a copy and chop off ":boot" */ - raw = strdup(device); - end = strstr(raw, "p0:boot"); - if (end) - end[2] = 0; - /* open p0 (whole disk) */ - len = strlen(raw); - raw[len - 2] = 'p'; - raw[len - 1] = '0'; - fd = open(raw, O_WRONLY); - if (fd == -1 || fstat(fd, &stat) != 0) { - (void) fprintf(stderr, OPEN_FAIL, raw); - exit(-1); - } - if (!nowrite && - pwrite(fd, stage1_buffer, SECTOR_SIZE, 0) != SECTOR_SIZE) { - (void) fprintf(stderr, WRITE_FAIL_BOOTSEC); - exit(-1); +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) fprintf(stdout, WRITE_MBOOT); (void) close(fd); + return (BC_SUCCESS); } -static void -modify_and_write_stage1(int dev_fd) +static int +read_stage2_from_file(char *path, ig_data_t *dest) { - if (is_floppy) { - stage2_first_sector = blocklist[0]; - /* copy bios parameter block (for fat fs) */ - bcopy(bpb_sect + STAGE1_BPB_OFFSET, - stage1_buffer + STAGE1_BPB_OFFSET, STAGE1_BPB_SIZE); - } else if (is_bootpar) { - stage2_first_sector = get_start_sector(dev_fd) + blocklist[0]; - /* copy bios parameter block (for fat fs) and MBR */ - bcopy(bpb_sect + STAGE1_BPB_OFFSET, - stage1_buffer + STAGE1_BPB_OFFSET, STAGE1_BPB_SIZE); - bcopy(boot_sect + BOOTSZ, stage1_buffer + BOOTSZ, 512 - BOOTSZ); - *((unsigned char *)(stage1_buffer + STAGE1_FORCE_LBA)) = 1; + 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 { - stage2_first_sector = get_start_sector(dev_fd) + STAGE2_BLKOFF; - /* copy MBR to stage1 in case of overwriting MBR sector */ - bcopy(boot_sect + BOOTSZ, stage1_buffer + BOOTSZ, 512 - BOOTSZ); - *((unsigned char *)(stage1_buffer + STAGE1_FORCE_LBA)) = 1; + /* In the PCFS case we only need to read in stage2. */ + buf_size = stage2->file_size; } - /* modify default stage1 file generated by GRUB */ - *((ulong_t *)(stage1_buffer + STAGE1_STAGE2_SECTOR)) - = stage2_first_sector; - *((ushort_t *)(stage1_buffer + STAGE1_STAGE2_ADDRESS)) - = STAGE2_MEMADDR; - *((ushort_t *)(stage1_buffer + STAGE1_STAGE2_SEGMENT)) - = STAGE2_MEMADDR >> 4; + 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; /* - * XXX the default grub distribution also: - * - Copy the possible MBR/extended part table - * - Set the boot drive of stage1 + * 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); - /* write stage1/pboot to 1st sector */ - if (!nowrite && - pwrite(dev_fd, stage1_buffer, SECTOR_SIZE, 0) != SECTOR_SIZE) { - (void) fprintf(stderr, WRITE_FAIL_PBOOT); - exit(-1); + if (lseek(fd, 0, SEEK_SET) == -1) { + perror("lseek"); + goto out_alloc; } - if (is_floppy) { - (void) fprintf(stdout, WRITE_BOOTSEC_FLOPPY); - } else { - (void) fprintf(stdout, WRITE_PBOOT, - partition, get_start_sector(dev_fd)); + if (read(fd, stage2->file, stage2->file_size) < 0) { + perror(gettext("unable to read stage2")); + goto out_alloc; } -} -static void check_extended_support(char *stage2) -{ - char *cmp = stage2_buffer + STAGE2_SIGN_OFFSET - 1; + (void) close(fd); + return (BC_SUCCESS); - if ((*cmp++ != '\xEE') && memcmp(cmp, extended_sig, HASH_SIZE) != 0) { - fprintf(stderr, "%s does not support extended versioning\n", - stage2); - do_version = 0; - } +out_alloc: + free(stage2->buf); + stage2->buf = NULL; +out_fd: + (void) close(fd); +out: + return (BC_ERROR); } - -static void print_info() +static int +prepare_stage1(ig_data_t *install) { - int i; + ig_device_t *device = &install->device; - if (strip) { - fprintf(stdout, "%s\n", verstring); - } else { - fprintf(stdout, "Grub extended version information : %s\n", - verstring); - fprintf(stdout, "Grub stage2 (MD5) signature : "); + 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); } - for (i = 0; i < HASH_SIZE; i++) - fprintf(stdout, "%02x", (unsigned char)signature[i]); + /* 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; - fprintf(stdout, "\n"); + return (BC_SUCCESS); } +/* + * Grab stage1 from the specified device file descriptor. + */ static int -read_stage2_info(int dev_fd) +read_stage1_from_disk(int dev_fd, char *stage1_buf) { - int ret; - int first_offset, second_offset; - char *sign; + assert(stage1_buf != NULL); - if (is_floppy || is_bootpar) { - - ret = pread(dev_fd, stage1_buffer, SECTOR_SIZE, 0); - if (ret != SECTOR_SIZE) { - perror("Error reading stage1 sector"); - return (1); - } + 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); +} - first_offset = *((ulong_t *)(stage1_buffer + - STAGE1_STAGE2_SECTOR)); +static int +read_stage2_from_disk(int dev_fd, ig_stage2_t *stage2) +{ + 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 * SECTOR_SIZE) != BC_SUCCESS) { + perror(gettext("Error reading stage2 sectors")); + return (BC_ERROR); + } - /* Start reading in the first sector of stage 2 */ + /* 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); - ret = pread(dev_fd, stage2_buffer, SECTOR_SIZE, first_offset * - SECTOR_SIZE); - if (ret != SECTOR_SIZE) { - perror("Error reading stage2 first sector"); - return (1); - } + /* + * 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); - /* From the block list section grab stage2 second sector */ + /* + * 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); - second_offset = *((ulong_t *)(stage2_buffer + - STAGE2_BLOCKLIST)); + stage2->buf = malloc(buf_size); + if (stage2->buf == NULL) { + perror(gettext("Memory allocation failed")); + return (BC_ERROR); + } + stage2->buf_size = buf_size; - ret = pread(dev_fd, stage2_buffer + SECTOR_SIZE, SECTOR_SIZE, - second_offset * SECTOR_SIZE); - if (ret != SECTOR_SIZE) { - perror("Error reading stage2 second sector"); - return (1); - } - } else { - ret = pread(dev_fd, stage2_buffer, 2 * SECTOR_SIZE, - STAGE2_BLKOFF * SECTOR_SIZE); - if (ret != 2 * SECTOR_SIZE) { - perror("Error reading stage2 sectors"); - return (1); - } + if (read_in(dev_fd, stage2->buf, buf_size, STAGE2_BLKOFF * + SECTOR_SIZE) != BC_SUCCESS) { + perror("read"); + free(stage2->buf); + return (BC_ERROR); } - sign = stage2_buffer + STAGE2_SIGN_OFFSET - 1; - if (*sign++ != '\xEE') - return (1); - (void) memcpy(signature, sign, HASH_SIZE); - sign = stage2_buffer + STAGE2_PKG_VERSION; - (void) strncpy(verstring, sign, VERSION_SIZE); - return (0); -} + /* 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); + return (BC_SUCCESS); +} -static int -compute_and_write_md5hash(char *dest) +static boolean_t +is_update_necessary(ig_data_t *data, char *updt_str) { - struct stat sb; - char *buffer; + 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) != 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); + } - if (fstat(stage2_fd, &sb) == -1) - return (-1); + /* + * Look for the extended information structure in the extra payload + * area. + */ + einfo = find_einfo(stage2_disk.extra); + if (einfo == NULL) { + BOOT_DEBUG("No extended information available\n"); + return (B_TRUE); + } - buffer = malloc(sb.st_size); - if (buffer == NULL) - return (-1); + 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 (lseek(stage2_fd, 0, SEEK_SET) == -1) - return (-1); - if (read(stage2_fd, buffer, sb.st_size) < 0) - return (-1); + if (force_update) { + BOOT_DEBUG("Forcing update of %s bootblock\n", device->path); + return (B_TRUE); + } - md5_calc(dest, buffer, sb.st_size); - free(buffer); - return (0); + /* 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)); } @@ -687,198 +1281,230 @@ compute_and_write_md5hash(char *dest) #define NUM_BLOCK(pos) (*(ushort_t *)((pos) + 4)) #define START_SEG(pos) (*(ushort_t *)((pos) + 6)) -static void -modify_and_write_stage2(int dev_fd) +static int +prepare_stage2(ig_data_t *install, char *updt_str) { - int nrecord; - off_t offset; - char *dest; - - if (do_version) { - dest = stage2_buffer + STAGE2_SIGN_OFFSET; - if (compute_and_write_md5hash(dest) < 0) - perror("MD5 operation"); - dest = stage2_buffer + STAGE2_PKG_VERSION; - (void) strncpy(dest, verstring, VERSION_SIZE); + 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_floppy || is_bootpar) { - int i = 0; - uint32_t partition_offset; - uint32_t install_addr = 0x8200; - uchar_t *pos = (uchar_t *)stage2_buffer + STAGE2_BLOCKLIST; + 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); - stage2_first_sector = blocklist[0]; - /* figure out the second sector */ if (blocklist[1] > 1) { blocklist[0]++; blocklist[1]--; } else { i += 2; } - stage2_second_sector = blocklist[i]; - if (is_floppy) - partition_offset = 0; - else /* solaris boot partition */ - partition_offset = get_start_sector(dev_fd); + stage2->pcfs_first_sectors[1] = blocklist[i]; - /* install the blocklist at the end of stage2_buffer */ while (blocklist[i]) { if (START_BLOCK(pos - 8) != 0 && START_BLOCK(pos - 8) != blocklist[i + 2]) { (void) fprintf(stderr, PCFS_FRAGMENTED); - exit(-1); + return (BC_ERROR); } - START_BLOCK(pos) = blocklist[i] + partition_offset; + 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 */ + stage2->first_sector = device->start_sector + STAGE2_BLKOFF; + 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_buffer + STAGE2_BLOCKLIST)) = - stage2_first_sector + 1; + *((ulong_t *)(stage2->file + STAGE2_BLOCKLIST)) = + stage2->first_sector + 1; } - if (is_floppy) { - /* modify the config file to add (fd0) */ - char *config_file = stage2_buffer + STAGE2_VER_STRING; - while (*config_file++) - ; - strcpy(config_file, "(fd0)/boot/grub/menu.lst"); - } else { - /* force lba and set disk partition */ - *((unsigned char *) (stage2_buffer + STAGE2_FORCE_LBA)) = 1; - *((long *)(stage2_buffer + STAGE2_INSTALLPART)) - = (partition << 16) | (slice << 8) | 0xff; - } - - /* modification done, now do the writing */ - if (is_floppy || is_bootpar) { - /* we rewrite block 0 and 1 and that's it */ - if (!nowrite && - (pwrite(dev_fd, stage2_buffer, SECTOR_SIZE, - stage2_first_sector * SECTOR_SIZE) != SECTOR_SIZE || - pwrite(dev_fd, stage2_buffer + SECTOR_SIZE, SECTOR_SIZE, - stage2_second_sector * SECTOR_SIZE) != SECTOR_SIZE)) { - (void) fprintf(stderr, WRITE_FAIL_STAGE2); - exit(-1); - } - (void) fprintf(stdout, WRITE_STAGE2_PCFS); - return; - } - - /* for disk, write stage2 starting at STAGE2_BLKOFF sector */ - offset = STAGE2_BLKOFF; - - /* write the modified first two sectors */ - if (!nowrite && pwrite(dev_fd, stage2_buffer, 2 * SECTOR_SIZE, - offset * SECTOR_SIZE) != 2 * SECTOR_SIZE) { - (void) fprintf(stderr, WRITE_FAIL_STAGE2); - exit(-1); - } - - /* write the remaining sectors */ - nrecord = 2; - offset += 2; - for (;;) { - int nread, nwrite; - nread = pread(stage2_fd, stage2_buffer, SECTOR_SIZE, - nrecord * SECTOR_SIZE); - if (nread > 0 && !nowrite) - nwrite = pwrite(dev_fd, stage2_buffer, SECTOR_SIZE, - offset * SECTOR_SIZE); - else - nwrite = SECTOR_SIZE; - if (nread < 0 || nwrite != SECTOR_SIZE) { - (void) fprintf(stderr, WRITE_FAIL_STAGE2_BLOCKS, - nread, nwrite); - break; - } - if (nread > 0) { - nrecord ++; - offset ++; - } - if (nread < SECTOR_SIZE) - break; /* end of file */ - } - (void) fprintf(stdout, WRITE_STAGE2_DISK, - partition, nrecord, STAGE2_BLKOFF, stage2_first_sector); + /* 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 char * -get_raw_partition(char *device) +static int +find_x86_bootpar(struct mboot *mboot, int *part_num, uint32_t *start_sect) { - int len; - struct mboot *mboot; - static char *raw = NULL; - - if (raw) - return (raw); - raw = strdup(device); + int i; - if (is_floppy) - return (raw); + for (i = 0; i < FD_NUMPART; i++) { + struct ipart *part; - if (is_bootpar) { - int i; - char *end = strstr(raw, "p0:boot"); + 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); +} - end[2] = 0; /* chop off :boot */ - read_boot_sect(raw); - mboot = (struct mboot *)boot_sect; - for (i = 0; i < FD_NUMPART; i++) { - struct ipart *part = (struct ipart *)mboot->parts + i; - if (part->systid == 0xbe) /* solaris boot part */ - break; +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); } - if (i == FD_NUMPART) { - (void) fprintf(stderr, BOOTPAR_NOTFOUND, device); - exit(-1); + raw = strdup(device->path_p0); + if (raw == NULL) { + perror(gettext("Memory allocation failed")); + return (NULL); } - end[1] = '1' + i; /* set partition name */ + + 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 (raw[len - 2] != 's' || raw[len - 1] == '2') { (void) fprintf(stderr, NOT_ROOT_SLICE); - exit(-1); + free(raw); + return (NULL); } - slice = atoi(&raw[len - 1]); + device->slice = atoi(&raw[len - 1]); 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); +} + #define TMP_MNTPT "/tmp/installgrub_pcfs" -static void -copy_stage2(int dev_fd, char *device) +static int +copy_stage2_to_pcfs(ig_data_t *install) { - FILE *mntfp; - int i, pcfs_fp; - char buf[SECTOR_SIZE]; - char *cp; - struct mnttab mp = {0}, mpref = {0}; + FILE *mntfp; + int pcfs_fp; + int status = BC_ERROR; + char buf[SECTOR_SIZE]; + char *cp; + struct mnttab mp = {0}, mpref = {0}; + ig_device_t *device = &install->device; + ig_stage2_t *stage2 = &install->stage2; /* convert raw to block device name by removing the first 'r' */ - (void) strncpy(buf, device, sizeof (buf)); + (void) strncpy(buf, device->path, sizeof (buf)); buf[sizeof (buf) - 1] = 0; cp = strchr(buf, 'r'); if (cp == NULL) { - (void) fprintf(stderr, CONVERT_FAIL, device); - exit(-1); + (void) fprintf(stderr, CONVERT_FAIL, device->path); + return (BC_ERROR); } do { *cp = *(cp + 1); @@ -888,7 +1514,7 @@ copy_stage2(int dev_fd, char *device) mntfp = fopen("/etc/mnttab", "r"); if (mntfp == NULL) { (void) fprintf(stderr, OPEN_FAIL_FILE, "/etc/mnttab"); - exit(-1); + return (BC_ERROR); } mpref.mnt_special = buf; @@ -904,7 +1530,7 @@ copy_stage2(int dev_fd, char *device) bzero(&mp, sizeof (mp)); if (getmntany(mntfp, &mp, &mpref) != 0) { (void) fprintf(stderr, MOUNT_FAIL, buf); - exit(-1); + return (BC_ERROR); } } @@ -919,33 +1545,21 @@ copy_stage2(int dev_fd, char *device) if (pcfs_fp == -1) { (void) fprintf(stderr, OPEN_FAIL_FILE, buf); perror("open:"); - (void) umount(TMP_MNTPT); - exit(-1); - } - - /* write stage2 to pcfs */ - for (i = 0; ; i++) { - int nread, nwrite; - nread = pread(stage2_fd, buf, SECTOR_SIZE, i * SECTOR_SIZE); - if (nowrite) - nwrite = nread; - else - nwrite = pwrite(pcfs_fp, buf, nread, i * SECTOR_SIZE); - if (nread < 0 || nwrite != nread) { - (void) fprintf(stderr, WRITE_FAIL_STAGE2_BLOCKS, - nread, nwrite); - break; - } - if (nread < SECTOR_SIZE) - break; /* end of file */ + goto out; + } + + /* write stage2 to the pcfs mounted filesystem. */ + if (write(pcfs_fp, stage2->file, stage2->file_size) + != stage2->file_size) { + perror(gettext("Error writing stage2")); + goto out; } + + status = BC_SUCCESS; +out_fd: (void) close(pcfs_fp); +out: (void) umount(TMP_MNTPT); - - /* - * Now, get the blocklist from the device. - */ - bzero(blocklist, sizeof (blocklist)); - if (read_stage2_blocklist(dev_fd, blocklist) != 0) - exit(-1); + (void) rmdir(TMP_MNTPT); + return (status); } |