stmsboot_util.c revision ed141cfca535fc26451c4bd9a2cfb6172af449c2
/*
* 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
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stropts.h>
#include <strings.h>
#include <libdevinfo.h>
#include <locale.h>
#include <libintl.h>
#include <devid.h>
#include <sys/libdevid.h>
/*
* SAVE_DIR is the directory in which system files are saved.
* SAVE_DIR must be under the root filesystem, as this program is
* typically run before any other filesystems are mounted.
*/
#define VHCI_CTL_NODE "/devices/scsi_vhci:devctl"
/* nvlist property names, these are ALL string types */
#define NVL_DEVID "nvl-devid"
#define NVL_PATH "nvl-path"
#define NVL_PHYSPATH "nvl-physpath"
#define NVL_MPXPATH "nvl-mpxiopath"
#define NVL_MPXEN "nvl-mpxioenabled"
#define MPX_LIST 0x01
#define MPX_MAP 0x02
#define MPX_CAPABLE_CTRL 0x04
#define MPX_INIT 0x08
#define MPX_PHYSICAL 0x10
#define MPX_BOOTPATH 0x20
#define MPX_UPDATEVFSTAB 0x40
#define MPX_USAGE 0x80
#define MSG_INFO 0x01
#define MSG_ERROR 0x02
#define MSG_PANIC 0x04
#define BOOT 0x01
#define NONBOOT 0x00
static char *ondiskname = "/etc/mpxio/devid_path.cache";
/*
* We use devid-keyed nvlists to keep track of the guid, traditional and
* added to our global nvlist and our on-disk nvlist.
*/
static int mpxenabled = 0;
static int limctrl = -1;
static int guid = 0;
static char *drvlimit;
static int globarg = 0;
static int debugflag = 0;
static char *devicep;
static int readonlyroot = 0;
static int cap_N_option = 0;
char *strdevid);
static int validate_devnvl();
static void usage();
static int print_bootpath();
static int update_vfstab();
int
{
int mapfd = 0;
int rv = 0;
char *ondiskbuf;
errno = 0;
"di_init(/,DINFOCPYALL|DINFOFORCE)\n", errno);
if (devinfo_root == NULL) {
gettext("Unable to take device tree snapshot "
return (-1);
}
if (globarg == MPX_CAPABLE_CTRL) {
/* we just want to find MPxIO-capable controllers and exit */
devinfo_root));
} else {
devinfo_root));
devinfo_root));
}
return (0);
}
if (mapfd < 0) {
/* we could be in single-user, so try for RO */
gettext("Unable to open or create %s:%s\n"),
return (errno);
}
readonlyroot = 1;
}
gettext("Unable to stat() %s: %s\n"),
return (errno);
}
gettext("Unable to allocate memory for the devid "
return (errno);
}
gettext("Unable to read all of devid cache file (got %d "
"from expected %d bytes): %s\n"),
return (errno);
}
errno = 0;
if (rv) {
"Unable to unpack devid cache file %s: %s (%d)\n",
gettext("Unable to allocate root property"
"list\n"));
return (errno);
}
}
if (validate_devnvl() < 0) {
gettext("unable to validate kernel with on-disk devid "
"cache file\n"));
return (errno);
}
/*
* If we're in single-user mode or maintenance mode, we won't
* necessarily have a writable root device (ZFSroot; ufs root is
* different in that we _do_ have a writable root device.
* This causes problems for the devlink calls (see
* $SRC/lib/libdevinfo/devinfo_devlink.c) and we do not try to
* write out the devnvl if root is readonly.
*/
if (!readonlyroot) {
if (rv) {
gettext("Unable to determine size of packed "
"on-disk devid cache file %s: %s (%d).\n"),
return (rv);
}
"Unable to allocate space for writing out new "
return (errno);
}
NV_ENCODE_NATIVE, 0);
if (rv) {
gettext("Unable to pack on-disk devid cache "
return (rv);
}
if (rv == -1) {
gettext("Unable to seek to start of devid cache "
return (-1);
}
gettext("Unable to completely write out "
return (errno);
}
} /* !readonlyroot */
/* Now we can process the command line args */
if (globarg == MPX_PHYSICAL) {
} else if (globarg == MPX_BOOTPATH) {
rv = print_bootpath();
return (rv);
} else if (globarg == MPX_UPDATEVFSTAB) {
rv = update_vfstab();
return (rv);
} else {
}
return (0);
}
static void
usage()
{
gettext("usage: stmsboot_util -b | -m devname | "
"-l <ctrl> | -L | [-g] | -n | -N | -i | -p devname\n"));
"setting\n"));
"devname\n"));
"devices. This\n"));
"or -l options\n"));
"device mappings. If <ctrl>\n"));
"for those devices\n"));
"controller.\n"));
"cache file and exit\n"));
"others.\n"));
"multipath-capable\n"));
"multipath-capable\n"));
"devfs path for\n"));
"to set the bootpath\n"));
"to /etc/mpxio/vfstab.new\n\n"));
exit(2);
}
static void
{
char opt;
if (argc == 1)
usage();
/*
* -b prints the bootpath property
* -d turns on debug mode for this utility (copious output!)
* -D drvname
* if supplied, indicates that we're going to operate on
* devices attached to this driver.
* -g if (-l or -L), prints guids for devices rather than paths
* -h prints the usage() help text.
* -i initialises the cache file and exits.
* -l controller
* list non-STMS to STMS device name mappings for the specific
* controller, when MPxIO is enabled only.
* -L list non-STMS to STMS device name mappings for all controllers
* when MPxIO is enabled only.
* -m devname
* in the currently-running system.
* -n
* if supplied, returns name of STMS-capable controller nodes.
* If the -D drvname option is specified as well, we only report
* nodes attached with drvname.
* -N
* same as the -n option, except that we only print the
* node-name (dev_info :: devi_node_name). Multiple instances
* through the libdevinfo snapshot are uniqified and separated
* by the "|" character for direct use by egrep(1).
* -p devname
* prints the physical devfs path for devname. Only used to
* determine the bootpath.
* -u
* file to /etc/mpxio/vfstab.new. If we have any remapped
* devices, exit with status 0, otherwise -1 for error.
*/
switch (opt) {
case 'b':
break;
case 'd':
debugflag = 1;
break;
case 'D':
gettext("Unable to allocate memory for a "
}
/* update this if adding support for a new driver */
gettext("invalid parent driver (%s) "
"specified"), drvlimit);
usage();
}
break;
case 'h':
/* Just drop out and print the usage() output */
break;
case 'i':
break;
case 'l':
if (limctrl < 0) {
gettext("invalid controller number "
"(%d), checking all controllers\n"),
limctrl);
}
break;
case 'L':
break;
case 'g':
guid = 1;
break;
case 'm':
gettext("Unable to allocate space for a "
"device name\n"));
}
break;
case 'N':
cap_N_option = 1;
break;
case 'n':
break;
case 'p':
gettext("Unable to allocate space for a "
"device name\n"));
}
break;
case 'u':
break;
default:
gettext("Invalid command line option (%c)\n"),
opt);
usage();
}
}
usage();
(globarg != MPX_CAPABLE_CTRL)))
usage();
}
static void
{
}
}
/*
* It's up to the caller to do any sorting or pretty-printing of the device
* mappings we report. Since we're storing the device links as just the cXtYdZ
* compatibility with previous versions of this tool. There's a little bit
* of footwork involved to make sure that we show all the paths to a device
* rather than just the first one we stashed away.
*/
static void
{
char checkctrl[MAXPATHLEN];
int rv;
if (!mpxenabled) {
return;
}
if (listguids) {
"------------------------------------------"
"------------------------\n"));
} else {
"STMS device name\n"
"------------------------------------------"
"------------------------\n"));
}
!= NULL) {
"list_devs: rv = %d; (%s) is not a devlink, "
"continuing.\n", rv,
continue;
}
&livepath);
if ((!livescsivhcip) ||
(livescsivhcip &&
continue;
&diskpath);
"list_devs: %s :: %s ::%s :: MPXEN (%s)\n",
if (ctrl > -1) {
continue;
}
if (listguids != 0) {
char *tempguid;
int rv;
if (rv == -1) {
key);
continue;
}
continue;
}
livepath);
}
}
/*
* We get passed a device name which we search the mapnvl for. If we find
* it, we print the mapping as it is found. It is up to the caller of this
* utility to do any pretty-printing of the results. If a device listed on
* the command line does not exist in the mapnvl, then we print NOT_MAPPED.
* Otherwise we print the command-line device name as it maps to what is
* stashed in the mapnvl - even if that's a "no change" device mapping.
*
* Example output (-p maps to physpath=BOOT)
* # /lib/mpxio/stmsboot_util -p \
* /pci@0,0/pci1022,7450@2/pci1000,3060@3/sd@1,0:a
*
* Or the reverse:
* # /lib/mpxio/stmsboot_util -p /scsi_vhci/disk@g500000e011e17720:a
* /pci@0,0/pci1022,7450@2/pci1000,3060@3/sd@1,0:a
*
* For the -m option, used when we're trying to find the root device mapping:
*
*/
static void
{
int rv = 0;
char *thisdevid;
(void) printf("NOT_MAPPED\n");
return;
}
if (slicelen > 3)
/* invalid size - max is 3 chars */
slicelen = 0;
}
(void) printf("NOT_MAPPED\n");
return;
}
} else {
(void) printf("NOT_MAPPED\n");
return;
}
}
"stripdev (%s), prefixt(%s), prefixp(%s), slice(%s)\n",
if (slicelen > 0)
/* search for the shortened version */
if (rv) {
"searched mapnvl for '%s', got %s (%d)\n",
(void) printf("NOT_MAPPED\n");
return;
}
}
"not mapped!\n", thisdevid);
(void) printf("NOT_MAPPED\n");
return;
}
/* quick exit */
return;
}
/* Need to translate vhci to phci */
char *realpath;
gettext("Unable to allocate "
"memory for a path element\n"));
return;
}
} else {
}
} else {
(void) nvlist_lookup_string(thisdev,
((readonlyroot) ? NVL_PHYSPATH :
&mpxpath);
if (readonlyroot ||
/*
* If we see a physical path here it means that
* devlinks aren't fully initialised yet, so we
* are still in maintenance/single-user mode.
*/
} else {
(void) printf("%s%s%s\n",
}
}
}
/*
* Validate the in-kernel and on-disk forms of our devid cache,
* returns -1 for unfixable error and 0 for success.
*/
static int
{
int rv1 = -1;
int rv2 = -1;
/*
* Method: we walk through the kernel's concept of the device tree
* looking for "ssd" then "sd" nodes.
* We check to see whether the device's devid is already in our nvlist
* (on disk) nvlist cache file. If it is, we check that it's components
* match what we've got already and fill any missing fields.
* If the devid isn't in our on-disk nvlist already then we add it
* and populate the property nvpairs.
*
* At the end of this function we should have this program's concept
* of the devid-keyed nvlist matching what is in the ondisk form which
* is ready to be written out.
* If we can't do this, then we return -1.
*/
if (curnode != DI_NODE_NIL)
if (curnode != DI_NODE_NIL)
return (-1);
return (0);
}
static int
{
int rv;
char *strdevid;
errno = 0;
/*
* There's no devid registered for this device
* so it's not cool enough to play with us
*/
continue;
/* does this exist in the on-disk cache? */
/* no, so alloc a new nvl to store it */
gettext("Unable to allocate space for "
"a devid property list: %s\n"),
return (-1);
}
} else {
"%s exists in ondisknvl, verifying\n",
strdevid);
}
gettext("Unable to populate devid nvpair "
"for device with devid %s\n"),
strdevid);
return (-1);
}
/* Now add newnvl into our cache. */
errno = 0;
if (rv) {
gettext("Unable to add device (devid %s) "
"to in-kernel nvl: %s (%d)\n"),
return (-1);
}
gettext("added device (devid %s) to mapnvl\n\n"),
strdevid);
}
return (0);
}
/*
* Operates on a single di_node_t, collecting all the device properties
* that we need. devnvl is allocated by the caller, and we add our nvpairs
* to it if they don't already exist.
*
* We are _only_ interested in devices which have a devid. We pull in
* devices even when they're excluded via stmsboot -D (driver), because
* we don't want to miss out on any devid data that might be handy later.
*/
static int
{
int scsivhciparent = 0;
int rv = 0;
errno = 0;
gettext("Unable to determine devfs path for node: %s\n"),
return (-1);
}
/* Add a convenient devfspath to devid inverse map */
gettext("Unable to add device path %s with devid "
return (-1);
}
"scsi_vhci", 9) == 0) {
scsivhciparent = 1;
if (!mpxenabled)
mpxenabled++;
if (rv) {
gettext("Unable to add property %s "
"(set to B_TRUE) for device %s: "
"%s (%d)\n"),
return (-1);
}
}
} else {
/* turn _off_ the flag if it was enabled */
if (rv) {
gettext("Unable to add property %s "
"(set to B_FALSE) for device %s: %s (%d)\n"),
return (-1);
}
}
if (rv) {
gettext("Unable to add physical device path (%s) "
"property to nvl\n"));
return (-1);
}
gettext("Unable to allocate space for current path\n"));
return (-1);
}
if (readonlyroot) {
return (0);
}
gettext("Unable to determine device path for node %s\n"),
return (-1);
}
if (!scsivhciparent) {
}
/*
* This next block provides the path to devid inverse mapping
* that other functions require
*/
gettext("Unable to add device %s with devid "
return (-1);
}
gettext("Unable to add device %s with devid "
"%s to mapnvl: %s\n"),
return (-1);
}
}
if (scsivhciparent) {
gettext("Unable to add property %s for device "
"%s: %s\n"),
return (-1);
} else {
}
}
return (0);
}
static void
{
char *prop;
char *path;
if (cap_N_option) {
gettext("Unable to allocate memory for a device "
"alias list\n"));
return;
}
}
"initiator-port", &prop) >= 0) {
"Unable to find devfs path for device "
continue;
}
if (cap_N_option) {
/* nodename is never going to be null */
/* haven't seen this nodename before */
nodename);
} else
}
}
if (cap_N_option)
}
static int
{
const char *result;
arg = (void *)"(null)";
} else {
}
return (DI_WALK_CONTINUE);
}
static char *
{
if (cnode == DI_NODE_NIL) {
gettext("find_ctrl must be called with non-null "
"di_node_t\n"));
return (NULL);
}
return (NULL);
}
gettext("unable to take devlink snapshot: %s\n"),
return (NULL);
}
linkname = "^dsk/";
errno = 0;
gettext("Unable to walk devlink snapshot for %s: %s\n"),
return (NULL);
}
if (di_devlink_fini(&hdl) < 0) {
gettext("Unable to close devlink snapshot: %s\n"),
}
return (devfspath);
/* strip off the trailing "s2" */
return (++linkname);
}
/*
* handle case where device has been probed but its target driver is not
* attached so enumeration has not quite finished. Opening the /devices
* pathname will force the kernel to finish the enumeration process and
* let us get the data we need.
*/
static void
{
int fd;
char realpath[MAXPATHLEN];
errno = 0;
"/devices%s:c,raw", openpath);
} else {
}
if (fd < 0) {
return;
}
"'%s' node (%s) without a devid registered\n",
}
}
static int
{
"bootpath", &bootprop) >= 0) {
return (0);
"boot-path", &bootprop) >= 0) {
return (0);
} else {
return (ENOENT);
}
}
/*
* We only call this routine if we have a scsi_vhci node and must
* determine the actual physical path of its first online client
* path.
*/
static void
{
int vhci_fd;
int rv;
if (vhci_fd < 0)
goto failure;
"SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO ioctl() failed, "
goto failure;
}
== NULL)
goto failure;
"SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO ioctl() (#2) "
goto failure;
}
while (npaths--) {
char nodename[4];
/* A hack, but nicer than a platform-specific ifdef */
} else {
}
return;
}
pi++;
}
}
/*
* Write /etc/vfstab to /etc/vfstab.new, with any remapped device
* names substituted.
*
* Returns:
* 0 successful operation
* -1 failed
*/
static int
{
char fname[MAXPATHLEN];
char cdev[MAXPATHLEN];
char bdev[MAXPATHLEN];
char mntpt[MAXPATHLEN];
char fstype[512];
char fsckpass[512];
char mntboot[512];
char mntopt[MAXPATHLEN];
char fmt[80];
return (-1);
"vfstab %s\n");
return (-1);
}
"exceeded %2$d: \"%3$s\"\n"),
goto out;
}
/* LINTED - variable format specifier */
/*
* skipping anything which is _not_ a COGD (common or garden
*/
slice++; /* advance past the s */
&thisdevid);
if (rval) {
/* Whoa, where did this device go?! */
"error looking up device %s\n", prefixt);
/* Comment-out this line in the new version */
"# DEVICE NOT FOUND %s", buf);
continue;
} else {
/* The device exists in our mapnvl */
&thisdev);
(void) nvlist_lookup_boolean_value(thisdev,
(void) nvlist_lookup_string(thisdev,
? NVL_MPXPATH : NVL_PATH),
&curdev);
}
}
/* Mapping change for this device */
"%s\t%s\t%s\n",
} else {
"%s\t%s\t%s\t%s\t%s\n",
}
errno = 0;
} else {
}
errno = 0;
gettext("fprintf failed to write to %s: %s (%d)\n"),
goto out;
}
}
out:
return (errno);
}