opendir.c revision 270002
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 270002 2014-08-14 20:20:21Z jhb $");
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, bool);
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
71	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
72		return (NULL);
73	return (__opendir_common(fd, DTF_HIDEW|DTF_NODUP, true));
74}
75
76DIR *
77__opendir2(const char *name, int flags)
78{
79	int fd;
80	DIR *dir;
81	int saved_errno;
82
83	if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0)
84		return (NULL);
85	if ((fd = _open(name,
86	    O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
87		return (NULL);
88
89	dir = __opendir_common(fd, flags, false);
90	if (dir == NULL) {
91		saved_errno = errno;
92		_close(fd);
93		errno = saved_errno;
94	}
95	return (dir);
96}
97
98static int
99opendir_compar(const void *p1, const void *p2)
100{
101
102	return (strcmp((*(const struct dirent **)p1)->d_name,
103	    (*(const struct dirent **)p2)->d_name));
104}
105
106/*
107 * For a directory at the top of a unionfs stack, the entire directory's
108 * contents are read and cached locally until the next call to rewinddir().
109 * For the fdopendir() case, the initial seek position must be preserved.
110 * For rewinddir(), the full directory should always be re-read from the
111 * beginning.
112 *
113 * If an error occurs, the existing buffer and state of 'dirp' is left
114 * unchanged.
115 */
116bool
117_filldir(DIR *dirp, bool use_current_pos)
118{
119	struct dirent **dpv;
120	char *buf, *ddptr, *ddeptr;
121	off_t pos;
122	int fd2, incr, len, n, saved_errno, space;
123
124	len = 0;
125	space = 0;
126	buf = NULL;
127	ddptr = NULL;
128
129	/*
130	 * Use the system page size if that is a multiple of DIRBLKSIZ.
131	 * Hopefully this can be a big win someday by allowing page
132	 * trades to user space to be done by _getdirentries().
133	 */
134	incr = getpagesize();
135	if ((incr % DIRBLKSIZ) != 0)
136		incr = DIRBLKSIZ;
137
138	/*
139	 * The strategy here is to read all the directory
140	 * entries into a buffer, sort the buffer, and
141	 * remove duplicate entries by setting the inode
142	 * number to zero.
143	 *
144	 * We reopen the directory because _getdirentries()
145	 * on a MNT_UNION mount modifies the open directory,
146	 * making it refer to the lower directory after the
147	 * upper directory's entries are exhausted.
148	 * This would otherwise break software that uses
149	 * the directory descriptor for fchdir or *at
150	 * functions, such as fts.c.
151	 */
152	if ((fd2 = _openat(dirp->dd_fd, ".", O_RDONLY | O_CLOEXEC)) == -1)
153		return (false);
154
155	if (use_current_pos) {
156		pos = lseek(dirp->dd_fd, 0, SEEK_CUR);
157		if (pos == -1 || lseek(fd2, pos, SEEK_SET) == -1) {
158			saved_errno = errno;
159			_close(fd2);
160			errno = saved_errno;
161			return (false);
162		}
163	}
164
165	do {
166		/*
167		 * Always make at least DIRBLKSIZ bytes
168		 * available to _getdirentries
169		 */
170		if (space < DIRBLKSIZ) {
171			space += incr;
172			len += incr;
173			buf = reallocf(buf, len);
174			if (buf == NULL) {
175				saved_errno = errno;
176				_close(fd2);
177				errno = saved_errno;
178				return (false);
179			}
180			ddptr = buf + (len - space);
181		}
182
183		n = _getdirentries(fd2, ddptr, space, &dirp->dd_seek);
184		if (n > 0) {
185			ddptr += n;
186			space -= n;
187		}
188		if (n < 0) {
189			saved_errno = errno;
190			_close(fd2);
191			errno = saved_errno;
192			return (false);
193		}
194	} while (n > 0);
195	_close(fd2);
196
197	ddeptr = ddptr;
198
199	/*
200	 * There is now a buffer full of (possibly) duplicate
201	 * names.
202	 */
203	dirp->dd_buf = buf;
204
205	/*
206	 * Go round this loop twice...
207	 *
208	 * Scan through the buffer, counting entries.
209	 * On the second pass, save pointers to each one.
210	 * Then sort the pointers and remove duplicate names.
211	 */
212	for (dpv = 0;;) {
213		n = 0;
214		ddptr = buf;
215		while (ddptr < ddeptr) {
216			struct dirent *dp;
217
218			dp = (struct dirent *) ddptr;
219			if ((long)dp & 03L)
220				break;
221			if ((dp->d_reclen <= 0) ||
222			    (dp->d_reclen > (ddeptr + 1 - ddptr)))
223				break;
224			ddptr += dp->d_reclen;
225			if (dp->d_fileno) {
226				if (dpv)
227					dpv[n] = dp;
228				n++;
229			}
230		}
231
232		if (dpv) {
233			struct dirent *xp;
234
235			/*
236			 * This sort must be stable.
237			 */
238			mergesort(dpv, n, sizeof(*dpv), opendir_compar);
239
240			dpv[n] = NULL;
241			xp = NULL;
242
243			/*
244			 * Scan through the buffer in sort order,
245			 * zapping the inode number of any
246			 * duplicate names.
247			 */
248			for (n = 0; dpv[n]; n++) {
249				struct dirent *dp = dpv[n];
250
251				if ((xp == NULL) ||
252				    strcmp(dp->d_name, xp->d_name)) {
253					xp = dp;
254				} else {
255					dp->d_fileno = 0;
256				}
257				if (dp->d_type == DT_WHT &&
258				    (dirp->dd_flags & DTF_HIDEW))
259					dp->d_fileno = 0;
260			}
261
262			free(dpv);
263			break;
264		} else {
265			dpv = malloc((n+1) * sizeof(struct dirent *));
266			if (dpv == NULL)
267				break;
268		}
269	}
270
271	dirp->dd_len = len;
272	dirp->dd_size = ddptr - dirp->dd_buf;
273	return (true);
274}
275
276
277/*
278 * Common routine for opendir(3), __opendir2(3) and fdopendir(3).
279 */
280static DIR *
281__opendir_common(int fd, int flags, bool use_current_pos)
282{
283	DIR *dirp;
284	int incr;
285	int saved_errno;
286	int unionstack;
287
288	if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
289		return (NULL);
290
291	dirp->dd_buf = NULL;
292	dirp->dd_fd = fd;
293	dirp->dd_flags = flags;
294	dirp->dd_loc = 0;
295	dirp->dd_lock = NULL;
296	dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR));
297	LIST_INIT(&dirp->dd_td->td_locq);
298	dirp->dd_td->td_loccnt = 0;
299
300	/*
301	 * Use the system page size if that is a multiple of DIRBLKSIZ.
302	 * Hopefully this can be a big win someday by allowing page
303	 * trades to user space to be done by _getdirentries().
304	 */
305	incr = getpagesize();
306	if ((incr % DIRBLKSIZ) != 0)
307		incr = DIRBLKSIZ;
308
309	/*
310	 * Determine whether this directory is the top of a union stack.
311	 */
312	if (flags & DTF_NODUP) {
313		struct statfs sfb;
314
315		if (_fstatfs(fd, &sfb) < 0)
316			goto fail;
317		unionstack = !strcmp(sfb.f_fstypename, "unionfs")
318		    || (sfb.f_flags & MNT_UNION);
319	} else {
320		unionstack = 0;
321	}
322
323	if (unionstack) {
324		if (!_filldir(dirp, use_current_pos))
325			goto fail;
326		dirp->dd_flags |= __DTF_READALL;
327	} else {
328		dirp->dd_len = incr;
329		dirp->dd_buf = malloc(dirp->dd_len);
330		if (dirp->dd_buf == NULL)
331			goto fail;
332		if (use_current_pos) {
333			/*
334			 * Read the first batch of directory entries
335			 * to prime dd_seek.  This also checks if the
336			 * fd passed to fdopendir() is a directory.
337			 */
338			dirp->dd_size = _getdirentries(dirp->dd_fd,
339			    dirp->dd_buf, dirp->dd_len, &dirp->dd_seek);
340			if (dirp->dd_size < 0) {
341				if (errno == EINVAL)
342					errno = ENOTDIR;
343				goto fail;
344			}
345			dirp->dd_flags |= __DTF_SKIPREAD;
346		} else {
347			dirp->dd_size = 0;
348			dirp->dd_seek = 0;
349		}
350	}
351
352	return (dirp);
353
354fail:
355	saved_errno = errno;
356	free(dirp->dd_buf);
357	free(dirp);
358	errno = saved_errno;
359	return (NULL);
360}
361