xref: /illumos-gate/usr/src/lib/libdevid/deviceid.c (revision 8c69cc8fbe729fa7b091e901c4b50508ccc6bb33)
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
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
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 *
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 *
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
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
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
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