opendir.c revision 69841
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 * $FreeBSD: head/lib/libc/gen/opendir.c 69841 2000-12-11 04:00:36Z deischen $
34 */
35
36#if defined(LIBC_SCCS) && !defined(lint)
37static char sccsid[] = "@(#)opendir.c	8.8 (Berkeley) 5/1/95";
38#endif /* LIBC_SCCS and not lint */
39
40#include <sys/param.h>
41#include <sys/mount.h>
42#include <sys/stat.h>
43
44#include <dirent.h>
45#include <errno.h>
46#include <fcntl.h>
47#include <stdlib.h>
48#include <unistd.h>
49
50#include "telldir.h"
51
52/*
53 * Open a directory.
54 */
55DIR *
56opendir(name)
57	const char *name;
58{
59
60	return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
61}
62
63DIR *
64__opendir2(name, flags)
65	const char *name;
66	int flags;
67{
68	DIR *dirp;
69	int fd;
70	int incr;
71	int saved_errno;
72	int unionstack;
73	struct stat statb;
74
75	/*
76	 * stat() before open() because opening of special files may be
77	 * harmful.  fstat() after open because the file may have changed.
78	 */
79	if (stat(name, &statb) != 0)
80		return (NULL);
81	if (!S_ISDIR(statb.st_mode)) {
82		errno = ENOTDIR;
83		return (NULL);
84	}
85	if ((fd = _open(name, O_RDONLY | O_NONBLOCK)) == -1)
86		return (NULL);
87	dirp = NULL;
88	if (fstat(fd, &statb) != 0)
89		goto fail;
90	if (!S_ISDIR(statb.st_mode)) {
91		errno = ENOTDIR;
92		goto fail;
93	}
94	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 ||
95	    (dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
96		goto fail;
97
98	dirp->dd_td = (void *)dirp + sizeof(DIR);
99	LIST_INIT(&dirp->dd_td->td_locq);
100	dirp->dd_td->td_loccnt = 0;
101
102	/*
103	 * Use the system page size if that is a multiple of DIRBLKSIZ.
104	 * Hopefully this can be a big win someday by allowing page
105	 * trades to user space to be done by getdirentries().
106	 */
107	incr = getpagesize();
108	if ((incr % DIRBLKSIZ) != 0)
109		incr = DIRBLKSIZ;
110
111	/*
112	 * Determine whether this directory is the top of a union stack.
113	 */
114	if (flags & DTF_NODUP) {
115		struct statfs sfb;
116
117		if (fstatfs(fd, &sfb) < 0)
118			goto fail;
119		unionstack = !strcmp(sfb.f_fstypename, "union");
120	} else {
121		unionstack = 0;
122	}
123
124	if (unionstack) {
125		int len = 0;
126		int space = 0;
127		char *buf = 0;
128		char *ddptr = 0;
129		char *ddeptr;
130		int n;
131		struct dirent **dpv;
132
133		/*
134		 * The strategy here is to read all the directory
135		 * entries into a buffer, sort the buffer, and
136		 * remove duplicate entries by setting the inode
137		 * number to zero.
138		 */
139
140		do {
141			/*
142			 * Always make at least DIRBLKSIZ bytes
143			 * available to getdirentries
144			 */
145			if (space < DIRBLKSIZ) {
146				space += incr;
147				len += incr;
148				buf = reallocf(buf, len);
149				if (buf == NULL)
150					goto fail;
151				ddptr = buf + (len - space);
152			}
153
154			n = getdirentries(fd, ddptr, space, &dirp->dd_seek);
155			if (n > 0) {
156				ddptr += n;
157				space -= n;
158			}
159		} while (n > 0);
160
161		ddeptr = ddptr;
162		flags |= __DTF_READALL;
163
164		/*
165		 * Re-open the directory.
166		 * This has the effect of rewinding back to the
167		 * top of the union stack and is needed by
168		 * programs which plan to fchdir to a descriptor
169		 * which has also been read -- see fts.c.
170		 */
171		if (flags & DTF_REWIND) {
172			(void)_close(fd);
173			if ((fd = _open(name, O_RDONLY)) == -1) {
174				saved_errno = errno;
175				free(buf);
176				free(dirp);
177				errno = saved_errno;
178				return (NULL);
179			}
180		}
181
182		/*
183		 * There is now a buffer full of (possibly) duplicate
184		 * names.
185		 */
186		dirp->dd_buf = buf;
187
188		/*
189		 * Go round this loop twice...
190		 *
191		 * Scan through the buffer, counting entries.
192		 * On the second pass, save pointers to each one.
193		 * Then sort the pointers and remove duplicate names.
194		 */
195		for (dpv = 0;;) {
196			n = 0;
197			ddptr = buf;
198			while (ddptr < ddeptr) {
199				struct dirent *dp;
200
201				dp = (struct dirent *) ddptr;
202				if ((long)dp & 03L)
203					break;
204				if ((dp->d_reclen <= 0) ||
205				    (dp->d_reclen > (ddeptr + 1 - ddptr)))
206					break;
207				ddptr += dp->d_reclen;
208				if (dp->d_fileno) {
209					if (dpv)
210						dpv[n] = dp;
211					n++;
212				}
213			}
214
215			if (dpv) {
216				struct dirent *xp;
217
218				/*
219				 * This sort must be stable.
220				 */
221				mergesort(dpv, n, sizeof(*dpv), alphasort);
222
223				dpv[n] = NULL;
224				xp = NULL;
225
226				/*
227				 * Scan through the buffer in sort order,
228				 * zapping the inode number of any
229				 * duplicate names.
230				 */
231				for (n = 0; dpv[n]; n++) {
232					struct dirent *dp = dpv[n];
233
234					if ((xp == NULL) ||
235					    strcmp(dp->d_name, xp->d_name)) {
236						xp = dp;
237					} else {
238						dp->d_fileno = 0;
239					}
240					if (dp->d_type == DT_WHT &&
241					    (flags & DTF_HIDEW))
242						dp->d_fileno = 0;
243				}
244
245				free(dpv);
246				break;
247			} else {
248				dpv = malloc((n+1) * sizeof(struct dirent *));
249				if (dpv == NULL)
250					break;
251			}
252		}
253
254		dirp->dd_len = len;
255		dirp->dd_size = ddptr - dirp->dd_buf;
256	} else {
257		dirp->dd_len = incr;
258		dirp->dd_buf = malloc(dirp->dd_len);
259		if (dirp->dd_buf == NULL)
260			goto fail;
261		dirp->dd_seek = 0;
262		flags &= ~DTF_REWIND;
263	}
264
265	dirp->dd_loc = 0;
266	dirp->dd_fd = fd;
267	dirp->dd_flags = flags;
268
269	/*
270	 * Set up seek point for rewinddir.
271	 */
272	dirp->dd_rewind = telldir(dirp);
273
274	return (dirp);
275
276fail:
277	saved_errno = errno;
278	free(dirp);
279	(void)_close(fd);
280	errno = saved_errno;
281	return (NULL);
282}
283