stmsboot_util.c revision 5b5046010dc014958659914f953b1197da4054ac
/*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stropts.h>
#include <strings.h>
#include <sys/param.h>
#include <libdevinfo.h>
#include <locale.h>
#include <libintl.h>
#include <devid.h>
#include <sys/libdevid.h>
#include <sys/modctl.h> /* for MAXMODCONFNAME */
#include <sys/scsi/adapters/scsi_vhci.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 SAVE_DIR "/etc/mpxio"
#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 di_node_t devinfo_root = DI_NODE_NIL;
static char *ondiskname = "/etc/mpxio/devid_path.cache";
/*
* We use devid-keyed nvlists to keep track of the guid, traditional and
* MPxIO-enabled /dev/rdsk paths. Each of these nvlists is eventually
* added to our global nvlist and our on-disk nvlist.
*/
static nvlist_t *mapnvl;
static int mpxenabled = 0;
static int limctrl = -1;
static int mpxprop = 0;
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;
static void print_mpx_capable(di_node_t curnode);
static int popcheck_devnvl(di_node_t thisnode, nvlist_t *devnvl,
char *strdevid);
static int mpxio_nvl_boilerplate(di_node_t curnode);
static int validate_devnvl();
static void report_map(char *argdev, int physpath);
static void list_devs(int listguids, int ctrl);
static void logmsg(int level, const char *msg, ...);
static char *find_link(di_node_t cnode);
static void usage();
static void parse_args(int argc, char *argv[]);
static void get_devid(di_node_t node, ddi_devid_t *thisdevid);
static int print_bootpath();
static void vhci_to_phci(char *devpath, char *physpath);
static int update_vfstab();
int
main(int argc, char **argv)
{
struct stat cachestat;
int mapfd = 0;
int rv = 0;
char *ondiskbuf;
size_t newsz = 0;
parse_args(argc, argv);
errno = 0;
devinfo_root = di_init("/", DINFOCPYALL|DINFOFORCE);
logmsg(MSG_INFO, "errno = %d after "
"di_init(/,DINFOCPYALL|DINFOFORCE)\n", errno);
if (devinfo_root == NULL) {
logmsg(MSG_ERROR,
gettext("Unable to take device tree snapshot "
"(%s: %d)\n"), strerror(errno), errno);
return (-1);
}
logmsg(MSG_INFO, "opened root di_node\n");
if (globarg == MPX_CAPABLE_CTRL) {
/* we just want to find MPxIO-capable controllers and exit */
if (drvlimit != NULL) {
print_mpx_capable(di_drv_first_node(drvlimit,
devinfo_root));
} else {
print_mpx_capable(di_drv_first_node("fp",
devinfo_root));
print_mpx_capable(di_drv_first_node("mpt",
devinfo_root));
print_mpx_capable(di_drv_first_node("mpt_sas",
devinfo_root));
}
di_fini(devinfo_root);
return (0);
}
mapfd = open(ondiskname, O_RDWR|O_CREAT|O_SYNC, S_IRUSR | S_IWUSR);
if (mapfd < 0) {
/* we could be in single-user, so try for RO */
if ((mapfd = open(ondiskname, O_RDONLY)) < 0) {
logmsg(MSG_ERROR,
gettext("Unable to open or create %s:%s\n"),
ondiskname, strerror(errno));
return (errno);
}
readonlyroot = 1;
}
if (stat(ondiskname, &cachestat) != 0) {
logmsg(MSG_ERROR,
gettext("Unable to stat() %s: %s\n"),
ondiskname, strerror(errno));
return (errno);
}
ondiskbuf = calloc(1, cachestat.st_size);
if (ondiskbuf == NULL) {
logmsg(MSG_ERROR,
gettext("Unable to allocate memory for the devid "
"cache file: %s\n"), strerror(errno));
return (errno);
}
rv = read(mapfd, ondiskbuf, cachestat.st_size);
if (rv != cachestat.st_size) {
logmsg(MSG_ERROR,
gettext("Unable to read all of devid cache file (got %d "
"from expected %d bytes): %s\n"),
rv, cachestat.st_size, strerror(errno));
return (errno);
}
errno = 0;
rv = nvlist_unpack(ondiskbuf, cachestat.st_size, &mapnvl, 0);
if (rv) {
logmsg(MSG_INFO,
"Unable to unpack devid cache file %s: %s (%d)\n",
ondiskname, strerror(rv), rv);
if (nvlist_alloc(&mapnvl, NV_UNIQUE_NAME, 0) != 0) {
logmsg(MSG_ERROR,
gettext("Unable to allocate root property"
"list\n"));
return (errno);
}
}
free(ondiskbuf);
if (validate_devnvl() < 0) {
logmsg(MSG_ERROR,
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) {
rv = nvlist_size(mapnvl, &newsz, NV_ENCODE_NATIVE);
if (rv) {
logmsg(MSG_ERROR,
gettext("Unable to determine size of packed "
"on-disk devid cache file %s: %s (%d).\n"),
ondiskname, strerror(rv), rv);
logmsg(MSG_ERROR, gettext("Terminating\n"));
nvlist_free(mapnvl);
(void) close(mapfd);
return (rv);
}
if ((ondiskbuf = calloc(1, newsz)) == NULL) {
logmsg(MSG_ERROR,
"Unable to allocate space for writing out new "
"on-disk devid cache file: %s\n", strerror(errno));
(void) close(mapfd);
nvlist_free(mapnvl);
return (errno);
}
rv = nvlist_pack(mapnvl, &ondiskbuf, &newsz,
NV_ENCODE_NATIVE, 0);
if (rv) {
logmsg(MSG_ERROR,
gettext("Unable to pack on-disk devid cache "
"file: %s (%d)\n"), strerror(rv), rv);
(void) close(mapfd);
free(ondiskbuf);
nvlist_free(mapnvl);
return (rv);
}
rv = lseek(mapfd, 0, 0);
if (rv == -1) {
logmsg(MSG_ERROR,
gettext("Unable to seek to start of devid cache "
"file: %s (%d)\n"), strerror(errno), errno);
(void) close(mapfd);
free(ondiskbuf);
nvlist_free(mapnvl);
return (-1);
}
if (write(mapfd, ondiskbuf, newsz) != newsz) {
logmsg(MSG_ERROR,
gettext("Unable to completely write out "
"on-disk devid cache file: %s\n"), strerror(errno));
(void) close(mapfd);
nvlist_free(mapnvl);
free(ondiskbuf);
return (errno);
}
} /* !readonlyroot */
/* Now we can process the command line args */
if (globarg == MPX_PHYSICAL) {
report_map(devicep, BOOT);
} else if (globarg == MPX_BOOTPATH) {
rv = print_bootpath();
di_fini(devinfo_root);
return (rv);
} else if (globarg == MPX_UPDATEVFSTAB) {
rv = update_vfstab();
di_fini(devinfo_root);
return (rv);
} else if (globarg != MPX_INIT) {
if (globarg & MPX_LIST)
list_devs(guid, limctrl);
if (globarg == MPX_MAP)
report_map(devicep, NONBOOT);
} else {
logmsg(MSG_INFO, "\nprivate devid cache file initialised\n");
}
nvlist_free(mapnvl);
di_fini(devinfo_root);
return (0);
}
static void
usage()
{
(void) fprintf(stderr,
gettext("usage: stmsboot_util -b | -m devname | "
"-l <ctrl> | -L | [-g] | -n | -N | -i | -p devname\n"));
(void) fprintf(stderr, "\n\n");
(void) fprintf(stderr, gettext("\t-h\tprint this usage message\n"));
(void) fprintf(stderr, gettext("\t-b\tretrieve the system's bootpath "
"setting\n"));
(void) fprintf(stderr, gettext("\t-m devname\n"));
(void) fprintf(stderr, gettext("\t\tReports the current mapping for "
"devname\n"));
(void) fprintf(stderr, gettext("\t-g\tprint the GUID for MPxIO-capable "
"devices. This\n"));
(void) fprintf(stderr, gettext("\t\toption is only valid with the -L "
"or -l options\n"));
(void) fprintf(stderr, gettext("\t-L | -l <ctrl>\n"));
(void) fprintf(stderr, gettext("\t\tList the 'native' to 'MPxIO' "
"device mappings. If <ctrl>\n"));
(void) fprintf(stderr, gettext("\t\tis specified, only print mappings "
"for those devices\n"));
(void) fprintf(stderr, gettext("\t\tattached via the specified "
"controller.\n"));
(void) fprintf(stderr, gettext("\t-i\tinitialise the private devid "
"cache file and exit\n"));
(void) fprintf(stderr, gettext("\t\tThis option excludes all "
"others.\n"));
(void) fprintf(stderr, gettext("\t-n\tprint the devfs paths for "
"multipath-capable\n"));
(void) fprintf(stderr, gettext("\t\tcontroller ports.\n"));
(void) fprintf(stderr, gettext("\t-N\tprint the device aliases of "
"multipath-capable\n"));
(void) fprintf(stderr, gettext("\t\tcontroller ports.\n"));
(void) fprintf(stderr, gettext("\t-p\tdevname\n"));
(void) fprintf(stderr, gettext("\t\tThis option provides the physical "
"devfs path for\n"));
(void) fprintf(stderr, gettext("\t\ta specific device (devname). Used "
"to set the bootpath\n"));
(void) fprintf(stderr, gettext("\t\tvariable on x86/x64 systems\n"));
(void) fprintf(stderr, gettext("\t-u\ttranslates device mappings in "
"/etc/vfstab as \n"));
(void) fprintf(stderr, gettext("\t\trequired. The output is written "
"to /etc/mpxio/vfstab.new\n\n"));
exit(2);
}
static void
parse_args(int argc, char *argv[])
{
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
* prints the device path (/dev/rdsk) that devname maps to
* 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
* remaps devices in /etc/vfstab, saving the newly generated
* file to /etc/mpxio/vfstab.new. If we have any remapped
* devices, exit with status 0, otherwise -1 for error.
*/
while ((opt = getopt(argc, argv, "bdD:ghil:Lm:nNp:u")) != EOF) {
switch (opt) {
case 'b':
globarg = MPX_BOOTPATH;
break;
case 'd':
debugflag = 1;
break;
case 'D':
if ((drvlimit = calloc(1, MAXMODCONFNAME)) == NULL) {
logmsg(MSG_ERROR,
gettext("Unable to allocate memory for a "
"driver name: %s\n"), strerror(errno));
exit(errno);
}
bcopy(optarg, drvlimit, strlen(optarg));
/* update this if adding support for a new driver */
if ((strncmp(drvlimit, "fp", 2) == NULL) &&
(strncmp(drvlimit, "mpt", 3) == NULL) &&
(strncmp(drvlimit, "mpt_sas", 7) == NULL)) {
logmsg(MSG_ERROR,
gettext("invalid parent driver (%s) "
"specified"), drvlimit);
usage();
}
break;
case 'h':
/* Just drop out and print the usage() output */
globarg = MPX_USAGE;
break;
case 'i':
globarg = MPX_INIT;
break;
case 'l':
globarg |= MPX_LIST;
limctrl = (int)atol(optarg);
if (limctrl < 0) {
logmsg(MSG_INFO,
gettext("invalid controller number "
"(%d), checking all controllers\n"),
limctrl);
}
break;
case 'L':
globarg |= MPX_LIST;
break;
case 'g':
guid = 1;
break;
case 'm':
globarg = MPX_MAP;
if ((devicep = calloc(1, MAXPATHLEN)) == NULL) {
logmsg(MSG_ERROR,
gettext("Unable to allocate space for a "
"device name\n"));
exit(errno);
}
devicep = strdup(optarg);
break;
case 'N':
cap_N_option = 1;
globarg = MPX_CAPABLE_CTRL;
break;
case 'n':
globarg = MPX_CAPABLE_CTRL;
break;
case 'p':
globarg = MPX_PHYSICAL;
if ((devicep = calloc(1, MAXPATHLEN)) == NULL) {
logmsg(MSG_ERROR,
gettext("Unable to allocate space for a "
"device name\n"));
exit(errno);
}
devicep = strdup(optarg);
break;
case 'u':
globarg = MPX_UPDATEVFSTAB;
break;
default:
logmsg(MSG_ERROR,
gettext("Invalid command line option (%c)\n"),
opt);
usage();
}
}
if ((globarg >= MPX_USAGE) || (guid && (globarg != MPX_LIST)))
usage();
if ((drvlimit != NULL) &&
((globarg != MPX_LIST) &&
(globarg != MPX_CAPABLE_CTRL)))
usage();
}
static void
logmsg(int level, const char *msg, ...)
{
va_list ap;
if ((level >= MSG_ERROR) ||
((debugflag > 0) && (level >= MSG_INFO))) {
(void) fprintf(stdout, "stmsboot: ");
va_start(ap, msg);
(void) vfprintf(stdout, msg, ap);
va_end(ap);
}
}
/*
* 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
* part, we'll add /dev/rdsk/ back on when we print the listing so we maintain
* 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
list_devs(int listguids, int ctrl)
{
nvlist_t *thisdevnvl;
nvpair_t *pair;
char *diskpath, *livepath, *key, *querydev;
char *matchctrl = NULL;
char checkctrl[MAXPATHLEN];
int rv;
if (!mpxenabled) {
if (mpxprop) {
logmsg(MSG_ERROR, gettext("MPXIO disabled\n"));
} else {
logmsg(MSG_ERROR, gettext("No STMS devices have "
"been found\n"));
}
return;
}
if (listguids) {
(void) printf(gettext("non-STMS device name\t\t\tGUID\n"
"------------------------------------------"
"------------------------\n"));
} else {
(void) printf(gettext("non-STMS device name\t\t\t"
"STMS device name\n"
"------------------------------------------"
"------------------------\n"));
}
bzero(checkctrl, MAXPATHLEN);
pair = NULL;
while ((pair = nvlist_next_nvpair(mapnvl, pair))
!= NULL) {
boolean_t livescsivhcip = B_FALSE;
if ((((rv = nvpair_value_string(pair, &querydev)) < 0) ||
((key = nvpair_name(pair)) == NULL)) ||
((strstr(key, "/pci") != NULL) ||
(strstr(key, "/sbus") != NULL) ||
(strstr(key, "/scsi_vhci") != NULL) ||
(strncmp(key, "id1", 3) == 0))) {
logmsg(MSG_INFO,
"list_devs: rv = %d; (%s) is not a devlink, "
"continuing.\n", rv,
(key != NULL) ? key : "null");
querydev = NULL;
continue;
}
(void) nvlist_lookup_nvlist(mapnvl, querydev, &thisdevnvl);
(void) nvlist_lookup_boolean_value(thisdevnvl, NVL_MPXEN,
&livescsivhcip);
(void) nvlist_lookup_string(thisdevnvl, NVL_MPXPATH,
&livepath);
if ((!livescsivhcip) ||
(livescsivhcip &&
(strncmp(key, livepath, strlen(key)) == 0)))
continue;
(void) nvlist_lookup_string(thisdevnvl, NVL_PATH,
&diskpath);
logmsg(MSG_INFO,
"list_devs: %s :: %s ::%s :: MPXEN (%s)\n",
key, diskpath, livepath,
((livescsivhcip) ? "TRUE" : "FALSE"));
if (ctrl > -1) {
(void) sprintf(checkctrl, "c%dt", ctrl);
matchctrl = strstr(key, checkctrl);
if (matchctrl == NULL)
continue;
}
if (listguids != 0) {
char *tempguid;
ddi_devid_t curdevid;
int rv;
rv = devid_str_decode(querydev, &curdevid, NULL);
if (rv == -1) {
logmsg(MSG_INFO, "Unable to decode devid %s\n",
key);
continue;
}
tempguid = devid_to_guid(curdevid);
if (tempguid != NULL)
(void) printf("/dev/rdsk/%s\t%s\n",
diskpath, tempguid);
devid_free_guid(tempguid);
devid_free(curdevid);
continue;
}
(void) printf("/dev/rdsk/%s\t/dev/rdsk/%s\n",
(strstr(key, diskpath) == NULL) ? key : diskpath,
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
* /scsi_vhci/disk@g500000e011e17720: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:
*
* # /lib/mpxio/stmsboot_util -m /dev/dsk/c2t0d0s2
* /dev/dsk/c3t500000E011637CF0d0s2
*/
static void
report_map(char *argdev, int physpath)
{
nvlist_t *thisdev;
int rv = 0;
char *thisdevid;
char *mpxpath = NULL;
char *prefixt = NULL;
char *prefixp = NULL;
char *stripdev = NULL;
char *slice = NULL;
boolean_t mpxenp;
uint_t slicelen = 0;
mpxenp = B_FALSE;
if ((prefixt = calloc(1, strlen(argdev) + 1)) == NULL) {
logmsg(MSG_INFO, "Unable to allocate memory\n");
(void) printf("NOT_MAPPED\n");
return;
}
(void) strlcpy(prefixt, argdev, strlen(argdev) + 1);
slice = strrchr(argdev, (physpath == BOOT) ? ':' : 's');
if (slice != NULL) {
slicelen = strlen(slice);
if (slicelen > 3)
/* invalid size - max is 3 chars */
slicelen = 0;
}
if ((stripdev = calloc(1, strlen(prefixt) + 1)) == NULL) {
logmsg(MSG_INFO, "Unable to allocate memory\n");
(void) printf("NOT_MAPPED\n");
free(prefixt);
return;
}
if ((strstr(prefixt, "/scsi_vhci") == NULL) &&
(strstr(prefixt, "/pci") == NULL) &&
(strstr(prefixt, "/sbus") == NULL)) {
prefixp = strrchr(prefixt, '/');
(void) strlcpy(stripdev,
(prefixp == NULL) ? prefixt : prefixp + 1,
(prefixp == NULL) ?
strlen(prefixt) + 1: strlen(prefixp) + 1);
if (prefixp != NULL)
prefixt[strlen(argdev) - strlen(prefixp) + 1] = '\0';
} else {
if (physpath != BOOT) {
logmsg(MSG_INFO, "Invalid device path provided\n");
(void) printf("NOT_MAPPED\n");
free(stripdev);
free(prefixt);
return;
}
(void) strlcpy(stripdev, argdev, strlen(argdev) + 1);
}
logmsg(MSG_INFO,
"stripdev (%s), prefixt(%s), prefixp(%s), slice(%s)\n",
(stripdev == NULL) ? "null" : stripdev,
(prefixt == NULL) ? "null" : prefixt,
(prefixp == NULL) ? "null" : prefixp,
(slice == NULL) ? "null" : slice);
if (slicelen > 0)
stripdev[strlen(stripdev) - slicelen] = '\0';
/* search for the shortened version */
rv = nvlist_lookup_string(mapnvl, stripdev, &thisdevid);
if (rv) {
if (physpath != BOOT) {
logmsg(MSG_INFO,
"searched mapnvl for '%s', got %s (%d)\n",
stripdev, strerror(rv), rv);
(void) printf("NOT_MAPPED\n");
free(stripdev);
free(prefixt);
return;
}
}
logmsg(MSG_INFO, "device %s has devid %s\n", stripdev, thisdevid);
if (nvlist_lookup_nvlist(mapnvl, thisdevid, &thisdev) != 0) {
logmsg(MSG_INFO, "device (%s) in mapnvl but "
"not mapped!\n", thisdevid);
(void) printf("NOT_MAPPED\n");
free(stripdev);
free(prefixt);
return;
}
/* quick exit */
if (!mpxenabled && (strstr(argdev, "/pci") != NULL ||
strstr(argdev, "/sbus") != NULL)) {
(void) printf("%s\n", argdev);
free(stripdev);
free(prefixt);
return;
}
(void) nvlist_lookup_boolean_value(thisdev, NVL_MPXEN, &mpxenp);
if (physpath == BOOT) {
(void) nvlist_lookup_string(thisdev, NVL_PHYSPATH, &mpxpath);
if ((strstr(argdev, "/scsi_vhci") != NULL) &&
(strncmp(argdev, mpxpath, strlen(mpxpath)) == 0)) {
/* Need to translate vhci to phci */
char *realpath;
if ((realpath = calloc(1, MAXPATHLEN + 1)) == NULL) {
logmsg(MSG_ERROR,
gettext("Unable to allocate "
"memory for a path element\n"));
free(stripdev);
free(prefixt);
return;
}
vhci_to_phci(stripdev, realpath);
(void) printf("%s%s\n", realpath,
((slicelen > 0) && slice != NULL) ? slice : "");
free(realpath);
} else {
(void) printf("%s%s\n", mpxpath,
((slicelen > 0) && slice != NULL) ? slice : "");
}
} else {
(void) nvlist_lookup_string(thisdev,
((readonlyroot) ? NVL_PHYSPATH :
((mpxenp == B_TRUE) ? NVL_MPXPATH : NVL_PATH)),
&mpxpath);
logmsg(MSG_INFO, "mpxpath = %s\n",
(mpxpath == NULL) ? "null" : mpxpath);
if (readonlyroot ||
(strstr(mpxpath, "/scsi_vhci") != NULL) ||
(strstr(mpxpath, "/pci") != NULL) ||
(strstr(mpxpath, "/sbus") != NULL)) {
/*
* 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.
*/
(void) printf("/devices%s:%c\n", mpxpath,
slice[1] + '1');
} else {
(void) printf("%s%s%s\n",
(prefixt[0] == '/') ? prefixt : "",
mpxpath,
((slicelen > 0) && slice != NULL) ? slice : "");
}
}
free(prefixt);
free(stripdev);
}
/*
* Validate the in-kernel and on-disk forms of our devid cache,
* returns -1 for unfixable error and 0 for success.
*/
static int
validate_devnvl()
{
di_node_t curnode;
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.
*/
curnode = di_drv_first_node("ssd", devinfo_root);
if (curnode != DI_NODE_NIL)
rv1 = mpxio_nvl_boilerplate(curnode);
curnode = di_drv_first_node("sd", devinfo_root);
if (curnode != DI_NODE_NIL)
rv2 = mpxio_nvl_boilerplate(curnode);
if (rv1 + rv2 == -2)
return (-1);
return (0);
}
static int
mpxio_nvl_boilerplate(di_node_t curnode)
{
int rv;
char *strdevid;
ddi_devid_t curdevid;
nvlist_t *newnvl;
for (; curnode != DI_NODE_NIL; curnode = di_drv_next_node(curnode)) {
errno = 0;
curdevid = NULL;
get_devid(curnode, &curdevid);
if (curdevid == NULL)
/*
* There's no devid registered for this device
* so it's not cool enough to play with us
*/
continue;
strdevid = devid_str_encode(curdevid, NULL);
/* does this exist in the on-disk cache? */
rv = nvlist_lookup_nvlist(mapnvl, strdevid, &newnvl);
if (rv == ENOENT) {
logmsg(MSG_INFO, "nvlist for %s not found\n", strdevid);
/* no, so alloc a new nvl to store it */
if (nvlist_alloc(&newnvl, NV_UNIQUE_NAME, 0) != 0) {
logmsg(MSG_ERROR,
gettext("Unable to allocate space for "
"a devid property list: %s\n"),
strerror(errno));
return (-1);
}
} else {
if ((rv != ENOTSUP) && (rv != EINVAL))
logmsg(MSG_INFO,
"%s exists in ondisknvl, verifying\n",
strdevid);
}
if (popcheck_devnvl(curnode, newnvl, strdevid) != 0) {
logmsg(MSG_ERROR,
gettext("Unable to populate devid nvpair "
"for device with devid %s\n"),
strdevid);
devid_str_free(strdevid);
nvlist_free(newnvl);
return (-1);
}
/* Now add newnvl into our cache. */
errno = 0;
rv = nvlist_add_nvlist(mapnvl, strdevid, newnvl);
if (rv) {
logmsg(MSG_ERROR,
gettext("Unable to add device (devid %s) "
"to in-kernel nvl: %s (%d)\n"),
strdevid, strerror(rv), rv);
devid_str_free(strdevid);
nvlist_free(newnvl);
return (-1);
}
logmsg(MSG_INFO,
gettext("added device (devid %s) to mapnvl\n\n"),
strdevid);
devid_str_free(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
popcheck_devnvl(di_node_t thisnode, nvlist_t *devnvl, char *strdevid)
{
char *path = NULL;
char *curpath = NULL;
char *devfspath = NULL;
char *prop = NULL;
int scsivhciparent = 0;
int rv = 0;
boolean_t mpxenp = B_FALSE;
errno = 0;
devfspath = di_devfs_path(thisnode);
if (devfspath == NULL) {
logmsg(MSG_ERROR,
gettext("Unable to determine devfs path for node: %s\n"),
strerror(errno));
return (-1);
}
/* Add a convenient devfspath to devid inverse map */
if (nvlist_add_string(mapnvl, devfspath, strdevid) != 0) {
logmsg(MSG_ERROR,
gettext("Unable to add device path %s with devid "
"%s to mapnvl\n"), devfspath, strdevid);
return (-1);
}
if (di_prop_lookup_strings(DDI_DEV_T_ANY, di_parent_node(thisnode),
"mpxio-disable", &prop) >= 0) {
if (strncmp(prop, "yes", 3) == 0) {
if (!mpxprop)
mpxprop++;
}
}
if (strncmp(di_driver_name(di_parent_node(thisnode)),
"scsi_vhci", 9) == 0) {
scsivhciparent = 1;
if (!mpxenabled)
mpxenabled++;
rv = nvlist_lookup_boolean_value(devnvl, NVL_MPXEN, &mpxenp);
if (rv || (mpxenp == B_FALSE)) {
rv = nvlist_add_boolean_value(devnvl,
NVL_MPXEN, B_TRUE);
if (rv) {
logmsg(MSG_ERROR,
gettext("Unable to add property %s "
"(set to B_TRUE) for device %s: "
"%s (%d)\n"),
NVL_MPXEN, devfspath,
strerror(rv), rv);
return (-1);
}
logmsg(MSG_INFO, "NVL_MPXEN :: (B_FALSE->B_TRUE)\n");
}
} else {
/* turn _off_ the flag if it was enabled */
rv = nvlist_add_boolean_value(devnvl, NVL_MPXEN, B_FALSE);
if (rv) {
logmsg(MSG_ERROR,
gettext("Unable to add property %s "
"(set to B_FALSE) for device %s: %s (%d)\n"),
NVL_MPXEN, devfspath,
strerror(rv), rv);
return (-1);
}
logmsg(MSG_INFO, "NVL_MPXEN :: (B_TRUE-> B_FALSE)\n");
}
rv = nvlist_add_string(devnvl, NVL_PHYSPATH, devfspath);
if (rv) {
logmsg(MSG_ERROR,
gettext("Unable to add physical device path (%s) "
"property to nvl\n"));
return (-1);
}
if ((curpath = calloc(1, MAXPATHLEN)) == NULL) {
logmsg(MSG_ERROR,
gettext("Unable to allocate space for current path\n"));
return (-1);
}
curpath = find_link(thisnode);
if (curpath == NULL) {
if (readonlyroot) {
return (0);
}
logmsg(MSG_ERROR,
gettext("Unable to determine device path for node %s\n"),
devfspath);
return (-1);
}
rv = nvlist_lookup_string(devnvl, NVL_MPXPATH, &path);
if (path == NULL && scsivhciparent)
(void) nvlist_add_string(devnvl, NVL_MPXPATH, curpath);
if (!scsivhciparent) {
(void) nvlist_add_string(devnvl, NVL_PATH, curpath);
path = curpath;
}
/*
* This next block provides the path to devid inverse mapping
* that other functions require
*/
if (path != NULL) {
if (nvlist_add_string(mapnvl, path, strdevid) != 0) {
logmsg(MSG_ERROR,
gettext("Unable to add device %s with devid "
"%s to mapnvl\n"), path, strdevid);
return (-1);
}
logmsg(MSG_INFO, "popcheck_devnvl: added path %s :: %s\n",
path, strdevid);
if (nvlist_add_string(mapnvl, curpath, strdevid) != 0) {
logmsg(MSG_ERROR,
gettext("Unable to add device %s with devid "
"%s to mapnvl: %s\n"),
curpath, strdevid, strerror(errno));
return (-1);
}
logmsg(MSG_INFO, "popcheck_devnvl: added curpath %s :: %s\n",
curpath, strdevid);
}
if (scsivhciparent) {
if (nvlist_add_string(devnvl, NVL_MPXPATH, curpath) != 0) {
logmsg(MSG_ERROR,
gettext("Unable to add property %s for device "
"%s: %s\n"),
NVL_MPXPATH, devfspath, strerror(errno));
return (-1);
} else {
logmsg(MSG_INFO, "added curpath (%s) as NVL_MPXPATH "
"to devnvl for devid %s\n", curpath, strdevid);
}
}
return (0);
}
static void
print_mpx_capable(di_node_t curnode)
{
char *prop;
char *path;
char *aliases = NULL;
if (cap_N_option) {
aliases = calloc(1, MAXPATHLEN + 1);
if (aliases == NULL) {
logmsg(MSG_ERROR,
gettext("Unable to allocate memory for a device "
"alias list\n"));
return;
}
}
for (; curnode != DI_NODE_NIL; curnode = di_drv_next_node(curnode)) {
if (di_prop_lookup_strings(DDI_DEV_T_ANY, curnode,
"initiator-port", &prop) >= 0) {
if ((path = di_devfs_path(curnode)) == NULL) {
logmsg(MSG_INFO,
"Unable to find devfs path for device "
"%s: %s\n", &curnode, strerror(errno));
continue;
}
if (cap_N_option) {
char *nodename = di_node_name(curnode);
/* nodename is never going to be null */
if (strstr(aliases, nodename) == NULL)
/* haven't seen this nodename before */
(void) snprintf(aliases,
MAXPATHLEN + 1, "%s|%s",
((aliases != NULL) ? aliases : ""),
nodename);
} else
(void) printf("%s\n", path);
}
}
if (cap_N_option)
(void) printf("%s\n", aliases);
}
static int
link_cb(di_devlink_t devlink, void *arg)
{
const char *result;
result = di_devlink_path(devlink);
if (result == NULL) {
arg = (void *)"(null)";
} else {
(void) strlcpy(arg, result, strlen(result));
}
logmsg(MSG_INFO, "\nlink_cb::linkdata->resultstr = %s\n",
((result != NULL) ? result : "(null)"));
return (DI_WALK_CONTINUE);
}
static char *
find_link(di_node_t cnode)
{
di_minor_t devminor = DI_MINOR_NIL;
di_devlink_handle_t hdl;
char *devfspath = NULL;
char *minorpath = NULL;
char *linkname = NULL;
char *cbresult = NULL;
devfspath = di_devfs_path(cnode);
if (cnode == DI_NODE_NIL) {
logmsg(MSG_ERROR,
gettext("find_ctrl must be called with non-null "
"di_node_t\n"));
return (NULL);
}
logmsg(MSG_INFO, "find_link: devfspath %s\n", devfspath);
if (((cbresult = calloc(1, MAXPATHLEN)) == NULL) ||
((minorpath = calloc(1, MAXPATHLEN)) == NULL) ||
((linkname = calloc(1, MAXPATHLEN)) == NULL)) {
logmsg(MSG_ERROR, "unable to allocate space for dev link\n");
return (NULL);
}
devminor = di_minor_next(cnode, devminor);
hdl = di_devlink_init(di_devfs_minor_path(devminor), DI_MAKE_LINK);
if (hdl == NULL) {
logmsg((readonlyroot ? MSG_INFO : MSG_ERROR),
gettext("unable to take devlink snapshot: %s\n"),
strerror(errno));
return (NULL);
}
linkname = "^dsk/";
(void) snprintf(minorpath, MAXPATHLEN, "%s:c", devfspath);
errno = 0;
if (di_devlink_walk(hdl, linkname, minorpath, DI_PRIMARY_LINK,
(void *)cbresult, link_cb) < 0) {
logmsg(MSG_ERROR,
gettext("Unable to walk devlink snapshot for %s: %s\n"),
minorpath, strerror(errno));
return (NULL);
}
if (di_devlink_fini(&hdl) < 0) {
logmsg(MSG_ERROR,
gettext("Unable to close devlink snapshot: %s\n"),
strerror(errno));
}
if (strstr(cbresult, "dsk/") == NULL)
return (devfspath);
bzero(minorpath, MAXPATHLEN);
/* strip off the trailing "s2" */
bcopy(cbresult, minorpath, strlen(cbresult) - 1);
/* Now strip off the /dev/dsk/ prefix for output flexibility */
linkname = strrchr(minorpath, '/');
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
get_devid(di_node_t node, ddi_devid_t *thisdevid)
{
int fd;
char realpath[MAXPATHLEN];
char *openpath = di_devfs_path(node);
errno = 0;
bzero(realpath, MAXPATHLEN);
if (strstr(openpath, "/devices") == NULL) {
(void) snprintf(realpath, MAXPATHLEN,
"/devices%s:c,raw", openpath);
fd = open(realpath, O_RDONLY|O_NDELAY);
} else {
fd = open(openpath, O_RDONLY|O_NDELAY);
}
if (fd < 0) {
logmsg(MSG_INFO, "Unable to open path %s: %s\n",
openpath, strerror(errno));
return;
}
if (devid_get(fd, thisdevid) != 0) {
logmsg(MSG_INFO,
"'%s' node (%s) without a devid registered\n",
di_driver_name(node), di_devfs_path(node));
}
(void) close(fd);
}
static int
print_bootpath()
{
char *bootprop = NULL;
if (di_prop_lookup_strings(DDI_DEV_T_ANY, devinfo_root,
"bootpath", &bootprop) >= 0) {
(void) printf("%s\n", bootprop);
return (0);
} else if (di_prop_lookup_strings(DDI_DEV_T_ANY, devinfo_root,
"boot-path", &bootprop) >= 0) {
(void) printf("%s\n", bootprop);
return (0);
} else {
(void) printf("ERROR: no bootpath/boot-path property found\n");
return (ENOENT);
}
}
static void
get_phci_driver_name(char *phci_path, char **driver_name)
{
di_node_t phci_node = DI_NODE_NIL;
char *tmp = NULL;
phci_node = di_init(phci_path, DINFOCPYONE);
if (phci_node == DI_NODE_NIL) {
logmsg(MSG_ERROR,
gettext("Unable to take phci snapshot "
"(%s: %d)\n"), strerror(errno), errno);
return;
}
tmp = di_driver_name(phci_node);
if (tmp != NULL) {
(void) strncpy(*driver_name, tmp, 10);
}
di_fini(phci_node);
}
/*
* 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
vhci_to_phci(char *devpath, char *physpath)
{
sv_iocdata_t ioc;
sv_path_info_t *pi;
int vhci_fd;
int rv;
uint_t npaths = 0;
vhci_fd = open(VHCI_CTL_NODE, O_RDWR);
if (vhci_fd < 0)
goto failure;
bzero(&ioc, sizeof (sv_iocdata_t));
ioc.client = devpath;
ioc.ret_elem = &npaths;
rv = ioctl(vhci_fd, SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO, &ioc);
if (rv || npaths == 0) {
logmsg(MSG_INFO,
"SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO ioctl() failed, "
"%s (%d)\n", strerror(rv), rv);
goto failure;
}
bzero(&ioc, sizeof (sv_iocdata_t));
ioc.client = devpath;
ioc.buf_elem = npaths;
ioc.ret_elem = &npaths;
if ((ioc.ret_buf = calloc(npaths, sizeof (sv_path_info_t)))
== NULL)
goto failure;
rv = ioctl(vhci_fd, SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO, &ioc);
if (rv || npaths == 0) {
logmsg(MSG_INFO,
"SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO ioctl() (#2) "
"failed, %s (%d)\n", strerror(rv), rv);
goto failure;
}
if (ioc.buf_elem < npaths)
npaths = ioc.buf_elem;
pi = (sv_path_info_t *)ioc.ret_buf;
while (npaths--) {
if (pi->ret_state == MDI_PATHINFO_STATE_ONLINE) {
char nodename[5];
char *phci_driver = NULL;
bzero(nodename, 5);
phci_driver = malloc(10);
if (phci_driver == NULL) {
logmsg(MSG_INFO,
"vhci_to_phci: Memory allocation failed\n");
goto failure;
}
bzero(phci_driver, 10);
get_phci_driver_name(pi->device.ret_phci,
&phci_driver);
logmsg(MSG_INFO, "phci driver name: %s\n", phci_driver);
/*
* A hack, but nicer than a platform-specific ifdef
* fp on SPARC using "ssd" as nodename
* mpt use "sd" when mpxio disabled, use "disk" when
* mpxio is enabled
* for alll other cases, "disk" should be used as the
* nodename
*/
if (strstr(devpath, "ssd") != NULL) {
(void) snprintf(nodename, 5, "ssd");
} else if (strncmp(phci_driver, "mpt", 10) == 0) {
(void) snprintf(nodename, 5, "sd");
} else {
(void) snprintf(nodename, 5, "disk");
}
(void) snprintf(physpath, MAXPATHLEN, "%s/%s@%s",
pi->device.ret_phci, nodename, pi->ret_addr);
free(ioc.ret_buf);
free(phci_driver);
return;
}
pi++;
}
failure:
(void) snprintf(physpath, MAXPATHLEN, "NOT_MAPPED");
}
/*
* Write /etc/vfstab to /etc/vfstab.new, with any remapped device
* names substituted.
*
* Returns:
* 0 successful operation
* -1 failed
*/
static int
update_vfstab()
{
FILE *fdin, *fdout;
char *buf, *tmpbuf;
char fname[MAXPATHLEN];
int rv = -1, rval = -1;
char cdev[MAXPATHLEN];
char bdev[MAXPATHLEN];
char mntpt[MAXPATHLEN];
char fstype[512];
char fsckpass[512];
char mntboot[512];
char mntopt[MAXPATHLEN];
char fmt[80];
char *prefixt = NULL;
char *curdev = NULL;
char *thisdevid = NULL;
char *slice = NULL;
nvlist_t *thisdev;
boolean_t devmpx = B_FALSE;
buf = calloc(1, MAXPATHLEN);
tmpbuf = calloc(1, MAXPATHLEN);
if (buf == NULL || tmpbuf == NULL)
return (-1);
(void) snprintf(fname, MAXPATHLEN, "/etc/mpxio/vfstab.new");
fdin = fopen("/etc/vfstab", "r");
fdout = fopen(fname, "w+");
if (fdin == NULL || fdout == NULL) {
logmsg(MSG_INFO, "Unable to open vfstab or create a backup "
"vfstab %s\n");
return (-1);
}
(void) snprintf(fmt, sizeof (fmt),
"%%%ds %%%ds %%%ds %%%ds %%%ds %%%ds %%%ds", sizeof (bdev) - 1,
sizeof (cdev) - 1, sizeof (mntpt) - 1, sizeof (fstype) - 1,
sizeof (fsckpass) - 1, sizeof (mntboot) - 1, sizeof (mntopt) - 1);
while (fgets(buf, MAXPATHLEN, fdin) != NULL) {
if (strlen(buf) == (MAXPATHLEN - 1) &&
buf[MAXPATHLEN-2] != '\n') {
logmsg(MSG_ERROR,
gettext("/etc/vfstab line length too long, "
"exceeded %2$d: \"%3$s\"\n"),
MAXPATHLEN - 2, buf);
goto out;
}
prefixt = NULL;
curdev = NULL;
slice = NULL;
thisdevid = NULL;
thisdev = NULL;
/* LINTED - variable format specifier */
rv = sscanf(buf, fmt, bdev, cdev, mntpt, fstype, fsckpass,
mntboot, mntopt);
/*
* Walk through the lines in the input file (/etc/vfstab),
* skipping anything which is _not_ a COGD (common or garden
* disk), ie all the /devices, /system, /dev/md, /dev/vx and
* /dev/zvol and so forth.
*/
if ((rv == 7) && (bdev[0] == '/') &&
(strstr(bdev, "/dev/dsk"))) {
slice = strrchr(bdev, 's');
/* take a copy, strip off /dev/dsk/ */
prefixt = strrchr(bdev, 'c');
prefixt[strlen(bdev) - 9 - strlen(slice)] = '\0';
slice++; /* advance past the s */
rval = nvlist_lookup_string(mapnvl, prefixt,
&thisdevid);
if (rval) {
/* Whoa, where did this device go?! */
logmsg(MSG_INFO,
"error looking up device %s\n", prefixt);
/* Comment-out this line in the new version */
(void) snprintf(tmpbuf, MAXPATHLEN,
"# DEVICE NOT FOUND %s", buf);
(void) fprintf(fdout, "%s", tmpbuf);
continue;
} else {
/* The device exists in our mapnvl */
(void) nvlist_lookup_nvlist(mapnvl, thisdevid,
&thisdev);
(void) nvlist_lookup_boolean_value(thisdev,
NVL_MPXEN, &devmpx);
(void) nvlist_lookup_string(thisdev,
((devmpx == B_TRUE)
? NVL_MPXPATH : NVL_PATH),
&curdev);
}
}
if ((prefixt != NULL) && (curdev != NULL) &&
(rv = (strncmp(prefixt, curdev, strlen(prefixt)) != 0))) {
/* Mapping change for this device */
if (strcmp(fstype, "swap") == 0) {
(void) snprintf(tmpbuf, MAXPATHLEN,
"/dev/dsk/%ss%s\t-\t-\tswap\t"
"%s\t%s\t%s\n",
curdev, slice, fsckpass, mntboot, mntopt);
} else {
(void) snprintf(tmpbuf, MAXPATHLEN,
"/dev/dsk/%ss%s\t/dev/rdsk/%ss%s\t"
"%s\t%s\t%s\t%s\t%s\n",
curdev, slice, curdev, slice,
mntpt, fstype, fsckpass, mntboot, mntopt);
}
errno = 0;
(void) fprintf(fdout, "%s", tmpbuf);
} else {
(void) fprintf(fdout, "%s", buf);
}
errno = 0;
if (fflush(fdout) != 0) {
logmsg(MSG_ERROR,
gettext("fprintf failed to write to %s: %s (%d)\n"),
fname, strerror(errno), errno);
goto out;
}
}
out:
(void) fclose(fdin);
(void) fclose(fdout);
free(buf);
free(tmpbuf);
return (errno);
}