| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License, Version 1.0 only |
| * (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 2004 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| * Copyright 2017 Nexenta Systems, Inc. |
| */ |
| |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <ftw.h> |
| #include <string.h> |
| #include <thread.h> |
| #include <synch.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <sys/modctl.h> |
| #include <strings.h> |
| |
| #include <libdevinfo.h> |
| #include "libdevid.h" |
| |
| /* |
| * Get Device Id from an open file descriptor |
| */ |
| int |
| devid_get(int fd, ddi_devid_t *devidp) |
| { |
| int len = 0; |
| dev_t dev; |
| struct stat statb; |
| ddi_devid_t mydevid; |
| |
| if (fstat(fd, &statb) != 0) |
| return (-1); |
| |
| /* If not char or block device, then error */ |
| if (!S_ISCHR(statb.st_mode) && !S_ISBLK(statb.st_mode)) |
| return (-1); |
| |
| /* Get the device id size */ |
| dev = statb.st_rdev; |
| if (modctl(MODSIZEOF_DEVID, dev, &len) != 0) |
| return (-1); |
| |
| /* Allocate space to return device id */ |
| if ((mydevid = (ddi_devid_t)malloc(len)) == NULL) |
| return (-1); |
| |
| /* Get the device id */ |
| if (modctl(MODGETDEVID, dev, len, mydevid) != 0) { |
| free(mydevid); |
| return (-1); |
| } |
| |
| /* Return the device id copy */ |
| *devidp = mydevid; |
| return (0); |
| } |
| |
| /* |
| * Get the minor name |
| */ |
| int |
| devid_get_minor_name(int fd, char **minor_namep) |
| { |
| int len = 0; |
| dev_t dev; |
| int spectype; |
| char *myminor_name; |
| struct stat statb; |
| |
| if (fstat(fd, &statb) != 0) |
| return (-1); |
| |
| /* If not a char or block device, then return an error */ |
| if (!S_ISCHR(statb.st_mode) && !S_ISBLK(statb.st_mode)) |
| return (-1); |
| |
| spectype = statb.st_mode & S_IFMT; |
| dev = statb.st_rdev; |
| |
| /* Get the minor name size */ |
| if (modctl(MODSIZEOF_MINORNAME, dev, spectype, &len) != 0) |
| return (-1); |
| |
| /* Allocate space for the minor name */ |
| if ((myminor_name = (char *)malloc(len)) == NULL) |
| return (-1); |
| |
| /* Get the minor name */ |
| if (modctl(MODGETMINORNAME, dev, spectype, len, myminor_name) != 0) { |
| free(myminor_name); |
| return (-1); |
| } |
| |
| /* return the minor name copy */ |
| *minor_namep = myminor_name; |
| return (0); |
| } |
| |
| char * |
| devid_str_from_path(const char *path) |
| { |
| int fd; |
| ddi_devid_t devid; |
| char *minor, *ret = NULL; |
| |
| if ((fd = open(path, O_RDONLY)) < 0) |
| return (NULL); |
| |
| if (devid_get(fd, &devid) == 0) { |
| if (devid_get_minor_name(fd, &minor) != 0) |
| minor = NULL; |
| ret = devid_str_encode(devid, minor); |
| if (minor != NULL) |
| devid_str_free(minor); |
| devid_free(devid); |
| } |
| (void) close(fd); |
| |
| return (ret); |
| } |
| |
| /* list element of devid_nmlist_t information */ |
| struct nmlist { |
| struct nmlist *nl_next; |
| char *nl_devname; |
| dev_t nl_dev; |
| }; |
| |
| /* add list element to end of nmlist headed by *nlhp */ |
| struct nmlist * |
| nmlist_add(struct nmlist **nlhp, char *path) |
| { |
| struct stat statb; |
| dev_t dev; |
| struct nmlist *nl; |
| |
| /* stat and get the devt for char or block */ |
| if ((stat(path, &statb) == 0) && |
| (S_ISCHR(statb.st_mode) || S_ISBLK(statb.st_mode))) |
| dev = statb.st_rdev; |
| else |
| dev = NODEV; |
| |
| /* find the end of the list */ |
| for (; (nl = *nlhp) != NULL; nlhp = &nl->nl_next) |
| ; |
| |
| /* allocate and initialize new entry */ |
| if ((nl = malloc(sizeof (*nl))) == NULL) |
| return (NULL); |
| |
| if ((nl->nl_devname = strdup(path)) == NULL) { |
| free(nl); |
| return (NULL); |
| } |
| nl->nl_next = NULL; |
| nl->nl_dev = dev; |
| |
| /* link new entry at end */ |
| *nlhp = nl; |
| return (nl); |
| } |
| |
| /* information needed by devlink_callback to call nmlist_add */ |
| struct devlink_cbinfo { |
| struct nmlist **cbi_nlhp; |
| char *cbi_search_path; |
| int cbi_error; |
| }; |
| |
| /* di_devlink callback to add a /dev entry to nmlist */ |
| static int |
| devlink_callback(di_devlink_t dl, void *arg) |
| { |
| struct devlink_cbinfo *cbip = (struct devlink_cbinfo *)arg; |
| char *devpath = (char *)di_devlink_path(dl); |
| |
| if (strncmp(devpath, cbip->cbi_search_path, |
| strlen(cbip->cbi_search_path)) == 0) { |
| if (nmlist_add(cbip->cbi_nlhp, devpath) == NULL) { |
| cbip->cbi_error = 1; |
| return (DI_WALK_TERMINATE); |
| } |
| } |
| return (DI_WALK_CONTINUE); |
| } |
| |
| /* |
| * Resolve /dev names to DI_PRIMARY_LINK, DI_SECONDARY_LINK, or both. |
| * The default is to resolve to just the DI_PRIMARY_LINK. |
| */ |
| int devid_deviceid_to_nmlist_link = DI_PRIMARY_LINK; |
| |
| /* |
| * Options for the devid_deviceid_to_nmlist implementation: |
| * |
| * DEVICEID_NMLIST_SLINK - reduce overhead by reuse the previous |
| * di_devlink_init. |
| */ |
| #define DEVICEID_NMLIST_SLINK 1 |
| int devid_deviceid_to_nmlist_flg = 0; |
| static di_devlink_handle_t devid_deviceid_to_nmlist_dlh = NULL; /* SLINK */ |
| |
| #define DEVICEID_NMLIST_NRETRY 10 |
| |
| /* |
| * Convert the specified devid/minor_name into a devid_nmlist_t array |
| * with names that resolve into /devices or /dev depending on search_path. |
| * |
| * The man page indicates that: |
| * |
| * This function traverses the file tree, starting at search_path. |
| * |
| * This is not true, we reverse engineer the paths relative to |
| * the specified search path to avoid attaching all devices. |
| */ |
| int |
| devid_deviceid_to_nmlist(char *search_path, ddi_devid_t devid, char *minor_name, |
| devid_nmlist_t **retlist) |
| { |
| char *cp; |
| int dev; |
| char *paths = NULL; |
| char *path; |
| int lens; |
| di_devlink_handle_t dlh = NULL; |
| int ret = -1; |
| struct devlink_cbinfo cbi; |
| struct nmlist *nlh = NULL; |
| struct nmlist *nl; |
| devid_nmlist_t *rl; |
| int nret; |
| int nagain = 0; |
| int err = 0; |
| |
| *retlist = NULL; |
| |
| /* verify valid search path starts with "/devices" or "/dev" */ |
| if ((strcmp(search_path, "/devices") == 0) || |
| (strncmp(search_path, "/devices/", 9) == 0)) |
| dev = 0; |
| else if ((strcmp(search_path, "/dev") == 0) || |
| (strncmp(search_path, "/dev/", 5) == 0)) |
| dev = 1; |
| else { |
| errno = EINVAL; |
| return (-1); |
| } |
| |
| |
| /* translate devid/minor_name to /devices paths */ |
| again: if (modctl(MODDEVID2PATHS, devid, minor_name, 0, &lens, NULL) != 0) |
| goto out; |
| if ((paths = (char *)malloc(lens)) == NULL) |
| goto out; |
| if (modctl(MODDEVID2PATHS, devid, minor_name, 0, &lens, paths) != 0) { |
| if ((errno == EAGAIN) && (nagain++ < DEVICEID_NMLIST_NRETRY)) { |
| free(paths); |
| paths = NULL; |
| goto again; |
| } |
| goto out; |
| } |
| |
| /* |
| * initialize for /devices path to /dev path translation. To reduce |
| * overhead we reuse the last snapshot if DEVICEID_NMLIST_SLINK is set. |
| */ |
| if (dev) { |
| dlh = devid_deviceid_to_nmlist_dlh; |
| if (dlh && |
| !(devid_deviceid_to_nmlist_flg & DEVICEID_NMLIST_SLINK)) { |
| (void) di_devlink_fini(&dlh); |
| dlh = devid_deviceid_to_nmlist_dlh = NULL; |
| } |
| if ((dlh == NULL) && |
| ((dlh = di_devlink_init(NULL, 0)) == NULL)) |
| goto out; |
| } |
| |
| /* |
| * iterate over all the devtspectype resolutions of the devid and |
| * convert them into the appropriate path form and add items to return |
| * to the nmlist list; |
| */ |
| for (path = paths; *path; path += strlen(path) + 1) { |
| if (dev) { |
| /* add /dev entries */ |
| cbi.cbi_nlhp = &nlh; |
| cbi.cbi_search_path = search_path; |
| cbi.cbi_error = 0; |
| |
| (void) di_devlink_walk(dlh, NULL, path, |
| devid_deviceid_to_nmlist_link, |
| (void *)&cbi, devlink_callback); |
| if (cbi.cbi_error) |
| goto out; |
| } else { |
| /* add /devices entry */ |
| cp = malloc(strlen("/devices") + strlen(path) + 1); |
| (void) strcpy(cp, "/devices"); |
| (void) strcat(cp, path); |
| if (strncmp(cp, search_path, |
| strlen(search_path)) == 0) { |
| if (nmlist_add(&nlh, cp) == NULL) { |
| free(cp); |
| goto out; |
| } |
| } |
| free(cp); |
| } |
| } |
| |
| /* convert from nmlist to retlist array */ |
| for (nl = nlh, nret = 0; nl; nl = nl->nl_next) |
| nret++; |
| if (nret == 0) { |
| err = ENODEV; |
| goto out; |
| } |
| if ((*retlist = calloc(nret + 1, sizeof (devid_nmlist_t))) == NULL) { |
| err = ENOMEM; |
| goto out; |
| } |
| for (nl = nlh, rl = *retlist; nl; nl = nl->nl_next, rl++) { |
| rl->devname = nl->nl_devname; |
| rl->dev = nl->nl_dev; |
| } |
| rl->devname = NULL; |
| rl->dev = NODEV; |
| |
| ret = 0; |
| |
| out: |
| while ((nl = nlh) != NULL) { /* free the nmlist */ |
| nlh = nl->nl_next; |
| free(nl); |
| } |
| if (paths) |
| free(paths); |
| if (dlh) { |
| if ((ret == 0) && |
| (devid_deviceid_to_nmlist_flg & DEVICEID_NMLIST_SLINK)) |
| devid_deviceid_to_nmlist_dlh = dlh; |
| else |
| (void) di_devlink_fini(&dlh); |
| } |
| if (ret && *retlist) |
| free(*retlist); |
| if (ret && err != 0) |
| errno = err; |
| return (ret); |
| } |
| |
| /* |
| * Free Device Id Name List |
| */ |
| void |
| devid_free_nmlist(devid_nmlist_t *list) |
| { |
| devid_nmlist_t *p = list; |
| |
| if (list == NULL) |
| return; |
| |
| /* Free all the device names */ |
| while (p->devname != NULL) { |
| free(p->devname); |
| p++; |
| } |
| |
| /* Free the array */ |
| free(list); |
| } |