/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for MAXMODCONFNAME */ #include /* * 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 | -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 \n")); (void) fprintf(stderr, gettext("\t\tList the 'native' to 'MPxIO' " "device mappings. If \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); }