view usr/src/cmd/boot/installgrub/installgrub.c @ 13836:37bf491c434c

3027 installgrub can segfault when encountering bogus data on disk Reviewed by: Garrett D'Amore <garrett@damore.org> Reviewed by: Albert Lee <trisk@nexenta.com> Approved by: Richard Lowe <richlowe@richlowe.net>
author Hans Rosenfeld <hans.rosenfeld@nexenta.com>
date Thu, 02 Aug 2012 04:58:41 -0500
parents e5889df1eaac
children
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2012 Milan Jurik. All rights reserved.
 * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
 */

#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <malloc.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <libintl.h>
#include <locale.h>
#include <errno.h>
#include <libfdisk.h>
#include <stdarg.h>
#include <assert.h>

#include <sys/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 "message.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

/*
 * 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 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	opt;
	int	params = 3;
	int	ret;
	char	**handle_args;
	char	*progname;

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	/*
	 * retro-compatibility: installing the bootblock is the default
	 * and there is no switch for it.
	 */
	do_install = B_TRUE;

	while ((opt = getopt(argc, argv, "dVMFfmneiu:")) != EOF) {
		switch (opt) {
		case 'm':
			write_mbr = B_TRUE;
			break;
		case 'n':
			nowrite = B_TRUE;
			break;
		case 'f':
			force_mbr = B_TRUE;
			break;
		case 'i':
			do_getinfo = B_TRUE;
			do_install = B_FALSE;
			params = 1;
			break;
		case 'V':
			verbose_dump = B_TRUE;
			break;
		case 'd':
			boot_debug = B_TRUE;
			break;
		case 'F':
			force_update = B_TRUE;
			break;
		case 'e':
			strip = B_TRUE;
			break;
		case 'M':
			do_mirror_bblk = B_TRUE;
			do_install = B_FALSE;
			params = 2;
			break;
		case 'u':
			do_version = B_TRUE;

			update_str = malloc(strlen(optarg) + 1);
			if (update_str == NULL) {
				(void) fprintf(stderr, gettext("Unable to "
				    "allocate memory\n"));
				exit(BC_ERROR);
			}
			(void) strlcpy(update_str, optarg, strlen(optarg) + 1);
			break;
		default:
			/* fall through to process non-optional args */
			break;
		}
	}

	/* check arguments */
	if (argc != optind + params) {
		usage(argv[0]);
		exit(BC_ERROR);
	}

	/*
	 * clean up options (and bail out if an unrecoverable combination is
	 * requested.
	 */
	progname = argv[0];
	check_options(progname);
	handle_args = argv + optind;

	if (nowrite)
		(void) fprintf(stdout, DRY_RUN);

	if (do_getinfo) {
		ret = handle_getinfo(progname, handle_args);
	} else if (do_mirror_bblk) {
		ret = handle_mirror(progname, handle_args);
	} else {
		ret = handle_install(progname, handle_args);
	}
	return (ret);
}

#define	MEANINGLESS_OPT	gettext("%s specified but meaningless, ignoring\n")
static void
check_options(char *progname)
{
	if (do_getinfo && do_mirror_bblk) {
		(void) fprintf(stderr, gettext("Only one of -M and -i can be "
		    "specified at the same time\n"));
		usage(progname);
		exit(BC_ERROR);
	}

	if (do_mirror_bblk) {
		/*
		 * -u and -F may actually reflect a user intent that is not
		 * correct with this command (mirror can be interpreted
		 * "similar" to install. Emit a message and continue.
		 * -e and -V have no meaning, be quiet here and only report the
		 * incongruence if a debug output is requested.
		 */
		if (do_version) {
			(void) fprintf(stderr, MEANINGLESS_OPT, "-u");
			do_version = B_FALSE;
		}
		if (force_update) {
			(void) fprintf(stderr, MEANINGLESS_OPT, "-F");
			force_update = B_FALSE;
		}
		if (strip || verbose_dump) {
			BOOT_DEBUG(MEANINGLESS_OPT, "-e|-V");
			strip = B_FALSE;
			verbose_dump = B_FALSE;
		}
	}

	if (do_getinfo) {
		if (write_mbr || force_mbr || do_version || force_update) {
			BOOT_DEBUG(MEANINGLESS_OPT, "-m|-f|-u|-F");
			write_mbr = force_mbr = do_version = B_FALSE;
			force_update = B_FALSE;
		}
	}
}

/*
 * Install a new stage1/stage2 pair on the specified device. handle_install()
 * expects argv to contain 3 parameters (the path to stage1, the path to stage2,
 * the target device).
 *
 * Returns:	BC_SUCCESS - if the installation is successful
 *		BC_ERROR   - if the installation failed
 *		BC_NOUPDT  - if no installation was performed because the GRUB
 *		             version currently installed is more recent than the
 *			     supplied one.
 *
 */
static int
handle_install(char *progname, char **argv)
{
	ig_data_t	install_data;
	char		*stage1_path = NULL;
	char		*stage2_path = NULL;
	char		*device_path = NULL;
	int		ret = BC_ERROR;

	stage1_path = strdup(argv[0]);
	stage2_path = strdup(argv[1]);
	device_path = strdup(argv[2]);

	bzero(&install_data, sizeof (ig_data_t));

	if (!stage1_path || !stage2_path || !device_path) {
		(void) fprintf(stderr, gettext("Missing parameter"));
		usage(progname);
		goto out;
	}

	BOOT_DEBUG("stage1 path: %s, stage2 path: %s, device: %s\n",
	    stage1_path, stage2_path, device_path);

	if (init_device(&install_data.device, device_path) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Unable to gather device "
		    "information for %s\n"), device_path);
		goto out;
	}

	/* read in stage1 and stage2. */
	if (read_stage1_from_file(stage1_path, &install_data) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Error opening %s\n"),
		    stage1_path);
		goto out_dev;
	}

	if (read_stage2_from_file(stage2_path, &install_data) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Error opening %s\n"),
		    stage2_path);
		goto out_dev;
	}

	/* We do not support versioning on PCFS. */
	if (is_bootpar(install_data.device.type) && do_version)
		do_version = B_FALSE;

	/*
	 * is_update_necessary() will take care of checking if versioning and/or
	 * forcing the update have been specified. It will also emit a warning
	 * if a non-versioned update is attempted over a versioned bootblock.
	 */
	if (!is_update_necessary(&install_data, update_str)) {
		(void) fprintf(stderr, gettext("GRUB version installed "
		    "on %s is more recent or identical\n"
		    "Use -F to override or install without the -u option\n"),
		    device_path);
		ret = BC_NOUPDT;
		goto out_dev;
	}
	/*
	 * We get here if:
	 * - the installed GRUB version is older than the one about to be
	 *   installed.
	 * - no versioning string has been passed through the command line.
	 * - a forced update is requested (-F).
	 */
	BOOT_DEBUG("Ready to commit to disk\n");
	ret = commit_to_disk(&install_data, update_str);

out_dev:
	cleanup_device(&install_data.device);
out:
	free(stage1_path);
	free(stage2_path);
	free(device_path);
	return (ret);
}

/*
 * Retrieves from a device the extended information (einfo) associated to the
 * installed stage2.
 * Expects one parameter, the device path, in the form: /dev/rdsk/c?[t?]d?s0.
 * Returns:
 *        - BC_SUCCESS (and prints out einfo contents depending on 'flags')
 *	  - BC_ERROR (on error)
 *        - BC_NOEINFO (no extended information available)
 */
static int
handle_getinfo(char *progname, char **argv)
{
	ig_data_t	data;
	ig_stage2_t	*stage2 = &data.stage2;
	ig_device_t	*device = &data.device;
	bblk_einfo_t	*einfo;
	uint8_t		flags = 0;
	uint32_t	size;
	char		*device_path;
	int		retval = BC_ERROR;
	int		ret;

	device_path = strdup(argv[0]);
	if (!device_path) {
		(void) fprintf(stderr, gettext("Missing parameter"));
		usage(progname);
		goto out;
	}

	bzero(&data, sizeof (ig_data_t));
	BOOT_DEBUG("device path: %s\n", device_path);

	if (init_device(device, device_path) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Unable to gather device "
		    "information for %s\n"), device_path);
		goto out_dev;
	}

	if (is_bootpar(device->type)) {
		(void) fprintf(stderr, gettext("Versioning not supported on "
		    "PCFS\n"));
		goto out_dev;
	}

	ret = read_stage2_from_disk(device->part_fd, stage2);
	if (ret == BC_ERROR) {
		(void) fprintf(stderr, gettext("Error reading stage2 from "
		    "%s\n"), device_path);
		goto out_dev;
	}

	if (ret == BC_NOEXTRA) {
		(void) fprintf(stdout, gettext("No multiboot header found on "
		    "%s, unable to locate extra information area\n"),
		    device_path);
		retval = BC_NOEINFO;
		goto out_dev;
	}

	einfo = find_einfo(stage2->extra, stage2->extra_size);
	if (einfo == NULL) {
		retval = BC_NOEINFO;
		(void) fprintf(stderr, gettext("No extended information "
		    "found\n"));
		goto out_dev;
	}

	/* Print the extended information. */
	if (strip)
		flags |= EINFO_EASY_PARSE;
	if (verbose_dump)
		flags |= EINFO_PRINT_HEADER;

	size = stage2->buf_size - P2ROUNDUP(stage2->file_size, 8);
	print_einfo(flags, einfo, size);
	retval = BC_SUCCESS;

out_dev:
	cleanup_device(&data.device);
out:
	free(device_path);
	return (retval);
}

/*
 * Attempt to mirror (propagate) the current stage2 over the attaching disk.
 *
 * Returns:
 *	- BC_SUCCESS (a successful propagation happened)
 *	- BC_ERROR (an error occurred)
 *	- BC_NOEXTRA (it is not possible to dump the current bootblock since
 *			there is no multiboot information)
 */
static int
handle_mirror(char *progname, char **argv)
{
	ig_data_t	curr_data;
	ig_data_t	attach_data;
	ig_device_t	*curr_device = &curr_data.device;
	ig_device_t	*attach_device = &attach_data.device;
	ig_stage2_t	*stage2_curr = &curr_data.stage2;
	ig_stage2_t	*stage2_attach = &attach_data.stage2;
	bblk_einfo_t	*einfo_curr = NULL;
	char		*curr_device_path;
	char		*attach_device_path;
	char		*updt_str = NULL;
	int		retval = BC_ERROR;
	int		ret;

	curr_device_path = strdup(argv[0]);
	attach_device_path = strdup(argv[1]);

	if (!curr_device_path || !attach_device_path) {
		(void) fprintf(stderr, gettext("Missing parameter"));
		usage(progname);
		goto out;
	}
	BOOT_DEBUG("Current device path is: %s, attaching device path is: "
	    " %s\n", curr_device_path, attach_device_path);

	bzero(&curr_data, sizeof (ig_data_t));
	bzero(&attach_data, sizeof (ig_data_t));

	if (init_device(curr_device, curr_device_path) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Unable to gather device "
		    "information for %s (current device)\n"), curr_device_path);
		goto out_currdev;
	}

	if (init_device(attach_device, attach_device_path) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Unable to gather device "
		    "information for %s (attaching device)\n"),
		    attach_device_path);
		goto out_devs;
	}

	if (is_bootpar(curr_device->type) || is_bootpar(attach_device->type)) {
		(void) fprintf(stderr, gettext("boot block mirroring is not "
		    "supported on PCFS\n"));
		goto out_devs;
	}

	ret = read_stage2_from_disk(curr_device->part_fd, stage2_curr);
	if (ret == BC_ERROR) {
		BOOT_DEBUG("Error reading first stage2 blocks from %s\n",
		    curr_device->path);
		retval = BC_ERROR;
		goto out_devs;
	}

	if (ret == BC_NOEXTRA) {
		BOOT_DEBUG("No multiboot header found on %s, unable to grab "
		    "stage2\n", curr_device->path);
		retval = BC_NOEXTRA;
		goto out_devs;
	}

	einfo_curr = find_einfo(stage2_curr->extra, stage2_curr->extra_size);
	if (einfo_curr != NULL)
		updt_str = einfo_get_string(einfo_curr);

	write_mbr = B_TRUE;
	force_mbr = B_TRUE;
	retval = propagate_bootblock(&curr_data, &attach_data, updt_str);
	cleanup_stage2(stage2_curr);
	cleanup_stage2(stage2_attach);

out_devs:
	cleanup_device(attach_device);
out_currdev:
	cleanup_device(curr_device);
out:
	free(curr_device_path);
	free(attach_device_path);
	return (retval);
}

static int
commit_to_disk(ig_data_t *install, char *updt_str)
{
	assert(install != NULL);
	/*
	 * vanilla stage1 and stage2 need to be updated at runtime.
	 * Update stage2 before stage1 because stage1 needs to know the first
	 * sector stage2 will be written to.
	 */
	if (prepare_stage2(install, updt_str) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Error building stage2\n"));
		return (BC_ERROR);
	}
	if (prepare_stage1(install) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Error building stage1\n"));
		return (BC_ERROR);
	}

	/* Write stage2 out to disk. */
	if (write_stage2(install) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Error writing stage2 to "
		    "disk\n"));
		return (BC_ERROR);
	}

	/* Write stage1 to disk and, if requested, to the MBR. */
	if (write_stage1(install) != BC_SUCCESS) {
		(void) fprintf(stderr, gettext("Error writing stage1 to "
		    "disk\n"));
		return (BC_ERROR);
	}

	return (BC_SUCCESS);
}

/*
 * Propagate the bootblock on the source disk to the destination disk and
 * version it with 'updt_str' in the process. Since we cannot trust any data
 * on the attaching disk, we do not perform any specific check on a potential
 * target extended information structure and we just blindly update.
 */
static int
propagate_bootblock(ig_data_t *source, ig_data_t *target, char *updt_str)
{
	ig_device_t	*src_device = &source->device;
	ig_device_t	*dest_device = &target->device;
	ig_stage2_t	*src_stage2 = &source->stage2;
	ig_stage2_t	*dest_stage2 = &target->stage2;
	uint32_t	buf_size;
	int		retval;

	assert(source != NULL);
	assert(target != NULL);

	/* read in stage1 from the source disk. */
	if (read_stage1_from_disk(src_device->part_fd, target->stage1_buf)
	    != BC_SUCCESS)
		return (BC_ERROR);

	/* Prepare target stage2 for commit_to_disk. */
	cleanup_stage2(dest_stage2);

	if (updt_str != NULL)
		do_version = B_TRUE;
	else
		do_version = B_FALSE;

	buf_size = src_stage2->file_size + SECTOR_SIZE;

	dest_stage2->buf_size = P2ROUNDUP(buf_size, SECTOR_SIZE);
	dest_stage2->buf = malloc(dest_stage2->buf_size);
	if (dest_stage2->buf == NULL) {
		perror(gettext("Memory allocation failed"));
		return (BC_ERROR);
	}
	dest_stage2->file = dest_stage2->buf;
	dest_stage2->file_size = src_stage2->file_size;
	memcpy(dest_stage2->file, src_stage2->file, dest_stage2->file_size);
	dest_stage2->extra = dest_stage2->buf +
	    P2ROUNDUP(dest_stage2->file_size, 8);

	/* If we get down here we do have a mboot structure. */
	assert(src_stage2->mboot);

	dest_stage2->mboot_off = src_stage2->mboot_off;
	dest_stage2->mboot = (multiboot_header_t *)(dest_stage2->buf +
	    dest_stage2->mboot_off);

	(void) fprintf(stdout, gettext("Propagating %s stage1/stage2 to %s\n"),
	    src_device->path, dest_device->path);
	retval = commit_to_disk(target, updt_str);

	return (retval);
}

/*
 * open the device and fill the various members of ig_device_t.
 */
static int
init_device(ig_device_t *device, char *path)
{
	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;
		}
	}

	/*
	 * Search for Solaris fdisk partition
	 * Get the solaris partition information from the device
	 * and compare the offset of S2 with offset of solaris partition
	 * from fdisk partition table.
	 */
	if (ioctl(device->part_fd, DKIOCEXTPARTINFO, &edkpi) < 0) {
		if (ioctl(device->part_fd, DKIOCPARTINFO, &dkpi) < 0) {
			(void) fprintf(stderr, PART_FAIL);
			return (BC_ERROR);
		} else {
			edkpi.p_start = dkpi.p_start;
		}
	}

	for (i = 0; i < FD_NUMPART; i++) {
		part = (struct ipart *)mboot->parts + i;

		if (part->relsect == 0) {
			(void) fprintf(stderr, BAD_PART, i);
			return (BC_ERROR);
		}

		if (edkpi.p_start >= part->relsect &&
		    edkpi.p_start < (part->relsect + part->numsect)) {
			/* Found the partition */
			break;
		}
	}

	if (i == FD_NUMPART) {
		/* No solaris fdisk partitions (primary or logical) */
		(void) fprintf(stderr, NOSOLPAR);
		return (BC_ERROR);
	}

	/*
	 * We have found a Solaris fdisk partition (primary or extended)
	 * Handle the simple case first: Solaris in a primary partition
	 */
	if (!fdisk_is_dos_extended(part->systid)) {
		device->start_sector = part->relsect;
		device->partition = i;
		goto found_part;
	}

	/*
	 * Solaris in a logical partition. Find that partition in the
	 * extended part.
	 */
	if ((rval = libfdisk_init(&epp, device->path_p0, NULL, FDISK_READ_DISK))
	    != FDISK_SUCCESS) {
		switch (rval) {
			/*
			 * The first 3 cases are not an error per-se, just that
			 * there is no Solaris logical partition
			 */
			case FDISK_EBADLOGDRIVE:
			case FDISK_ENOLOGDRIVE:
			case FDISK_EBADMAGIC:
				(void) fprintf(stderr, NOSOLPAR);
				return (BC_ERROR);
			case FDISK_ENOVGEOM:
				(void) fprintf(stderr, NO_VIRT_GEOM);
				return (BC_ERROR);
			case FDISK_ENOPGEOM:
				(void) fprintf(stderr, NO_PHYS_GEOM);
				return (BC_ERROR);
			case FDISK_ENOLGEOM:
				(void) fprintf(stderr, NO_LABEL_GEOM);
				return (BC_ERROR);
			default:
				(void) fprintf(stderr, LIBFDISK_INIT_FAIL);
				return (BC_ERROR);
		}
	}

	rval = fdisk_get_solaris_part(epp, &pno, &secnum, &numsec);
	libfdisk_fini(&epp);
	if (rval != FDISK_SUCCESS) {
		/* No solaris logical partition */
		(void) fprintf(stderr, NOSOLPAR);
		return (BC_ERROR);
	}

	device->start_sector = secnum;
	device->partition = pno - 1;
	log_part = 1;

found_part:
	/* get confirmation for -m */
	if (write_mbr && !force_mbr) {
		(void) fprintf(stdout, MBOOT_PROMPT);
		if (getchar() != 'y') {
			write_mbr = 0;
			(void) fprintf(stdout, MBOOT_NOT_UPDATED);
			return (BC_ERROR);
		}
	}

	/*
	 * Currently if Solaris is in an extended partition we need to
	 * write GRUB to the MBR. Check for this.
	 */
	if (log_part && !write_mbr) {
		(void) fprintf(stdout, gettext("Installing Solaris on an "
		    "extended partition... forcing MBR update\n"));
		write_mbr = 1;
	}

	/*
	 * warn, if Solaris in primary partition and GRUB not in MBR and
	 * partition is not active
	 */
	if (!log_part && part->bootid != 128 && !write_mbr) {
		(void) fprintf(stdout, SOLPAR_INACTIVE, device->partition + 1);
	}

	return (BC_SUCCESS);
}

static int
get_disk_fd(ig_device_t *device)
{
	int	i;
	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';
	}

	if (nowrite)
		device->disk_fd = open(device->path, O_RDONLY);
	else
		device->disk_fd = open(device->path, O_RDWR);

	device->path_p0 = strdup(device->path);
	if (device->path_p0 == NULL) {
		perror("strdup");
		return (BC_ERROR);
	}

	if (is_bootpar(device->type)) {
		end[2] = save[0];
	} else {
		device->path[i - 2] = save[0];
		device->path[i - 1] = save[1];
	}

	if (device->disk_fd == -1) {
		perror("open");
		return (BC_ERROR);
	}

	return (BC_SUCCESS);
}

static void
prepare_fake_multiboot(ig_stage2_t *stage2)
{
	multiboot_header_t	*mboot;

	assert(stage2 != NULL);
	assert(stage2->mboot != NULL);
	assert(stage2->buf != NULL);

	mboot = stage2->mboot;

	/*
	 * Currently we expect find_multiboot() to have located a multiboot
	 * header with the AOUT kludge flag set.
	 */
	assert(mboot->flags & BB_MBOOT_AOUT_FLAG);

	/* Insert the information necessary to locate stage2. */
	mboot->header_addr = stage2->mboot_off;
	mboot->load_addr = 0;
	mboot->load_end_addr = stage2->file_size;
}

static void
add_stage2_einfo(ig_stage2_t *stage2, char *updt_str)
{
	bblk_hs_t	hs;
	uint32_t	avail_space;

	assert(stage2 != NULL);

	/* Fill bootblock hashing source information. */
	hs.src_buf = (unsigned char *)stage2->file;
	hs.src_size = stage2->file_size;
	/* How much space for the extended information structure? */
	avail_space = stage2->buf_size - P2ROUNDUP(stage2->file_size, 8);
	add_einfo(stage2->extra, updt_str, &hs, avail_space);
}


static int
write_stage2(ig_data_t *install)
{
	ig_device_t		*device = &install->device;
	ig_stage2_t		*stage2 = &install->stage2;
	off_t			offset;

	assert(install != NULL);

	if (is_bootpar(device->type)) {
		/*
		 * stage2 is already on the filesystem, we only need to update
		 * the first two blocks (that we have modified during
		 * prepare_stage2())
		 */
		if (write_out(device->part_fd, stage2->file, SECTOR_SIZE,
		    stage2->pcfs_first_sectors[0] * SECTOR_SIZE)
		    != BC_SUCCESS ||
		    write_out(device->part_fd, stage2->file + SECTOR_SIZE,
		    SECTOR_SIZE, stage2->pcfs_first_sectors[1] * SECTOR_SIZE)
		    != BC_SUCCESS) {
			(void) fprintf(stderr, WRITE_FAIL_STAGE2);
			return (BC_ERROR);
		}
		(void) fprintf(stdout, WRITE_STAGE2_PCFS);
		return (BC_SUCCESS);
	}

	/*
	 * For disk, write stage2 starting at STAGE2_BLKOFF sector.
	 * Note that we use stage2->buf rather than stage2->file, because we
	 * may have extended information after the latter.
	 */
	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 int
write_stage1(ig_data_t *install)
{
	ig_device_t	*device = &install->device;

	assert(install != NULL);

	if (write_out(device->part_fd, install->stage1_buf,
	    sizeof (install->stage1_buf), 0) != BC_SUCCESS) {
		(void) fprintf(stdout, WRITE_FAIL_PBOOT);
		perror("write");
		return (BC_ERROR);
	}

	/* Simulate "old" installgrub output. */
	(void) fprintf(stdout, WRITE_PBOOT, device->partition,
	    device->start_sector);

	if (write_mbr) {
		if (write_out(device->disk_fd, install->stage1_buf,
		    sizeof (install->stage1_buf), 0) != BC_SUCCESS) {
			(void) fprintf(stdout, WRITE_FAIL_BOOTSEC);
			perror("write");
			return (BC_ERROR);
		}
		/* Simulate "old" installgrub output. */
		(void) fprintf(stdout, WRITE_MBOOT);
	}

	return (BC_SUCCESS);
}

#define	USAGE_STRING	"%s [-m|-f|-n|-F|-u verstr] stage1 stage2 device\n"    \
			"%s -M [-n] device1 device2\n"			       \
			"%s [-V|-e] -i device\n"			       \

#define	CANON_USAGE_STR	gettext(USAGE_STRING)

static void
usage(char *progname)
{
	(void) fprintf(stdout, CANON_USAGE_STR, progname, progname, progname);
}


static int
read_stage1_from_file(char *path, ig_data_t *dest)
{
	int	fd;

	assert(dest);

	/* read the stage1 file from filesystem */
	fd = open(path, O_RDONLY);
	if (fd == -1 ||
	    read(fd, dest->stage1_buf, SECTOR_SIZE) != SECTOR_SIZE) {
		(void) fprintf(stderr, READ_FAIL_STAGE1, path);
		return (BC_ERROR);
	}
	(void) close(fd);
	return (BC_SUCCESS);
}

static int
read_stage2_from_file(char *path, ig_data_t *dest)
{
	int		fd;
	struct stat	sb;
	ig_stage2_t	*stage2 = &dest->stage2;
	ig_device_t	*device = &dest->device;
	uint32_t	buf_size;

	assert(dest);
	assert(stage2->buf == NULL);

	fd = open(path, O_RDONLY);
	if (fstat(fd, &sb) == -1) {
		perror("fstat");
		goto out;
	}

	stage2->file_size = sb.st_size;

	if (!is_bootpar(device->type)) {
		/*
		 * buffer size needs to account for stage2 plus the extra
		 * versioning information at the end of it. We reserve one
		 * extra sector (plus we round up to the next sector boundary).
		 */
		buf_size = stage2->file_size + SECTOR_SIZE;
	} else {
		/* In the PCFS case we only need to read in stage2. */
		buf_size = stage2->file_size;
	}

	stage2->buf_size = P2ROUNDUP(buf_size, SECTOR_SIZE);

	BOOT_DEBUG("stage2 buffer size = %d (%d sectors)\n", stage2->buf_size,
	    stage2->buf_size / SECTOR_SIZE);

	stage2->buf = malloc(stage2->buf_size);
	if (stage2->buf == NULL) {
		perror(gettext("Memory allocation failed"));
		goto out_fd;
	}

	stage2->file = stage2->buf;

	/*
	 * Extra information (e.g. the versioning structure) is placed at the
	 * end of stage2, aligned on a 8-byte boundary.
	 */
	if (!(is_bootpar(device->type)))
		stage2->extra = stage2->file + P2ROUNDUP(stage2->file_size, 8);

	if (lseek(fd, 0, SEEK_SET) == -1) {
		perror("lseek");
		goto out_alloc;
	}

	if (read(fd, stage2->file, stage2->file_size) < 0) {
		perror(gettext("unable to read stage2"));
		goto out_alloc;
	}

	(void) close(fd);
	return (BC_SUCCESS);

out_alloc:
	free(stage2->buf);
	stage2->buf = NULL;
out_fd:
	(void) close(fd);
out:
	return (BC_ERROR);
}

static int
prepare_stage1(ig_data_t *install)
{
	ig_device_t	*device = &install->device;

	assert(install != NULL);

	/* If PCFS add the BIOS Parameter Block. */
	if (is_bootpar(device->type)) {
		char	bpb_sect[SECTOR_SIZE];

		if (pread(device->part_fd, bpb_sect, SECTOR_SIZE, 0)
		    != SECTOR_SIZE) {
			(void) fprintf(stderr, READ_FAIL_BPB);
			return (BC_ERROR);
		}
		bcopy(bpb_sect + STAGE1_BPB_OFFSET,
		    install->stage1_buf + STAGE1_BPB_OFFSET, STAGE1_BPB_SIZE);
	}

	/* copy MBR to stage1 in case of overwriting MBR sector. */
	bcopy(device->boot_sector + BOOTSZ, install->stage1_buf + BOOTSZ,
	    SECTOR_SIZE - BOOTSZ);
	/* modify default stage1 file generated by GRUB. */
	*((unsigned char *)(install->stage1_buf + STAGE1_FORCE_LBA)) = 1;
	*((ulong_t *)(install->stage1_buf + STAGE1_STAGE2_SECTOR))
	    = install->stage2.first_sector;
	*((ushort_t *)(install->stage1_buf + STAGE1_STAGE2_ADDRESS))
	    = STAGE2_MEMADDR;
	*((ushort_t *)(install->stage1_buf + STAGE1_STAGE2_SEGMENT))
	    = STAGE2_MEMADDR >> 4;

	return (BC_SUCCESS);
}

/*
 * Grab stage1 from the specified device file descriptor.
 */
static int
read_stage1_from_disk(int dev_fd, char *stage1_buf)
{
	assert(stage1_buf != NULL);

	if (read_in(dev_fd, stage1_buf, SECTOR_SIZE, 0) != BC_SUCCESS) {
		perror(gettext("Unable to read stage1 from disk"));
		return (BC_ERROR);
	}
	return (BC_SUCCESS);
}

static int
read_stage2_from_disk(int dev_fd, ig_stage2_t *stage2)
{
	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);
	}

	/* No multiboot means no chance of knowing stage2 size */
	if (find_multiboot(mboot_scan, sizeof (mboot_scan), &mboot_off)
	    != BC_SUCCESS) {
		BOOT_DEBUG("Unable to find multiboot header\n");
		return (BC_NOEXTRA);
	}
	mboot = (multiboot_header_t *)(mboot_scan + mboot_off);

	/*
	 * Unfilled mboot values mean an older version of installgrub installed
	 * the stage2. Again we have no chance of knowing stage2 size.
	 */
	if (mboot->load_end_addr == 0 ||
	    mboot->load_end_addr < mboot->load_addr)
		return (BC_NOEXTRA);

	/*
	 * Currently, the amount of space reserved for extra information
	 * is "fixed". We may have to scan for the terminating extra payload
	 * in the future.
	 */
	size = mboot->load_end_addr - mboot->load_addr;
	buf_size = P2ROUNDUP(size + SECTOR_SIZE, SECTOR_SIZE);

	stage2->buf = malloc(buf_size);
	if (stage2->buf == NULL) {
		perror(gettext("Memory allocation failed"));
		return (BC_ERROR);
	}
	stage2->buf_size = buf_size;

	if (read_in(dev_fd, stage2->buf, buf_size, STAGE2_BLKOFF *
	    SECTOR_SIZE) != BC_SUCCESS) {
		perror("read");
		free(stage2->buf);
		return (BC_ERROR);
	}

	/* Update pointers. */
	stage2->file = stage2->buf;
	stage2->file_size = size;
	stage2->mboot_off = mboot_off;
	stage2->mboot = (multiboot_header_t *)(stage2->buf + stage2->mboot_off);
	stage2->extra = stage2->buf + P2ROUNDUP(stage2->file_size, 8);
	stage2->extra_size = stage2->buf_size - P2ROUNDUP(stage2->file_size, 8);

	return (BC_SUCCESS);
}

static boolean_t
is_update_necessary(ig_data_t *data, char *updt_str)
{
	bblk_einfo_t	*einfo;
	bblk_hs_t	stage2_hs;
	ig_stage2_t	stage2_disk;
	ig_stage2_t	*stage2_file = &data->stage2;
	ig_device_t	*device = &data->device;
	int		dev_fd = device->part_fd;

	assert(data != NULL);
	assert(device->part_fd != -1);

	bzero(&stage2_disk, sizeof (ig_stage2_t));

	/* Gather stage2 (if present) from the target device. */
	if (read_stage2_from_disk(dev_fd, &stage2_disk) != BC_SUCCESS) {
		BOOT_DEBUG("Unable to read stage2 from %s\n", device->path);
		BOOT_DEBUG("No multiboot wrapped stage2 on %s\n", device->path);
		return (B_TRUE);
	}

	/*
	 * Look for the extended information structure in the extra payload
	 * area.
	 */
	einfo = find_einfo(stage2_disk.extra, stage2_disk.extra_size);
	if (einfo == NULL) {
		BOOT_DEBUG("No extended information available\n");
		return (B_TRUE);
	}

	if (!do_version || updt_str == NULL) {
		(void) fprintf(stdout, "WARNING: target device %s has a "
		    "versioned stage2 that is going to be overwritten by a non "
		    "versioned one\n", device->path);
		return (B_TRUE);
	}

	if (force_update) {
		BOOT_DEBUG("Forcing update of %s bootblock\n", device->path);
		return (B_TRUE);
	}

	/* Compare the two extended information structures. */
	stage2_hs.src_buf = (unsigned char *)stage2_file->file;
	stage2_hs.src_size = stage2_file->file_size;

	return (einfo_should_update(einfo, &stage2_hs, updt_str));
}


#define	START_BLOCK(pos)	(*(ulong_t *)(pos))
#define	NUM_BLOCK(pos)		(*(ushort_t *)((pos) + 4))
#define	START_SEG(pos)		(*(ushort_t *)((pos) + 6))

static int
prepare_stage2(ig_data_t *install, char *updt_str)
{
	ig_device_t	*device = &install->device;
	ig_stage2_t	*stage2 = &install->stage2;
	uint32_t	mboot_off = 0;

	assert(install != NULL);
	assert(stage2->file != NULL);

	/* New stage2 files come with an embedded stage2. */
	if (find_multiboot(stage2->file, stage2->file_size, &mboot_off)
	    != BC_SUCCESS) {
		BOOT_DEBUG("WARNING: no multiboot structure found in stage2, "
		    "are you using an old GRUB stage2?\n");
		if (do_version == B_TRUE) {
			(void) fprintf(stderr, gettext("Versioning requested "
			    "but stage2 does not support it.. skipping.\n"));
			do_version = B_FALSE;
		}
	} else {
		/* Keep track of where the multiboot header is. */
		stage2->mboot_off = mboot_off;
		stage2->mboot = (multiboot_header_t *)(stage2->file +
		    mboot_off);
		if (do_version) {
			/*
			 * Adding stage2 information needs to happen before
			 * we modify the copy of stage2 we have in memory, so
			 * that the hashing reflects the one of the file.
			 * An error here is not fatal.
			 */
			add_stage2_einfo(stage2, updt_str);
		}
		/*
		 * Fill multiboot information. We add them even without
		 * versioning to support as much as possible mirroring.
		 */
		prepare_fake_multiboot(stage2);
	}

	if (is_bootpar(device->type)) {
		uint32_t	blocklist[SECTOR_SIZE / sizeof (uint32_t)];
		uint32_t	install_addr = STAGE2_MEMADDR + SECTOR_SIZE;
		int		i = 0;
		uchar_t		*pos;

		bzero(blocklist, sizeof (blocklist));
		if (read_stage2_blocklist(device->part_fd, blocklist) != 0) {
			(void) fprintf(stderr, gettext("Error reading pcfs "
			    "stage2 blocklist\n"));
			return (BC_ERROR);
		}

		pos = (uchar_t *)stage2->file + STAGE2_BLOCKLIST;
		stage2->first_sector = device->start_sector + blocklist[0];
		stage2->pcfs_first_sectors[0] = blocklist[0];
		BOOT_DEBUG("stage2 first sector: %d\n", stage2->first_sector);


		if (blocklist[1] > 1) {
			blocklist[0]++;
			blocklist[1]--;
		} else {
			i += 2;
		}

		stage2->pcfs_first_sectors[1] = blocklist[i];

		while (blocklist[i]) {
			if (START_BLOCK(pos - 8) != 0 &&
			    START_BLOCK(pos - 8) != blocklist[i + 2]) {
				(void) fprintf(stderr, PCFS_FRAGMENTED);
				return (BC_ERROR);
			}
			START_BLOCK(pos) = blocklist[i] + device->start_sector;
			START_SEG(pos) = (ushort_t)(install_addr >> 4);
			NUM_BLOCK(pos) = blocklist[i + 1];
			install_addr += blocklist[i + 1] * SECTOR_SIZE;
			pos -= 8;
			i += 2;
		}
	} else {
		/* Solaris VTOC */
		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->file + STAGE2_BLOCKLIST)) =
		    stage2->first_sector + 1;
	}

	/* force lba and set disk partition */
	*((unsigned char *) (stage2->file + STAGE2_FORCE_LBA)) = 1;
	*((long *)(stage2->file + STAGE2_INSTALLPART))
	    = (device->partition << 16) | (device->slice << 8) | 0xff;

	return (BC_SUCCESS);
}

static int
find_x86_bootpar(struct mboot *mboot, int *part_num, uint32_t *start_sect)
{
	int	i;

	for (i = 0; i < FD_NUMPART; i++) {
		struct ipart	*part;

		part = (struct ipart *)mboot->parts + i;
		if (part->systid == 0xbe) {
			if (start_sect)
				*start_sect = part->relsect;
			if (part_num)
				*part_num = i;
			/* solaris boot part */
			return (BC_SUCCESS);
		}
	}
	return (BC_ERROR);
}

static char *
get_raw_partition_path(ig_device_t *device)
{
	char	*raw;
	int	len;

	if (is_bootpar(device->type)) {
		int		part;
		struct mboot	*mboot;

		mboot = (struct mboot *)device->boot_sector;
		if (find_x86_bootpar(mboot, &part, NULL) != BC_SUCCESS) {
			(void) fprintf(stderr, BOOTPAR_NOTFOUND,
			    device->path_p0);
			return (NULL);
		}

		raw = strdup(device->path_p0);
		if (raw == NULL) {
			perror(gettext("Memory allocation failed"));
			return (NULL);
		}

		raw[strlen(raw) - 2] = '1' + part;
		return (raw);
	}

	/* For disk, remember slice and return whole fdisk partition  */
	raw = strdup(device->path);
	if (raw == NULL) {
		perror(gettext("Memory allocation failed"));
		return (NULL);
	}

	len = strlen(raw);
	if (raw[len - 2] != 's' || raw[len - 1] == '2') {
		(void) fprintf(stderr, NOT_ROOT_SLICE);
		free(raw);
		return (NULL);
	}
	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 int
copy_stage2_to_pcfs(ig_data_t *install)
{
	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->path, sizeof (buf));
	buf[sizeof (buf) - 1] = 0;
	cp = strchr(buf, 'r');
	if (cp == NULL) {
		(void) fprintf(stderr, CONVERT_FAIL, device->path);
		return (BC_ERROR);
	}
	do {
		*cp = *(cp + 1);
	} while (*(++cp));

	/* get the mount point, if any */
	mntfp = fopen("/etc/mnttab", "r");
	if (mntfp == NULL) {
		(void) fprintf(stderr, OPEN_FAIL_FILE, "/etc/mnttab");
		return (BC_ERROR);
	}

	mpref.mnt_special = buf;
	if (getmntany(mntfp, &mp, &mpref) != 0) {
		char cmd[128];

		/* not mounted, try remount */
		(void) mkdir(TMP_MNTPT, S_IRWXU);
		(void) snprintf(cmd, sizeof (cmd), "mount -F pcfs %s %s",
		    buf, TMP_MNTPT);
		(void) system(cmd);
		rewind(mntfp);
		bzero(&mp, sizeof (mp));
		if (getmntany(mntfp, &mp, &mpref) != 0) {
			(void) fprintf(stderr, MOUNT_FAIL, buf);
			return (BC_ERROR);
		}
	}

	(void) snprintf(buf, sizeof (buf),
	    "%s/boot", mp.mnt_mountp);
	(void) mkdir(buf, S_IRWXU);
	(void) strcat(buf, "/grub");
	(void) mkdir(buf, S_IRWXU);

	(void) strcat(buf, "/stage2");
	pcfs_fp = open(buf, O_WRONLY | O_CREAT, S_IRWXU);
	if (pcfs_fp == -1) {
		(void) fprintf(stderr, OPEN_FAIL_FILE, buf);
		perror("open:");
		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);
	(void) rmdir(TMP_MNTPT);
	return (status);
}