OpenSolaris_b135/lib/libdevid/deviceid.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

#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);
}

/* 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);
}