/* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #ifdef lint #define _REENTRANT /* for localtime_r */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __sparc #include #include #endif /* __sparc */ #include "libdevinfo.h" #include "device_info.h" #include #define isnewline(ch) ((ch) == '\n' || (ch) == '\r' || (ch) == '\f') #define isnamechar(ch) (isalpha(ch) || isdigit(ch) || (ch) == '_' ||\ (ch) == '-') #define MAX_TOKEN_SIZE 1024 #define BUFSIZE 1024 #define STRVAL(s) ((s) ? (s) : "NULL") #define SCSI_VHCI_CONF "/kernel/drv/scsi_vhci.conf" #define QLC_CONF "/kernel/drv/qlc.conf" #define FP_CONF "/kernel/drv/fp.conf" #define DRIVER_CLASSES "/etc/driver_classes" #define FP_AT "fp@" #define VHCI_CTL_NODE "/devices/scsi_vhci:devctl" #define SLASH_DEVICES "/devices" #define SLASH_DEVICES_SLASH "/devices/" #define SLASH_FP_AT "/fp@" #define SLASH_SCSI_VHCI "/scsi_vhci" #define META_DEV "/dev/md/dsk/" #define SLASH_DEV_SLASH "/dev/" /* * Macros to produce a quoted string containing the value of a * preprocessor macro. For example, if SIZE is defined to be 256, * VAL2STR(SIZE) is "256". This is used to construct format * strings for scanf-family functions below. */ #define QUOTE(x) #x #define VAL2STR(x) QUOTE(x) typedef enum { CLIENT_TYPE_UNKNOWN, CLIENT_TYPE_PHCI, CLIENT_TYPE_VHCI } client_type_t; typedef enum { T_EQUALS, T_AMPERSAND, T_BIT_OR, T_STAR, T_POUND, T_COLON, T_SEMICOLON, T_COMMA, T_SLASH, T_WHITE_SPACE, T_NEWLINE, T_EOF, T_STRING, T_HEXVAL, T_DECVAL, T_NAME } token_t; typedef enum { begin, parent, drvname, drvclass, prop, parent_equals, name_equals, drvclass_equals, parent_equals_string, name_equals_string, drvclass_equals_string, prop_equals, prop_equals_string, prop_equals_integer, prop_equals_string_comma, prop_equals_integer_comma } conf_state_t; /* structure to hold entries with mpxio-disable property in driver.conf file */ struct conf_entry { char *name; char *parent; char *class; char *unit_address; int port; int mpxio_disable; struct conf_entry *next; }; struct conf_file { char *filename; FILE *fp; int linenum; }; static char *tok_err = "Unexpected token '%s'\n"; /* #define DEBUG */ #ifdef DEBUG int devfsmap_debug = 0; /* /var/run is not mounted at install time. Therefore use /tmp */ char *devfsmap_logfile = "/tmp/devfsmap.log"; static FILE *logfp; #define logdmsg(args) log_debug_msg args static void vlog_debug_msg(char *, va_list); static void log_debug_msg(char *, ...); #ifdef __sparc static void log_confent_list(char *, struct conf_entry *, int); static void log_pathlist(char **); #endif /* __sparc */ #else /* DEBUG */ #define logdmsg(args) /* nothing */ #endif /* DEBUG */ /* * Leave NEWLINE as the next character. */ static void find_eol(FILE *fp) { int ch; while ((ch = getc(fp)) != EOF) { if (isnewline(ch)) { (void) ungetc(ch, fp); break; } } } /* ignore parsing errors */ /*ARGSUSED*/ static void file_err(struct conf_file *filep, char *fmt, ...) { #ifdef DEBUG va_list ap; va_start(ap, fmt); log_debug_msg("WARNING: %s line # %d: ", filep->filename, filep->linenum); vlog_debug_msg(fmt, ap); va_end(ap); #endif /* DEBUG */ } /* return the next token from the given driver.conf file, or -1 on error */ static token_t lex(struct conf_file *filep, char *val, size_t size) { char *cp; int ch, oval, badquote; size_t remain; token_t token; FILE *fp = filep->fp; if (size < 2) return (-1); cp = val; while ((ch = getc(fp)) == ' ' || ch == '\t') ; remain = size - 1; *cp++ = (char)ch; switch (ch) { case '=': token = T_EQUALS; break; case '&': token = T_AMPERSAND; break; case '|': token = T_BIT_OR; break; case '*': token = T_STAR; break; case '#': token = T_POUND; break; case ':': token = T_COLON; break; case ';': token = T_SEMICOLON; break; case ',': token = T_COMMA; break; case '/': token = T_SLASH; break; case ' ': case '\t': case '\f': while ((ch = getc(fp)) == ' ' || ch == '\t' || ch == '\f') { if (--remain == 0) { *cp = '\0'; return (-1); } *cp++ = (char)ch; } (void) ungetc(ch, fp); token = T_WHITE_SPACE; break; case '\n': case '\r': token = T_NEWLINE; break; case '"': remain++; cp--; badquote = 0; while (!badquote && (ch = getc(fp)) != '"') { switch (ch) { case '\n': case EOF: file_err(filep, "Missing \"\n"); remain = size - 1; cp = val; *cp++ = '\n'; badquote = 1; /* since we consumed the newline/EOF */ (void) ungetc(ch, fp); break; case '\\': if (--remain == 0) { *cp = '\0'; return (-1); } ch = (char)getc(fp); if (!isdigit(ch)) { /* escape the character */ *cp++ = (char)ch; break; } oval = 0; while (ch >= '0' && ch <= '7') { ch -= '0'; oval = (oval << 3) + ch; ch = (char)getc(fp); } (void) ungetc(ch, fp); /* check for character overflow? */ if (oval > 127) { file_err(filep, "Character " "overflow detected.\n"); } *cp++ = (char)oval; break; default: if (--remain == 0) { *cp = '\0'; return (-1); } *cp++ = (char)ch; break; } } token = T_STRING; break; case EOF: token = T_EOF; break; default: /* * detect a lone '-' (including at the end of a line), and * identify it as a 'name' */ if (ch == '-') { if (--remain == 0) { *cp = '\0'; return (-1); } *cp++ = (char)(ch = getc(fp)); if (ch == ' ' || ch == '\t' || ch == '\n') { (void) ungetc(ch, fp); remain++; cp--; token = T_NAME; break; } } else if (ch == '~' || ch == '-') { if (--remain == 0) { *cp = '\0'; return (-1); } *cp++ = (char)(ch = getc(fp)); } if (isdigit(ch)) { if (ch == '0') { if ((ch = getc(fp)) == 'x') { if (--remain == 0) { *cp = '\0'; return (-1); } *cp++ = (char)ch; ch = getc(fp); while (isxdigit(ch)) { if (--remain == 0) { *cp = '\0'; return (-1); } *cp++ = (char)ch; ch = getc(fp); } (void) ungetc(ch, fp); token = T_HEXVAL; } else { goto digit; } } else { ch = getc(fp); digit: while (isdigit(ch)) { if (--remain == 0) { *cp = '\0'; return (-1); } *cp++ = (char)ch; ch = getc(fp); } (void) ungetc(ch, fp); token = T_DECVAL; } } else if (isalpha(ch) || ch == '\\') { if (ch != '\\') { ch = getc(fp); } else { /* * if the character was a backslash, * back up so we can overwrite it with * the next (i.e. escaped) character. */ remain++; cp--; } while (isnamechar(ch) || ch == '\\') { if (ch == '\\') ch = getc(fp); if (--remain == 0) { *cp = '\0'; return (-1); } *cp++ = (char)ch; ch = getc(fp); } (void) ungetc(ch, fp); token = T_NAME; } else { return (-1); } break; } *cp = '\0'; return (token); } #ifdef __sparc static void free_confent(struct conf_entry *confent) { if (confent->name) free(confent->name); if (confent->parent) free(confent->parent); if (confent->class) free(confent->class); if (confent->unit_address) free(confent->unit_address); free(confent); } static void free_confent_list(struct conf_entry *confent_list) { struct conf_entry *confent, *next; for (confent = confent_list; confent != NULL; confent = next) { next = confent->next; free_confent(confent); } } /* * Parse the next entry from the driver.conf file and return in the form of * a pointer to the conf_entry. */ static struct conf_entry * parse_conf_entry(struct conf_file *filep, char *tokbuf, size_t linesize) { char *prop_name, *string; token_t token; struct conf_entry *confent; conf_state_t state; int failed = 1; if ((confent = calloc(1, sizeof (*confent))) == NULL) return (NULL); confent->port = -1; confent->mpxio_disable = -1; state = begin; token = T_NAME; prop_name = NULL; string = NULL; do { switch (token) { case T_NAME: switch (state) { case prop_equals_string: case prop_equals_integer: case begin: state = prop; if ((prop_name = strdup(tokbuf)) == NULL) goto bad; break; default: file_err(filep, tok_err, tokbuf); } break; case T_EQUALS: switch (state) { case prop: state = prop_equals; break; default: file_err(filep, tok_err, tokbuf); } break; case T_STRING: switch (state) { case prop_equals: if ((string = strdup(tokbuf)) == NULL) goto bad; state = begin; if (strcmp(prop_name, "PARENT") == 0 || strcmp(prop_name, "parent") == 0) { if (confent->parent) { file_err(filep, "'parent' property already specified\n"); goto bad; } confent->parent = string; } else if (strcmp(prop_name, "NAME") == 0 || strcmp(prop_name, "name") == 0) { if (confent->name) { file_err(filep, "'name' property already specified\n"); goto bad; } confent->name = string; } else if (strcmp(prop_name, "CLASS") == 0 || strcmp(prop_name, "class") == 0) { if (confent->class) { file_err(filep, "'class' property already specified\n"); goto bad; } confent->class = string; } else if (strcmp(prop_name, "unit-address") == 0) { if (confent->unit_address) { file_err(filep, "'unit-address' property already specified\n"); goto bad; } confent->unit_address = string; } else if (strcmp(prop_name, "mpxio-disable") == 0) { if (confent->mpxio_disable != -1) { file_err(filep, "'mpxio-disable' property already specified\n"); goto bad; } if (strcmp(string, "yes") == 0) confent->mpxio_disable = 1; else if (strcmp(string, "no") == 0) confent->mpxio_disable = 0; else { file_err(filep, "'mpxio-disable' property setting is invalid. " "The value must be either \"yes\" or \"no\"\n"); goto bad; } free(string); } else { free(string); state = prop_equals_string; } string = NULL; free(prop_name); prop_name = NULL; break; case prop_equals_string_comma: state = prop_equals_string; break; default: file_err(filep, tok_err, tokbuf); } break; case T_HEXVAL: case T_DECVAL: switch (state) { case prop_equals: if (strcmp(prop_name, "port") == 0) { if (confent->port != -1) { file_err(filep, "'port' property already specified\n"); goto bad; } confent->port = (int)strtol(tokbuf, NULL, 0); state = begin; } else state = prop_equals_integer; free(prop_name); prop_name = NULL; break; case prop_equals_integer_comma: state = prop_equals_integer; break; default: file_err(filep, tok_err, tokbuf); } break; case T_COMMA: switch (state) { case prop_equals_string: state = prop_equals_string_comma; break; case prop_equals_integer: state = prop_equals_integer_comma; break; default: file_err(filep, tok_err, tokbuf); } break; case T_NEWLINE: filep->linenum++; break; case T_POUND: find_eol(filep->fp); break; case T_EOF: file_err(filep, "Unexpected EOF\n"); goto bad; default: file_err(filep, tok_err, tokbuf); goto bad; } } while ((token = lex(filep, tokbuf, linesize)) != T_SEMICOLON); failed = 0; bad: if (prop_name) free(prop_name); if (string) free(string); if (failed == 1) { free_confent(confent); return (NULL); } return (confent); } /* * Parse all entries with mpxio-disable property in the given driver.conf * file. * * fname driver.conf file name * confent_list on return *confent_list will contain the list of * driver.conf file entries with mpxio-disable property. * mpxio_disable on return *mpxio_disable is set to the setting of the * driver global mpxio-dissable property as follows. * 0 if driver mpxio-disable="no" * 1 if driver mpxio-disable="yes" * -1 if driver mpxio-disable property isn't specified. */ static void parse_conf_file(char *fname, struct conf_entry **confent_list, int *mpxio_disable) { struct conf_entry *confent, *tail = NULL; token_t token; struct conf_file file; char tokval[MAX_TOKEN_SIZE]; *confent_list = NULL; *mpxio_disable = -1; if ((file.fp = fopen(fname, "r")) == NULL) return; file.filename = fname; file.linenum = 1; while ((token = lex(&file, tokval, MAX_TOKEN_SIZE)) != T_EOF) { switch (token) { case T_POUND: /* * Skip comments. */ find_eol(file.fp); break; case T_NAME: if ((confent = parse_conf_entry(&file, tokval, MAX_TOKEN_SIZE)) == NULL) break; /* * No name indicates global property. * Make sure parent and class not NULL. */ if (confent->name == NULL) { if (confent->parent || confent->class) { file_err(&file, "missing name attribute\n"); } else if (confent->mpxio_disable != -1) { if (*mpxio_disable == -1) *mpxio_disable = confent->mpxio_disable; else file_err(&file, "'mpxio-disable' property already specified\n"); } free_confent(confent); break; } /* * This is a node spec, either parent or class * must be specified. */ if (confent->parent == NULL && confent->class == NULL) { file_err(&file, "missing parent or class attribute\n"); free_confent(confent); break; } /* only need entries with mpxio_disable property */ if (confent->mpxio_disable == -1) { free_confent(confent); break; } if (tail) tail->next = confent; else *confent_list = confent; tail = confent; break; case T_NEWLINE: file.linenum++; break; default: break; } } (void) fclose(file.fp); } /* * Return the driver class of the given driver_name. * The memory for the driver class is allocated by this function and the * caller must free it. */ static char * get_driver_class(char *rootdir, char *driver_name) { FILE *fp; char buf[BUFSIZE]; char driver[BUFSIZE]; char class_name[BUFSIZE]; logdmsg(("get_driver_class: rootdir = %s, driver name = %s\n", rootdir, driver_name)); (void) snprintf(buf, sizeof (buf), "%s%s", rootdir, DRIVER_CLASSES); if ((fp = fopen(buf, "r")) == NULL) { logdmsg(("get_driver_class: failed to open %s: %s\n", buf, strerror(errno))); return (NULL); } while (fgets(buf, sizeof (buf), fp) != NULL) { /* LINTED - unbounded string specifier */ if ((sscanf(buf, "%s %s", driver, class_name) == 2) && driver[0] != '#' && strcmp(driver, driver_name) == 0) { logdmsg(("get_driver_class: driver class = %s\n", class_name)); (void) fclose(fp); return (strdup(class_name)); } } (void) fclose(fp); return (NULL); } static int lookup_in_confent_list(struct conf_entry *confent_list, int match_class, char *parent, char *unit_addr, int port) { struct conf_entry *confent; char *par; logdmsg(("lookup_in_confent_list: %s = \"%s\", unit_addr = \"%s\", " "port = %d\n", (match_class) ? "class" : "parent", parent, STRVAL(unit_addr), port)); for (confent = confent_list; confent != NULL; confent = confent->next) { par = (match_class) ? confent->class : confent->parent; if (unit_addr) { if (confent->unit_address != NULL && strcmp(confent->unit_address, unit_addr) == 0 && par != NULL && strcmp(par, parent) == 0) return (confent->mpxio_disable); } else { if (confent->port == port && par != NULL && strcmp(par, parent) == 0) return (confent->mpxio_disable); } } return (-1); } /* * lookup mpxio-disabled property setting for the given path in the given * driver.conf file. Match the entries from most specific to least specific. * * conf_file the path name of either fp.conf, qlc.conf or scsi_vhci.conf * path /devices node path without the /devices prefix. * If the conf_file is fp.conf, path must be a fp node path * if the conf_file is qlc.conf, path must be a qlc node path. * if the conf_file is scsi_vhci.conf, path must be NULL. * ex: /pci@8,600000/SUNW,qlc@4/fp@0,0 * /pci@8,600000/SUNW,qlc@4 * * returns: * 0 if mpxio-disable="no" * 1 if mpxio-disable="yes" * -1 if mpxio-disable property isn't specified. */ static int lookup_in_conf_file(char *rootdir, char *conf_file, char *path) { struct conf_entry *confent_list = NULL; int mpxio_disable; di_node_t par_node = DI_NODE_NIL; char *node_name = NULL, *node_addr = NULL; char *unit_addr = NULL; int port = -1; char *par_node_name = NULL, *par_node_addr = NULL; char *par_binding_name = NULL, *par_driver_name = NULL; char *par_driver_class = NULL, *par_node_name_addr; int rv = -1; char buf[MAXPATHLEN]; logdmsg(("lookup_in_conf_file: rootdir = \"%s\", conf_file = \"%s\", " "path = \"%s\"\n", rootdir, conf_file, STRVAL(path))); (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, conf_file); parse_conf_file(buf, &confent_list, &mpxio_disable); #ifdef DEBUG log_confent_list(buf, confent_list, mpxio_disable); #endif /* if path is NULL, return driver global mpxio-disable setting */ if (path == NULL) { rv = mpxio_disable; goto done; } if ((node_name = strrchr(path, '/')) == NULL) goto done; *node_name = '\0'; node_name++; if ((node_addr = strchr(node_name, '@')) == NULL) goto done; *node_addr = '\0'; node_addr++; if (strcmp(node_name, "fp") == 0) { /* get port number; encoded in the node addr as a hex number */ port = (int)strtol(node_addr, NULL, 16); } else unit_addr = node_addr; /* * Match from most specific to least specific; * first, start the lookup based on full path. */ if ((rv = lookup_in_confent_list(confent_list, 0, path, unit_addr, port)) != -1) goto done; /* lookup nodename@address */ if ((par_node_name_addr = strrchr(path, '/')) != NULL) { par_node_name_addr++; if ((rv = lookup_in_confent_list(confent_list, 0, par_node_name_addr, unit_addr, port)) != -1) goto done; } /* di_init() doesn't work when 0 is passed in flags */ par_node = di_init(path, DINFOMINOR); if (par_node != DI_NODE_NIL) { par_node_name = di_node_name(par_node); par_node_addr = di_bus_addr(par_node); par_binding_name = di_binding_name(par_node); par_driver_name = di_driver_name(par_node); } logdmsg(("par_node_name = %s\n", STRVAL(par_node_name))); logdmsg(("par_node_addr = %s\n", STRVAL(par_node_addr))); logdmsg(("par_binding_name = %s\n", STRVAL(par_binding_name))); logdmsg(("par_driver_name = %s\n", STRVAL(par_driver_name))); /* lookup bindingname@address */ if (par_binding_name != NULL && par_binding_name != par_node_name && par_node_addr != NULL) { (void) snprintf(buf, sizeof (buf), "%s@%s", par_binding_name, par_node_addr); if ((rv = lookup_in_confent_list(confent_list, 0, buf, unit_addr, port)) != -1) goto done; } /* lookup binding name */ if (par_binding_name != NULL) { if ((rv = lookup_in_confent_list(confent_list, 0, par_binding_name, unit_addr, port)) != -1) goto done; } if (par_driver_name != NULL) { /* lookup driver name */ if ((rv = lookup_in_confent_list(confent_list, 0, par_driver_name, unit_addr, port)) != -1) goto done; /* finally, lookup class name */ par_driver_class = get_driver_class(rootdir, par_driver_name); if (par_driver_class != NULL) { if ((rv = lookup_in_confent_list(confent_list, 1, par_driver_class, unit_addr, port)) != -1) goto done; } } /* * no match so far; * use the driver global mpxio-disable setting if exists. */ rv = mpxio_disable; done: if (node_name != NULL) *(node_name - 1) = '/'; if (node_addr != NULL) *(node_addr - 1) = '@'; if (par_driver_class != NULL) free(par_driver_class); if (confent_list != NULL) free_confent_list(confent_list); if (par_node != DI_NODE_NIL) di_fini(par_node); return (rv); } /* * Given client_name return whether it is a phci or vhci based name. * client_name is /devices name of a client without the /devices prefix. * * client_name Return value * .../fp@xxx/ssd@yyy CLIENT_TYPE_PHCI * .../scsi_vhci/ssd@yyy CLIENT_TYPE_VHCI * other CLIENT_TYPE_UNKNOWN */ static client_type_t client_name_type(char *client_name) { client_type_t client_type; char *p1, *p2; logdmsg(("client_name_type: client_name = %s\n", client_name)); if (strncmp(client_name, SLASH_SCSI_VHCI, sizeof (SLASH_SCSI_VHCI) - 1) == 0) return (CLIENT_TYPE_VHCI); if (*client_name != '/') return (CLIENT_TYPE_UNKNOWN); if ((p1 = strrchr(client_name, '/')) == NULL) return (CLIENT_TYPE_UNKNOWN); *p1 = '\0'; if ((p2 = strrchr(client_name, '/')) != NULL && strncmp(p2, SLASH_FP_AT, sizeof (SLASH_FP_AT) - 1) == 0) client_type = CLIENT_TYPE_PHCI; else client_type = CLIENT_TYPE_UNKNOWN; *p1 = '/'; return (client_type); } /* * Compare controller name portion of dev1 and dev2. * * rootdir root directory of the target environment * dev1 can be either a /dev link or /devices name in the target * environemnt * dev2 /devices name of a device without the /devices prefix * * Returns: * 0 if controller names match * 1 if controller names don't match * -1 an error occurred. */ static int compare_controller(char *rootdir, char *dev1, char *dev2) { int linksize; char *p1, *p; char physdev1[MAXPATHLEN]; char buf[MAXPATHLEN]; logdmsg(("compare_controller: rootdir = %s, dev1 = %s, dev2 = %s\n", rootdir, dev1, dev2)); if (strncmp(dev1, SLASH_DEV_SLASH, sizeof (SLASH_DEV_SLASH) - 1) == 0) { (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, dev1); if ((linksize = readlink(buf, physdev1, MAXPATHLEN)) > 0 && linksize < (MAXPATHLEN - 1)) { physdev1[linksize] = '\0'; logdmsg(("compare_controller: physdev1 = %s\n", physdev1)); } else return (-1); } else (void) strlcpy(physdev1, dev1, MAXPATHLEN); if ((p1 = strstr(physdev1, SLASH_DEVICES)) == NULL) return (-1); p1 += sizeof (SLASH_DEVICES) - 1; /* strip the device portion */ if ((p = strrchr(p1, '/')) == NULL) return (-1); *p = '\0'; if ((p = strrchr(dev2, '/')) == NULL) return (-1); *p = '\0'; logdmsg(("compare_controller: path1 = %s, path2 = %s\n", p1, dev2)); if (strcmp(p1, dev2) == 0) { *p = '/'; return (0); } else { *p = '/'; return (1); } } /* * Check if the specified device path is on the root controller. * * rootdir root directory of the target environment * path /devices name of a device without the /devices prefix * * Returns * 1 if the path is on the root controller * 0 if the path is not on the root controller * -1 if an error occurs */ static int is_root_controller(char *rootdir, char *path) { FILE *fp; char *tmpfile; int rv = -1; struct vfstab vfsent; char buf[MAXPATHLEN]; char ctd[MAXNAMELEN + 1]; logdmsg(("is_root_controller: rootdir = %s, path = %s\n", rootdir, path)); (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, VFSTAB); if ((fp = fopen(buf, "r")) == NULL) { logdmsg(("is_root_controller: failed to open %s: %s\n", buf, strerror(errno))); return (-1); } if (getvfsfile(fp, &vfsent, "/") != 0) { logdmsg(("is_root_controller: getvfsfile: failed to read " "vfstab entry for mount point \"/\": %s\n", strerror(errno))); (void) fclose(fp); return (-1); } (void) fclose(fp); /* check if the root is an svm metadisk */ if (strncmp(vfsent.vfs_special, META_DEV, sizeof (META_DEV) - 1) != 0) { if (compare_controller(rootdir, vfsent.vfs_special, path) == 0) return (1); else return (0); } /* Don't use /var/run as it is not mounted in miniroot */ if ((tmpfile = tempnam("/tmp", "diirc")) == NULL) { logdmsg(("is_root_controller: tempnam: failed: %s\n", strerror(errno))); return (-1); } /* get metadisk components using metastat command */ (void) snprintf(buf, MAXPATHLEN, "/usr/sbin/metastat -p %s 2>/dev/null | " "/usr/bin/grep ' 1 1 ' | " "/usr/bin/sed -e 's/^.* 1 1 //' | " "/usr/bin/cut -f1 -d ' ' > %s", vfsent.vfs_special + sizeof (META_DEV) - 1, tmpfile); logdmsg(("is_root_controller: command = %s\n", buf)); fp = NULL; if (system(buf) == 0 && (fp = fopen(tmpfile, "r")) != NULL) { while (fscanf(fp, "%" VAL2STR(MAXNAMELEN) "s", ctd) == 1) { (void) snprintf(buf, MAXPATHLEN, "/dev/dsk/%s", ctd); if (compare_controller(rootdir, buf, path) == 0) { rv = 1; goto out; } } rv = 0; } out: if (fp) (void) fclose(fp); (void) unlink(tmpfile); free(tmpfile); return (rv); } static int file_exists(char *rootdir, char *path) { struct stat stbuf; char fullpath[MAXPATHLEN]; int x; (void) snprintf(fullpath, MAXPATHLEN, "%s%s", rootdir, path); x = stat(fullpath, &stbuf); logdmsg(("file_exists: %s: %s\n", fullpath, (x == 0) ? "yes" : "no")); if (x == 0) return (1); else return (0); } /* * Check if mpxio is enabled or disabled on the specified device path. * Looks through the .conf files to determine the mpxio setting. * * rootdir root directory of the target environment * path /devices name of a device without the /devices prefix and * minor name component. * * Returns * 1 if mpxio is disabled * 0 if mpxio is enabled * -1 if an error occurs */ static int is_mpxio_disabled(char *rootdir, char *path) { int mpxio_disable; char *p; int check_root_controller; logdmsg(("is_mpxio_disabled: rootdir = %s, path = %s\n", rootdir, path)); if (file_exists(rootdir, SCSI_VHCI_CONF) == 0) { /* * scsi_vhci.conf doesn't exist: * if upgrading from a pre solaris 9 release. or * if this function is called during fresh or flash install * prior to installing scsi_vhci.conf file. */ if (file_exists(rootdir, "/kernel/drv")) /* upgrading from pre solaris 9 */ return (1); else /* fresh or flash install */ return (0); } mpxio_disable = lookup_in_conf_file(rootdir, SCSI_VHCI_CONF, NULL); /* * scsi_vhci.conf contains mpxio-disable property only in s9 and * s8+sfkpatch. This property is no longer present from s10 onwards. */ if (mpxio_disable == 1) { /* upgrading from s8 or s9 with mpxio globally disabled */ return (1); } else if (mpxio_disable == 0) { /* upgrading from s8 or s9 with mpxio globally enabled */ check_root_controller = 1; } else { /* * We are looking at the s10 version of the file. This is * the case if this function is called after installing the * new scsi_vhci.conf file. */ check_root_controller = 0; } if ((mpxio_disable = lookup_in_conf_file(rootdir, FP_CONF, path)) != -1) return (mpxio_disable); if ((p = strrchr(path, '/')) == NULL) return (-1); *p = '\0'; if ((mpxio_disable = lookup_in_conf_file(rootdir, QLC_CONF, path)) != -1) { *p = '/'; return (mpxio_disable); } *p = '/'; /* * mpxio-disable setting is not found in the .conf files. * The default is to enable mpxio, except if the path is on the root * controller. * * In s8 and s9 mpxio is not supported on the root controller. * NWS supplies a patch to enable root controller support in s8 and s9. * If the system had the patch installed, the fp.conf file would have * explicit "mpxio-disable=no" for the root controller. So we would * have found the mpxio-disable setting when we looked up this property * in the fp.conf file. */ if (check_root_controller) { mpxio_disable = is_root_controller(rootdir, path); logdmsg(("is_mpxio_disabled: is_root_controller returned %d\n", mpxio_disable)); } else mpxio_disable = 0; return (mpxio_disable); } static int vhci_ctl(sv_iocdata_t *iocp, int cmd) { int fd, rv; if ((fd = open(VHCI_CTL_NODE, O_RDWR)) < 0) return (-1); rv = ioctl(fd, cmd, iocp); (void) close(fd); return (rv); } /* * Convert a phci client name to vhci client name. * * phci_name phci client /devices name without the /devices prefix and * minor name component. * ex: /pci@8,600000/SUNW,qlc@4/fp@0,0/ssd@w2100002037cd9f72,0 * * Returns on success, vhci client name is returned. The memory for * the vhci name is allocated by this function and the caller * must free it. * on failure, NULL is returned. */ static char * phci_to_vhci(char *phci_name) { sv_iocdata_t ioc; char *slash, *addr, *retp; char vhci_name_buf[MAXPATHLEN]; char phci_name_buf[MAXPATHLEN]; char addr_buf[MAXNAMELEN]; logdmsg(("phci_to_vhci: pchi_name = %s\n", phci_name)); (void) strlcpy(phci_name_buf, phci_name, MAXPATHLEN); if ((slash = strrchr(phci_name_buf, '/')) == NULL || (addr = strchr(slash, '@')) == NULL) return (NULL); *slash = '\0'; addr++; (void) strlcpy(addr_buf, addr, MAXNAMELEN); bzero(&ioc, sizeof (sv_iocdata_t)); ioc.client = vhci_name_buf; ioc.phci = phci_name_buf; ioc.addr = addr_buf; if (vhci_ctl(&ioc, SCSI_VHCI_GET_CLIENT_NAME) != 0) { logdmsg(("phci_to_vhci: vhci_ctl failed: %s\n", strerror(errno))); return (NULL); } retp = strdup(vhci_name_buf); logdmsg(("phci_to_vhci: vhci name = %s\n", STRVAL(retp))); return (retp); } static int add_to_phci_list(char **phci_list, sv_path_info_t *pi, int npaths, int state, char *node_name) { int rv = 0; char name[MAXPATHLEN]; while (npaths--) { if (state == pi->ret_state) { (void) snprintf(name, MAXPATHLEN, "%s/%s@%s", pi->device.ret_phci, node_name, pi->ret_addr); if ((*phci_list = strdup(name)) == NULL) return (-1); phci_list++; rv++; } pi++; } return (rv); } static void free_pathlist(char **pathlist) { char **p; if (pathlist != NULL) { for (p = pathlist; *p != NULL; p++) free(*p); free(pathlist); } } /* * Convert a vhci client name to phci client names. * * vhci_name vhci client /devices name without the /devices prefix and * minor name component. * num_paths On return, *num_paths is set to the number paths in the * returned path list. * * Returns NULL terminated path list containing phci client paths is * returned on success. The memory for the path list is * allocated by this function and the caller must free it by * calling free_pathlist(). * NULL is returned on failure. */ static char ** vhci_to_phci(char *vhci_name, int *num_paths) { sv_iocdata_t ioc; uint_t npaths; int n; char **phci_list = NULL; char *node_name, *at; char vhci_name_buf[MAXPATHLEN]; logdmsg(("vhci_to_phci: vchi_name = %s\n", vhci_name)); *num_paths = 0; (void) strlcpy(vhci_name_buf, vhci_name, MAXPATHLEN); /* first get the number paths */ bzero(&ioc, sizeof (sv_iocdata_t)); ioc.client = vhci_name_buf; ioc.ret_elem = &npaths; if (vhci_ctl(&ioc, SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO) != 0 || npaths == 0) { logdmsg(("vhci_to_phci: vhci_ctl failed to get npaths: %s\n", strerror(errno))); return (NULL); } /* now allocate memory for the path information and get all paths */ bzero(&ioc, sizeof (sv_iocdata_t)); ioc.client = vhci_name_buf; ioc.buf_elem = npaths; ioc.ret_elem = &npaths; if ((ioc.ret_buf = (sv_path_info_t *)calloc(npaths, sizeof (sv_path_info_t))) == NULL) return (NULL); if (vhci_ctl(&ioc, SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO) != 0 || npaths == 0) { logdmsg(("vhci_to_phci: vhci_ctl failed: %s\n", strerror(errno))); goto out; } if (ioc.buf_elem < npaths) npaths = ioc.buf_elem; if ((node_name = strrchr(vhci_name_buf, '/')) == NULL || (at = strchr(node_name, '@')) == NULL) goto out; node_name++; *at = '\0'; /* allocate one more (than npaths) for the terminating NULL pointer */ if ((phci_list = calloc(npaths + 1, sizeof (char *))) == NULL) goto out; /* * add only online paths as non-online paths may not be accessible * in the target environment. */ if ((n = add_to_phci_list(phci_list, ioc.ret_buf, npaths, MDI_PATHINFO_STATE_ONLINE, node_name)) <= 0) goto out; free(ioc.ret_buf); *num_paths = n; #ifdef DEBUG logdmsg(("vhci_to_phci: phci list:\n")); log_pathlist(phci_list); #endif return (phci_list); out: free(ioc.ret_buf); if (phci_list) free_pathlist(phci_list); return (NULL); } /* * build list of paths accessible from the target environment */ static int build_pathlist(char *rootdir, char *vhcipath, char **pathlist, int npaths) { int mpxio_disabled; int i, j; char *vpath = NULL; for (i = 0; i < npaths; i++) { mpxio_disabled = is_mpxio_disabled(rootdir, pathlist[i]); logdmsg(("build_pathlist: mpxio_disabled = %d " "on path %s\n", mpxio_disabled, pathlist[i])); if (mpxio_disabled == -1) return (-1); if (mpxio_disabled == 0) { /* * mpxio is enabled on this phci path. * So use vhci path instead of phci path. */ if (vpath == NULL) { if ((vpath = strdup(vhcipath)) == NULL) return (-1); free(pathlist[i]); /* keep vhci path at beginning of the list */ for (j = i; j > 0; j--) pathlist[j] = pathlist[j - 1]; pathlist[0] = vpath; } else { free(pathlist[i]); npaths--; for (j = i; j < npaths; j++) pathlist[j] = pathlist[j + 1]; pathlist[npaths] = NULL; /* compensate for i++ in the for loop */ i--; } } } #ifdef DEBUG logdmsg(("build_pathlist: returning npaths = %d, pathlist:\n", npaths)); log_pathlist(pathlist); #endif return (npaths); } /* * Check if the specified device is refenced in the vfstab file. * Return 1 if referenced, 0 if not. * * rootdir root directory of the target environment * nodepath /devices path of a device in the target environment without * the /devices prefix and minor component. */ static int is_dev_in_vfstab(char *rootdir, char *nodepath) { FILE *fp; int linksize; struct vfstab vfsent; char *abspath, *minor; char physpath[MAXPATHLEN]; char buf[MAXPATHLEN]; logdmsg(("is_dev_in_vfstab: rootdir = %s, nodepath = %s\n", rootdir, nodepath)); (void) snprintf(buf, sizeof (buf), "%s%s", rootdir, VFSTAB); if ((fp = fopen(buf, "r")) == NULL) return (0); /* * read device specials from vfstab and compare names at physical * node path level. */ while (getvfsent(fp, &vfsent) == 0) { if (strncmp(vfsent.vfs_special, SLASH_DEV_SLASH, sizeof (SLASH_DEV_SLASH) - 1) == 0) { (void) snprintf(buf, MAXPATHLEN, "%s%s", rootdir, vfsent.vfs_special); if ((linksize = readlink(buf, physpath, MAXPATHLEN)) > 0 && linksize < (MAXPATHLEN - 1)) { physpath[linksize] = '\0'; if ((abspath = strstr(physpath, SLASH_DEVICES_SLASH)) == NULL) continue; } else continue; } else if (strncmp(vfsent.vfs_special, SLASH_DEVICES_SLASH, sizeof (SLASH_DEVICES_SLASH) - 1) == 0) { (void) strlcpy(physpath, vfsent.vfs_special, MAXPATHLEN); abspath = physpath; } else continue; /* point to / after /devices */ abspath += sizeof (SLASH_DEVICES_SLASH) - 2; /* strip minor component */ if ((minor = strrchr(abspath, ':')) != NULL) *minor = '\0'; if (strcmp(nodepath, abspath) == 0) { (void) fclose(fp); logdmsg(("is_dev_in_vfstab: returning 1\n")); return (1); } } (void) fclose(fp); return (0); } #endif /* __sparc */ static int devlink_callback(di_devlink_t devlink, void *argp) { const char *link; if ((link = di_devlink_path(devlink)) != NULL) (void) strlcpy((char *)argp, link, MAXPATHLEN); return (DI_WALK_CONTINUE); } /* * Get the /dev name in the install environment corresponding to physpath. * * physpath /devices path in the install environment without the /devices * prefix. * buf caller supplied buffer where the /dev name is placed on return * bufsz length of the buffer * * Returns strlen of the /dev name on success, -1 on failure. */ static int get_install_devlink(char *physpath, char *buf, size_t bufsz) { di_devlink_handle_t devlink_hdl; char devname[MAXPATHLEN]; logdmsg(("get_install_devlink: physpath = %s\n", physpath)); if ((devlink_hdl = di_devlink_init(NULL, 0)) == NULL) { logdmsg(("get_install_devlink: di_devlink_init() failed: %s\n", strerror(errno))); return (-1); } devname[0] = '\0'; if (di_devlink_walk(devlink_hdl, NULL, physpath, DI_PRIMARY_LINK, devname, devlink_callback) != 0 || devname[0] == '\0') { logdmsg(("get_install_devlink: di_devlink_walk failed: %s\n", strerror(errno))); (void) di_devlink_fini(&devlink_hdl); return (-1); } (void) di_devlink_fini(&devlink_hdl); logdmsg(("get_install_devlink: devlink = %s\n", devname)); return (strlcpy(buf, devname, bufsz)); } /* * Get the /dev name in the target environment corresponding to physpath. * * rootdir root directory of the target environment * physpath /devices path in the target environment without the /devices * prefix. * buf caller supplied buffer where the /dev name is placed on return * bufsz length of the buffer * * Returns strlen of the /dev name on success, -1 on failure. */ static int get_target_devlink(char *rootdir, char *physpath, char *buf, size_t bufsz) { char *p; int linksize; DIR *dirp; struct dirent *direntry; char dirpath[MAXPATHLEN]; char devname[MAXPATHLEN]; char physdev[MAXPATHLEN]; logdmsg(("get_target_devlink: rootdir = %s, physpath = %s\n", rootdir, physpath)); if ((p = strrchr(physpath, '/')) == NULL) return (-1); if (strstr(p, ",raw") != NULL) { (void) snprintf(dirpath, MAXPATHLEN, "%s/dev/rdsk", rootdir); } else { (void) snprintf(dirpath, MAXPATHLEN, "%s/dev/dsk", rootdir); } if ((dirp = opendir(dirpath)) == NULL) return (-1); while ((direntry = readdir(dirp)) != NULL) { if (strcmp(direntry->d_name, ".") == 0 || strcmp(direntry->d_name, "..") == 0) continue; (void) snprintf(devname, MAXPATHLEN, "%s/%s", dirpath, direntry->d_name); if ((linksize = readlink(devname, physdev, MAXPATHLEN)) > 0 && linksize < (MAXPATHLEN - 1)) { physdev[linksize] = '\0'; if ((p = strstr(physdev, SLASH_DEVICES_SLASH)) != NULL && strcmp(p + sizeof (SLASH_DEVICES) - 1, physpath) == 0) { (void) closedir(dirp); logdmsg(("get_target_devlink: devlink = %s\n", devname + strlen(rootdir))); return (strlcpy(buf, devname + strlen(rootdir), bufsz)); } } } (void) closedir(dirp); return (-1); } /* * Convert device name to physpath. * * rootdir root directory * devname a /dev name or /devices name under rootdir * physpath caller supplied buffer where the /devices path will be placed * on return (without the /devices prefix). * physpathlen length of the physpath buffer * * Returns 0 on success, -1 on failure. */ static int devname2physpath(char *rootdir, char *devname, char *physpath, int physpathlen) { int linksize; char *p; char devlink[MAXPATHLEN]; char tmpphyspath[MAXPATHLEN]; logdmsg(("devname2physpath: rootdir = %s, devname = %s\n", rootdir, devname)); if (strncmp(devname, SLASH_DEVICES_SLASH, sizeof (SLASH_DEVICES_SLASH) - 1) != 0) { if (*rootdir == '\0') linksize = readlink(devname, tmpphyspath, MAXPATHLEN); else { (void) snprintf(devlink, MAXPATHLEN, "%s%s", rootdir, devname); linksize = readlink(devlink, tmpphyspath, MAXPATHLEN); } if (linksize > 0 && linksize < (MAXPATHLEN - 1)) { tmpphyspath[linksize] = '\0'; if ((p = strstr(tmpphyspath, SLASH_DEVICES_SLASH)) == NULL) return (-1); } else return (-1); } else p = devname; (void) strlcpy(physpath, p + sizeof (SLASH_DEVICES) - 1, physpathlen); logdmsg(("devname2physpath: physpath = %s\n", physpath)); return (0); } /* * Map a device name (devname) from the target environment to the * install environment. * * rootdir root directory of the target environment * devname /dev or /devices name under the target environment * buf caller supplied buffer where the mapped /dev name is placed * on return * bufsz length of the buffer * * Returns strlen of the mapped /dev name on success, -1 on failure. */ int devfs_target2install(const char *rootdir, const char *devname, char *buf, size_t bufsz) { char physpath[MAXPATHLEN]; logdmsg(("devfs_target2install: rootdir = %s, devname = %s\n", STRVAL(rootdir), STRVAL(devname))); if (rootdir == NULL || devname == NULL || buf == NULL || bufsz == 0) return (-1); if (strcmp(rootdir, "/") == 0) rootdir = ""; if (devname2physpath((char *)rootdir, (char *)devname, physpath, MAXPATHLEN) != 0) return (-1); #ifdef __sparc if (client_name_type(physpath) == CLIENT_TYPE_PHCI) { char *mapped_node_path, *minor; char minorbuf[MAXNAMELEN]; /* strip minor component if present */ if ((minor = strrchr(physpath, ':')) != NULL) { *minor = '\0'; minor++; (void) strlcpy(minorbuf, minor, MAXNAMELEN); } if ((mapped_node_path = phci_to_vhci(physpath)) != NULL) { if (minor) (void) snprintf(physpath, MAXPATHLEN, "%s:%s", mapped_node_path, minorbuf); else (void) strlcpy(physpath, mapped_node_path, MAXPATHLEN); free(mapped_node_path); logdmsg(("devfs_target2install: mapped physpath: %s\n", physpath)); } else if (minor) *(minor - 1) = ':'; } #endif /* __sparc */ return (get_install_devlink(physpath, buf, bufsz)); } /* * Map a device name (devname) from the install environment to the target * environment. * * rootdir root directory of the target environment * devname /dev or /devices name under the install environment * buf caller supplied buffer where the mapped /dev name is placed * on return * bufsz length of the buffer * * Returns strlen of the mapped /dev name on success, -1 on failure. */ int devfs_install2target(const char *rootdir, const char *devname, char *buf, size_t bufsz) { char physpath[MAXPATHLEN]; logdmsg(("devfs_install2target: rootdir = %s, devname = %s\n", STRVAL(rootdir), STRVAL(devname))); if (rootdir == NULL || devname == NULL || buf == NULL || bufsz == 0) return (-1); if (strcmp(rootdir, "/") == 0) rootdir = ""; if (devname2physpath("", (char *)devname, physpath, MAXPATHLEN) != 0) return (-1); #ifdef __sparc if (client_name_type(physpath) == CLIENT_TYPE_VHCI) { char **pathlist; int npaths, i, j; char *minor; char minorbuf[MAXNAMELEN]; /* strip minor component if present */ if ((minor = strrchr(physpath, ':')) != NULL) { *minor = '\0'; minor++; (void) strlcpy(minorbuf, minor, MAXNAMELEN); } if ((pathlist = vhci_to_phci(physpath, &npaths)) == NULL) return (-1); if ((npaths = build_pathlist((char *)rootdir, physpath, pathlist, npaths)) <= 0) { free_pathlist(pathlist); return (-1); } /* * in case of more than one path, try to use the path * referenced in the vfstab file, otherwise use the first path. */ j = 0; if (npaths > 1) { for (i = 0; i < npaths; i++) { if (is_dev_in_vfstab((char *)rootdir, pathlist[i])) { j = i; break; } } } if (minor) (void) snprintf(physpath, MAXPATHLEN, "%s:%s", pathlist[j], minorbuf); else (void) strlcpy(physpath, pathlist[j], MAXPATHLEN); free_pathlist(pathlist); } #endif /* __sparc */ return (get_target_devlink((char *)rootdir, physpath, buf, bufsz)); } /* * A parser for /etc/path_to_inst. * The user-supplied callback is called once for each entry in the file. * Returns 0 on success, ENOMEM/ENOENT/EINVAL on error. * Callback may return DI_WALK_TERMINATE to terminate the walk, * otherwise DI_WALK_CONTINUE. */ int devfs_parse_binding_file(const char *binding_file, int (*callback)(void *, const char *, int, const char *), void *cb_arg) { token_t token; struct conf_file file; char tokval[MAX_TOKEN_SIZE]; enum { STATE_RESET, STATE_DEVPATH, STATE_INSTVAL } state; char *devpath; char *bindname; int instval = 0; int rv; if ((devpath = calloc(1, MAXPATHLEN)) == NULL) return (ENOMEM); if ((bindname = calloc(1, MAX_TOKEN_SIZE)) == NULL) { free(devpath); return (ENOMEM); } if ((file.fp = fopen(binding_file, "r")) == NULL) { free(devpath); free(bindname); return (errno); } file.filename = (char *)binding_file; file.linenum = 1; state = STATE_RESET; while ((token = lex(&file, tokval, MAX_TOKEN_SIZE)) != T_EOF) { switch (token) { case T_POUND: /* * Skip comments. */ find_eol(file.fp); break; case T_NAME: case T_STRING: switch (state) { case STATE_RESET: if (strlcpy(devpath, tokval, MAXPATHLEN) >= MAXPATHLEN) goto err; state = STATE_DEVPATH; break; case STATE_INSTVAL: if (strlcpy(bindname, tokval, MAX_TOKEN_SIZE) >= MAX_TOKEN_SIZE) goto err; rv = callback(cb_arg, devpath, instval, bindname); if (rv == DI_WALK_TERMINATE) goto done; if (rv != DI_WALK_CONTINUE) goto err; state = STATE_RESET; break; default: file_err(&file, tok_err, tokval); state = STATE_RESET; break; } break; case T_DECVAL: case T_HEXVAL: switch (state) { case STATE_DEVPATH: instval = (int)strtol(tokval, NULL, 0); state = STATE_INSTVAL; break; default: file_err(&file, tok_err, tokval); state = STATE_RESET; break; } break; case T_NEWLINE: file.linenum++; state = STATE_RESET; break; default: file_err(&file, tok_err, tokval); state = STATE_RESET; break; } } done: (void) fclose(file.fp); free(devpath); free(bindname); return (0); err: (void) fclose(file.fp); free(devpath); free(bindname); return (EINVAL); } /* * Walk the minor nodes of all children below the specified device * by calling the provided callback with the path to each minor. */ static int devfs_walk_children_minors(const char *device_path, struct stat *st, int (*callback)(void *, const char *), void *cb_arg, int *terminate) { DIR *dir; struct dirent *dp; char *minor_path = NULL; int need_close = 0; int rv; if ((minor_path = calloc(1, MAXPATHLEN)) == NULL) return (ENOMEM); if ((dir = opendir(device_path)) == NULL) { rv = ENOENT; goto err; } need_close = 1; while ((dp = readdir(dir)) != NULL) { if ((strcmp(dp->d_name, ".") == 0) || (strcmp(dp->d_name, "..") == 0)) continue; (void) snprintf(minor_path, MAXPATHLEN, "%s/%s", device_path, dp->d_name); if (stat(minor_path, st) == -1) continue; if (S_ISDIR(st->st_mode)) { rv = devfs_walk_children_minors( (const char *)minor_path, st, callback, cb_arg, terminate); if (rv != 0) goto err; if (*terminate) break; } else { rv = callback(cb_arg, minor_path); if (rv == DI_WALK_TERMINATE) { *terminate = 1; break; } if (rv != DI_WALK_CONTINUE) { rv = EINVAL; goto err; } } } rv = 0; err: if (need_close) (void) closedir(dir); if (minor_path) free(minor_path); return (rv); } /* * Return the path to each minor node for a device by * calling the provided callback. */ static int devfs_walk_device_minors(const char *device_path, struct stat *st, int (*callback)(void *, const char *), void *cb_arg, int *terminate) { char *minor_path; char *devpath; char *expr; regex_t regex; int need_regfree = 0; int need_close = 0; DIR *dir; struct dirent *dp; int rv; char *p; minor_path = calloc(1, MAXPATHLEN); devpath = calloc(1, MAXPATHLEN); expr = calloc(1, MAXNAMELEN); if (devpath == NULL || expr == NULL || minor_path == NULL) { rv = ENOMEM; goto err; } rv = EINVAL; if (strlcpy(devpath, device_path, MAXPATHLEN) >= MAXPATHLEN) goto err; if ((p = strrchr(devpath, '/')) == NULL) goto err; *p++ = 0; if (strlen(p) == 0) goto err; if (snprintf(expr, MAXNAMELEN, "%s:.*", p) >= MAXNAMELEN) goto err; if (regcomp(®ex, expr, REG_EXTENDED) != 0) goto err; need_regfree = 1; if ((dir = opendir(devpath)) == NULL) { rv = ENOENT; goto err; } need_close = 1; while ((dp = readdir(dir)) != NULL) { if ((strcmp(dp->d_name, ".") == 0) || (strcmp(dp->d_name, "..") == 0)) continue; (void) snprintf(minor_path, MAXPATHLEN, "%s/%s", devpath, dp->d_name); if (stat(minor_path, st) == -1) continue; if ((S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) && regexec(®ex, dp->d_name, 0, NULL, 0) == 0) { rv = callback(cb_arg, minor_path); if (rv == DI_WALK_TERMINATE) { *terminate = 1; break; } if (rv != DI_WALK_CONTINUE) { rv = EINVAL; goto err; } } } rv = 0; err: if (need_close) (void) closedir(dir); if (need_regfree) regfree(®ex); if (devpath) free(devpath); if (minor_path) free(minor_path); if (expr) free(expr); return (rv); } /* * Perform a walk of all minor nodes for the specified device, * and minor nodes below the device. */ int devfs_walk_minor_nodes(const char *device_path, int (*callback)(void *, const char *), void *cb_arg) { struct stat stbuf; int rv; int terminate = 0; rv = devfs_walk_device_minors(device_path, &stbuf, callback, cb_arg, &terminate); if (rv == 0 && terminate == 0) { rv = devfs_walk_children_minors(device_path, &stbuf, callback, cb_arg, &terminate); } return (rv); } #ifdef DEBUG static void vlog_debug_msg(char *fmt, va_list ap) { time_t clock; struct tm t; if (!devfsmap_debug) return; if (logfp == NULL) { if (*devfsmap_logfile != '\0') { logfp = fopen(devfsmap_logfile, "a"); if (logfp) (void) fprintf(logfp, "\nNew Log:\n"); } if (logfp == NULL) logfp = stdout; } clock = time(NULL); (void) localtime_r(&clock, &t); (void) fprintf(logfp, "%02d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec); (void) vfprintf(logfp, fmt, ap); (void) fflush(logfp); } static void log_debug_msg(char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog_debug_msg(fmt, ap); va_end(ap); } #ifdef __sparc static char * mpxio_disable_string(int mpxio_disable) { if (mpxio_disable == 0) return ("no"); else if (mpxio_disable == 1) return ("yes"); else return ("not specified"); } static void log_confent_list(char *filename, struct conf_entry *confent_list, int global_mpxio_disable) { struct conf_entry *confent; log_debug_msg("log_confent_list: filename = %s:\n", filename); if (global_mpxio_disable != -1) log_debug_msg("\tdriver global mpxio_disable = \"%s\"\n\n", mpxio_disable_string(global_mpxio_disable)); for (confent = confent_list; confent != NULL; confent = confent->next) { if (confent->name) log_debug_msg("\tname = %s\n", confent->name); if (confent->parent) log_debug_msg("\tparent = %s\n", confent->parent); if (confent->class) log_debug_msg("\tclass = %s\n", confent->class); if (confent->unit_address) log_debug_msg("\tunit_address = %s\n", confent->unit_address); if (confent->port != -1) log_debug_msg("\tport = %d\n", confent->port); log_debug_msg("\tmpxio_disable = \"%s\"\n\n", mpxio_disable_string(confent->mpxio_disable)); } } static void log_pathlist(char **pathlist) { char **p; for (p = pathlist; *p != NULL; p++) log_debug_msg("\t%s\n", *p); } #endif /* __sparc */ #endif /* DEBUG */