opendir.c revision 133723
1/*
2 * Copyright (c) 1983, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#if defined(LIBC_SCCS) && !defined(lint)
35static char sccsid[] = "@(#)opendir.c	8.8 (Berkeley) 5/1/95";
36#endif /* LIBC_SCCS and not lint */
37#include <sys/cdefs.h>
38__FBSDID("$FreeBSD: head/lib/libc/gen/opendir.c 133723 2004-08-14 17:46:10Z stefanf $");
39
40#include "namespace.h"
41#include <sys/param.h>
42#include <sys/mount.h>
43#include <sys/stat.h>
44
45#include <dirent.h>
46#include <errno.h>
47#include <fcntl.h>
48#include <stdlib.h>
49#include <string.h>
50#include <unistd.h>
51#include "un-namespace.h"
52
53#include "telldir.h"
54/*
55 * Open a directory.
56 */
57DIR *
58opendir(name)
59	const char *name;
60{
61
62	return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
63}
64
65DIR *
66__opendir2(name, flags)
67	const char *name;
68	int flags;
69{
70	DIR *dirp;
71	int fd;
72	int incr;
73	int saved_errno;
74	int unionstack;
75	struct stat statb;
76
77	/*
78	 * stat() before _open() because opening of special files may be
79	 * harmful.  _fstat() after open because the file may have changed.
80	 */
81	if (stat(name, &statb) != 0)
82		return (NULL);
83	if (!S_ISDIR(statb.st_mode)) {
84		errno = ENOTDIR;
85		return (NULL);
86	}
87	if ((fd = _open(name, O_RDONLY | O_NONBLOCK)) == -1)
88		return (NULL);
89	dirp = NULL;
90	if (_fstat(fd, &statb) != 0)
91		goto fail;
92	if (!S_ISDIR(statb.st_mode)) {
93		errno = ENOTDIR;
94		goto fail;
95	}
96	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 ||
97	    (dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
98		goto fail;
99
100	dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR));
101	LIST_INIT(&dirp->dd_td->td_locq);
102	dirp->dd_td->td_loccnt = 0;
103
104	/*
105	 * Use the system page size if that is a multiple of DIRBLKSIZ.
106	 * Hopefully this can be a big win someday by allowing page
107	 * trades to user space to be done by _getdirentries().
108	 */
109	incr = getpagesize();
110	if ((incr % DIRBLKSIZ) != 0)
111		incr = DIRBLKSIZ;
112
113	/*
114	 * Determine whether this directory is the top of a union stack.
115	 */
116	if (flags & DTF_NODUP) {
117		struct statfs sfb;
118
119		if (_fstatfs(fd, &sfb) < 0)
120			goto fail;
121		unionstack = !strcmp(sfb.f_fstypename, "unionfs")
122		    || (sfb.f_flags & MNT_UNION);
123	} else {
124		unionstack = 0;
125	}
126
127	if (unionstack) {
128		int len = 0;
129		int space = 0;
130		char *buf = 0;
131		char *ddptr = 0;
132		char *ddeptr;
133		int n;
134		struct dirent **dpv;
135
136		/*
137		 * The strategy here is to read all the directory
138		 * entries into a buffer, sort the buffer, and
139		 * remove duplicate entries by setting the inode
140		 * number to zero.
141		 */
142
143		do {
144			/*
145			 * Always make at least DIRBLKSIZ bytes
146			 * available to _getdirentries
147			 */
148			if (space < DIRBLKSIZ) {
149				space += incr;
150				len += incr;
151				buf = reallocf(buf, len);
152				if (buf == NULL)
153					goto fail;
154				ddptr = buf + (len - space);
155			}
156
157			n = _getdirentries(fd, ddptr, space, &dirp->dd_seek);
158			if (n > 0) {
159				ddptr += n;
160				space -= n;
161			}
162		} while (n > 0);
163
164		ddeptr = ddptr;
165		flags |= __DTF_READALL;
166
167		/*
168		 * Re-open the directory.
169		 * This has the effect of rewinding back to the
170		 * top of the union stack and is needed by
171		 * programs which plan to fchdir to a descriptor
172		 * which has also been read -- see fts.c.
173		 */
174		if (flags & DTF_REWIND) {
175			(void)_close(fd);
176			if ((fd = _open(name, O_RDONLY)) == -1) {
177				saved_errno = errno;
178				free(buf);
179				free(dirp);
180				errno = saved_errno;
181				return (NULL);
182			}
183		}
184
185		/*
186		 * There is now a buffer full of (possibly) duplicate
187		 * names.
188		 */
189		dirp->dd_buf = buf;
190
191		/*
192		 * Go round this loop twice...
193		 *
194		 * Scan through the buffer, counting entries.
195		 * On the second pass, save pointers to each one.
196		 * Then sort the pointers and remove duplicate names.
197		 */
198		for (dpv = 0;;) {
199			n = 0;
200			ddptr = buf;
201			while (ddptr < ddeptr) {
202				struct dirent *dp;
203
204				dp = (struct dirent *) ddptr;
205				if ((long)dp & 03L)
206					break;
207				if ((dp->d_reclen <= 0) ||
208				    (dp->d_reclen > (ddeptr + 1 - ddptr)))
209					break;
210				ddptr += dp->d_reclen;
211				if (dp->d_fileno) {
212					if (dpv)
213						dpv[n] = dp;
214					n++;
215				}
216			}
217
218			if (dpv) {
219				struct dirent *xp;
220
221				/*
222				 * This sort must be stable.
223				 */
224				mergesort(dpv, n, sizeof(*dpv), alphasort);
225
226				dpv[n] = NULL;
227				xp = NULL;
228
229				/*
230				 * Scan through the buffer in sort order,
231				 * zapping the inode number of any
232				 * duplicate names.
233				 */
234				for (n = 0; dpv[n]; n++) {
235					struct dirent *dp = dpv[n];
236
237					if ((xp == NULL) ||
238					    strcmp(dp->d_name, xp->d_name)) {
239						xp = dp;
240					} else {
241						dp->d_fileno = 0;
242					}
243					if (dp->d_type == DT_WHT &&
244					    (flags & DTF_HIDEW))
245						dp->d_fileno = 0;
246				}
247
248				free(dpv);
249				break;
250			} else {
251				dpv = malloc((n+1) * sizeof(struct dirent *));
252				if (dpv == NULL)
253					break;
254			}
255		}
256
257		dirp->dd_len = len;
258		dirp->dd_size = ddptr - dirp->dd_buf;
259	} else {
260		dirp->dd_len = incr;
261		dirp->dd_size = 0;
262		dirp->dd_buf = malloc(dirp->dd_len);
263		if (dirp->dd_buf == NULL)
264			goto fail;
265		dirp->dd_seek = 0;
266		flags &= ~DTF_REWIND;
267	}
268
269	dirp->dd_loc = 0;
270	dirp->dd_fd = fd;
271	dirp->dd_flags = flags;
272	dirp->dd_lock = NULL;
273
274	/*
275	 * Set up seek point for rewinddir.
276	 */
277	dirp->dd_rewind = telldir(dirp);
278
279	return (dirp);
280
281fail:
282	saved_errno = errno;
283	free(dirp);
284	(void)_close(fd);
285	errno = saved_errno;
286	return (NULL);
287}
288