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 #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 /*
260  * Return true if a directory is empty
261  * Use the standard library readdir to determine if a directory is
262  * empty.
263  */
264 static int
265 finddev_emptydir_alt(const char *path)
266 {
267 	DIR		*dir;
268 	struct dirent	*dp;
269 
270 	if ((dir = opendir(path)) == NULL)
271 		return (ENOENT);
272 
273 	while ((dp = readdir(dir)) != NULL) {
274 		if ((strcmp(dp->d_name, ".") == 0) ||
275 		    (strcmp(dp->d_name, "..") == 0))
276 			continue;
277 		(void) closedir(dir);
278 		return (0);		/* not empty */
279 	}
280 	(void) closedir(dir);
281 	return (1);			/* empty */
282 }
283 
284 /*
285  * Use of the dev filesystem's private readdir does (not trigger
286  * the implicit device reconfiguration) to determine if a directory
287  * is empty.
288  *
289  * Note: only useable with paths mounted on an instance of the
290  * dev filesystem.
291  *
292  * Does not return the . and .. entries.
293  * Empty directories are returned as an zero-length list.
294  * ENOENT is returned as a NULL list pointer.
295  */
296 static int
297 finddev_emptydir_devfs(const char *path)
298 {
299 	int	rv;
300 	int	empty;
301 
302 	rv = modctl(MODDEVEMPTYDIR, path, strlen(path), &empty);
303 	if (rv == 0) {
304 		return (empty);
305 	}
306 	return (0);
307 }
308 
309 int
310 finddev_emptydir(const char *path)
311 {
312 	if (GLOBAL_DEV_PATH(path)) {
313 		return (finddev_emptydir_devfs(path));
314 	}
315 	return (finddev_emptydir_alt(path));
316 }
317 
318 void
319 finddev_close(finddevhdl_t arg)
320 {
321 	struct finddevhdl *handle = (struct finddevhdl *)arg;
322 	int i;
323 
324 	for (i = 0; i < handle->npaths; i++) {
325 		if (handle->paths[i])
326 			free(handle->paths[i]);
327 	}
328 	free(handle->paths);
329 	free(handle);
330 }
331 
332 const char *
333 finddev_next(finddevhdl_t arg)
334 {
335 	struct finddevhdl *handle = (struct finddevhdl *)arg;
336 	const char *path = NULL;
337 
338 	if (handle->curpath < handle->npaths) {
339 		path = handle->paths[handle->curpath];
340 		handle->curpath++;
341 	}
342 	return (path);
343 }
344