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