/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "installboot.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 /* * SPARC bootblock installation: * * The bootblock resides in blocks 1 to 15 (disk label is at block 0). * The ZFS boot block is larger than what will fit into these first 7.5K so we * break it up and write the remaining portion into the ZFS provided boot block * region at offset 512K. If versioning is requested, we add a multiboot * header at the end of the bootblock, followed by the extra payload area and * place the extended information structure within the latter. */ 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; static char *update_str; static int tgt_fs_type = TARGET_IS_UFS; char mboot_scan[MBOOT_SCAN_SIZE]; /* Function prototypes. */ static int read_bootblock_from_file(char *, ib_data_t *data); static int read_bootblock_from_disk(int, ib_bootblock_t *); static void add_bootblock_einfo(ib_bootblock_t *, char *); static int prepare_bootblock(ib_data_t *, char *); static int write_zfs_bootblock(ib_data_t *); static int write_bootblock(ib_data_t *); static int open_device(ib_device_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 *); static int read_bootblock_from_file(char *file, ib_data_t *data) { ib_device_t *device = &data->device; ib_bootblock_t *bblock = &data->bootblock; struct stat sb; uint32_t buf_size; int fd = -1; int retval = BC_ERROR; assert(data != 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; } bblock->file_size = sb.st_size; BOOT_DEBUG("bootblock file size is %x\n", bblock->file_size); /* UFS and HSFS bootblocks need to fit in the reserved 7.5K. */ if (!is_zfs(device->type)) { buf_size = P2ROUNDUP(bblock->file_size, SECTOR_SIZE); if (buf_size > BBLK_DATA_RSVD_SIZE) { BOOT_DEBUG("boot block size is bigger than allowed\n"); goto outfd; } } else { buf_size = P2ROUNDUP(bblock->file_size + SECTOR_SIZE, SECTOR_SIZE); if (buf_size > BBLK_DATA_RSVD_SIZE + MBOOT_SCAN_SIZE) { (void) fprintf(stderr, gettext("WARNING, bootblock size" " does not allow to place extended versioning " "information.. skipping\n")); do_version = B_FALSE; } } bblock->buf_size = buf_size; BOOT_DEBUG("bootblock in-memory buffer size is %x\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->file_size) != bblock->file_size) { BOOT_DEBUG("Read from %s failed\n", file); perror("read"); goto outfd; } /* If not on ZFS, we are done here. */ if (!is_zfs(device->type)) { BOOT_DEBUG("Reading of the bootblock done\n"); retval = BC_SUCCESS; goto outfd; } /* * We place the multiboot header right after the file, followed by * the extended information structure. */ bblock->mboot = (multiboot_header_t *)(bblock->file + P2ROUNDUP(bblock->file_size, 8)); bblock->extra = (char *)bblock->mboot + sizeof (multiboot_header_t); BOOT_DEBUG("mboot at %p, extra at %p, buf=%p (size=%d)\n", bblock->mboot, bblock->extra, 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(int dev_fd, ib_bootblock_t *bblock) { char *dest; uint32_t size; uint32_t buf_size; uint32_t mboot_off; multiboot_header_t *mboot; assert(bblock != NULL); assert(dev_fd != -1); /* * The ZFS bootblock is divided in two parts, but the fake multiboot * header can only be in the second part (the one contained in the ZFS * reserved area). */ if (read_in(dev_fd, mboot_scan, sizeof (mboot_scan), BBLK_ZFS_EXTRA_OFF) != BC_SUCCESS) { BOOT_DEBUG("Error reading ZFS reserved 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); /* * 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; dest = bblock->buf; size = BBLK_DATA_RSVD_SIZE; if (read_in(dev_fd, dest, size, SECTOR_SIZE) != BC_SUCCESS) { BOOT_DEBUG("Error reading first %d bytes of the bootblock\n", size); (void) free(bblock->buf); bblock->buf = NULL; return (BC_ERROR); } dest += BBLK_DATA_RSVD_SIZE; size = bblock->buf_size - BBLK_DATA_RSVD_SIZE; if (read_in(dev_fd, dest, size, BBLK_ZFS_EXTRA_OFF) != BC_SUCCESS) { BOOT_DEBUG("Error reading ZFS reserved area the second time\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 + BBLK_DATA_RSVD_SIZE); bblock->extra = (char *)bblock->mboot + sizeof (multiboot_header_t); return (BC_SUCCESS); } static boolean_t is_update_necessary(ib_data_t *data, char *updt_str) { bblk_einfo_t *einfo; bblk_hs_t bblock_hs; ib_bootblock_t bblock_disk; ib_bootblock_t *bblock_file = &data->bootblock; ib_device_t *device = &data->device; int dev_fd = device->fd; assert(data != NULL); assert(device->fd != -1); /* Nothing to do if we are not updating a ZFS bootblock. */ if (!is_zfs(device->type)) return (B_TRUE); bzero(&bblock_disk, sizeof (ib_bootblock_t)); if (read_bootblock_from_disk(dev_fd, &bblock_disk) != BC_SUCCESS) { BOOT_DEBUG("Unable to read bootblock from %s\n", device->path); return (B_TRUE); } einfo = find_einfo(bblock_disk.extra); if (einfo == NULL) { BOOT_DEBUG("No extended information available\n"); return (B_TRUE); } if (!do_version || updt_str == NULL) { (void) fprintf(stdout, "WARNING: target device %s has a " "versioned 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); } static int prepare_bootblock(ib_data_t *data, char *updt_str) { ib_device_t *device = &data->device; ib_bootblock_t *bblock = &data->bootblock; multiboot_header_t *mboot; assert(data != NULL); /* Nothing to do if we are not on ZFS. */ if (!is_zfs(device->type)) return (BC_SUCCESS); /* * Write the fake multiboot structure followed by the extra information * data. Both mboot and extra pointers have already been filled up to * point to the right location in the buffer. We prepare the fake * multiboot regardless if versioning was requested or not because * we need it for mirroring support. */ assert(bblock->mboot != NULL); assert(bblock->extra != NULL); mboot = bblock->mboot; mboot->magic = MB_HEADER_MAGIC; mboot->flags = MB_HEADER_FLAGS_64; mboot->checksum = -(mboot->flags + mboot->magic); /* * Flags include the AOUT_KLUDGE and we use the extra members to specify * the size of the bootblock. */ mboot->header_addr = bblock->mboot_off; mboot->load_addr = 0; mboot->load_end_addr = bblock->file_size; /* * Now that we have the mboot header in place, we can add the extended * versioning information. Since the multiboot header has been placed * after the file image, the hashing will still reflect the one of the * file on the disk. */ if (do_version) add_bootblock_einfo(bblock, updt_str); return (BC_SUCCESS); } static int write_zfs_bootblock(ib_data_t *data) { ib_device_t *device = &data->device; ib_bootblock_t *bblock = &data->bootblock; char *bufptr; uint32_t size; assert(data != NULL); assert(device->fd != -1); /* * In the ZFS case we actually perform two different steps: * - write the first 15 blocks of the bootblock to the reserved disk * blocks. * - write the remaining blocks in the ZFS reserved area at offset * 512K. */ bufptr = bblock->buf; size = BBLK_DATA_RSVD_SIZE; if (write_out(device->fd, bufptr, size, SECTOR_SIZE) != BC_SUCCESS) { BOOT_DEBUG("Error writing first 15 blocks of %s\n", device->path); perror("write"); return (BC_ERROR); } bufptr += BBLK_DATA_RSVD_SIZE; size = bblock->buf_size - BBLK_DATA_RSVD_SIZE; if (write_out(device->fd, bufptr, size, BBLK_ZFS_EXTRA_OFF) != BC_SUCCESS) { BOOT_DEBUG("Error writing the second part of ZFS bootblock " "to %s at offset %d\n", device->path, BBLK_ZFS_EXTRA_OFF); return (BC_ERROR); } return (BC_SUCCESS); } static int write_bootblock(ib_data_t *data) { ib_device_t *device = &data->device; ib_bootblock_t *bblock = &data->bootblock; int ret; assert(data != NULL); /* * If we are on UFS or HSFS we simply write out to the reserved * blocks (1 to 15) the boot block. */ if (!is_zfs(device->type)) { if (write_out(device->fd, bblock->buf, bblock->buf_size, SECTOR_SIZE) != BC_SUCCESS) { BOOT_DEBUG("Error writing bootblock to %s\n", device->path); return (BC_ERROR); } else { return (BC_SUCCESS); } } else { ret = write_zfs_bootblock(data); return (ret); } } static int open_device(ib_device_t *device) { struct stat statbuf; device->fd = open(device->path, O_RDWR); if (device->fd == -1) { BOOT_DEBUG("Unable to open %s\n", device->path); perror("open"); return (BC_ERROR); } if (fstat(device->fd, &statbuf) != 0) { BOOT_DEBUG("Unable to stat %s\n", device->path); perror("stat"); (void) close(device->fd); return (BC_ERROR); } if (S_ISCHR(statbuf.st_mode) == 0) { (void) fprintf(stderr, gettext("%s: Not a character device\n"), device->path); return (BC_ERROR); } return (BC_SUCCESS); } static int init_device(ib_device_t *device, char *path) { bzero(device, sizeof (*device)); device->fd = -1; device->path = strdup(path); if (path == NULL) { perror(gettext("Memory allocation failure")); return (BC_ERROR); } device->type = tgt_fs_type; if (open_device(device) != BC_SUCCESS) return (BC_ERROR); return (BC_SUCCESS); } static void cleanup_device(ib_device_t *device) { free(device->path); bzero(device, sizeof (*device)); if (device->fd != -1) (void) close(device->fd); } 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; uint32_t buf_size; assert(src != NULL); assert(dest != NULL); cleanup_bootblock(dest_bblock); if (updt_str != NULL) { do_version = B_TRUE; } else { do_version = B_FALSE; } buf_size = src_bblock->file_size + SECTOR_SIZE; dest_bblock->buf_size = P2ROUNDUP(buf_size, SECTOR_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->file, src_bblock->file, dest_bblock->file_size); dest_bblock->mboot = (multiboot_header_t *)(dest_bblock->file + P2ROUNDUP(dest_bblock->file_size, 8)); dest_bblock->extra = (char *)dest_bblock->mboot + sizeof (multiboot_header_t); (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 (write_bootblock(data) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error writing bootblock to " "disk\n")); return (BC_ERROR); } return (BC_SUCCESS); } /* * Install a new bootblock on the given device. handle_install() expects argv * to contain 2 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; char *bootblock = NULL; char *device_path = NULL; int ret = BC_ERROR; bootblock = strdup(argv[0]); device_path = strdup(argv[1]); if (!device_path || !bootblock) { (void) fprintf(stderr, gettext("Missing parameter")); usage(progname); goto out; } BOOT_DEBUG("device path: %s, bootblock file path: %s\n", device_path, 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_bootblock_from_file(bootblock, &install_data) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Error reading %s\n"), bootblock); goto out_dev; } /* Versioning is only supported for the ZFS bootblock. */ if (do_version && !is_zfs(install_data.device.type)) { (void) fprintf(stderr, gettext("Versioning is only supported on" " ZFS... skipping.\n")); do_version = B_FALSE; } /* * is_update_necessary() will take care of checking if versioning and/or * forcing the update have been specified. It will also emit a warning * if a non-versioned update is attempted over a versioned bootblock. */ if (!is_update_necessary(&install_data, update_str)) { (void) fprintf(stderr, gettext("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(bootblock); free(device_path); return (ret); } /* * Retrieves from a device the extended information (einfo) associated to the * installed bootblock. * 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) { ib_data_t data; ib_bootblock_t *bblock = &data.bootblock; ib_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 (ib_data_t)); BOOT_DEBUG("device path: %s\n", device_path); if (init_device(device, device_path) != BC_SUCCESS) { (void) fprintf(stderr, gettext("Unable to gather device " "information from %s\n"), device_path); goto out_dev; } if (!is_zfs(device->type)) { (void) fprintf(stderr, gettext("Versioning only supported on " "ZFS\n")); goto out_dev; } ret = read_bootblock_from_disk(device->fd, bblock); if (ret == BC_ERROR) { (void) fprintf(stderr, gettext("Error reading bootblock from " "%s\n"), device_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); 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 = bblock->buf_size - P2ROUNDUP(bblock->file_size, 8) - sizeof (multiboot_header_t); print_einfo(flags, einfo, size); retval = BC_SUCCESS; out_dev: cleanup_device(&data.device); out: free(device_path); return (retval); } /* * Attempt to mirror (propagate) the current 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; 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 (ib_data_t)); bzero(&attach_data, sizeof (ib_data_t)); if (tgt_fs_type != TARGET_IS_ZFS) { (void) fprintf(stderr, gettext("Mirroring is only supported on " "ZFS\n")); return (BC_ERROR); } 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->fd, bblock_curr); if (ret == BC_ERROR) { BOOT_DEBUG("Error reading bootblock 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 retrieve" " the bootblock\n", curr_device->path); retval = BC_NOEXTRA; goto out_devs; } einfo_curr = find_einfo(bblock_curr->extra); 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); out: free(curr_device_path); free(attach_device_path); return (retval); } #define USAGE_STRING "Usage: %s [-h|-f|-F fstype|-u verstr] bootblk " \ "raw-device\n" \ "\t%s [-e|-V] -i -F zfs raw-device\n" \ "\t%s -M -F zfs raw-device attach-raw-device\n" \ "\tfstype is one of: 'ufs', 'hsfs' or 'zfs'\n" #define CANON_USAGE_STR gettext(USAGE_STRING) static void usage(char *progname) { (void) fprintf(stdout, CANON_USAGE_STR, progname, progname, progname); } int main(int argc, char **argv) { int opt; int params = 2; int ret; char *progname; char **handle_args; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); while ((opt = getopt(argc, argv, "F:efiVMndhu:")) != EOF) { switch (opt) { case 'F': if (strcmp(optarg, "ufs") == 0) { tgt_fs_type = TARGET_IS_UFS; } else if (strcmp(optarg, "hsfs") == 0) { tgt_fs_type = TARGET_IS_HSFS; } else if (strcmp(optarg, "zfs") == 0) { tgt_fs_type = TARGET_IS_ZFS; } else { (void) fprintf(stderr, gettext("Wrong " "filesystem specified\n\n")); usage(argv[0]); exit(BC_ERROR); } break; case 'e': strip = B_TRUE; break; case 'f': force_update = B_TRUE; break; case 'V': verbose_dump = B_TRUE; break; case 'i': do_getinfo = B_TRUE; params = 1; break; case 'u': do_version = B_TRUE; update_str = malloc(strlen(optarg) + 1); if (update_str == NULL) { perror(gettext("Memory allocation failure")); exit(BC_ERROR); } (void) strlcpy(update_str, optarg, strlen(optarg) + 1); break; case 'M': do_mirror_bblk = B_TRUE; break; case 'h': usage(argv[0]); exit(BC_SUCCESS); break; case 'd': boot_debug = B_TRUE; break; case 'n': nowrite = B_TRUE; break; default: /* fall through to process non-optional args */ break; } } /* check arguments */ if (argc != optind + params) { usage(argv[0]); exit(BC_ERROR); } progname = argv[0]; handle_args = argv + optind; /* check options. */ 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 (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); }