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