1123059Ssimon/*-
2123016Sphk * Copyright (c) 1983, 1993
3123016Sphk *	The Regents of the University of California.  All rights reserved.
4123016Sphk *
5123016Sphk * Redistribution and use in source and binary forms, with or without
6123016Sphk * modification, are permitted provided that the following conditions
7123016Sphk * are met:
8123016Sphk * 1. Redistributions of source code must retain the above copyright
9123016Sphk *    notice, this list of conditions and the following disclaimer.
10123016Sphk * 2. Redistributions in binary form must reproduce the above copyright
11123016Sphk *    notice, this list of conditions and the following disclaimer in the
12123016Sphk *    documentation and/or other materials provided with the distribution.
13123016Sphk * 4. Neither the name of the University nor the names of its contributors
14123016Sphk *    may be used to endorse or promote products derived from this software
15123016Sphk *    without specific prior written permission.
16123016Sphk *
17123016Sphk * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18123016Sphk * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19123016Sphk * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20123016Sphk * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21123016Sphk * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22123016Sphk * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23123016Sphk * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24123016Sphk * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25123016Sphk * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26123016Sphk * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27123016Sphk * SUCH DAMAGE.
28123059Ssimon */
29123016Sphk
30123016Sphk#if defined(LIBC_SCCS) && !defined(lint)
31123016Sphkstatic char sccsid[] = "@(#)opendir.c	8.8 (Berkeley) 5/1/95";
32123016Sphk#endif /* LIBC_SCCS and not lint */
33123016Sphk#include <sys/cdefs.h>
34123059Ssimon__FBSDID("$FreeBSD: releng/11.0/lib/libc/gen/opendir.c 297790 2016-04-10 19:33:58Z pfg $");
35123059Ssimon
36123059Ssimon#include "namespace.h"
37123059Ssimon#include <sys/param.h>
38123059Ssimon#include <sys/mount.h>
39123059Ssimon#include <sys/stat.h>
40123059Ssimon
41123059Ssimon#include <dirent.h>
42123059Ssimon#include <errno.h>
43123059Ssimon#include <fcntl.h>
44123016Sphk#include <stdlib.h>
45123059Ssimon#include <string.h>
46123059Ssimon#include <unistd.h>
47123059Ssimon#include "un-namespace.h"
48123016Sphk
49123059Ssimon#include "gen-private.h"
50123059Ssimon#include "telldir.h"
51123059Ssimon
52123016Sphkstatic DIR * __opendir_common(int, int, bool);
53123016Sphk
54123016Sphk/*
55123059Ssimon * Open a directory.
56123059Ssimon */
57123059SsimonDIR *
58123059Ssimonopendir(const char *name)
59123016Sphk{
60123059Ssimon
61123059Ssimon	return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
62123059Ssimon}
63123016Sphk
64123059Ssimon/*
65123059Ssimon * Open a directory with existing file descriptor.
66123059Ssimon */
67123016SphkDIR *
68123016Sphkfdopendir(int fd)
69123016Sphk{
70123059Ssimon
71123059Ssimon	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
72123059Ssimon		return (NULL);
73123016Sphk	return (__opendir_common(fd, DTF_HIDEW|DTF_NODUP, true));
74123016Sphk}
75123059Ssimon
76123059SsimonDIR *
77123059Ssimon__opendir2(const char *name, int flags)
78123059Ssimon{
79123059Ssimon	int fd;
80123059Ssimon	DIR *dir;
81123016Sphk	int saved_errno;
82123016Sphk
83123059Ssimon	if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0)
84123059Ssimon		return (NULL);
85123059Ssimon	if ((fd = _open(name,
86123016Sphk	    O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
87123059Ssimon		return (NULL);
88123059Ssimon
89123059Ssimon	dir = __opendir_common(fd, flags, false);
90123059Ssimon	if (dir == NULL) {
91123016Sphk		saved_errno = errno;
92123016Sphk		_close(fd);
93123059Ssimon		errno = saved_errno;
94123059Ssimon	}
95123059Ssimon	return (dir);
96123059Ssimon}
97123059Ssimon
98123059Ssimonstatic int
99123059Ssimonopendir_compar(const void *p1, const void *p2)
100123059Ssimon{
101123059Ssimon
102123016Sphk	return (strcmp((*(const struct dirent **)p1)->d_name,
103123016Sphk	    (*(const struct dirent **)p2)->d_name));
104123059Ssimon}
105123059Ssimon
106123059Ssimon/*
107123059Ssimon * For a directory at the top of a unionfs stack, the entire directory's
108123059Ssimon * contents are read and cached locally until the next call to rewinddir().
109123016Sphk * For the fdopendir() case, the initial seek position must be preserved.
110123016Sphk * For rewinddir(), the full directory should always be re-read from the
111123016Sphk * beginning.
112123016Sphk *
113123059Ssimon * If an error occurs, the existing buffer and state of 'dirp' is left
114123016Sphk * unchanged.
115123059Ssimon */
116123059Ssimonbool
117123059Ssimon_filldir(DIR *dirp, bool use_current_pos)
118123059Ssimon{
119123059Ssimon	struct dirent **dpv;
120123059Ssimon	char *buf, *ddptr, *ddeptr;
121123016Sphk	off_t pos;
122123016Sphk	int fd2, incr, len, n, saved_errno, space;
123123016Sphk
124123059Ssimon	len = 0;
125123059Ssimon	space = 0;
126123059Ssimon	buf = NULL;
127123059Ssimon	ddptr = NULL;
128123016Sphk
129123059Ssimon	/*
130123059Ssimon	 * Use the system page size if that is a multiple of DIRBLKSIZ.
131123059Ssimon	 * Hopefully this can be a big win someday by allowing page
132123059Ssimon	 * trades to user space to be done by _getdirentries().
133123059Ssimon	 */
134123059Ssimon	incr = getpagesize();
135123059Ssimon	if ((incr % DIRBLKSIZ) != 0)
136123059Ssimon		incr = DIRBLKSIZ;
137123016Sphk
138123016Sphk	/*
139123016Sphk	 * The strategy here is to read all the directory
140123059Ssimon	 * entries into a buffer, sort the buffer, and
141123059Ssimon	 * remove duplicate entries by setting the inode
142123016Sphk	 * number to zero.
143123059Ssimon	 *
144123059Ssimon	 * We reopen the directory because _getdirentries()
145123059Ssimon	 * on a MNT_UNION mount modifies the open directory,
146123059Ssimon	 * making it refer to the lower directory after the
147123059Ssimon	 * upper directory's entries are exhausted.
148123016Sphk	 * This would otherwise break software that uses
149123016Sphk	 * the directory descriptor for fchdir or *at
150123059Ssimon	 * functions, such as fts.c.
151123059Ssimon	 */
152123016Sphk	if ((fd2 = _openat(dirp->dd_fd, ".", O_RDONLY | O_CLOEXEC)) == -1)
153123016Sphk		return (false);
154123016Sphk
155123016Sphk	if (use_current_pos) {
156123059Ssimon		pos = lseek(dirp->dd_fd, 0, SEEK_CUR);
157123016Sphk		if (pos == -1 || lseek(fd2, pos, SEEK_SET) == -1) {
158123016Sphk			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 = NULL;;) {
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