opendir.c revision 256281
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: stable/10/lib/libc/gen/opendir.c 247236 2013-02-24 20:53:32Z jilles $");
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 "un-namespace.h"
48
49#include "gen-private.h"
50#include "telldir.h"
51
52static DIR * __opendir_common(int, 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, DTF_HIDEW|DTF_NODUP));
82}
83
84DIR *
85__opendir2(const char *name, int flags)
86{
87	int fd;
88	DIR *dir;
89	int saved_errno;
90
91	if ((fd = _open(name,
92	    O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
93		return (NULL);
94
95	dir = __opendir_common(fd, flags);
96	if (dir == NULL) {
97		saved_errno = errno;
98		_close(fd);
99		errno = saved_errno;
100	}
101	return (dir);
102}
103
104static int
105opendir_compar(const void *p1, const void *p2)
106{
107
108	return (strcmp((*(const struct dirent **)p1)->d_name,
109	    (*(const struct dirent **)p2)->d_name));
110}
111
112/*
113 * Common routine for opendir(3), __opendir2(3) and fdopendir(3).
114 */
115static DIR *
116__opendir_common(int fd, int flags)
117{
118	DIR *dirp;
119	int incr;
120	int saved_errno;
121	int unionstack;
122	int fd2;
123
124	fd2 = -1;
125
126	if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
127		return (NULL);
128
129	dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR));
130	LIST_INIT(&dirp->dd_td->td_locq);
131	dirp->dd_td->td_loccnt = 0;
132
133	/*
134	 * Use the system page size if that is a multiple of DIRBLKSIZ.
135	 * Hopefully this can be a big win someday by allowing page
136	 * trades to user space to be done by _getdirentries().
137	 */
138	incr = getpagesize();
139	if ((incr % DIRBLKSIZ) != 0)
140		incr = DIRBLKSIZ;
141
142	/*
143	 * Determine whether this directory is the top of a union stack.
144	 */
145	if (flags & DTF_NODUP) {
146		struct statfs sfb;
147
148		if (_fstatfs(fd, &sfb) < 0)
149			goto fail;
150		unionstack = !strcmp(sfb.f_fstypename, "unionfs")
151		    || (sfb.f_flags & MNT_UNION);
152	} else {
153		unionstack = 0;
154	}
155
156	if (unionstack) {
157		int len = 0;
158		int space = 0;
159		char *buf = 0;
160		char *ddptr = 0;
161		char *ddeptr;
162		int n;
163		struct dirent **dpv;
164
165		/*
166		 * The strategy here is to read all the directory
167		 * entries into a buffer, sort the buffer, and
168		 * remove duplicate entries by setting the inode
169		 * number to zero.
170		 *
171		 * We reopen the directory because _getdirentries()
172		 * on a MNT_UNION mount modifies the open directory,
173		 * making it refer to the lower directory after the
174		 * upper directory's entries are exhausted.
175		 * This would otherwise break software that uses
176		 * the directory descriptor for fchdir or *at
177		 * functions, such as fts.c.
178		 */
179		if ((fd2 = _openat(fd, ".", O_RDONLY | O_CLOEXEC)) == -1) {
180			saved_errno = errno;
181			free(buf);
182			free(dirp);
183			errno = saved_errno;
184			return (NULL);
185		}
186
187		do {
188			/*
189			 * Always make at least DIRBLKSIZ bytes
190			 * available to _getdirentries
191			 */
192			if (space < DIRBLKSIZ) {
193				space += incr;
194				len += incr;
195				buf = reallocf(buf, len);
196				if (buf == NULL)
197					goto fail;
198				ddptr = buf + (len - space);
199			}
200
201			n = _getdirentries(fd2, ddptr, space, &dirp->dd_seek);
202			if (n > 0) {
203				ddptr += n;
204				space -= n;
205			}
206		} while (n > 0);
207
208		ddeptr = ddptr;
209		flags |= __DTF_READALL;
210
211		_close(fd2);
212		fd2 = -1;
213
214		/*
215		 * There is now a buffer full of (possibly) duplicate
216		 * names.
217		 */
218		dirp->dd_buf = buf;
219
220		/*
221		 * Go round this loop twice...
222		 *
223		 * Scan through the buffer, counting entries.
224		 * On the second pass, save pointers to each one.
225		 * Then sort the pointers and remove duplicate names.
226		 */
227		for (dpv = 0;;) {
228			n = 0;
229			ddptr = buf;
230			while (ddptr < ddeptr) {
231				struct dirent *dp;
232
233				dp = (struct dirent *) ddptr;
234				if ((long)dp & 03L)
235					break;
236				if ((dp->d_reclen <= 0) ||
237				    (dp->d_reclen > (ddeptr + 1 - ddptr)))
238					break;
239				ddptr += dp->d_reclen;
240				if (dp->d_fileno) {
241					if (dpv)
242						dpv[n] = dp;
243					n++;
244				}
245			}
246
247			if (dpv) {
248				struct dirent *xp;
249
250				/*
251				 * This sort must be stable.
252				 */
253				mergesort(dpv, n, sizeof(*dpv),
254				    opendir_compar);
255
256				dpv[n] = NULL;
257				xp = NULL;
258
259				/*
260				 * Scan through the buffer in sort order,
261				 * zapping the inode number of any
262				 * duplicate names.
263				 */
264				for (n = 0; dpv[n]; n++) {
265					struct dirent *dp = dpv[n];
266
267					if ((xp == NULL) ||
268					    strcmp(dp->d_name, xp->d_name)) {
269						xp = dp;
270					} else {
271						dp->d_fileno = 0;
272					}
273					if (dp->d_type == DT_WHT &&
274					    (flags & DTF_HIDEW))
275						dp->d_fileno = 0;
276				}
277
278				free(dpv);
279				break;
280			} else {
281				dpv = malloc((n+1) * sizeof(struct dirent *));
282				if (dpv == NULL)
283					break;
284			}
285		}
286
287		dirp->dd_len = len;
288		dirp->dd_size = ddptr - dirp->dd_buf;
289	} else {
290		dirp->dd_len = incr;
291		dirp->dd_size = 0;
292		dirp->dd_buf = malloc(dirp->dd_len);
293		if (dirp->dd_buf == NULL)
294			goto fail;
295		dirp->dd_seek = 0;
296	}
297
298	dirp->dd_loc = 0;
299	dirp->dd_fd = fd;
300	dirp->dd_flags = flags;
301	dirp->dd_lock = NULL;
302
303	/*
304	 * Set up seek point for rewinddir.
305	 */
306	dirp->dd_rewind = telldir(dirp);
307
308	return (dirp);
309
310fail:
311	saved_errno = errno;
312	if (fd2 != -1)
313		_close(fd2);
314	free(dirp);
315	errno = saved_errno;
316	return (NULL);
317}
318