/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012 Nexenta Systems, Inc. All rights reserved. * Copyright 2017 Toomas Soome */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "installboot.h" #include "bblk_einfo.h" #include "boot_utils.h" #include "mboot_extra.h" #include "getresponse.h" #ifndef TEXT_DOMAIN #define TEXT_DOMAIN "SUNW_OST_OSCMD" #endif /* * BIOS bootblock installation: * * 1. MBR is first sector of the disk. If the file system on target is * ufs or zfs, the same MBR code is installed on first sector of the * partition as well; this will allow to have real MBR sector to be * replaced by some other boot loader and have illumos chainloaded. * * installboot will record the start LBA and size of stage2 code in MBR code. * On boot, the MBR code will read the stage2 code and executes it. * * 2. Stage2 location depends on file system type; * In case of zfs, installboot will store stage2 to zfs bootblk area, * which is 512k bytes from partition start and size is 3.5MB. * * In case of ufs, the stage2 location is 50 512B sectors from * Solaris2 MBR partition start, within boot slice, boot slice size is * one cylinder. * * In case of pcfs, the stage2 location is 50 512B sectors from beginning * of the disk, filling the space between MBR and first partition. * This location assumes no other bootloader and the space is one cylinder, * as first partition is starting from cylinder 1. * * In case of GPT partitioning and if file system is not zfs, the boot * support is only possible with dedicated boot partition. For GPT, * the current implementation is using BOOT partition, which must exist. * BOOT partition does only contain raw boot blocks, without any file system. * * Loader stage2 is created with embedded version, by using fake multiboot (MB) * header within first 32k and EINFO block is at the end of the actual * boot block. MB header load_addr is set to 0 and load_end_addr is set to * actual block end, so the EINFO size is (file size - load_end_addr). * installboot does also store the illumos boot partition LBA to MB space, * starting from bss_end_addr structure member location; stage2 will * detect the partition and file system based on this value. * * Stored location values in MBR/stage2 also mean the bootblocks must be * reinstalled in case the partition content is relocated. */ static boolean_t write_mbr = B_FALSE; static boolean_t force_mbr = B_FALSE; static boolean_t force_update = B_FALSE; static boolean_t do_getinfo = B_FALSE; static boolean_t do_version = B_FALSE; static boolean_t do_mirror_bblk = B_FALSE; static boolean_t strip = B_FALSE; static boolean_t verbose_dump = B_FALSE; /* Versioning string, if present. */ static char *update_str; /* * Temporary buffer to store the first 32K of data looking for a multiboot * signature. */ char mboot_scan[MBOOT_SCAN_SIZE]; /* Function prototypes. */ static void check_options(char *); static int get_start_sector(ib_device_t *); static int read_stage1_from_file(char *, ib_data_t *); static int read_bootblock_from_file(char *, ib_bootblock_t *); static int read_bootblock_from_disk(ib_device_t *, ib_bootblock_t *, char **); static void add_bootblock_einfo(ib_bootblock_t *, char *); static int prepare_stage1(ib_data_t *); static int prepare_bootblock(ib_data_t *, char *); static int write_stage1(ib_data_t *); static int write_bootblock(ib_data_t *); static int init_device(ib_device_t *, char *); static void cleanup_device(ib_device_t *); static int commit_to_disk(ib_data_t *, char *); static int handle_install(char *, char **); static int handle_getinfo(char *, char **); static int handle_mirror(char *, char **); static boolean_t is_update_necessary(ib_data_t *, char *); static int propagate_bootblock(ib_data_t *, ib_data_t *, char *); static void usage(char *, int) __NORETURN; static int read_stage1_from_file(char *path, ib_data_t *dest) { int fd; assert(dest != NULL); /* read the stage1 file from filesystem */ fd = open(path, O_RDONLY); if (fd == -1 || read(fd, dest->stage1, SECTOR_SIZE) != SECTOR_SIZE) { (void) fprintf(stderr, gettext("cannot read stage1 file %s\n"), path); return (BC_ERROR); } (void) close(fd); return (BC_SUCCESS); } static int read_bootblock_from_file(char *file, ib_bootblock_t *bblock) { struct stat sb; uint32_t buf_size; uint32_t mboot_off; int fd = -1; int retval = BC_ERROR; assert(bblock != NULL); assert(file != NULL); fd = open(file, O_RDONLY); if (fd == -1) { BOOT_DEBUG("Error opening %s\n", file); perror("open"); goto out; } if (fstat(fd, &sb) == -1) { BOOT_DEBUG("Error getting information (stat) about %s", file); perror("stat"); goto outfd; } /* loader bootblock has version built in */ buf_size = sb.st_size; bblock->buf_size = buf_size; BOOT_DEBUG("bootblock in-memory buffer size is %d\n", bblock->buf_size); bblock->buf = malloc(buf_size); if (bblock->buf == NULL) { perror(gettext("Memory allocation failure")); goto outbuf; } bblock->file = bblock->buf; if (read(fd, bblock->file, bblock->buf_size) != bblock->buf_size) { BOOT_DEBUG("Read from %s failed\n", file); perror("read"); goto outfd; } if (find_multiboot(bblock->file, MBOOT_SCAN_SIZE, &mboot_off) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Unable to find multiboot header\n")); goto outfd; } bblock->mboot = (multiboot_header_t *)(bblock->file + mboot_off); bblock->mboot_off = mboot_off; bblock->file_size = bblock->mboot->load_end_addr - bblock->mboot->load_addr; BOOT_DEBUG("bootblock file size is %d\n", bblock->file_size); bblock->extra = bblock->buf + P2ROUNDUP(bblock->file_size, 8); bblock->extra_size = bblock->buf_size - P2ROUNDUP(bblock->file_size, 8); BOOT_DEBUG("mboot at %p offset %d, extra at %p size %d, buf=%p " "(size=%d)\n", bblock->mboot, bblock->mboot_off, bblock->extra, bblock->extra_size, bblock->buf, bblock->buf_size); (void) close(fd); return (BC_SUCCESS); outbuf: (void) free(bblock->buf); bblock->buf = NULL; outfd: (void) close(fd); out: return (retval); } static int read_bootblock_from_disk(ib_device_t *device, ib_bootblock_t *bblock, char **path) { int dev_fd; uint32_t size, offset; uint32_t buf_size; uint32_t mboot_off; multiboot_header_t *mboot; assert(device != NULL); assert(bblock != NULL); if (device->target.fstype == IG_FS_ZFS) { dev_fd = device->target.fd; offset = BBLK_ZFS_BLK_OFF * SECTOR_SIZE; *path = device->target.path; } else { dev_fd = device->stage.fd; offset = device->stage.offset * SECTOR_SIZE; *path = device->stage.path; } if (read_in(dev_fd, mboot_scan, sizeof (mboot_scan), offset) != BC_SUCCESS) { BOOT_DEBUG("Error reading bootblock area\n"); perror("read"); return (BC_ERROR); } /* No multiboot means no chance of knowing bootblock size */ if (find_multiboot(mboot_scan, sizeof (mboot_scan), &mboot_off) != BC_SUCCESS) { BOOT_DEBUG("Unable to find multiboot header\n"); return (BC_NOEXTRA); } mboot = (multiboot_header_t *)(mboot_scan + mboot_off); /* * make sure mboot has sane values */ if (mboot->load_end_addr == 0 || mboot->load_end_addr < mboot->load_addr) return (BC_NOEXTRA); /* * Currently, the amount of space reserved for extra information * is "fixed". We may have to scan for the terminating extra payload * in the future. */ size = mboot->load_end_addr - mboot->load_addr; buf_size = P2ROUNDUP(size + SECTOR_SIZE, SECTOR_SIZE); bblock->file_size = size; bblock->buf = malloc(buf_size); if (bblock->buf == NULL) { BOOT_DEBUG("Unable to allocate enough memory to read" " the extra bootblock from the disk\n"); perror(gettext("Memory allocation failure")); return (BC_ERROR); } bblock->buf_size = buf_size; if (read_in(dev_fd, bblock->buf, buf_size, offset) != BC_SUCCESS) { BOOT_DEBUG("Error reading the bootblock\n"); (void) free(bblock->buf); bblock->buf = NULL; return (BC_ERROR); } /* Update pointers. */ bblock->file = bblock->buf; bblock->mboot_off = mboot_off; bblock->mboot = (multiboot_header_t *)(bblock->buf + bblock->mboot_off); bblock->extra = bblock->buf + P2ROUNDUP(bblock->file_size, 8); bblock->extra_size = bblock->buf_size - P2ROUNDUP(bblock->file_size, 8); BOOT_DEBUG("mboot at %p offset %d, extra at %p size %d, buf=%p " "(size=%d)\n", bblock->mboot, bblock->mboot_off, bblock->extra, bblock->extra_size, bblock->buf, bblock->buf_size); return (BC_SUCCESS); } static boolean_t is_update_necessary(ib_data_t *data, char *updt_str) { bblk_einfo_t *einfo; bblk_einfo_t *einfo_file; bblk_hs_t bblock_hs; ib_bootblock_t bblock_disk; ib_bootblock_t *bblock_file = &data->bootblock; ib_device_t *device = &data->device; int ret; char *path; assert(data != NULL); bzero(&bblock_disk, sizeof (ib_bootblock_t)); ret = read_bootblock_from_disk(device, &bblock_disk, &path); if (ret != BC_SUCCESS) { BOOT_DEBUG("Unable to read bootblock from %s\n", path); return (B_TRUE); } einfo = find_einfo(bblock_disk.extra, bblock_disk.extra_size); if (einfo == NULL) { BOOT_DEBUG("No extended information available on disk\n"); return (B_TRUE); } einfo_file = find_einfo(bblock_file->extra, bblock_file->extra_size); if (einfo_file == NULL) { /* * loader bootblock is versioned. missing version means * probably incompatible block. installboot can not install * grub, for example. */ (void) fprintf(stderr, gettext("ERROR: non versioned bootblock in file\n")); return (B_FALSE); } else { if (updt_str == NULL) { updt_str = einfo_get_string(einfo_file); do_version = B_TRUE; } } if (!do_version || updt_str == NULL) { (void) fprintf(stderr, gettext("WARNING: target device %s has a " "versioned bootblock that is going to be overwritten by a " "non versioned one\n"), device->path); return (B_TRUE); } if (force_update) { BOOT_DEBUG("Forcing update of %s bootblock\n", device->path); return (B_TRUE); } BOOT_DEBUG("Ready to check installed version vs %s\n", updt_str); bblock_hs.src_buf = (unsigned char *)bblock_file->file; bblock_hs.src_size = bblock_file->file_size; return (einfo_should_update(einfo, &bblock_hs, updt_str)); } static void add_bootblock_einfo(ib_bootblock_t *bblock, char *updt_str) { bblk_hs_t hs; uint32_t avail_space; assert(bblock != NULL); if (updt_str == NULL) { BOOT_DEBUG("WARNING: no update string passed to " "add_bootblock_einfo()\n"); return; } /* Fill bootblock hashing source information. */ hs.src_buf = (unsigned char *)bblock->file; hs.src_size = bblock->file_size; /* How much space for the extended information structure? */ avail_space = bblock->buf_size - P2ROUNDUP(bblock->file_size, 8); /* Place the extended information structure. */ add_einfo(bblock->extra, updt_str, &hs, avail_space); } /* * set up data for case stage1 is installed as MBR * set up location and size of bootblock * set disk guid to provide unique information for biosdev command */ static int prepare_stage1(ib_data_t *data) { ib_device_t *device; assert(data != NULL); device = &data->device; /* copy BPB */ bcopy(device->mbr + STAGE1_BPB_OFFSET, data->stage1 + STAGE1_BPB_OFFSET, STAGE1_BPB_SIZE); /* copy MBR, note STAGE1_SIG == BOOTSZ */ bcopy(device->mbr + STAGE1_SIG, data->stage1 + STAGE1_SIG, SECTOR_SIZE - STAGE1_SIG); /* set stage2 size */ *((uint16_t *)(data->stage1 + STAGE1_STAGE2_SIZE)) = (uint16_t)(data->bootblock.buf_size / SECTOR_SIZE); /* * set stage2 location. * for zfs always use zfs embedding, for ufs/pcfs use partition_start * as base for stage2 location, for ufs/pcfs in MBR partition, use * free space after MBR record. */ if (device->target.fstype == IG_FS_ZFS) *((uint64_t *)(data->stage1 + STAGE1_STAGE2_LBA)) = device->target.start + device->target.offset; else { *((uint64_t *)(data->stage1 + STAGE1_STAGE2_LBA)) = device->stage.start + device->stage.offset; } /* * set disk uuid. we only need reasonable amount of uniqueness * to allow biosdev to identify disk based on mbr differences. */ uuid_generate(data->stage1 + STAGE1_STAGE2_UUID); return (BC_SUCCESS); } static int prepare_bootblock(ib_data_t *data, char *updt_str) { ib_bootblock_t *bblock; ib_device_t *device; uint64_t *ptr; assert(data != NULL); bblock = &data->bootblock; device = &data->device; ptr = (uint64_t *)(&bblock->mboot->bss_end_addr); *ptr = device->target.start; /* * the loader bootblock has built in version, if custom * version was provided, update it. */ if (do_version) add_bootblock_einfo(bblock, updt_str); return (BC_SUCCESS); } static int write_bootblock(ib_data_t *data) { ib_device_t *device = &data->device; ib_bootblock_t *bblock = &data->bootblock; uint64_t abs; int dev_fd, ret; off_t offset; char *path; assert(data != NULL); /* * ZFS bootblock area is 3.5MB, make sure we can fit. * buf_size is size of bootblk+EINFO. */ if (bblock->buf_size > BBLK_ZFS_BLK_SIZE) { (void) fprintf(stderr, gettext("bootblock is too large\n")); return (BC_ERROR); } if (device->target.fstype == IG_FS_ZFS) { dev_fd = device->target.fd; abs = device->target.start + device->target.offset; offset = BBLK_ZFS_BLK_OFF * SECTOR_SIZE; path = device->target.path; } else { dev_fd = device->stage.fd; abs = device->stage.start + device->stage.offset; offset = device->stage.offset * SECTOR_SIZE; path = device->stage.path; if (bblock->buf_size > (device->stage.size - device->stage.offset) * SECTOR_SIZE) { (void) fprintf(stderr, gettext("Device %s is " "too small to fit the stage2\n"), path); return (BC_ERROR); } } ret = write_out(dev_fd, bblock->buf, bblock->buf_size, offset); if (ret != BC_SUCCESS) { BOOT_DEBUG("Error writing the ZFS bootblock " "to %s at offset %d\n", path, offset); return (BC_ERROR); } (void) fprintf(stdout, gettext("bootblock written for %s," " %d sectors starting at %d (abs %lld)\n"), path, (bblock->buf_size / SECTOR_SIZE) + 1, offset / SECTOR_SIZE, abs); return (BC_SUCCESS); } /* * Partition boot block or volume boot record (VBR). The VBR is * stored on partition relative sector 0 and allows chainloading * to read boot program from partition. * * As the VBR will use the first sector of the partition, * this means, we need to be sure the space is not used. * We do support three partitioning chemes: * 1. GPT: zfs and ufs have reserved space for first 8KB, but * only zfs does have space for boot2. The pcfs has support * for VBR, but no space for boot2. So with GPT, to support * ufs or pcfs boot, we must have separate dedicated boot * partition and we will store VBR on it. * 2. MBR: we have almost the same situation as with GPT, except that * if the partitions start from cylinder 1, we will have space * between MBR and cylinder 0. If so, we do not require separate * boot partition. * 3. MBR+VTOC: with this combination we store VBR in sector 0 of the * solaris2 MBR partition. The slice 0 will start from cylinder 1, * and we do have space for boot2, so we do not require separate * boot partition. */ static int write_stage1(ib_data_t *data) { ib_device_t *device = &data->device; uint64_t start = 0; assert(data != NULL); /* * We have separate partition for boot programs and the stage1 * location is not absolute sector 0. * We will write VBR and trigger MBR to read 1 sector from VBR. * This case does also cover MBR+VTOC case, as the solaris 2 partition * name and the root file system slice names are different. */ if (device->stage.start != 0 && strcmp(device->target.path, device->stage.path)) { /* we got separate stage area, use it */ if (write_out(device->stage.fd, data->stage1, sizeof (data->stage1), 0) != BC_SUCCESS) { (void) fprintf(stdout, gettext("cannot write " "partition boot sector\n")); perror("write"); return (BC_ERROR); } (void) fprintf(stdout, gettext("stage1 written to " "%s %d sector 0 (abs %d)\n"), device->devtype == IG_DEV_MBR? "partition":"slice", device->stage.id, device->stage.start); start = device->stage.start; } /* * We have either GPT or MBR (without VTOC) and if the root * file system is not pcfs, we can store VBR. Also trigger * MBR to read 1 sector from VBR. */ if (device->devtype != IG_DEV_VTOC && device->target.fstype != IG_FS_PCFS) { if (write_out(device->target.fd, data->stage1, sizeof (data->stage1), 0) != BC_SUCCESS) { (void) fprintf(stdout, gettext("cannot write " "partition boot sector\n")); perror("write"); return (BC_ERROR); } (void) fprintf(stdout, gettext("stage1 written to " "%s %d sector 0 (abs %d)\n"), device->devtype == IG_DEV_MBR? "partition":"slice", device->target.id, device->target.start); start = device->target.start; } if (write_mbr) { /* * If we did write partition boot block, update MBR to * read partition boot block, not boot2. */ if (start != 0) { *((uint16_t *)(data->stage1 + STAGE1_STAGE2_SIZE)) = 1; *((uint64_t *)(data->stage1 + STAGE1_STAGE2_LBA)) = start; } if (write_out(device->fd, data->stage1, sizeof (data->stage1), 0) != BC_SUCCESS) { (void) fprintf(stdout, gettext("cannot write master boot sector\n")); perror("write"); return (BC_ERROR); } (void) fprintf(stdout, gettext("stage1 written to master boot sector\n")); } return (BC_SUCCESS); } /* * find partition/slice start sector. will be recorded in stage2 and used * by stage2 to identify partition with boot file system. */ static int get_start_sector(ib_device_t *device) { uint32_t secnum = 0, numsec = 0; int i, pno, rval, log_part = 0; struct mboot *mboot; struct ipart *part = NULL; ext_part_t *epp; struct part_info dkpi; struct extpart_info edkpi; if (device->devtype == IG_DEV_EFI) { struct dk_gpt *vtoc; if (efi_alloc_and_read(device->fd, &vtoc) < 0) return (BC_ERROR); if (device->stage.start == 0) { /* zero size means the fstype must be zfs */ assert(device->target.fstype == IG_FS_ZFS); device->stage.start = vtoc->efi_parts[device->stage.id].p_start; device->stage.size = vtoc->efi_parts[device->stage.id].p_size; device->stage.offset = BBLK_ZFS_BLK_OFF; device->target.offset = BBLK_ZFS_BLK_OFF; } device->target.start = vtoc->efi_parts[device->target.id].p_start; device->target.size = vtoc->efi_parts[device->target.id].p_size; /* with pcfs we always write MBR */ if (device->target.fstype == IG_FS_PCFS) { force_mbr = 1; write_mbr = 1; } efi_free(vtoc); goto found_part; } mboot = (struct mboot *)device->mbr; /* For MBR we have device->stage filled already. */ if (device->devtype == IG_DEV_MBR) { /* MBR partition starts from 0 */ pno = device->target.id - 1; part = (struct ipart *)mboot->parts + pno; if (part->relsect == 0) { (void) fprintf(stderr, gettext("Partition %d of the " "disk has an incorrect offset\n"), device->target.id); return (BC_ERROR); } device->target.start = part->relsect; device->target.size = part->numsect; /* with pcfs we always write MBR */ if (device->target.fstype == IG_FS_PCFS) { force_mbr = 1; write_mbr = 1; } if (device->target.fstype == IG_FS_ZFS) device->target.offset = BBLK_ZFS_BLK_OFF; goto found_part; } /* * Search for Solaris fdisk partition * Get the solaris partition information from the device * and compare the offset of S2 with offset of solaris partition * from fdisk partition table. */ if (ioctl(device->target.fd, DKIOCEXTPARTINFO, &edkpi) < 0) { if (ioctl(device->target.fd, DKIOCPARTINFO, &dkpi) < 0) { (void) fprintf(stderr, gettext("cannot get the " "slice information of the disk\n")); return (BC_ERROR); } else { edkpi.p_start = dkpi.p_start; edkpi.p_length = dkpi.p_length; } } device->target.start = edkpi.p_start; device->target.size = edkpi.p_length; if (device->target.fstype == IG_FS_ZFS) device->target.offset = BBLK_ZFS_BLK_OFF; for (i = 0; i < FD_NUMPART; i++) { part = (struct ipart *)mboot->parts + i; if (part->relsect == 0) { (void) fprintf(stderr, gettext("Partition %d of the " "disk has an incorrect offset\n"), i+1); return (BC_ERROR); } if (edkpi.p_start >= part->relsect && edkpi.p_start < (part->relsect + part->numsect)) { /* Found the partition */ break; } } if (i == FD_NUMPART) { /* No solaris fdisk partitions (primary or logical) */ (void) fprintf(stderr, gettext("Solaris partition not found. " "Aborting operation.\n")); return (BC_ERROR); } /* * We have found a Solaris fdisk partition (primary or extended) * Handle the simple case first: Solaris in a primary partition */ if (!fdisk_is_dos_extended(part->systid)) { device->stage.start = part->relsect; device->stage.size = part->numsect; if (device->target.fstype == IG_FS_ZFS) device->stage.offset = BBLK_ZFS_BLK_OFF; else device->stage.offset = BBLK_BLKLIST_OFF; device->stage.id = i + 1; goto found_part; } /* * Solaris in a logical partition. Find that partition in the * extended part. */ if ((rval = libfdisk_init(&epp, device->path, NULL, FDISK_READ_DISK)) != FDISK_SUCCESS) { switch (rval) { /* * The first 3 cases are not an error per-se, just that * there is no Solaris logical partition */ case FDISK_EBADLOGDRIVE: case FDISK_ENOLOGDRIVE: case FDISK_EBADMAGIC: (void) fprintf(stderr, gettext("Solaris " "partition not found. " "Aborting operation.\n")); return (BC_ERROR); case FDISK_ENOVGEOM: (void) fprintf(stderr, gettext("Could not get " "virtual geometry\n")); return (BC_ERROR); case FDISK_ENOPGEOM: (void) fprintf(stderr, gettext("Could not get " "physical geometry\n")); return (BC_ERROR); case FDISK_ENOLGEOM: (void) fprintf(stderr, gettext("Could not get " "label geometry\n")); return (BC_ERROR); default: (void) fprintf(stderr, gettext("Failed to " "initialize libfdisk.\n")); return (BC_ERROR); } } rval = fdisk_get_solaris_part(epp, &pno, &secnum, &numsec); libfdisk_fini(&epp); if (rval != FDISK_SUCCESS) { /* No solaris logical partition */ (void) fprintf(stderr, gettext("Solaris partition not found. " "Aborting operation.\n")); return (BC_ERROR); } device->stage.start = secnum; device->stage.size = numsec; device->stage.id = pno; log_part = 1; found_part: /* get confirmation for -m */ if (write_mbr && !force_mbr) { (void) fprintf(stdout, gettext("Updating master boot sector " "destroys existing boot managers (if any).\n" "continue (y/n)? ")); if (!yes()) { write_mbr = 0; (void) fprintf(stdout, gettext("master boot sector " "not updated\n")); return (BC_ERROR); } } /* * warn, if illumos in primary partition and loader not in MBR and * partition is not active */ if (device->devtype != IG_DEV_EFI) { if (!log_part && part->bootid != 128 && !write_mbr) { (void) fprintf(stdout, gettext("Solaris fdisk " "partition is inactive.\n"), device->stage.id); } } return (BC_SUCCESS); } static int open_device(char *path) { struct stat statbuf = {0}; int fd = -1; if (nowrite) fd = open(path, O_RDONLY); else fd = open(path, O_RDWR); if (fd == -1) { BOOT_DEBUG("Unable to open %s\n", path); perror("open"); return (-1); } if (fstat(fd, &statbuf) != 0) { BOOT_DEBUG("Unable to stat %s\n", path); perror("stat"); (void) close(fd); return (-1); } if (S_ISCHR(statbuf.st_mode) == 0) { (void) fprintf(stderr, gettext("%s: Not a character device\n"), path); (void) close(fd); return (-1); } return (fd); } static int get_boot_partition(ib_device_t *device, struct mboot *mbr) { struct ipart *part; char *path, *ptr; int i; part = (struct ipart *)mbr->parts; for (i = 0; i < FD_NUMPART; i++) { if (part[i].systid == X86BOOT) break; } /* no X86BOOT, try to use space between MBR and first partition */ if (i == FD_NUMPART) { device->stage.path = strdup(device->path); if (device->stage.path == NULL) { perror(gettext("Memory allocation failure")); return (BC_ERROR); } device->stage.fd = dup(device->fd); device->stage.id = 0; device->stage.devtype = IG_DEV_MBR; device->stage.fstype = IG_FS_NONE; device->stage.start = 0; device->stage.size = part[0].relsect; device->stage.offset = BBLK_BLKLIST_OFF; return (BC_SUCCESS); } if ((path = strdup(device->path)) == NULL) { perror(gettext("Memory allocation failure")); return (BC_ERROR); } ptr = strrchr(path, 'p'); ptr++; *ptr = '\0'; (void) asprintf(&ptr, "%s%d", path, i+1); /* partitions are p1..p4 */ free(path); if (ptr == NULL) { perror(gettext("Memory allocation failure")); return (BC_ERROR); } device->stage.path = ptr; device->stage.fd = open_device(ptr); device->stage.id = i + 1; device->stage.devtype = IG_DEV_MBR; device->stage.fstype = IG_FS_NONE; device->stage.start = part[i].relsect; device->stage.size = part[i].numsect; device->stage.offset = 1; /* leave sector 0 for VBR */ return (BC_SUCCESS); } static int get_boot_slice(ib_device_t *device, struct dk_gpt *vtoc) { uint_t i; char *path, *ptr; for (i = 0; i < vtoc->efi_nparts; i++) { if (vtoc->efi_parts[i].p_tag == V_BOOT) { if ((path = strdup(device->target.path)) == NULL) { perror(gettext("Memory allocation failure")); return (BC_ERROR); } ptr = strrchr(path, 's'); ptr++; *ptr = '\0'; (void) asprintf(&ptr, "%s%d", path, i); free(path); if (ptr == NULL) { perror(gettext("Memory allocation failure")); return (BC_ERROR); } device->stage.path = ptr; device->stage.fd = open_device(ptr); device->stage.id = i; device->stage.devtype = IG_DEV_EFI; device->stage.fstype = IG_FS_NONE; device->stage.start = vtoc->efi_parts[i].p_start; device->stage.size = vtoc->efi_parts[i].p_size; device->stage.offset = 1; /* leave sector 0 for VBR */ return (BC_SUCCESS); } } return (BC_SUCCESS); } static int init_device(ib_device_t *device, char *path) { struct dk_gpt *vtoc; fstyp_handle_t fhdl; const char *fident; char *p; int pathlen = strlen(path); int ret; bzero(device, sizeof (*device)); device->fd = -1; /* whole disk fd */ device->stage.fd = -1; /* bootblock partition fd */ device->target.fd = -1; /* target fs partition fd */ /* basic check, whole disk is not allowed */ if ((p = strrchr(path, '/')) == NULL) p = path; if ((strrchr(p, 'p') == NULL && strrchr(p, 's') == NULL) || (path[pathlen-2] == 'p' && path[pathlen-1] == '0')) { (void) fprintf(stderr, gettext("installing loader to " "whole disk device is not supported\n")); } device->target.path = strdup(path); if (device->target.path == NULL) { perror(gettext("Memory allocation failure")); return (BC_ERROR); } device->path = strdup(path); if (device->path == NULL) { perror(gettext("Memory allocation failure")); return (BC_ERROR); } /* change device name to p0 */ device->path[pathlen - 2] = 'p'; device->path[pathlen - 1] = '0'; if (strstr(device->target.path, "diskette")) { (void) fprintf(stderr, gettext("installing loader to a floppy " "disk is not supported\n")); return (BC_ERROR); } /* Detect if the target device is a pcfs partition. */ if (strstr(device->target.path, "p0:boot")) { (void) fprintf(stderr, gettext("installing loader to x86 boot " "partition is not supported\n")); return (BC_ERROR); } if ((device->fd = open_device(device->path)) == -1) return (BC_ERROR); /* read in the device boot sector. */ if (read(device->fd, device->mbr, SECTOR_SIZE) != SECTOR_SIZE) { (void) fprintf(stderr, gettext("Error reading boot sector\n")); perror("read"); return (BC_ERROR); } device->devtype = IG_DEV_VTOC; if (efi_alloc_and_read(device->fd, &vtoc) >= 0) { ret = get_boot_slice(device, vtoc); device->devtype = IG_DEV_EFI; efi_free(vtoc); if (ret == BC_ERROR) return (BC_ERROR); } else if (device->target.path[pathlen - 2] == 'p') { device->devtype = IG_DEV_MBR; ret = get_boot_partition(device, (struct mboot *)device->mbr); if (ret == BC_ERROR) return (BC_ERROR); } else if (device->target.path[pathlen - 1] == '2') { /* * NOTE: we could relax there and allow zfs boot on * slice 2 for instance, but lets keep traditional limits. */ (void) fprintf(stderr, gettext("raw device must be a root slice (not s2)\n")); return (BC_ERROR); } /* fill stage partition for case there is no boot partition */ if (device->stage.path == NULL) { if ((device->stage.path = strdup(path)) == NULL) { perror(gettext("Memory allocation failure")); return (BC_ERROR); } if (device->devtype == IG_DEV_VTOC) { /* use slice 2 */ device->stage.path[pathlen - 2] = 's'; device->stage.path[pathlen - 1] = '2'; device->stage.id = 2; } else { p = strrchr(device->stage.path, 'p'); if (p == NULL) p = strrchr(device->stage.path, 's'); device->stage.id = atoi(++p); } device->stage.devtype = device->devtype; device->stage.fd = open_device(device->stage.path); } p = strrchr(device->target.path, 'p'); if (p == NULL) p = strrchr(device->target.path, 's'); device->target.id = atoi(++p); if (strcmp(device->stage.path, device->target.path) == 0) device->target.fd = dup(device->stage.fd); else device->target.fd = open_device(device->target.path); if (fstyp_init(device->target.fd, 0, NULL, &fhdl) != 0) return (BC_ERROR); if (fstyp_ident(fhdl, NULL, &fident) != 0) { fstyp_fini(fhdl); (void) fprintf(stderr, gettext("Failed to detect file " "system type\n")); return (BC_ERROR); } /* at this moment non-boot partition has no size set, use this fact */ if (device->devtype == IG_DEV_EFI && strcmp(fident, "zfs") && device->stage.size == 0) { fstyp_fini(fhdl); (void) fprintf(stderr, gettext("Booting %s of EFI labeled " "disks requires the boot partition.\n"), fident); return (BC_ERROR); } if (strcmp(fident, "zfs") == 0) device->target.fstype = IG_FS_ZFS; else if (strcmp(fident, "ufs") == 0) { device->target.fstype = IG_FS_UFS; } else if (strcmp(fident, "pcfs") == 0) { device->target.fstype = IG_FS_PCFS; } else { (void) fprintf(stderr, gettext("File system %s is not " "supported by loader\n"), fident); fstyp_fini(fhdl); return (BC_ERROR); } fstyp_fini(fhdl); /* check for boot partition content */ if (device->stage.size) { if (fstyp_init(device->stage.fd, 0, NULL, &fhdl) != 0) return (BC_ERROR); if (fstyp_ident(fhdl, NULL, &fident) == 0) { (void) fprintf(stderr, gettext("Unexpected %s file " "system on boot partition\n"), fident); fstyp_fini(fhdl); return (BC_ERROR); } fstyp_fini(fhdl); } return (get_start_sector(device)); } static void cleanup_device(ib_device_t *device) { if (device->path) free(device->path); if (device->stage.path) free(device->stage.path); if (device->target.path) free(device->target.path); if (device->fd != -1) (void) close(device->fd); if (device->stage.fd != -1) (void) close(device->stage.fd); if (device->target.fd != -1) (void) close(device->target.fd); bzero(device, sizeof (*device)); } static void cleanup_bootblock(ib_bootblock_t *bblock) { free(bblock->buf); bzero(bblock, sizeof (ib_bootblock_t)); } /* * Propagate the bootblock on the source disk to the destination disk and * version it with 'updt_str' in the process. Since we cannot trust any data * on the attaching disk, we do not perform any specific check on a potential * target extended information structure and we just blindly update. */ static int propagate_bootblock(ib_data_t *src, ib_data_t *dest, char *updt_str) { ib_bootblock_t *src_bblock = &src->bootblock; ib_bootblock_t *dest_bblock = &dest->bootblock; assert(src != NULL); assert(dest != NULL); /* read the stage1 file from source disk */ if (read(src->device.fd, dest->stage1, SECTOR_SIZE) != SECTOR_SIZE) { (void) fprintf(stderr, gettext("cannot read stage1 from %s\n"), src->device.path); return (BC_ERROR); } cleanup_bootblock(dest_bblock); dest_bblock->buf_size = src_bblock->buf_size; dest_bblock->buf = malloc(dest_bblock->buf_size); if (dest_bblock->buf == NULL) { perror(gettext("Memory Allocation Failure")); return (BC_ERROR); } dest_bblock->file = dest_bblock->buf; dest_bblock->file_size = src_bblock->file_size; (void) memcpy(dest_bblock->buf, src_bblock->buf, dest_bblock->buf_size); dest_bblock->mboot = (multiboot_header_t *)(dest_bblock->file + src_bblock->mboot_off); dest_bblock->mboot_off = src_bblock->mboot_off; dest_bblock->extra = (char *)dest_bblock->file + P2ROUNDUP(dest_bblock->file_size, 8); dest_bblock->extra_size = src_bblock->extra_size; (void) fprintf(stdout, gettext("Propagating %s bootblock to %s\n"), src->device.path, dest->device.path); return (commit_to_disk(dest, updt_str)); } static int commit_to_disk(ib_data_t *data, char *update_str) { assert(data != NULL); if (prepare_bootblock(data, update_str) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error updating the bootblock " "image\n")); return (BC_ERROR); } if (prepare_stage1(data) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error updating the stage1 " "image\n")); return (BC_ERROR); } if (write_bootblock(data) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error writing bootblock to " "disk\n")); return (BC_ERROR); } return (write_stage1(data)); } /* * Install a new bootblock on the given device. handle_install() expects argv * to contain 3 parameters (the target device path and the path to the * bootblock. * * Returns: BC_SUCCESS - if the installation is successful * BC_ERROR - if the installation failed * BC_NOUPDT - if no installation was performed because the * version currently installed is more recent than the * supplied one. * */ static int handle_install(char *progname, char **argv) { ib_data_t install_data; ib_bootblock_t *bblock = &install_data.bootblock; char *stage1 = NULL; char *bootblock = NULL; char *device_path = NULL; int ret = BC_ERROR; stage1 = strdup(argv[0]); bootblock = strdup(argv[1]); device_path = strdup(argv[2]); if (!device_path || !bootblock || !stage1) { (void) fprintf(stderr, gettext("Missing parameter")); usage(progname, BC_ERROR); } BOOT_DEBUG("device path: %s, stage1 path: %s bootblock path: %s\n", device_path, stage1, bootblock); bzero(&install_data, sizeof (ib_data_t)); if (init_device(&install_data.device, device_path) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Unable to open device %s\n"), device_path); goto out; } if (read_stage1_from_file(stage1, &install_data) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error opening %s\n"), stage1); goto out_dev; } if (read_bootblock_from_file(bootblock, bblock) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error reading %s\n"), bootblock); goto out_dev; } /* * is_update_necessary() will take care of checking if versioning and/or * forcing the update have been specified. It will also emit a warning * if a non-versioned update is attempted over a versioned bootblock. */ if (!is_update_necessary(&install_data, update_str)) { (void) fprintf(stderr, gettext("bootblock version installed " "on %s is more recent or identical\n" "Use -F to override or install without the -u option\n"), device_path); ret = BC_NOUPDT; goto out_dev; } BOOT_DEBUG("Ready to commit to disk\n"); ret = commit_to_disk(&install_data, update_str); out_dev: cleanup_device(&install_data.device); out: free(stage1); free(bootblock); free(device_path); return (ret); } /* * Retrieves from a device the extended information (einfo) associated to the * file or installed stage2. * Expects one parameter, the device path, in the form: /dev/rdsk/c?[t?]d?s0 * or file name. * 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) { struct stat sb; ib_bootblock_t bblock; ib_device_t device; bblk_einfo_t *einfo; uint8_t flags = 0; char *device_path, *path; int retval = BC_ERROR; int ret; device_path = strdup(argv[0]); if (!device_path) { (void) fprintf(stderr, gettext("Missing parameter")); usage(progname, BC_ERROR); } if (stat(device_path, &sb) == -1) { perror("stat"); goto out; } bzero(&bblock, sizeof (bblock)); bzero(&device, sizeof (device)); BOOT_DEBUG("device path: %s\n", device_path); if (S_ISREG(sb.st_mode) != 0) { path = device_path; ret = read_bootblock_from_file(device_path, &bblock); } else { if (init_device(&device, device_path) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Unable to gather " "device information from %s\n"), device_path); goto out_dev; } ret = read_bootblock_from_disk(&device, &bblock, &path); } if (ret == BC_ERROR) { (void) fprintf(stderr, gettext("Error reading bootblock from " "%s\n"), path); goto out_dev; } if (ret == BC_NOEXTRA) { BOOT_DEBUG("No multiboot header found on %s, unable " "to locate extra information area (old/non versioned " "bootblock?) \n", device_path); (void) fprintf(stderr, gettext("No extended information " "found\n")); retval = BC_NOEINFO; goto out_dev; } einfo = find_einfo(bblock.extra, bblock.extra_size); if (einfo == NULL) { retval = BC_NOEINFO; (void) fprintf(stderr, gettext("No extended information " "found\n")); goto out_dev; } /* Print the extended information. */ if (strip) flags |= EINFO_EASY_PARSE; if (verbose_dump) flags |= EINFO_PRINT_HEADER; print_einfo(flags, einfo, bblock.extra_size); retval = BC_SUCCESS; out_dev: if (S_ISREG(sb.st_mode) == 0) cleanup_device(&device); out: free(device_path); return (retval); } /* * Attempt to mirror (propagate) the current bootblock over the attaching disk. * * Returns: * - BC_SUCCESS (a successful propagation happened) * - BC_ERROR (an error occurred) * - BC_NOEXTRA (it is not possible to dump the current bootblock since * there is no multiboot information) */ static int handle_mirror(char *progname, char **argv) { ib_data_t curr_data; ib_data_t attach_data; ib_device_t *curr_device = &curr_data.device; ib_device_t *attach_device = &attach_data.device; ib_bootblock_t *bblock_curr = &curr_data.bootblock; ib_bootblock_t *bblock_attach = &attach_data.bootblock; bblk_einfo_t *einfo_curr = NULL; char *curr_device_path; char *attach_device_path; char *updt_str = NULL; char *path; int retval = BC_ERROR; int ret; curr_device_path = strdup(argv[0]); attach_device_path = strdup(argv[1]); if (!curr_device_path || !attach_device_path) { (void) fprintf(stderr, gettext("Missing parameter")); usage(progname, BC_ERROR); } BOOT_DEBUG("Current device path is: %s, attaching device path is: " " %s\n", curr_device_path, attach_device_path); bzero(&curr_data, sizeof (ib_data_t)); bzero(&attach_data, sizeof (ib_data_t)); if (init_device(curr_device, curr_device_path) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Unable to gather device " "information from %s (current device)\n"), curr_device_path); goto out_currdev; } if (init_device(attach_device, attach_device_path) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Unable to gather device " "information from %s (attaching device)\n"), attach_device_path); goto out_devs; } ret = read_bootblock_from_disk(curr_device, bblock_curr, &path); if (ret == BC_ERROR) { BOOT_DEBUG("Error reading bootblock from %s\n", path); retval = BC_ERROR; goto out_devs; } if (ret == BC_NOEXTRA) { BOOT_DEBUG("No multiboot header found on %s, unable to retrieve" " the bootblock\n", path); retval = BC_NOEXTRA; goto out_devs; } write_mbr = B_TRUE; force_mbr = B_TRUE; einfo_curr = find_einfo(bblock_curr->extra, bblock_curr->extra_size); if (einfo_curr != NULL) updt_str = einfo_get_string(einfo_curr); retval = propagate_bootblock(&curr_data, &attach_data, updt_str); cleanup_bootblock(bblock_curr); cleanup_bootblock(bblock_attach); out_devs: cleanup_device(attach_device); out_currdev: cleanup_device(curr_device); free(curr_device_path); free(attach_device_path); return (retval); } #define USAGE_STRING "Usage:\t%s [-h|-m|-f|-n|-F|-u verstr] stage1 stage2 " \ "raw-device\n" \ "\t%s -M [-n] raw-device attach-raw-device\n" \ "\t%s [-e|-V] -i raw-device | file\n" #define CANON_USAGE_STR gettext(USAGE_STRING) static void usage(char *progname, int rc) { (void) fprintf(stdout, CANON_USAGE_STR, progname, progname, progname); exit(rc); } int main(int argc, char **argv) { int opt; int params = 3; int ret; char *progname; char **handle_args; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); if (init_yes() < 0) errx(BC_ERROR, gettext(ERR_MSG_INIT_YES), strerror(errno)); /* Determine our name */ progname = basename(argv[0]); while ((opt = getopt(argc, argv, "deFfhiMmnu:V")) != EOF) { switch (opt) { case 'd': boot_debug = B_TRUE; break; case 'e': strip = B_TRUE; break; case 'F': force_update = B_TRUE; break; case 'f': force_mbr = B_TRUE; break; case 'h': usage(progname, BC_SUCCESS); break; case 'i': do_getinfo = B_TRUE; params = 1; break; case 'M': do_mirror_bblk = B_TRUE; params = 2; break; case 'm': write_mbr = B_TRUE; break; case 'n': nowrite = B_TRUE; break; case 'u': do_version = B_TRUE; update_str = strdup(optarg); if (update_str == NULL) { perror(gettext("Memory allocation failure")); exit(BC_ERROR); } break; case 'V': verbose_dump = B_TRUE; break; default: /* fall through to process non-optional args */ break; } } /* check arguments */ if (argc != optind + params) { usage(progname, BC_ERROR); } check_options(progname); handle_args = argv + optind; if (nowrite) (void) fprintf(stdout, gettext("Dry run requested. Nothing will" " be written to disk.\n")); if (do_getinfo) { ret = handle_getinfo(progname, handle_args); } else if (do_mirror_bblk) { ret = handle_mirror(progname, handle_args); } else { ret = handle_install(progname, handle_args); } return (ret); } #define MEANINGLESS_OPT gettext("%s specified but meaningless, ignoring\n") static void check_options(char *progname) { if (do_getinfo && do_mirror_bblk) { (void) fprintf(stderr, gettext("Only one of -M and -i can be " "specified at the same time\n")); usage(progname, 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; } } }