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