Home | History | Annotate | Download | only in libdevid
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License, Version 1.0 only
      6  * (the "License").  You may not use this file except in compliance
      7  * with the License.
      8  *
      9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10  * or http://www.opensolaris.org/os/licensing.
     11  * See the License for the specific language governing permissions
     12  * and limitations under the License.
     13  *
     14  * When distributing Covered Code, include this CDDL HEADER in each
     15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16  * If applicable, add the following below this CDDL HEADER, with the
     17  * fields enclosed by brackets "[]" replaced with your own identifying
     18  * information: Portions Copyright [yyyy] [name of copyright owner]
     19  *
     20  * CDDL HEADER END
     21  */
     22 /*
     23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     27 
     28 #include <unistd.h>
     29 #include <stdlib.h>
     30 #include <errno.h>
     31 #include <ftw.h>
     32 #include <string.h>
     33 #include <thread.h>
     34 #include <synch.h>
     35 #include <sys/types.h>
     36 #include <sys/stat.h>
     37 #include <fcntl.h>
     38 #include <sys/modctl.h>
     39 #include <strings.h>
     40 
     41 #include <libdevinfo.h>
     42 #include "libdevid.h"
     43 
     44 /*
     45  * Get Device Id from an open file descriptor
     46  */
     47 int
     48 devid_get(int fd, ddi_devid_t *devidp)
     49 {
     50 	int		len = 0;
     51 	dev_t		dev;
     52 	struct stat	statb;
     53 	ddi_devid_t	mydevid;
     54 
     55 	if (fstat(fd, &statb) != 0)
     56 		return (-1);
     57 
     58 	/* If not char or block device, then error */
     59 	if (!S_ISCHR(statb.st_mode) && !S_ISBLK(statb.st_mode))
     60 		return (-1);
     61 
     62 	/* Get the device id size */
     63 	dev = statb.st_rdev;
     64 	if (modctl(MODSIZEOF_DEVID, dev, &len) != 0)
     65 		return (-1);
     66 
     67 	/* Allocate space to return device id */
     68 	if ((mydevid = (ddi_devid_t)malloc(len)) == NULL)
     69 		return (-1);
     70 
     71 	/* Get the device id */
     72 	if (modctl(MODGETDEVID, dev, len, mydevid) != 0) {
     73 		free(mydevid);
     74 		return (-1);
     75 	}
     76 
     77 	/* Return the device id copy */
     78 	*devidp = mydevid;
     79 	return (0);
     80 }
     81 
     82 /*
     83  * Get the minor name
     84  */
     85 int
     86 devid_get_minor_name(int fd, char **minor_namep)
     87 {
     88 	int		len = 0;
     89 	dev_t		dev;
     90 	int		spectype;
     91 	char		*myminor_name;
     92 	struct stat	statb;
     93 
     94 	if (fstat(fd, &statb) != 0)
     95 		return (-1);
     96 
     97 	/* If not a char or block device, then return an error */
     98 	if (!S_ISCHR(statb.st_mode) && !S_ISBLK(statb.st_mode))
     99 		return (-1);
    100 
    101 	spectype = statb.st_mode & S_IFMT;
    102 	dev = statb.st_rdev;
    103 
    104 	/* Get the minor name size */
    105 	if (modctl(MODSIZEOF_MINORNAME, dev, spectype, &len) != 0)
    106 		return (-1);
    107 
    108 	/* Allocate space for the minor name */
    109 	if ((myminor_name = (char *)malloc(len)) == NULL)
    110 		return (-1);
    111 
    112 	/* Get the minor name */
    113 	if (modctl(MODGETMINORNAME, dev, spectype, len, myminor_name) != 0) {
    114 		free(myminor_name);
    115 		return (-1);
    116 	}
    117 
    118 	/* return the minor name copy */
    119 	*minor_namep = myminor_name;
    120 	return (0);
    121 }
    122 
    123 /* list element of devid_nmlist_t information */
    124 struct nmlist {
    125 	struct nmlist	*nl_next;
    126 	char		*nl_devname;
    127 	dev_t		nl_dev;
    128 };
    129 
    130 /* add list element to end of nmlist headed by *nlhp */
    131 struct nmlist *
    132 nmlist_add(struct nmlist **nlhp, char *path)
    133 {
    134 	struct stat	statb;
    135 	dev_t		dev;
    136 	struct nmlist	*nl;
    137 
    138 	/* stat and get the devt for char or block */
    139 	if ((stat(path, &statb) == 0) &&
    140 	    (S_ISCHR(statb.st_mode) || S_ISBLK(statb.st_mode)))
    141 		dev = statb.st_rdev;
    142 	else
    143 		dev = NODEV;
    144 
    145 	/* find the end of the list */
    146 	for (; (nl = *nlhp) != NULL; nlhp = &nl->nl_next)
    147 		;
    148 
    149 	/* allocate and initialize new entry */
    150 	if ((nl = malloc(sizeof (*nl))) == NULL)
    151 		return (NULL);
    152 
    153 	if ((nl->nl_devname = strdup(path)) == NULL) {
    154 		free(nl);
    155 		return (NULL);
    156 	}
    157 	nl->nl_next = NULL;
    158 	nl->nl_dev = dev;
    159 
    160 	/* link new entry at end */
    161 	*nlhp = nl;
    162 	return (nl);
    163 }
    164 
    165 /* information needed by devlink_callback to call nmlist_add */
    166 struct devlink_cbinfo {
    167 	struct nmlist	**cbi_nlhp;
    168 	char		*cbi_search_path;
    169 	int		cbi_error;
    170 };
    171 
    172 /* di_devlink callback to add a /dev entry to nmlist */
    173 static int
    174 devlink_callback(di_devlink_t dl, void *arg)
    175 {
    176 	struct devlink_cbinfo	*cbip = (struct devlink_cbinfo *)arg;
    177 	char			*devpath = (char *)di_devlink_path(dl);
    178 
    179 	if (strncmp(devpath, cbip->cbi_search_path,
    180 	    strlen(cbip->cbi_search_path)) == 0) {
    181 		if (nmlist_add(cbip->cbi_nlhp, devpath) == NULL) {
    182 			cbip->cbi_error = 1;
    183 			return (DI_WALK_TERMINATE);
    184 		}
    185 	}
    186 	return (DI_WALK_CONTINUE);
    187 }
    188 
    189 /*
    190  * Resolve /dev names to DI_PRIMARY_LINK, DI_SECONDARY_LINK, or both.
    191  * The default is to resolve to just the DI_PRIMARY_LINK.
    192  */
    193 int			devid_deviceid_to_nmlist_link = DI_PRIMARY_LINK;
    194 
    195 /*
    196  * Options for the devid_deviceid_to_nmlist implementation:
    197  *
    198  *   DEVICEID_NMLIST_SLINK -	reduce overhead by reuse the previous
    199  *				di_devlink_init.
    200  */
    201 #define	DEVICEID_NMLIST_SLINK	1
    202 int			devid_deviceid_to_nmlist_flg = 0;
    203 static di_devlink_handle_t devid_deviceid_to_nmlist_dlh = NULL;	/* SLINK */
    204 
    205 #define	DEVICEID_NMLIST_NRETRY	10
    206 
    207 /*
    208  * Convert the specified devid/minor_name into a devid_nmlist_t array
    209  * with names that resolve into /devices or /dev depending on search_path.
    210  *
    211  * The man page indicates that:
    212  *
    213  *     This function traverses the file tree, starting at search_path.
    214  *
    215  * This is not true, we reverse engineer the paths relative to
    216  * the specified search path to avoid attaching all devices.
    217  */
    218 int
    219 devid_deviceid_to_nmlist(
    220 	char		*search_path,
    221 	ddi_devid_t	devid,
    222 	char		*minor_name,
    223 	devid_nmlist_t	**retlist)
    224 {
    225 	char			*cp;
    226 	int			dev;
    227 	char			*paths = NULL;
    228 	char			*path;
    229 	int			lens;
    230 	di_devlink_handle_t	dlh = NULL;
    231 	int			ret = -1;
    232 	struct devlink_cbinfo	cbi;
    233 	struct nmlist		*nlh = NULL;
    234 	struct nmlist		*nl;
    235 	devid_nmlist_t		*rl;
    236 	int			nret;
    237 	int			nagain = 0;
    238 	int			err = 0;
    239 
    240 	*retlist = NULL;
    241 
    242 	/* verify valid search path starts with "/devices" or "/dev" */
    243 	if ((strcmp(search_path, "/devices") == 0) ||
    244 	    (strncmp(search_path, "/devices/", 9) == 0))
    245 		dev = 0;
    246 	else if ((strcmp(search_path, "/dev") == 0) ||
    247 	    (strncmp(search_path, "/dev/", 5) == 0))
    248 		dev = 1;
    249 	else {
    250 		errno = EINVAL;
    251 		return (-1);
    252 	}
    253 
    254 
    255 	/* translate devid/minor_name to /devices paths */
    256 again:	if (modctl(MODDEVID2PATHS, devid, minor_name, 0, &lens, NULL) != 0)
    257 		goto out;
    258 	if ((paths = (char *)malloc(lens)) == NULL)
    259 		goto out;
    260 	if (modctl(MODDEVID2PATHS, devid, minor_name, 0, &lens, paths) != 0) {
    261 		if ((errno == EAGAIN) && (nagain++ < DEVICEID_NMLIST_NRETRY)) {
    262 			free(paths);
    263 			paths = NULL;
    264 			goto again;
    265 		}
    266 		goto out;
    267 	}
    268 
    269 	/*
    270 	 * initialize for /devices path to /dev path translation. To reduce
    271 	 * overhead we reuse the last snapshot if DEVICEID_NMLIST_SLINK is set.
    272 	 */
    273 	if (dev) {
    274 		dlh = devid_deviceid_to_nmlist_dlh;
    275 		if (dlh &&
    276 		    !(devid_deviceid_to_nmlist_flg & DEVICEID_NMLIST_SLINK)) {
    277 			(void) di_devlink_fini(&dlh);
    278 			dlh = devid_deviceid_to_nmlist_dlh = NULL;
    279 		}
    280 		if ((dlh == NULL) &&
    281 		    ((dlh = di_devlink_init(NULL, 0)) == NULL))
    282 				goto out;
    283 	}
    284 
    285 	/*
    286 	 * iterate over all the devtspectype resolutions of the devid and
    287 	 * convert them into the appropriate path form and add items to return
    288 	 * to the nmlist list;
    289 	 */
    290 	for (path = paths; *path; path += strlen(path) + 1) {
    291 		if (dev) {
    292 			/* add /dev entries */
    293 			cbi.cbi_nlhp = &nlh;
    294 			cbi.cbi_search_path = search_path;
    295 			cbi.cbi_error = 0;
    296 
    297 			(void) di_devlink_walk(dlh, NULL, path,
    298 			    devid_deviceid_to_nmlist_link,
    299 			    (void *)&cbi, devlink_callback);
    300 			if (cbi.cbi_error)
    301 				goto out;
    302 		} else {
    303 			/* add /devices entry */
    304 			cp = malloc(strlen("/devices") + strlen(path) + 1);
    305 			(void) strcpy(cp, "/devices");
    306 			(void) strcat(cp, path);
    307 			if (strncmp(cp, search_path,
    308 			    strlen(search_path)) == 0) {
    309 				if (nmlist_add(&nlh, cp) == NULL) {
    310 					free(cp);
    311 					goto out;
    312 				}
    313 			}
    314 			free(cp);
    315 		}
    316 	}
    317 
    318 	/* convert from nmlist to retlist array */
    319 	for (nl = nlh, nret = 0; nl; nl = nl->nl_next)
    320 		nret++;
    321 	if (nret == 0) {
    322 		err = ENODEV;
    323 		goto out;
    324 	}
    325 	if ((*retlist = calloc(nret + 1, sizeof (devid_nmlist_t))) == NULL) {
    326 		err = ENOMEM;
    327 		goto out;
    328 	}
    329 	for (nl = nlh, rl = *retlist; nl; nl = nl->nl_next, rl++) {
    330 		rl->devname = nl->nl_devname;
    331 		rl->dev = nl->nl_dev;
    332 	}
    333 	rl->devname = NULL;
    334 	rl->dev = NODEV;
    335 
    336 	ret = 0;
    337 
    338 out:
    339 	while ((nl = nlh) != NULL) {	/* free the nmlist */
    340 		nlh = nl->nl_next;
    341 		free(nl);
    342 	}
    343 	if (paths)
    344 		free(paths);
    345 	if (dlh) {
    346 		if ((ret == 0) &&
    347 		    (devid_deviceid_to_nmlist_flg & DEVICEID_NMLIST_SLINK))
    348 			devid_deviceid_to_nmlist_dlh = dlh;
    349 		else
    350 			(void) di_devlink_fini(&dlh);
    351 	}
    352 	if (ret && *retlist)
    353 		free(*retlist);
    354 	if (ret && err != 0)
    355 		errno = err;
    356 	return (ret);
    357 }
    358 
    359 /*
    360  * Free Device Id Name List
    361  */
    362 void
    363 devid_free_nmlist(devid_nmlist_t *list)
    364 {
    365 	devid_nmlist_t *p = list;
    366 
    367 	if (list == NULL)
    368 		return;
    369 
    370 	/* Free all the device names */
    371 	while (p->devname != NULL) {
    372 		free(p->devname);
    373 		p++;
    374 	}
    375 
    376 	/* Free the array */
    377 	free(list);
    378 }
    379