xref: /illumos-gate/usr/src/lib/libdevinfo/devinfo_finddev.c (revision 08516594b0e540dc0f415fa7ae31f54d943a0913)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <string.h>
32 #include <thread.h>
33 #include <synch.h>
34 #include <limits.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <dirent.h>
39 #include <regex.h>
40 #include <errno.h>
41 #include <stdarg.h>
42 #include <libdevinfo.h>
43 #include <zone.h>
44 #include <sys/modctl.h>
45 #include <syslog.h>
46 #include <sys/stat.h>
47 #include <assert.h>
48 
49 
50 struct finddevhdl {
51 	int	npaths;
52 	int	curpath;
53 	char	**paths;
54 };
55 
56 
57 #define	GLOBAL_DEV_PATH(devpath)			\
58 	((getzoneid() == GLOBAL_ZONEID) &&		\
59 	    ((strcmp(devpath, "/dev") == 0) ||		\
60 	    (strncmp(devpath, "/dev/", strlen("/dev/")) == 0)))
61 
62 /*
63  * Return true if a device exists
64  * If the path refers into the /dev filesystem, use a
65  * private interface to query if the device exists but
66  * without triggering an implicit reconfig if it does not.
67  * Note: can only function properly with absolute pathnames
68  * and only functions for persisted global /dev names, ie
69  * those managed by devfsadm.  For paths other than
70  * /dev, stat(2) is sufficient.
71  */
72 int
73 device_exists(const char *devname)
74 {
75 	int	rv;
76 	struct stat st;
77 
78 	if (GLOBAL_DEV_PATH(devname)) {
79 		rv = modctl(MODDEVEXISTS, devname, strlen(devname));
80 		return ((rv == 0) ? 1 : 0);
81 	}
82 	if (stat(devname, &st) == 0)
83 		return (1);
84 	return (0);
85 }
86 
87 
88 /*
89  * Use the standard library readdir to read the contents of
90  * directories on alternate root mounted filesystems.
91  * Return results as per dev_readdir_devfs().
92  *
93  * The directory is traversed twice.  First, to calculate
94  * the size of the buffer required; second, to copy the
95  * directory contents into the buffer.  If the directory
96  * contents grow in between passes, which should almost
97  * never happen, start over again.
98  */
99 static int
100 finddev_readdir_alt(const char *path, finddevhdl_t *handlep)
101 {
102 	struct finddevhdl *handle;
103 	DIR *dir;
104 	struct dirent *dp;
105 	size_t n;
106 
107 	*handlep = NULL;
108 	if ((dir = opendir(path)) == NULL)
109 		return (ENOENT);
110 
111 restart:
112 	handle = calloc(1, sizeof (struct finddevhdl));
113 	if (handle == NULL) {
114 		(void) closedir(dir);
115 		return (ENOMEM);
116 	}
117 
118 	handle->npaths = 0;
119 	handle->curpath = 0;
120 	handle->paths = NULL;
121 
122 	n = 0;
123 	rewinddir(dir);
124 	while ((dp = readdir(dir)) != NULL) {
125 		if ((strcmp(dp->d_name, ".") == 0) ||
126 		    (strcmp(dp->d_name, "..") == 0))
127 			continue;
128 		n++;
129 	}
130 
131 	handle->npaths = n;
132 	handle->paths = calloc(n, sizeof (char *));
133 	if (handle->paths == NULL) {
134 		free(handle);
135 		(void) closedir(dir);
136 		return (ENOMEM);
137 	}
138 
139 	n = 0;
140 	rewinddir(dir);
141 	while ((dp = readdir(dir)) != NULL) {
142 		if ((strcmp(dp->d_name, ".") == 0) ||
143 		    (strcmp(dp->d_name, "..") == 0))
144 			continue;
145 		if (n == handle->npaths) {
146 			/*
147 			 * restart if directory contents have out-grown
148 			 * buffer allocated in the first pass.
149 			 */
150 			finddev_close((finddevhdl_t)handle);
151 			goto restart;
152 		}
153 		handle->paths[n] = strdup(dp->d_name);
154 		if (handle->paths[n] == NULL) {
155 			(void) closedir(dir);
156 			finddev_close((finddevhdl_t)handle);
157 			return (ENOMEM);
158 		}
159 		n++;
160 	}
161 	(void) closedir(dir);
162 	*handlep = (finddevhdl_t)handle;
163 	return (0);
164 }
165 
166 /*
167  * Use of the dev filesystem's private readdir does not trigger
168  * the implicit device reconfiguration.
169  *
170  * Note: only useable with paths mounted on an instance of the
171  * dev filesystem.
172  *
173  * Does not return the . and .. entries.
174  * Empty directories are returned as an zero-length list.
175  * ENOENT is returned as a NULL list pointer.
176  */
177 static int
178 finddev_readdir_devfs(const char *path, finddevhdl_t *handlep)
179 {
180 	struct finddevhdl	*handle;
181 	int			n;
182 	int			rv;
183 	int64_t			bufsiz;
184 	char			*pathlist;
185 	char			*p;
186 	int			len;
187 
188 	*handlep = NULL;
189 	handle = calloc(1, sizeof (struct finddevhdl));
190 	if (handle == NULL)
191 		return (ENOMEM);
192 
193 	handle->npaths = 0;
194 	handle->curpath = 0;
195 	handle->paths = NULL;
196 
197 	rv = modctl(MODDEVREADDIR, path, strlen(path), NULL, &bufsiz);
198 	if (rv != 0) {
199 		free(handle);
200 		return (rv);
201 	}
202 
203 	for (;;) {
204 		assert(bufsiz != 0);
205 		if ((pathlist = malloc(bufsiz)) == NULL) {
206 			free(handle);
207 			return (ENOMEM);
208 		}
209 
210 		rv = modctl(MODDEVREADDIR, path, strlen(path),
211 		    pathlist, &bufsiz);
212 		if (rv == 0) {
213 			for (n = 0, p = pathlist;
214 			    (len = strlen(p)) > 0; p += len+1) {
215 				n++;
216 			}
217 			handle->npaths = n;
218 			handle->paths = calloc(n, sizeof (char *));
219 			if (handle->paths == NULL) {
220 				free(handle);
221 				free(pathlist);
222 				return (ENOMEM);
223 			}
224 			for (n = 0, p = pathlist;
225 			    (len = strlen(p)) > 0; p += len+1, n++) {
226 				handle->paths[n] = strdup(p);
227 				if (handle->paths[n] == NULL) {
228 					finddev_close((finddevhdl_t)handle);
229 					free(pathlist);
230 					return (ENOMEM);
231 				}
232 			}
233 			*handlep = (finddevhdl_t)handle;
234 			free(pathlist);
235 			return (0);
236 		}
237 		free(pathlist);
238 		switch (errno) {
239 		case EAGAIN:
240 			break;
241 		case ENOENT:
242 		default:
243 			free(handle);
244 			return (errno);
245 		}
246 	}
247 	/*NOTREACHED*/
248 }
249 
250 int
251 finddev_readdir(const char *path, finddevhdl_t *handlep)
252 {
253 	if (GLOBAL_DEV_PATH(path)) {
254 		return (finddev_readdir_devfs(path, handlep));
255 	}
256 	return (finddev_readdir_alt(path, handlep));
257 }
258 
259 void
260 finddev_close(finddevhdl_t arg)
261 {
262 	struct finddevhdl *handle = (struct finddevhdl *)arg;
263 	int i;
264 
265 	for (i = 0; i < handle->npaths; i++) {
266 		if (handle->paths[i])
267 			free(handle->paths[i]);
268 	}
269 	free(handle->paths);
270 	free(handle);
271 }
272 
273 const char *
274 finddev_next(finddevhdl_t arg)
275 {
276 	struct finddevhdl *handle = (struct finddevhdl *)arg;
277 	const char *path = NULL;
278 
279 	if (handle->curpath < handle->npaths) {
280 		path = handle->paths[handle->curpath];
281 		handle->curpath++;
282 	}
283 	return (path);
284 }
285