xref: /netbsd-src/lib/libc/gen/opendir.c (revision 9af71357713ca0d8ef0c2d4de659e970a68563d4)
1 /*	$NetBSD: opendir.c,v 1.22 2003/05/28 20:03:37 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1983, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include <sys/cdefs.h>
37 #if defined(LIBC_SCCS) && !defined(lint)
38 #if 0
39 static char sccsid[] = "@(#)opendir.c	8.7 (Berkeley) 12/10/94";
40 #else
41 __RCSID("$NetBSD: opendir.c,v 1.22 2003/05/28 20:03:37 christos Exp $");
42 #endif
43 #endif /* LIBC_SCCS and not lint */
44 
45 #include "namespace.h"
46 #include "reentrant.h"
47 #include <sys/param.h>
48 #include <sys/mount.h>
49 #include <sys/stat.h>
50 
51 #include <assert.h>
52 #include <dirent.h>
53 #include <errno.h>
54 #include <fcntl.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <unistd.h>
58 
59 #ifdef __weak_alias
60 __weak_alias(opendir,_opendir)
61 #endif
62 
63 /*
64  * Open a directory.
65  */
66 DIR *
67 opendir(name)
68 	const char *name;
69 {
70 
71 	_DIAGASSERT(name != NULL);
72 
73 	return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
74 }
75 
76 DIR *
77 __opendir2(name, flags)
78 	const char *name;
79 	int flags;
80 {
81 	DIR *dirp = NULL;
82 	int fd;
83 	int serrno;
84 	struct stat sb;
85 	int pagesz;
86 	int incr;
87 	int unionstack, nfsdir;
88 	struct statfs sfb;
89 
90 	_DIAGASSERT(name != NULL);
91 
92 	if ((fd = open(name, O_RDONLY | O_NONBLOCK)) == -1)
93 		return (NULL);
94 	if (fstat(fd, &sb) || !S_ISDIR(sb.st_mode)) {
95 		close(fd);
96 		errno = ENOTDIR;
97 		return (NULL);
98 	}
99 	if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 ||
100 	    (dirp = (DIR *)malloc(sizeof(DIR))) == NULL) {
101 		goto error;
102 	}
103 	dirp->dd_buf = NULL;
104 
105 	/*
106 	 * If the machine's page size is an exact multiple of DIRBLKSIZ,
107 	 * use a buffer that is cluster boundary aligned.
108 	 * Hopefully this can be a big win someday by allowing page trades
109 	 * to user space to be done by getdirentries()
110 	 */
111 	if (((pagesz = getpagesize()) % DIRBLKSIZ) == 0)
112 		incr = pagesz;
113 	else
114 		incr = DIRBLKSIZ;
115 
116 	/*
117 	 * Determine whether this directory is the top of a union stack.
118 	 */
119 
120 	if (fstatfs(fd, &sfb) < 0)
121 		goto error;
122 
123 	if (flags & DTF_NODUP)
124 		unionstack = !(strncmp(sfb.f_fstypename, MOUNT_UNION,
125 		    MFSNAMELEN)) || (sfb.f_flags & MNT_UNION);
126 	else
127 		unionstack = 0;
128 
129 	nfsdir = !(strncmp(sfb.f_fstypename, MOUNT_NFS, MFSNAMELEN));
130 
131 	if (unionstack || nfsdir) {
132 		size_t len;
133 		size_t space;
134 		char *buf, *nbuf;
135 		char *ddptr;
136 		char *ddeptr;
137 		int n;
138 		struct dirent **dpv;
139 
140 		/*
141 		 * The strategy here for directories on top of a union stack
142 		 * is to read all the directory entries into a buffer, sort
143 		 * the buffer, and remove duplicate entries by setting the
144 		 * inode number to zero.
145 		 *
146 		 * For directories on an NFS mounted filesystem, we try
147 	 	 * to get a consistent snapshot by trying until we have
148 		 * successfully read all of the directory without errors
149 		 * (i.e. 'bad cookie' errors from the server because
150 		 * the directory was modified). These errors should not
151 		 * happen often, but need to be dealt with.
152 		 */
153 retry:
154 		len = 0;
155 		space = 0;
156 		buf = 0;
157 		ddptr = 0;
158 
159 		do {
160 			/*
161 			 * Always make at least DIRBLKSIZ bytes
162 			 * available to getdirentries
163 			 */
164 			if (space < DIRBLKSIZ) {
165 				space += incr;
166 				len += incr;
167 				nbuf = realloc(buf, len);
168 				if (nbuf == NULL) {
169 					dirp->dd_buf = buf;
170 					goto error;
171 				}
172 				buf = nbuf;
173 				ddptr = buf + (len - space);
174 			}
175 
176 			dirp->dd_seek = lseek(fd, (off_t)0, SEEK_CUR);
177 			n = getdents(fd, ddptr, space);
178 			/*
179 			 * For NFS: EINVAL means a bad cookie error
180 			 * from the server. Keep trying to get a
181 			 * consistent view, in this case this means
182 			 * starting all over again.
183 			 */
184 			if (n == -1 && errno == EINVAL && nfsdir) {
185 				free(buf);
186 				lseek(fd, (off_t)0, SEEK_SET);
187 				goto retry;
188 			}
189 			if (n > 0) {
190 				ddptr += n;
191 				space -= n;
192 			}
193 		} while (n > 0);
194 
195 		ddeptr = ddptr;
196 		flags |= __DTF_READALL;
197 
198 		/*
199 		 * Re-open the directory.
200 		 * This has the effect of rewinding back to the
201 		 * top of the union stack and is needed by
202 		 * programs which plan to fchdir to a descriptor
203 		 * which has also been read -- see fts.c.
204 		 */
205 		if (flags & DTF_REWIND) {
206 			(void) close(fd);
207 			if ((fd = open(name, O_RDONLY)) == -1) {
208 				dirp->dd_buf = buf;
209 				goto error;
210 			}
211 		}
212 
213 		/*
214 		 * There is now a buffer full of (possibly) duplicate
215 		 * names.
216 		 */
217 		dirp->dd_buf = buf;
218 
219 		/*
220 		 * Go round this loop twice...
221 		 *
222 		 * Scan through the buffer, counting entries.
223 		 * On the second pass, save pointers to each one.
224 		 * Then sort the pointers and remove duplicate names.
225 		 */
226 		if (!nfsdir) {
227 			for (dpv = 0;;) {
228 				for (n = 0, ddptr = buf; ddptr < ddeptr;) {
229 					struct dirent *dp;
230 
231 					dp = (struct dirent *)(void *)ddptr;
232 					if ((long)dp & 03)
233 						break;
234 					/*
235 					 * d_reclen is unsigned,
236 					 * so no need to compare <= 0
237 					 */
238 					if (dp->d_reclen > (ddeptr + 1 - ddptr))
239 						break;
240 					ddptr += dp->d_reclen;
241 					if (dp->d_fileno) {
242 						if (dpv)
243 							dpv[n] = dp;
244 						n++;
245 					}
246 				}
247 
248 				if (dpv) {
249 					struct dirent *xp;
250 
251 					/*
252 					 * This sort must be stable.
253 					 */
254 					mergesort(dpv, (size_t)n, sizeof(*dpv),
255 					    alphasort);
256 
257 					dpv[n] = NULL;
258 					xp = NULL;
259 
260 					/*
261 					 * Scan through the buffer in sort
262 					 * order, zapping the inode number
263 					 * of any duplicate names.
264 					 */
265 					for (n = 0; dpv[n]; n++) {
266 						struct dirent *dp = dpv[n];
267 
268 						if ((xp == NULL) ||
269 						    strcmp(dp->d_name,
270 						      xp->d_name))
271 							xp = dp;
272 						else
273 							dp->d_fileno = 0;
274 						if (dp->d_type == DT_WHT &&
275 						    (flags & DTF_HIDEW))
276 							dp->d_fileno = 0;
277 					}
278 
279 					free(dpv);
280 					break;
281 				} else {
282 					dpv = malloc((n + 1) *
283 					    sizeof(struct dirent *));
284 					if (dpv == NULL)
285 						break;
286 				}
287 			}
288 		}
289 
290 		dirp->dd_len = len;
291 		dirp->dd_size = ddptr - dirp->dd_buf;
292 	} else {
293 		dirp->dd_len = incr;
294 		dirp->dd_buf = malloc((size_t)dirp->dd_len);
295 		if (dirp->dd_buf == NULL)
296 			goto error;
297 		dirp->dd_seek = 0;
298 		flags &= ~DTF_REWIND;
299 	}
300 
301 	dirp->dd_loc = 0;
302 	dirp->dd_fd = fd;
303 	dirp->dd_flags = flags;
304 
305 	/*
306 	 * Set up seek point for rewinddir.
307 	 */
308 	dirp->dd_rewind = telldir(dirp);
309 #ifdef _REENTRANT
310 	if (__isthreaded) {
311 		if ((dirp->dd_lock = malloc(sizeof(mutex_t))) == NULL)
312 			goto error;
313 		mutex_init((mutex_t *)dirp->dd_lock, NULL);
314 	}
315 #endif
316 	return (dirp);
317 error:
318 	serrno = errno;
319 	if (dirp && dirp->dd_buf)
320 		free(dirp->dd_buf);
321 	if (dirp)
322 		free(dirp);
323 	if (fd != -1)
324 		(void)close(fd);
325 	errno = serrno;
326 	return NULL;
327 }
328