opendir.c revision 69656
1212793Sdim/*
2212793Sdim * Copyright (c) 1983, 1993
3212793Sdim *	The Regents of the University of California.  All rights reserved.
4212793Sdim *
5212793Sdim * Redistribution and use in source and binary forms, with or without
6212793Sdim * modification, are permitted provided that the following conditions
7212793Sdim * are met:
8212793Sdim * 1. Redistributions of source code must retain the above copyright
9212793Sdim *    notice, this list of conditions and the following disclaimer.
10212793Sdim * 2. Redistributions in binary form must reproduce the above copyright
11212793Sdim *    notice, this list of conditions and the following disclaimer in the
12212793Sdim *    documentation and/or other materials provided with the distribution.
13212793Sdim * 3. All advertising materials mentioning features or use of this software
14212793Sdim *    must display the following acknowledgement:
15212793Sdim *	This product includes software developed by the University of
16212793Sdim *	California, Berkeley and its contributors.
17212793Sdim * 4. Neither the name of the University nor the names of its contributors
18212793Sdim *    may be used to endorse or promote products derived from this software
19212793Sdim *    without specific prior written permission.
20212793Sdim *
21212793Sdim * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22212793Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23212793Sdim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24212793Sdim * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25212793Sdim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26212793Sdim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27212793Sdim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28212793Sdim * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29212793Sdim * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30212793Sdim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31212793Sdim * SUCH DAMAGE.
32212793Sdim *
33221345Sdim * $FreeBSD: head/lib/libc/gen/opendir.c 69656 2000-12-06 03:15:49Z deischen $
34239462Sdim */
35239462Sdim
36239462Sdim#if defined(LIBC_SCCS) && !defined(lint)
37239462Sdimstatic char sccsid[] = "@(#)opendir.c	8.8 (Berkeley) 5/1/95";
38239462Sdim#endif /* LIBC_SCCS and not lint */
39239462Sdim
40239462Sdim#include <sys/param.h>
41239462Sdim#include <sys/mount.h>
42239462Sdim#include <sys/stat.h>
43249423Sdim
44249423Sdim#include <dirent.h>
45249423Sdim#include <errno.h>
46249423Sdim#include <fcntl.h>
47249423Sdim#include <stdlib.h>
48261991Sdim#include <unistd.h>
49280031Sdim
50261991Sdim/*
51261991Sdim * Open a directory.
52261991Sdim */
53261991SdimDIR *
54261991Sdimopendir(name)
55261991Sdim	const char *name;
56261991Sdim{
57261991Sdim
58261991Sdim	return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
59261991Sdim}
60261991Sdim
61261991SdimDIR *
62261991Sdim__opendir2(name, flags)
63261991Sdim	const char *name;
64261991Sdim	int flags;
65261991Sdim{
66261991Sdim	DIR *dirp;
67212793Sdim	int fd;
68212793Sdim	int incr;
69212793Sdim	int saved_errno;
70249423Sdim	int unionstack;
71249423Sdim	struct stat statb;
72249423Sdim
73249423Sdim	/*
74212793Sdim	 * stat() before open() because opening of special files may be
75212793Sdim	 * harmful.  fstat() after open because the file may have changed.
76212793Sdim	 */
77249423Sdim	if (stat(name, &statb) != 0)
78249423Sdim		return (NULL);
79212793Sdim	if (!S_ISDIR(statb.st_mode)) {
80212793Sdim		errno = ENOTDIR;
81280031Sdim		return (NULL);
82280031Sdim	}
83212793Sdim	if ((fd = _open(name, O_RDONLY | O_NONBLOCK)) == -1)
84212793Sdim		return (NULL);
85276479Sdim	dirp = NULL;
86276479Sdim	if (fstat(fd, &statb) != 0)
87212793Sdim		goto fail;
88212793Sdim	if (!S_ISDIR(statb.st_mode)) {
89212793Sdim		errno = ENOTDIR;
90212793Sdim		goto fail;
91212793Sdim	}
92218893Sdim	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 ||
93218893Sdim	    (dirp = malloc(sizeof(DIR))) == NULL)
94218893Sdim		goto fail;
95218893Sdim
96276479Sdim	/*
97280031Sdim	 * Use the system page size if that is a multiple of DIRBLKSIZ.
98276479Sdim	 * Hopefully this can be a big win someday by allowing page
99276479Sdim	 * trades to user space to be done by getdirentries().
100212793Sdim	 */
101221345Sdim	incr = getpagesize();
102234353Sdim	if ((incr % DIRBLKSIZ) != 0)
103239462Sdim		incr = DIRBLKSIZ;
104239462Sdim
105280031Sdim	/*
106280031Sdim	 * Determine whether this directory is the top of a union stack.
107212793Sdim	 */
108212793Sdim	if (flags & DTF_NODUP) {
109212793Sdim		struct statfs sfb;
110212793Sdim
111280031Sdim		if (fstatfs(fd, &sfb) < 0)
112212793Sdim			goto fail;
113212793Sdim		unionstack = !strcmp(sfb.f_fstypename, "union");
114212793Sdim	} else {
115212793Sdim		unionstack = 0;
116212793Sdim	}
117218893Sdim
118218893Sdim	if (unionstack) {
119218893Sdim		int len = 0;
120212793Sdim		int space = 0;
121276479Sdim		char *buf = 0;
122212793Sdim		char *ddptr = 0;
123276479Sdim		char *ddeptr;
124212793Sdim		int n;
125212793Sdim		struct dirent **dpv;
126212793Sdim
127212793Sdim		/*
128212793Sdim		 * The strategy here is to read all the directory
129212793Sdim		 * entries into a buffer, sort the buffer, and
130212793Sdim		 * remove duplicate entries by setting the inode
131212793Sdim		 * number to zero.
132212793Sdim		 */
133239462Sdim
134239462Sdim		do {
135280031Sdim			/*
136280031Sdim			 * Always make at least DIRBLKSIZ bytes
137280031Sdim			 * available to getdirentries
138280031Sdim			 */
139261991Sdim			if (space < DIRBLKSIZ) {
140280031Sdim				space += incr;
141280031Sdim				len += incr;
142280031Sdim				buf = reallocf(buf, len);
143280031Sdim				if (buf == NULL)
144218893Sdim					goto fail;
145218893Sdim				ddptr = buf + (len - space);
146218893Sdim			}
147239462Sdim
148218893Sdim			n = getdirentries(fd, ddptr, space, &dirp->dd_seek);
149218893Sdim			if (n > 0) {
150276479Sdim				ddptr += n;
151276479Sdim				space -= n;
152280031Sdim			}
153280031Sdim		} while (n > 0);
154280031Sdim
155280031Sdim		ddeptr = ddptr;
156280031Sdim		flags |= __DTF_READALL;
157280031Sdim
158280031Sdim		/*
159280031Sdim		 * Re-open the directory.
160280031Sdim		 * This has the effect of rewinding back to the
161280031Sdim		 * top of the union stack and is needed by
162280031Sdim		 * programs which plan to fchdir to a descriptor
163280031Sdim		 * which has also been read -- see fts.c.
164280031Sdim		 */
165280031Sdim		if (flags & DTF_REWIND) {
166280031Sdim			(void)_close(fd);
167280031Sdim			if ((fd = _open(name, O_RDONLY)) == -1) {
168280031Sdim				saved_errno = errno;
169280031Sdim				free(buf);
170280031Sdim				free(dirp);
171212793Sdim				errno = saved_errno;
172276479Sdim				return (NULL);
173276479Sdim			}
174276479Sdim		}
175276479Sdim
176276479Sdim		/*
177276479Sdim		 * There is now a buffer full of (possibly) duplicate
178276479Sdim		 * names.
179276479Sdim		 */
180276479Sdim		dirp->dd_buf = buf;
181276479Sdim
182276479Sdim		/*
183276479Sdim		 * Go round this loop twice...
184276479Sdim		 *
185276479Sdim		 * Scan through the buffer, counting entries.
186276479Sdim		 * On the second pass, save pointers to each one.
187276479Sdim		 * Then sort the pointers and remove duplicate names.
188276479Sdim		 */
189276479Sdim		for (dpv = 0;;) {
190276479Sdim			n = 0;
191276479Sdim			ddptr = buf;
192276479Sdim			while (ddptr < ddeptr) {
193276479Sdim				struct dirent *dp;
194276479Sdim
195276479Sdim				dp = (struct dirent *) ddptr;
196276479Sdim				if ((long)dp & 03L)
197276479Sdim					break;
198276479Sdim				if ((dp->d_reclen <= 0) ||
199276479Sdim				    (dp->d_reclen > (ddeptr + 1 - ddptr)))
200276479Sdim					break;
201276479Sdim				ddptr += dp->d_reclen;
202276479Sdim				if (dp->d_fileno) {
203276479Sdim					if (dpv)
204276479Sdim						dpv[n] = dp;
205276479Sdim					n++;
206276479Sdim				}
207280031Sdim			}
208280031Sdim
209280031Sdim			if (dpv) {
210276479Sdim				struct dirent *xp;
211280031Sdim
212276479Sdim				/*
213276479Sdim				 * This sort must be stable.
214276479Sdim				 */
215280031Sdim				mergesort(dpv, n, sizeof(*dpv), alphasort);
216276479Sdim
217280031Sdim				dpv[n] = NULL;
218276479Sdim				xp = NULL;
219280031Sdim
220276479Sdim				/*
221276479Sdim				 * Scan through the buffer in sort order,
222280031Sdim				 * zapping the inode number of any
223276479Sdim				 * duplicate names.
224276479Sdim				 */
225280031Sdim				for (n = 0; dpv[n]; n++) {
226276479Sdim					struct dirent *dp = dpv[n];
227276479Sdim
228280031Sdim					if ((xp == NULL) ||
229276479Sdim					    strcmp(dp->d_name, xp->d_name)) {
230276479Sdim						xp = dp;
231280031Sdim					} else {
232276479Sdim						dp->d_fileno = 0;
233276479Sdim					}
234280031Sdim					if (dp->d_type == DT_WHT &&
235276479Sdim					    (flags & DTF_HIDEW))
236280031Sdim						dp->d_fileno = 0;
237280031Sdim				}
238276479Sdim
239276479Sdim				free(dpv);
240276479Sdim				break;
241280031Sdim			} else {
242280031Sdim				dpv = malloc((n+1) * sizeof(struct dirent *));
243280031Sdim				if (dpv == NULL)
244280031Sdim					break;
245280031Sdim			}
246280031Sdim		}
247280031Sdim
248280031Sdim		dirp->dd_len = len;
249280031Sdim		dirp->dd_size = ddptr - dirp->dd_buf;
250280031Sdim	} else {
251280031Sdim		dirp->dd_len = incr;
252280031Sdim		dirp->dd_buf = malloc(dirp->dd_len);
253280031Sdim		if (dirp->dd_buf == NULL)
254280031Sdim			goto fail;
255280031Sdim		dirp->dd_seek = 0;
256280031Sdim		flags &= ~DTF_REWIND;
257280031Sdim	}
258280031Sdim
259280031Sdim	dirp->dd_loc = 0;
260280031Sdim	dirp->dd_fd = fd;
261280031Sdim	dirp->dd_flags = flags;
262280031Sdim	dirp->dd_loccnt = 0;
263280031Sdim	LIST_INIT(&dirp->dd_locq);
264280031Sdim
265276479Sdim	/*
266280031Sdim	 * Set up seek point for rewinddir.
267276479Sdim	 */
268280031Sdim	dirp->dd_rewind = telldir(dirp);
269276479Sdim
270280031Sdim	return (dirp);
271280031Sdim
272280031Sdimfail:
273276479Sdim	saved_errno = errno;
274276479Sdim	free(dirp);
275276479Sdim	(void)_close(fd);
276276479Sdim	errno = saved_errno;
277276479Sdim	return (NULL);
278280031Sdim}
279276479Sdim