xref: /illumos-gate/usr/src/lib/libdevinfo/devinfo_finddev.c (revision 1da57d551424de5a9d469760be7c4b4d4f10a755)
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 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <string.h>
30 #include <thread.h>
31 #include <synch.h>
32 #include <limits.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <strings.h>
36 #include <dirent.h>
37 #include <regex.h>
38 #include <errno.h>
39 #include <stdarg.h>
40 #include <libdevinfo.h>
41 #include <zone.h>
42 #include <sys/modctl.h>
43 #include <syslog.h>
44 #include <sys/stat.h>
45 #include <assert.h>
46 
47 
48 struct finddevhdl {
49 	int	npaths;
50 	int	curpath;
51 	char	**paths;
52 };
53 
54 
55 #define	GLOBAL_DEV_PATH(devpath)			\
56 	((getzoneid() == GLOBAL_ZONEID) &&		\
57 	    ((strcmp(devpath, "/dev") == 0) ||		\
58 	    (strncmp(devpath, "/dev/", strlen("/dev/")) == 0)))
59 
60 /*
61  * Return true if a device exists
62  * If the path refers into the /dev filesystem, use a
63  * private interface to query if the device exists but
64  * without triggering an implicit reconfig if it does not.
65  * Note: can only function properly with absolute pathnames
66  * and only functions for persisted global /dev names, ie
67  * those managed by devfsadm.  For paths other than
68  * /dev, stat(2) is sufficient.
69  */
70 int
device_exists(const char * devname)71 device_exists(const char *devname)
72 {
73 	int	rv;
74 	struct stat st;
75 
76 	if (GLOBAL_DEV_PATH(devname)) {
77 		rv = modctl(MODDEVEXISTS, devname, strlen(devname));
78 		return ((rv == 0) ? 1 : 0);
79 	}
80 	if (stat(devname, &st) == 0)
81 		return (1);
82 	return (0);
83 }
84 
85 
86 /*
87  * Use the standard library readdir to read the contents of
88  * directories on alternate root mounted filesystems.
89  * Return results as per dev_readdir_devfs().
90  *
91  * The directory is traversed twice.  First, to calculate
92  * the size of the buffer required; second, to copy the
93  * directory contents into the buffer.  If the directory
94  * contents grow in between passes, which should almost
95  * never happen, start over again.
96  */
97 static int
finddev_readdir_alt(const char * path,finddevhdl_t * handlep)98 finddev_readdir_alt(const char *path, finddevhdl_t *handlep)
99 {
100 	struct finddevhdl *handle;
101 	DIR *dir;
102 	struct dirent *dp;
103 	size_t n;
104 
105 	*handlep = NULL;
106 	if ((dir = opendir(path)) == NULL)
107 		return (ENOENT);
108 
109 restart:
110 	handle = calloc(1, sizeof (struct finddevhdl));
111 	if (handle == NULL) {
112 		(void) closedir(dir);
113 		return (ENOMEM);
114 	}
115 
116 	handle->npaths = 0;
117 	handle->curpath = 0;
118 	handle->paths = NULL;
119 
120 	n = 0;
121 	rewinddir(dir);
122 	while ((dp = readdir(dir)) != NULL) {
123 		if ((strcmp(dp->d_name, ".") == 0) ||
124 		    (strcmp(dp->d_name, "..") == 0))
125 			continue;
126 		n++;
127 	}
128 
129 	handle->npaths = n;
130 	handle->paths = calloc(n, sizeof (char *));
131 	if (handle->paths == NULL) {
132 		free(handle);
133 		(void) closedir(dir);
134 		return (ENOMEM);
135 	}
136 
137 	n = 0;
138 	rewinddir(dir);
139 	while ((dp = readdir(dir)) != NULL) {
140 		if ((strcmp(dp->d_name, ".") == 0) ||
141 		    (strcmp(dp->d_name, "..") == 0))
142 			continue;
143 		if (n == handle->npaths) {
144 			/*
145 			 * restart if directory contents have out-grown
146 			 * buffer allocated in the first pass.
147 			 */
148 			finddev_close((finddevhdl_t)handle);
149 			goto restart;
150 		}
151 		handle->paths[n] = strdup(dp->d_name);
152 		if (handle->paths[n] == NULL) {
153 			(void) closedir(dir);
154 			finddev_close((finddevhdl_t)handle);
155 			return (ENOMEM);
156 		}
157 		n++;
158 	}
159 	(void) closedir(dir);
160 	*handlep = (finddevhdl_t)handle;
161 	return (0);
162 }
163 
164 /*
165  * Use of the dev filesystem's private readdir does not trigger
166  * the implicit device reconfiguration.
167  *
168  * Note: only useable with paths mounted on an instance of the
169  * dev filesystem.
170  *
171  * Does not return the . and .. entries.
172  * Empty directories are returned as an zero-length list.
173  * ENOENT is returned as a NULL list pointer.
174  */
175 static int
finddev_readdir_devfs(const char * path,finddevhdl_t * handlep)176 finddev_readdir_devfs(const char *path, finddevhdl_t *handlep)
177 {
178 	struct finddevhdl	*handle;
179 	int			n;
180 	int			rv;
181 	int64_t			bufsiz;
182 	char			*pathlist;
183 	char			*p;
184 	int			len;
185 
186 	*handlep = NULL;
187 	handle = calloc(1, sizeof (struct finddevhdl));
188 	if (handle == NULL)
189 		return (ENOMEM);
190 
191 	handle->npaths = 0;
192 	handle->curpath = 0;
193 	handle->paths = NULL;
194 
195 	rv = modctl(MODDEVREADDIR, path, strlen(path), NULL, &bufsiz);
196 	if (rv != 0) {
197 		free(handle);
198 		return (rv);
199 	}
200 
201 	for (;;) {
202 		assert(bufsiz != 0);
203 		if ((pathlist = malloc(bufsiz)) == NULL) {
204 			free(handle);
205 			return (ENOMEM);
206 		}
207 
208 		rv = modctl(MODDEVREADDIR, path, strlen(path),
209 		    pathlist, &bufsiz);
210 		if (rv == 0) {
211 			for (n = 0, p = pathlist;
212 			    (len = strlen(p)) > 0; p += len+1) {
213 				n++;
214 			}
215 			handle->npaths = n;
216 			handle->paths = calloc(n, sizeof (char *));
217 			if (handle->paths == NULL) {
218 				free(handle);
219 				free(pathlist);
220 				return (ENOMEM);
221 			}
222 			for (n = 0, p = pathlist;
223 			    (len = strlen(p)) > 0; p += len+1, n++) {
224 				handle->paths[n] = strdup(p);
225 				if (handle->paths[n] == NULL) {
226 					finddev_close((finddevhdl_t)handle);
227 					free(pathlist);
228 					return (ENOMEM);
229 				}
230 			}
231 			*handlep = (finddevhdl_t)handle;
232 			free(pathlist);
233 			return (0);
234 		}
235 		free(pathlist);
236 		switch (errno) {
237 		case EAGAIN:
238 			break;
239 		case ENOENT:
240 		default:
241 			free(handle);
242 			return (errno);
243 		}
244 	}
245 	/*NOTREACHED*/
246 }
247 
248 int
finddev_readdir(const char * path,finddevhdl_t * handlep)249 finddev_readdir(const char *path, finddevhdl_t *handlep)
250 {
251 	if (GLOBAL_DEV_PATH(path)) {
252 		return (finddev_readdir_devfs(path, handlep));
253 	}
254 	return (finddev_readdir_alt(path, handlep));
255 }
256 
257 /*
258  * Return true if a directory is empty
259  * Use the standard library readdir to determine if a directory is
260  * empty.
261  */
262 static int
finddev_emptydir_alt(const char * path)263 finddev_emptydir_alt(const char *path)
264 {
265 	DIR		*dir;
266 	struct dirent	*dp;
267 
268 	if ((dir = opendir(path)) == NULL)
269 		return (ENOENT);
270 
271 	while ((dp = readdir(dir)) != NULL) {
272 		if ((strcmp(dp->d_name, ".") == 0) ||
273 		    (strcmp(dp->d_name, "..") == 0))
274 			continue;
275 		(void) closedir(dir);
276 		return (0);		/* not empty */
277 	}
278 	(void) closedir(dir);
279 	return (1);			/* empty */
280 }
281 
282 /*
283  * Use of the dev filesystem's private readdir does (not trigger
284  * the implicit device reconfiguration) to determine if a directory
285  * is empty.
286  *
287  * Note: only useable with paths mounted on an instance of the
288  * dev filesystem.
289  *
290  * Does not return the . and .. entries.
291  * Empty directories are returned as an zero-length list.
292  * ENOENT is returned as a NULL list pointer.
293  */
294 static int
finddev_emptydir_devfs(const char * path)295 finddev_emptydir_devfs(const char *path)
296 {
297 	int	rv;
298 	int	empty;
299 
300 	rv = modctl(MODDEVEMPTYDIR, path, strlen(path), &empty);
301 	if (rv == 0) {
302 		return (empty);
303 	}
304 	return (0);
305 }
306 
307 int
finddev_emptydir(const char * path)308 finddev_emptydir(const char *path)
309 {
310 	if (GLOBAL_DEV_PATH(path)) {
311 		return (finddev_emptydir_devfs(path));
312 	}
313 	return (finddev_emptydir_alt(path));
314 }
315 
316 void
finddev_close(finddevhdl_t arg)317 finddev_close(finddevhdl_t arg)
318 {
319 	struct finddevhdl *handle = (struct finddevhdl *)arg;
320 	int i;
321 
322 	for (i = 0; i < handle->npaths; i++) {
323 		if (handle->paths[i])
324 			free(handle->paths[i]);
325 	}
326 	free(handle->paths);
327 	free(handle);
328 }
329 
330 const char *
finddev_next(finddevhdl_t arg)331 finddev_next(finddevhdl_t arg)
332 {
333 	struct finddevhdl *handle = (struct finddevhdl *)arg;
334 	const char *path = NULL;
335 
336 	if (handle->curpath < handle->npaths) {
337 		path = handle->paths[handle->curpath];
338 		handle->curpath++;
339 	}
340 	return (path);
341 }
342