OpenSolaris_b135/cmd/fwflash/common/fwflash.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * fwflash.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>
#include <sys/queue.h>
#include <signal.h>
#include <locale.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/param.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <dirent.h>
#include <sys/varargs.h>
#include <libintl.h> /* for gettext(3c) */
#include <libdevinfo.h>
#include <libscf_priv.h>
#include <fwflash/fwflash.h>
#include <sys/modctl.h> /* for MAXMODCONFNAME */


#if !defined(lint)
/* embedded software license agreement */
static char *sla [] = { "Copyright 2009 Sun Microsystems, Inc., 4150 Network "
"Circle, Santa Clara, California 95054, U.S.A. All rights reserved. U.S. "
"Government Rights - Commercial software.  Government users are subject to the "
"Sun Microsystems, Inc. standard license agreement and applicable provisions "
"of the FAR and its supplements.  Use is subject to license terms.  Parts of "
"the product may be derived from Berkeley BSD systems, licensed from the "
"University of California. UNIX is a registered trademark in the U.S. and in "
"other countries, exclusively licensed through X/Open Company, Ltd.Sun, Sun "
"Microsystems, the Sun logo and Solaris are trademarks or registered "
"trademarks of Sun Microsystems, Inc. in the U.S. and other countries. This "
"product is covered and controlled by U.S. Export Control laws and may be "
"subject to the export or import laws in other countries.  Nuclear, missile, "
"chemical biological weapons or nuclear maritime end uses or end users, "
"whether direct or indirect, are strictly prohibited.  Export or reexport "
"to countries subject to U.S. embargo or to entities identified on U.S. export "
"exclusion lists, including, but not limited to, the denied persons and "
"specially designated nationals lists is strictly prohibited." };
#endif	/* lint */

/* global arg list */
int	fwflash_arg_list = 0;
char	*filelist[10];

/* exposed global args */
di_node_t rootnode;
struct PLUGINLIST *fw_pluginlist;
struct DEVICELIST *fw_devices;
struct vrfyplugin *verifier;
struct fw_plugin *self;
int fwflash_debug = 0;

/* are we writing to flash? */
static int fwflash_in_write = 0;

/*
 * If we *must* track the version string for fwflash, then
 * we should do so in this common file rather than the header
 * file since it will then be in sync with what the customer
 * sees. We should deprecate the "-v" option since it is not
 * actually of any use - it doesn't line up with Mercurial's
 * concept of the changeset.
 */
#define	FWFLASH_VERSION		"v1.9"
#define	FWFLASH_PROG_NAME	"fwflash"

static int get_fileopts(char *options);
static int flash_device_list();
static int flash_load_plugins();
static int fwflash_update(char *device, char *filename, int flags);
static int fwflash_read_file(char *device, char *filename);
static int fwflash_list_fw(char *class);
static int fwflash_load_verifier(char *drv, char *vendorid, char *fwimg);
static void fwflash_intr(int sig);
static void fwflash_handle_signals(void);
static void fwflash_usage(char *arg);
static void fwflash_version(void);
static int confirm_target(struct devicelist *thisdev, char *file);

/*
 * FWFlash main code
 */
int
main(int argc, char **argv)
{
	int		rv = FWFLASH_SUCCESS;
	int		i;
	char		ch;
	char		*read_file;
	extern char	*optarg;
	char		*devclass = NULL;
	char		*devpath = NULL;

	/* local variables from env */
	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D */
#define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it isn't. */
#endif

	(void) textdomain(TEXT_DOMAIN);

	read_file = NULL;

	if (argc < 2) {
		/* no args supplied */
		fwflash_usage(NULL);
		return (FWFLASH_FAILURE);
	}

	while ((ch = getopt(argc, argv, "hvylc:f:r:Qd:")) != EOF) {
		switch (ch) {
		case 'h':
			fwflash_arg_list |= FWFLASH_HELP_FLAG;
			break;
		case 'v':
			fwflash_arg_list |= FWFLASH_VER_FLAG;
			break;
		case 'y':
			fwflash_arg_list |= FWFLASH_YES_FLAG;
			break;
		case 'l':
			fwflash_arg_list |= FWFLASH_LIST_FLAG;
			break;
		case 'c':
			fwflash_arg_list |= FWFLASH_CLASS_FLAG;
			/* we validate later */
			devclass = strdup(optarg);
			break;
		case 'd':
			fwflash_arg_list |= FWFLASH_DEVICE_FLAG;
			devpath = strdup(optarg);
			break;
		case 'f':
			fwflash_arg_list |= FWFLASH_FW_FLAG;
			if ((rv = get_fileopts(optarg)) != FWFLASH_SUCCESS) {
				fwflash_usage(NULL);
				return (FWFLASH_FAILURE);
			}
			break;
		case 'r':
			fwflash_arg_list |= FWFLASH_READ_FLAG;
			read_file = strdup(optarg);
			break;
		case 'Q':
			/* NOT in the manpage */
			fwflash_debug = 1;
			break;
		/* illegal options */
		default:
			fwflash_usage(optarg);
			return (FWFLASH_FAILURE);
		}
	}

	/* Do Help */
	if ((fwflash_arg_list & FWFLASH_HELP_FLAG) ||
	    ((fwflash_arg_list & FWFLASH_DEVICE_FLAG) &&
	    !((fwflash_arg_list & FWFLASH_FW_FLAG) ||
	    (fwflash_arg_list & FWFLASH_READ_FLAG)))) {
		fwflash_usage(NULL);
		return (FWFLASH_SUCCESS);
	}

	/* Do Version */
	if (fwflash_arg_list == FWFLASH_VER_FLAG) {
		fwflash_version();
		return (FWFLASH_SUCCESS);
	}

	/* generate global list of devices */
	if ((rv = flash_load_plugins()) != FWFLASH_SUCCESS) {
		logmsg(MSG_ERROR,
		    gettext("Unable to load fwflash plugins\n"));
		fwflash_intr(0);
		return (rv);
	}

	if ((rv = flash_device_list()) != FWFLASH_SUCCESS) {
		logmsg(MSG_ERROR,
		    gettext("No flashable devices in this system\n"));
		fwflash_intr(0);
		return (rv);
	}

	/* Do list */
	if (fwflash_arg_list == (FWFLASH_LIST_FLAG) ||
	    fwflash_arg_list == (FWFLASH_LIST_FLAG | FWFLASH_CLASS_FLAG)) {
		rv = fwflash_list_fw(devclass);
		fwflash_intr(0);
		return (rv);
	}

	fwflash_handle_signals();

	/* Do flash update (write) */
	if ((fwflash_arg_list == (FWFLASH_FW_FLAG | FWFLASH_DEVICE_FLAG)) ||
	    (fwflash_arg_list == (FWFLASH_FW_FLAG | FWFLASH_DEVICE_FLAG |
	    FWFLASH_YES_FLAG))) {
		int fastreboot_disabled = 0;
		/* the update function handles the real arg parsing */
		i = 0;
		while (filelist[i] != NULL) {
			if ((rv = fwflash_update(devpath, filelist[i],
			    fwflash_arg_list)) == FWFLASH_SUCCESS) {
				/* failed ops have already been noted */
				if (!fastreboot_disabled &&
				    scf_fastreboot_default_set_transient(
				    B_FALSE) != SCF_SUCCESS)
					logmsg(MSG_ERROR, gettext(
					    "Failed to disable fast "
					    "reboot.\n"));
				else
					fastreboot_disabled = 1;
				logmsg(MSG_ERROR,
				    gettext("New firmware will be activated "
				    "after you reboot\n\n"));
			}
			++i;
		}

		fwflash_intr(0);
		return (rv);
	}

	/* Do flash read */
	if ((fwflash_arg_list == (FWFLASH_READ_FLAG | FWFLASH_DEVICE_FLAG)) ||
	    (fwflash_arg_list == (FWFLASH_READ_FLAG | FWFLASH_DEVICE_FLAG |
	    FWFLASH_YES_FLAG))) {
		rv = fwflash_read_file(devpath, read_file);
		fwflash_intr(0);
		return (rv);
	}

	fwflash_usage(NULL);

	return (FWFLASH_FAILURE);
}


static int
flash_load_plugins()
{

	int rval = FWFLASH_SUCCESS;
	DIR *dirp;
	struct dirent *plugdir;
	char *plugname;
	struct fw_plugin *tmpplug;
	struct pluginlist *tmpelem;
	void *sym;
	char *fwplugdirpath, *tempdirpath;


#define	CLOSEFREE()	{			\
	(void) dlclose(tmpplug->handle);	\
	free(tmpplug); }

	/*
	 * Procedure:
	 *
	 * cd /usr/lib/fwflash/identify
	 * open each .so file found therein
	 * dlopen(.sofile)
	 * if it's one of our plugins, add it to fw_pluginlist;
	 *
	 * functions we need here include dlopen and dlsym.
	 *
	 * If we get to the end and fw_pluginlist struct is empty,
	 * return FWFLASH_FAILURE so we return to the shell.
	 */

	if ((fwplugdirpath = calloc(1, MAXPATHLEN + 1)) == NULL) {
		logmsg(MSG_ERROR,
		    gettext("Unable to malloc %d bytes while "
		    "trying to load plugins: %s\n"),
		    MAXPATHLEN + 1, strerror(errno));
		return (FWFLASH_FAILURE);
	}

	tempdirpath = getenv("FWPLUGINDIR");

	if ((fwflash_debug > 0) && (tempdirpath != NULL)) {
		(void) strlcpy(fwplugdirpath, tempdirpath,
		    strlen(tempdirpath) + 1);
	} else {
		(void) strlcpy(fwplugdirpath, FWPLUGINDIR,
		    strlen(FWPLUGINDIR) + 1);
	}

	if ((dirp = opendir(fwplugdirpath)) == NULL) {
		logmsg(MSG_ERROR,
		    gettext("Unable to open %s\n"),
		    fwplugdirpath);
		return (errno);
	}

	if ((plugdir = calloc(1, sizeof (struct dirent) + MAXPATHLEN + 1))
	    == NULL) {
		logmsg(MSG_ERROR,
		    gettext("Unable to malloc %d bytes while "
		    "trying to load plugins: %s\n"),
		    MAXPATHLEN + 1 + sizeof (struct dirent),
		    strerror(errno));
		return (FWFLASH_FAILURE);
	}

	if ((fw_pluginlist = calloc(1, sizeof (struct fw_plugin)))
	    == NULL) {
		logmsg(MSG_ERROR,
		    gettext("Unable to malloc %d bytes while "
		    "trying to load plugins: %s\n"),
		    sizeof (struct fw_plugin), strerror(errno));
		return (FWFLASH_FAILURE);
	}

	TAILQ_INIT(fw_pluginlist);

	while ((readdir_r(dirp, plugdir, &plugdir) == 0) && (plugdir != NULL)) {

		errno = 0; /* remove chance of false results */

		if ((plugdir->d_name[0] == '.') ||
		    (strstr(plugdir->d_name, ".so") == NULL)) {
			continue;
		}

		if ((plugname = calloc(1, MAXPATHLEN + 1)) == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to malloc %d bytes while "
			    "trying to load plugins: %s\n"),
			    MAXPATHLEN + 1, strerror(errno));
			return (FWFLASH_FAILURE);
		}

		(void) snprintf(plugname, MAXPATHLEN, "%s/%s",
		    fwplugdirpath, plugdir->d_name);

		/* start allocating storage */
		if ((tmpelem = calloc(1, sizeof (struct pluginlist)))
		    == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to malloc %d bytes while "
			    "trying to load plugins: %s\n"),
			    sizeof (struct pluginlist), strerror(errno));
			return (FWFLASH_FAILURE);
		}

		if ((tmpplug = calloc(1, sizeof (struct fw_plugin)))
		    == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to malloc %d bytes while "
			    "trying to load plugins: %s\n"),
			    sizeof (struct fw_plugin), strerror(errno));
			return (FWFLASH_FAILURE);
		}

		/* load 'er up! */
		tmpplug->handle = dlopen(plugname, RTLD_NOW);
		if (tmpplug->handle == NULL) {
			free(tmpplug);
			continue; /* assume there are other plugins */
		}

		if ((tmpplug->filename = calloc(1, strlen(plugname) + 1))
		    == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to allocate %d bytes for plugin "
			    "filename %s:%s\n"),
			    strlen(plugname) + 1, plugname,
			    strerror(errno));
			return (rval);
		}

		(void) strlcpy(tmpplug->filename, plugname,
		    strlen(plugname) + 1);

		/* now sanity check the file */
		if ((sym = dlsym(tmpplug->handle, "drivername"))
		    != NULL) {
			/* max length of drivername */
			tmpplug->drvname = calloc(1, MAXMODCONFNAME);

			/* are we doing double-time? */
			if (strncmp((char *)sym, plugdir->d_name,
			    MAXMODCONFNAME) != 0) {
				char *tempnm = calloc(1, MAXMODCONFNAME);

				(void) memcpy(tempnm, plugdir->d_name,
				    MAXMODCONFNAME);
				(void) strlcpy(tmpplug->drvname,
				    strtok(tempnm, "."),
				    strlen(plugdir->d_name) + 1);
				free(tempnm);
			} else {
				(void) strlcpy(tmpplug->drvname,
				    (char *)sym, strlen(sym) + 1);
			}
		} else {
			CLOSEFREE();
			continue;
		}
		if ((sym = dlsym(tmpplug->handle, "fw_readfw"))
		    != NULL) {
			tmpplug->fw_readfw = (int (*)())sym;
		} else {
			CLOSEFREE();
			continue;
		}
		if ((sym = dlsym(tmpplug->handle, "fw_writefw"))
		    != NULL) {
			tmpplug->fw_writefw = (int (*)())sym;
		} else {
			CLOSEFREE();
			continue;
		}

		if ((sym = dlsym(tmpplug->handle, "fw_identify"))
		    != NULL) {
			tmpplug->fw_identify =
			    (int (*)(int))sym;
		} else {
			CLOSEFREE();
			continue;
		}
		if ((sym = dlsym(tmpplug->handle, "fw_devinfo"))
		    != NULL) {
			tmpplug->fw_devinfo =
			    (int (*)(struct devicelist *))sym;
		} else {
			CLOSEFREE();
			continue;
		}

		if ((sym = dlsym(tmpplug->handle, "plugin_version"))
		    != NULL) {
			if ((*(int *)sym) >= FWPLUGIN_VERSION_2) {
				if ((sym = dlsym(tmpplug->handle,
				    "fw_cleanup")) != NULL) {
					tmpplug->fw_cleanup =
					    (void (*)(struct devicelist *))sym;
				} else {
					logmsg(MSG_ERROR,
					    gettext("ERROR: v2 plugin (%s) "
					    "has no fw_cleanup function\n"),
					    tmpplug->filename);
					CLOSEFREE();
					continue;
				}
			} else {
				logmsg(MSG_INFO,
				    "Identification plugin %s defined "
				    "plugin_version < FWPLUGIN_VERSION_2 !");
			}
		}

		if ((tmpelem->drvname = calloc(1, MAXMODCONFNAME))
		    == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to allocate space for a"
			    "drivername %s\n"),
			    tmpplug->drvname);
			return (FWFLASH_FAILURE);
		}

		(void) strlcpy(tmpelem->drvname, tmpplug->drvname,
		    strlen(tmpplug->drvname) + 1);

		if ((tmpelem->filename = calloc(1,
		    strlen(tmpplug->filename) + 1)) == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to allocate %d bytes for "
			    "filename %s\n"),
			    strlen(tmpplug->filename) + 1,
			    tmpplug->filename);
			return (FWFLASH_FAILURE);
		}

		(void) strlcpy(tmpelem->filename, plugname,
		    strlen(plugname) + 1);
		tmpelem->plugin = tmpplug;

		/* CONSTCOND */
		TAILQ_INSERT_TAIL(fw_pluginlist, tmpelem, nextplugin);
	}

	if ((plugdir == NULL) && TAILQ_EMPTY(fw_pluginlist)) {
		return (FWFLASH_FAILURE);
	}

	if (errno != 0) {
		logmsg(MSG_ERROR,
		    gettext("Error reading directory entry in %s\n"),
		    fwplugdirpath);
		rval = errno;
	}

	free(fwplugdirpath);
	free(plugdir);
	(void) closedir(dirp);
	return (rval);
}

/*
 * fwflash_load_verifier dlload()s the appropriate firmware image
 * verification plugin, and attaches the designated fwimg's fd to
 * the vrfyplugin structure so we only have to load the image in
 * one place.
 */
int
fwflash_load_verifier(char *drv, char *vendorid, char *fwimg)
{

	int rv = FWFLASH_FAILURE;
	int imgfd;
	char *fwvrfydirpath, *tempdirpath, *filename;
	char *clean; /* for the space-removed vid */
	struct stat fwstat;
	struct vrfyplugin *vrfy;
	void *vrfysym;

	/*
	 * To make flashing multiple firmware images somewhat more
	 * efficient, we start this function by checking whether a
	 * verifier for this device has already been loaded. If it
	 * has been loaded, we replace the imgfile information, and
	 * then continue as if we were loading for the first time.
	 */

	if (verifier != NULL) {
		verifier->imgsize = 0;
		verifier->flashbuf = 0; /* set by the verifier function */

		if (verifier->imgfile != NULL) {
			free(verifier->imgfile);
			verifier->imgfile = NULL;
		}

		if (verifier->fwimage != NULL) {
			free(verifier->fwimage);
			verifier->fwimage = NULL;
		}
	} else {
		if ((fwvrfydirpath = calloc(1, MAXPATHLEN + 1)) == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to allocate space for a firmware "
			    "verifier file(1)"));
			return (rv);
		}

		if ((filename = calloc(1, MAXPATHLEN + 1)) == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to allocate space "
			    "for a firmware verifier file(2)"));
			free(fwvrfydirpath);
			return (rv);
		}

		/*
		 * Since SCSI devices can have a vendor id of up to 8
		 * left-aligned and _space-padded_ characters, we first need to
		 * strip off any space characters before we try to make a
		 * filename out of it
		 */
		clean = strtok(vendorid, " ");
		if (clean == NULL) {
			/* invalid vendorid, something's really wrong */
			logmsg(MSG_ERROR,
			    gettext("Invalid vendorid (null) specified for "
			    "device\n"));
			free(filename);
			free(fwvrfydirpath);
			return (rv);
		}

		tempdirpath = getenv("FWVERIFYPLUGINDIR");

		if ((fwflash_debug > 0) && (tempdirpath != NULL)) {
			(void) strlcpy(fwvrfydirpath, tempdirpath,
			    strlen(tempdirpath) + 1);
		} else {
			(void) strlcpy(fwvrfydirpath, FWVERIFYPLUGINDIR,
			    strlen(FWVERIFYPLUGINDIR) + 1);
		}

		if ((vrfy = calloc(1, sizeof (struct vrfyplugin))) == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to allocate space "
			    "for a firmware verifier structure"));
			free(filename);
			free(fwvrfydirpath);
			return (rv);
		}

		errno = 0; /* false positive removal */

		(void) snprintf(filename, MAXPATHLEN, "%s/%s-%s.so",
		    fwvrfydirpath, drv, clean);
		if ((vrfy->handle = dlopen(filename, RTLD_NOW)) == NULL) {
			logmsg(MSG_INFO, gettext(dlerror()));
			logmsg(MSG_INFO,
			    gettext("\nUnable to open verification plugin "
			    "%s. Looking for %s-GENERIC plugin instead.\n"),
			    filename, drv);

			/* Try the drv-GENERIC.so form, _then_ die */
			bzero(filename, strlen(filename) + 1);
			(void) snprintf(filename, MAXPATHLEN,
			    "%s/%s-GENERIC.so", fwvrfydirpath, drv);

			if ((vrfy->handle = dlopen(filename, RTLD_NOW))
			    == NULL) {
				logmsg(MSG_INFO, gettext(dlerror()));
				logmsg(MSG_ERROR,
				    gettext("\nUnable to open either "
				    "verification plugin %s/%s-%s.so or "
				    "generic plugin %s.\nUnable to verify "
				    "firmware image. Aborting.\n"),
				    fwvrfydirpath, drv, clean, filename);
				free(filename);
				free(fwvrfydirpath);
				return (rv);
			}
		}

		if ((vrfy->filename = calloc(1, strlen(filename) + 1))
		    == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to allocate space to store "
			    "a verifier filename\n"));
			free(filename);
			free(fwvrfydirpath);
			free(vrfy->handle);
			return (rv);
		}
		(void) strlcpy(vrfy->filename, filename, strlen(filename) + 1);

		if ((vrfysym = dlsym(vrfy->handle, "vendorvrfy")) == NULL) {
			logmsg(MSG_ERROR,
			    gettext("%s is an invalid firmware verification "
			    "plugin."), filename);
			(void) dlclose(vrfy->handle);
			free(filename);
			free(fwvrfydirpath);
			free(vrfy);
			return (rv);
		} else {
			vrfy->vendorvrfy =
			    (int (*)(struct devicelist *))vrfysym;
		}

		vrfysym = dlsym(vrfy->handle, "vendor");

		if (vrfysym == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Invalid vendor (null) in verification "
			    "plugin %s\n"), filename);
			(void) dlclose(vrfy->handle);
			free(vrfy);
			return (rv);
		} else {
			if (strncmp(vendorid, (char *)vrfysym,
			    strlen(vendorid)) != 0) {
				logmsg(MSG_INFO,
				    "Using a sym-linked (%s -> %s) "
				    "verification plugin\n",
				    vendorid, vrfysym);
				vrfy->vendor = calloc(1, strlen(vendorid) + 1);
			} else {
				vrfy->vendor = calloc(1, strlen(vrfysym) + 1);
			}
			(void) strlcpy(vrfy->vendor, (char *)vrfysym,
			    strlen(vendorid) + 1);
		}

		verifier = vrfy; /* a convenience variable */
		free(filename);
		free(fwvrfydirpath);
	}

	/*
	 * We don't do any verification that the fw image file is in
	 * an approved location, but it's easy enough to modify this
	 * function to do so. The verification plugin should provide
	 * sufficient protection.
	 */

	if ((imgfd = open(fwimg, O_RDONLY)) < 0) {
		logmsg(MSG_ERROR,
		    gettext("Unable to open designated firmware "
		    "image file %s: %s\n"),
		    (fwimg != NULL) ? fwimg : "(null)",
		    strerror(errno));
		rv = FWFLASH_FAILURE;
		goto cleanup;
	}

	if (stat(fwimg, &fwstat) == -1) {
		logmsg(MSG_ERROR,
		    gettext("Unable to stat() firmware image file "
		    "%s: %s\n"),
		    fwimg, strerror(errno));
		rv = FWFLASH_FAILURE;
		goto cleanup;
	} else {
		verifier->imgsize = fwstat.st_size;
		if ((verifier->fwimage = calloc(1, verifier->imgsize))
		    == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to load firmware image "
			    "%s: %s\n"),
			    fwimg, strerror(errno));
			rv = FWFLASH_FAILURE;
			goto cleanup;
		}
	}

	errno = 0;
	if ((rv = read(imgfd, verifier->fwimage,
	    (size_t)verifier->imgsize)) < verifier->imgsize) {
		/* we haven't read enough data, bail */
		logmsg(MSG_ERROR,
		    gettext("Failed to read sufficient data "
		    "(got %d bytes, expected %d bytes) from "
		    "firmware image file %s: %s\n"),
		    rv, verifier->imgsize,
		    verifier->filename, strerror(errno));
		rv = FWFLASH_FAILURE;
	} else {
		rv = FWFLASH_SUCCESS;
	}

	if ((verifier->imgfile = calloc(1, strlen(fwimg) + 1)) == NULL) {
		logmsg(MSG_ERROR,
		    gettext("Unable to save name of firmware image\n"));
		rv = FWFLASH_FAILURE;
	} else {
		(void) strlcpy(verifier->imgfile, fwimg, strlen(fwimg) + 1);
	}

	if (rv != FWFLASH_SUCCESS) {
		/* cleanup and let's get outta here */
cleanup:
		free(verifier->filename);
		free(verifier->vendor);

		if (!(fwflash_arg_list & FWFLASH_READ_FLAG) &&
		    verifier->fwimage)
			free(verifier->fwimage);

		verifier->filename = NULL;
		verifier->vendor = NULL;
		verifier->vendorvrfy = NULL;
		verifier->fwimage = NULL;
		(void) dlclose(verifier->handle);
		verifier->handle = NULL;
		free(verifier);
		if (imgfd >= 0) {
			(void) close(imgfd);
		}
		verifier = NULL;
	}

	return (rv);
}

/*
 * cycles through the global list of plugins to find
 * each flashable device, which is added to fw_devices
 *
 * Each plugin's identify routine must allocated storage
 * as required.
 *
 * Each plugin's identify routine must return
 * FWFLASH_FAILURE if it cannot find any devices
 * which it handles.
 */
static int
flash_device_list()
{
	int rv = FWFLASH_FAILURE;
	int startidx = 0;
	int sumrv = 0;
	struct pluginlist *plugins;

	/* we open rootnode here, and close it in fwflash_intr */
	if ((rootnode = di_init("/", DINFOCPYALL|DINFOFORCE)) == DI_NODE_NIL) {
		logmsg(MSG_ERROR,
		    gettext("Unable to take device tree snapshot: %s\n"),
		    strerror(errno));
		return (rv);
	}

	if ((fw_devices = calloc(1, sizeof (struct devicelist))) == NULL) {
		logmsg(MSG_ERROR,
		    gettext("Unable to malloc %d bytes while "
		    "trying to find devices: %s\n"),
		    sizeof (struct devicelist), strerror(errno));
		return (FWFLASH_FAILURE);
	}

	/* CONSTCOND */
	TAILQ_INIT(fw_devices);

	TAILQ_FOREACH(plugins, fw_pluginlist, nextplugin) {
		self = plugins->plugin;
		rv = plugins->plugin->fw_identify(startidx);

		logmsg(MSG_INFO,
		    gettext("fwflash:flash_device_list() got %d from "
		    "identify routine\n"), rv);

		/* only bump startidx if we've found at least one device */
		if (rv == FWFLASH_SUCCESS) {
			startidx += 100;
			sumrv++;
		} else {
			logmsg(MSG_INFO,
			    gettext("No flashable devices attached with "
			    "the %s driver in this system\n"),
			    plugins->drvname);
		}
	}

	if (sumrv > 0)
		rv = FWFLASH_SUCCESS;

	return (rv);
}

static int
fwflash_list_fw(char *class)
{
	int rv = 0;
	struct devicelist *curdev;
	int header = 1;

	TAILQ_FOREACH(curdev, fw_devices, nextdev) {

		/* we're either class-conscious, or we're not */
		if (((class != NULL) &&
		    ((strncmp(curdev->classname, "ALL", 3) == 0) ||
		    (strcmp(curdev->classname, class) == 0))) ||
		    (class == NULL)) {

			if (header != 0) {
				(void) fprintf(stdout,
				    gettext("List of available devices:\n"));
				header--;
			}
			/*
			 * If any plugin's fw_devinfo() function returns
			 * FWFLASH_FAILURE then we want to keep track of
			 * it. _Most_ plugins should always return
			 * FWFLASH_SUCCESS from this function. The only
			 * exception known at this point is the tavor plugin.
			 */
			rv += curdev->plugin->fw_devinfo(curdev);
		}
	}
	return (rv);
}

static int
fwflash_update(char *device, char *filename, int flags)
{

	int rv = FWFLASH_FAILURE;
	int needsfree = 0;
	int found = 0;
	struct devicelist *curdev;
	char *realfile;

	/*
	 * Here's how we operate:
	 *
	 * We perform some basic checks on the args, then walk
	 * through the device list looking for the device which
	 * matches. We then load the appropriate verifier for the
	 * image file and device, verify the image, then call the
	 * fw_writefw() function of the appropriate plugin.
	 *
	 * There is no "force" flag to enable you to flash a firmware
	 * image onto an incompatible device because the verifier
	 * will return FWFLASH_FAILURE if the image doesn't match.
	 */

	/* new firmware filename and device desc */
	if (filename == NULL) {
		logmsg(MSG_ERROR,
		    gettext("Invalid firmware filename (null)\n"));
		return (FWFLASH_FAILURE);
	}

	if (device == NULL) {
		logmsg(MSG_ERROR,
		    gettext("Invalid device requested (null)\n"));
		return (FWFLASH_FAILURE);
	}

	if ((realfile = calloc(1, PATH_MAX + 1)) == NULL) {
		logmsg(MSG_ERROR,
		    gettext("Unable to allocate space for device "
		    "filename, operation might fail if %s is"
		    "a symbolic link\n"),
		    device);
		realfile = device;
	} else {
		/*
		 * If realpath() succeeds, then we have a valid
		 * device filename in realfile.
		 */
		if (realpath(device, realfile) == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to resolve device filename"
			    ": %s\n"),
			    strerror(errno));
			/* tidy up */
			free(realfile);
			/* realpath didn't succeed, use fallback */
			realfile = device;
		} else {
			needsfree = 1;
		}
	}

	logmsg(MSG_INFO,
	    gettext("fwflash_update: fw_filename (%s) device (%s)\n"),
	    filename, device);

	TAILQ_FOREACH(curdev, fw_devices, nextdev) {
		if (strcmp(curdev->access_devname, realfile) == 0) {
			found++;
			rv = fwflash_load_verifier(curdev->drvname,
			    curdev->ident->vid, filename);
			if (rv == FWFLASH_FAILURE) {
				logmsg(MSG_ERROR,
				    gettext("Unable to load verifier "
				    "for device %s\n"),
				    curdev->access_devname);
				return (FWFLASH_FAILURE);
			}
			rv = verifier->vendorvrfy(curdev);
			if (rv == FWFLASH_FAILURE) {
				/* the verifier prints a message */
				logmsg(MSG_INFO,
				    "verifier (%s) for %s :: %s returned "
				    "FWFLASH_FAILURE\n",
				    verifier->filename,
				    filename, curdev->access_devname);
				return (rv);
			}

			if (((flags & FWFLASH_YES_FLAG) == FWFLASH_YES_FLAG) ||
			    (rv = confirm_target(curdev, filename)) ==
			    FWFLASH_YES_FLAG) {
				logmsg(MSG_INFO,
				    "about to flash using plugin %s\n",
				    curdev->plugin->filename);
				rv = curdev->plugin->fw_writefw(curdev,
				    filename);
				if (rv == FWFLASH_FAILURE) {
					logmsg(MSG_ERROR,
					    gettext("Failed to flash "
					    "firmware file %s on "
					    "device %s: %d\n"),
					    filename,
					    curdev->access_devname, rv);
				}
			} else {
				logmsg(MSG_ERROR,
				    gettext("Flash operation not confirmed "
				    "by user\n"),
				    curdev->access_devname);
				rv = FWFLASH_FAILURE;
			}
		}
	}

	if (!found)
		/* report the same device that the user passed in */
		logmsg(MSG_ERROR,
		    gettext("Device %s does not appear "
		    "to be flashable\n"),
		    ((strncmp(device, realfile, strlen(device)) == 0) ?
		    realfile : device));

	if (needsfree)
		free(realfile);

	return (rv);
}

/*
 * We validate that the device path is in our global device list and
 * that the filename exists, then palm things off to the relevant plugin.
 */
static int
fwflash_read_file(char *device, char *filename)
{
	struct devicelist *curdev;
	int rv;
	int found = 0;

	/* new firmware filename and device desc */

	TAILQ_FOREACH(curdev, fw_devices, nextdev) {
		if (strncmp(curdev->access_devname, device,
		    MAXPATHLEN) == 0) {
			rv = curdev->plugin->fw_readfw(curdev, filename);

			if (rv != FWFLASH_SUCCESS)
				logmsg(MSG_ERROR,
				    gettext("Unable to write out firmware "
				    "image for %s to file %s\n"),
				    curdev->access_devname, filename);
			found++;
		}

	}

	if (!found) {
		logmsg(MSG_ERROR,
		    gettext("No device matching %s was found.\n"),
		    device);
		rv = FWFLASH_FAILURE;
	}

	return (rv);
}

static void
fwflash_usage(char *arg)
{

	(void) fprintf(stderr, "\n");
	if (arg != NULL) {
		logmsg(MSG_ERROR,
		    gettext("Invalid argument (%s) supplied\n"), arg);
	}

	(void) fprintf(stderr, "\n");

	(void) fprintf(stdout, gettext("Usage:\n\t"));
	(void) fprintf(stdout, gettext("fwflash [-l [-c device_class "
	    "| ALL]] | [-v] | [-h]\n\t"));
	(void) fprintf(stdout, gettext("fwflash [-f file1,file2,file3"
	    ",... | -r file] [-y] -d device_path\n\n"));
	(void) fprintf(stdout, "\n"); /* workaround for xgettext */

	(void) fprintf(stdout,
	    gettext("\t-l\t\tlist flashable devices in this system\n"
	    "\t-c device_class limit search to a specific class\n"
	    "\t\t\teg IB for InfiniBand, ses for SCSI Enclosures\n"
	    "\t-v\t\tprint version number of fwflash utility\n"
	    "\t-h\t\tprint this usage message\n\n"));
	(void) fprintf(stdout,
	    gettext("\t-f file1,file2,file3,...\n"
	    "\t\t\tfirmware image file list to flash\n"
	    "\t-r file\t\tfile to dump device firmware to\n"
	    "\t-y\t\tanswer Yes/Y/y to prompts\n"
	    "\t-d device_path\tpathname of device to be flashed\n\n"));

	(void) fprintf(stdout,
	    gettext("\tIf -d device_path is specified, then one of -f "
	    "<files>\n"
	    "\tor -r <file> must also be specified\n\n"));

	(void) fprintf(stdout,
	    gettext("\tIf multiple firmware images are required to be "
	    "flashed\n"
	    "\tthey must be listed together, separated by commas. The\n"
	    "\timages will be flashed in the order specified.\n\n"));

	(void) fprintf(stdout, "\n");
}

static void
fwflash_version(void)
{
	(void) fprintf(stdout, gettext("\n%s: "), FWFLASH_PROG_NAME);
	(void) fprintf(stdout, gettext("version %s\n"),
	    FWFLASH_VERSION);
}

static void
fwflash_intr(int sig)
{

	struct devicelist *thisdev;
	struct pluginlist *thisplug;

	(void) signal(SIGINT, SIG_IGN);
	(void) signal(SIGTERM, SIG_IGN);
	(void) signal(SIGABRT, SIG_IGN);

	if (fwflash_in_write) {
		(void) fprintf(stderr,
		    gettext("WARNING: firmware image may be corrupted\n\t"));
		(void) fprintf(stderr,
		    gettext("Reflash firmware before rebooting!\n"));
	}

	if (sig > 0) {
		(void) logmsg(MSG_ERROR, gettext("\n"));
		(void) logmsg(MSG_ERROR,
		    gettext("fwflash exiting due to signal (%d)\n"), sig);
	}

	/*
	 * we need to close everything down properly, so
	 * call the plugin closure routines
	 */
	if (fw_devices != NULL) {
		TAILQ_FOREACH(thisdev, fw_devices, nextdev) {
			if (thisdev->plugin->fw_cleanup != NULL) {
				/*
				 * If we've got a cleanup routine, it
				 * cleans up _everything_ for thisdev
				 */
				thisdev->plugin->fw_cleanup(thisdev);
			} else {
				/* free the components first */
				free(thisdev->access_devname);
				free(thisdev->drvname);
				free(thisdev->classname);
				if (thisdev->ident != NULL)
					free(thisdev->ident);
				/* We don't free address[] for old plugins */
				thisdev->ident = NULL;
				thisdev->plugin = NULL;
			}
			/* CONSTCOND */
			TAILQ_REMOVE(fw_devices, thisdev, nextdev);
		}
	}

	if (fw_pluginlist != NULL) {
		TAILQ_FOREACH(thisplug, fw_pluginlist, nextplugin) {
			free(thisplug->filename);
			free(thisplug->drvname);
			free(thisplug->plugin->filename);
			free(thisplug->plugin->drvname);
			thisplug->filename = NULL;
			thisplug->drvname = NULL;
			thisplug->plugin->filename = NULL;
			thisplug->plugin->drvname = NULL;
			thisplug->plugin->fw_readfw = NULL;
			thisplug->plugin->fw_writefw = NULL;
			thisplug->plugin->fw_identify = NULL;
			thisplug->plugin->fw_devinfo = NULL;
			thisplug->plugin->fw_cleanup = NULL;
			(void) dlclose(thisplug->plugin->handle);
			thisplug->plugin->handle = NULL;
			free(thisplug->plugin);
			thisplug->plugin = NULL;
			/* CONSTCOND */
			TAILQ_REMOVE(fw_pluginlist, thisplug, nextplugin);
		}
	}

	if (verifier != NULL) {
		free(verifier->filename);
		free(verifier->vendor);
		free(verifier->imgfile);
		free(verifier->fwimage);
		verifier->filename = NULL;
		verifier->vendor = NULL;
		verifier->vendorvrfy = NULL;
		verifier->imgfile = NULL;
		verifier->fwimage = NULL;
		(void) dlclose(verifier->handle);
		verifier->handle = NULL;
		free(verifier);
	}
	di_fini(rootnode);

	if (sig > 0)
		exit(FWFLASH_FAILURE);
}

static void
fwflash_handle_signals(void)
{
	if (signal(SIGINT, fwflash_intr) == SIG_ERR) {
		perror("signal");
		exit(FWFLASH_FAILURE);
	}

	if (signal(SIGTERM, fwflash_intr) == SIG_ERR) {
		perror("signal");
		exit(FWFLASH_FAILURE);
	}
}

static int
confirm_target(struct devicelist *thisdev, char *file)
{
	int resp;

	(void) fflush(stdin);
	(void) printf(gettext("About to update firmware on %s\n"),
	    thisdev->access_devname);
	(void) printf(gettext("with file %s.\n"
	    "Do you want to continue? (Y/N): "), file);

	resp = getchar();
	if (resp == 'Y' || resp == 'y') {
		return (FWFLASH_YES_FLAG);
	} else {
		logmsg(MSG_INFO, "flash operation NOT confirmed.\n");
	}

	(void) fflush(stdin);
	return (FWFLASH_FAILURE);
}

int
get_fileopts(char *options)
{

	int i;
	char *files;

	if (files = strtok(options, ",")) {
		/* we have more than one */
		if ((filelist[0] = calloc(1, MAXPATHLEN + 1)) == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to allocate space for "
			    "a firmware image filename\n"));
			return (FWFLASH_FAILURE);
		}
		(void) strlcpy(filelist[0], files, strlen(files) + 1);
		i = 1;

		logmsg(MSG_INFO, "fwflash: filelist[0]: %s\n",
		    filelist[0]);


		while (files = strtok(NULL, ",")) {
			if ((filelist[i] = calloc(1, MAXPATHLEN + 1))
			    == NULL) {
				logmsg(MSG_ERROR,
				    gettext("Unable to allocate space for "
				    "a firmware image filename\n"));
				return (FWFLASH_FAILURE);
			}
			(void) strlcpy(filelist[i], files,
			    strlen(files) + 1);
			logmsg(MSG_INFO, "fwflash: filelist[%d]: %s\n",
			    i, filelist[i]);
			++i;
		}
	} else {
		if ((filelist[0] = calloc(1, MAXPATHLEN + 1)) == NULL) {
			logmsg(MSG_ERROR,
			    gettext("Unable to allocate space for "
			    "a firmware image filename\n"));
			return (FWFLASH_FAILURE);
		}
		(void) strlcpy(filelist[0], options, strlen(files) + 1);
		logmsg(MSG_INFO, "fwflash: filelist[0]: %s\n",
		    filelist[0]);
	}
	return (FWFLASH_SUCCESS);
}

/*
 * code reuse - cheerfully borrowed from stmsboot_util.c
 */
void
logmsg(int severity, const char *msg, ...)
{
	va_list ap;

	if ((severity > MSG_INFO) ||
	    ((severity == MSG_INFO) && (fwflash_debug > 0))) {
		(void) fprintf(stderr, "%s: ", FWFLASH_PROG_NAME);
		va_start(ap, msg);
		(void) vfprintf(stderr, msg, ap);
		va_end(ap);
	}
}