opendir.c revision 330897
1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1983, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 4. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#if defined(LIBC_SCCS) && !defined(lint)
33static char sccsid[] = "@(#)opendir.c	8.8 (Berkeley) 5/1/95";
34#endif /* LIBC_SCCS and not lint */
35#include <sys/cdefs.h>
36__FBSDID("$FreeBSD: stable/11/lib/libc/gen/opendir.c 330897 2018-03-14 03:19:51Z eadler $");
37
38#include "namespace.h"
39#include <sys/param.h>
40#include <sys/mount.h>
41#include <sys/stat.h>
42
43#include <dirent.h>
44#include <errno.h>
45#include <fcntl.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49#include "un-namespace.h"
50
51#include "gen-private.h"
52#include "telldir.h"
53
54static DIR * __opendir_common(int, int, bool);
55
56/*
57 * Open a directory.
58 */
59DIR *
60opendir(const char *name)
61{
62
63	return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
64}
65
66/*
67 * Open a directory with existing file descriptor.
68 */
69DIR *
70fdopendir(int fd)
71{
72
73	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
74		return (NULL);
75	return (__opendir_common(fd, DTF_HIDEW|DTF_NODUP, true));
76}
77
78DIR *
79__opendir2(const char *name, int flags)
80{
81	int fd;
82	DIR *dir;
83	int saved_errno;
84
85	if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0)
86		return (NULL);
87	if ((fd = _open(name,
88	    O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
89		return (NULL);
90
91	dir = __opendir_common(fd, flags, false);
92	if (dir == NULL) {
93		saved_errno = errno;
94		_close(fd);
95		errno = saved_errno;
96	}
97	return (dir);
98}
99
100static int
101opendir_compar(const void *p1, const void *p2)
102{
103
104	return (strcmp((*(const struct dirent **)p1)->d_name,
105	    (*(const struct dirent **)p2)->d_name));
106}
107
108/*
109 * For a directory at the top of a unionfs stack, the entire directory's
110 * contents are read and cached locally until the next call to rewinddir().
111 * For the fdopendir() case, the initial seek position must be preserved.
112 * For rewinddir(), the full directory should always be re-read from the
113 * beginning.
114 *
115 * If an error occurs, the existing buffer and state of 'dirp' is left
116 * unchanged.
117 */
118bool
119_filldir(DIR *dirp, bool use_current_pos)
120{
121	struct dirent **dpv;
122	char *buf, *ddptr, *ddeptr;
123	off_t pos;
124	int fd2, incr, len, n, saved_errno, space;
125
126	len = 0;
127	space = 0;
128	buf = NULL;
129	ddptr = NULL;
130
131	/*
132	 * Use the system page size if that is a multiple of DIRBLKSIZ.
133	 * Hopefully this can be a big win someday by allowing page
134	 * trades to user space to be done by _getdirentries().
135	 */
136	incr = getpagesize();
137	if ((incr % DIRBLKSIZ) != 0)
138		incr = DIRBLKSIZ;
139
140	/*
141	 * The strategy here is to read all the directory
142	 * entries into a buffer, sort the buffer, and
143	 * remove duplicate entries by setting the inode
144	 * number to zero.
145	 *
146	 * We reopen the directory because _getdirentries()
147	 * on a MNT_UNION mount modifies the open directory,
148	 * making it refer to the lower directory after the
149	 * upper directory's entries are exhausted.
150	 * This would otherwise break software that uses
151	 * the directory descriptor for fchdir or *at
152	 * functions, such as fts.c.
153	 */
154	if ((fd2 = _openat(dirp->dd_fd, ".", O_RDONLY | O_CLOEXEC)) == -1)
155		return (false);
156
157	if (use_current_pos) {
158		pos = lseek(dirp->dd_fd, 0, SEEK_CUR);
159		if (pos == -1 || lseek(fd2, pos, SEEK_SET) == -1) {
160			saved_errno = errno;
161			_close(fd2);
162			errno = saved_errno;
163			return (false);
164		}
165	}
166
167	do {
168		/*
169		 * Always make at least DIRBLKSIZ bytes
170		 * available to _getdirentries
171		 */
172		if (space < DIRBLKSIZ) {
173			space += incr;
174			len += incr;
175			buf = reallocf(buf, len);
176			if (buf == NULL) {
177				saved_errno = errno;
178				_close(fd2);
179				errno = saved_errno;
180				return (false);
181			}
182			ddptr = buf + (len - space);
183		}
184
185		n = _getdirentries(fd2, ddptr, space, &dirp->dd_seek);
186		if (n > 0) {
187			ddptr += n;
188			space -= n;
189		}
190		if (n < 0) {
191			saved_errno = errno;
192			_close(fd2);
193			errno = saved_errno;
194			return (false);
195		}
196	} while (n > 0);
197	_close(fd2);
198
199	ddeptr = ddptr;
200
201	/*
202	 * There is now a buffer full of (possibly) duplicate
203	 * names.
204	 */
205	dirp->dd_buf = buf;
206
207	/*
208	 * Go round this loop twice...
209	 *
210	 * Scan through the buffer, counting entries.
211	 * On the second pass, save pointers to each one.
212	 * Then sort the pointers and remove duplicate names.
213	 */
214	for (dpv = NULL;;) {
215		n = 0;
216		ddptr = buf;
217		while (ddptr < ddeptr) {
218			struct dirent *dp;
219
220			dp = (struct dirent *) ddptr;
221			if ((long)dp & 03L)
222				break;
223			if ((dp->d_reclen <= 0) ||
224			    (dp->d_reclen > (ddeptr + 1 - ddptr)))
225				break;
226			ddptr += dp->d_reclen;
227			if (dp->d_fileno) {
228				if (dpv)
229					dpv[n] = dp;
230				n++;
231			}
232		}
233
234		if (dpv) {
235			struct dirent *xp;
236
237			/*
238			 * This sort must be stable.
239			 */
240			mergesort(dpv, n, sizeof(*dpv), opendir_compar);
241
242			dpv[n] = NULL;
243			xp = NULL;
244
245			/*
246			 * Scan through the buffer in sort order,
247			 * zapping the inode number of any
248			 * duplicate names.
249			 */
250			for (n = 0; dpv[n]; n++) {
251				struct dirent *dp = dpv[n];
252
253				if ((xp == NULL) ||
254				    strcmp(dp->d_name, xp->d_name)) {
255					xp = dp;
256				} else {
257					dp->d_fileno = 0;
258				}
259				if (dp->d_type == DT_WHT &&
260				    (dirp->dd_flags & DTF_HIDEW))
261					dp->d_fileno = 0;
262			}
263
264			free(dpv);
265			break;
266		} else {
267			dpv = malloc((n+1) * sizeof(struct dirent *));
268			if (dpv == NULL)
269				break;
270		}
271	}
272
273	dirp->dd_len = len;
274	dirp->dd_size = ddptr - dirp->dd_buf;
275	return (true);
276}
277
278
279/*
280 * Common routine for opendir(3), __opendir2(3) and fdopendir(3).
281 */
282static DIR *
283__opendir_common(int fd, int flags, bool use_current_pos)
284{
285	DIR *dirp;
286	int incr;
287	int saved_errno;
288	int unionstack;
289
290	if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
291		return (NULL);
292
293	dirp->dd_buf = NULL;
294	dirp->dd_fd = fd;
295	dirp->dd_flags = flags;
296	dirp->dd_loc = 0;
297	dirp->dd_lock = NULL;
298	dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR));
299	LIST_INIT(&dirp->dd_td->td_locq);
300	dirp->dd_td->td_loccnt = 0;
301
302	/*
303	 * Use the system page size if that is a multiple of DIRBLKSIZ.
304	 * Hopefully this can be a big win someday by allowing page
305	 * trades to user space to be done by _getdirentries().
306	 */
307	incr = getpagesize();
308	if ((incr % DIRBLKSIZ) != 0)
309		incr = DIRBLKSIZ;
310
311	/*
312	 * Determine whether this directory is the top of a union stack.
313	 */
314	if (flags & DTF_NODUP) {
315		struct statfs sfb;
316
317		if (_fstatfs(fd, &sfb) < 0)
318			goto fail;
319		unionstack = !strcmp(sfb.f_fstypename, "unionfs")
320		    || (sfb.f_flags & MNT_UNION);
321	} else {
322		unionstack = 0;
323	}
324
325	if (unionstack) {
326		if (!_filldir(dirp, use_current_pos))
327			goto fail;
328		dirp->dd_flags |= __DTF_READALL;
329	} else {
330		dirp->dd_len = incr;
331		dirp->dd_buf = malloc(dirp->dd_len);
332		if (dirp->dd_buf == NULL)
333			goto fail;
334		if (use_current_pos) {
335			/*
336			 * Read the first batch of directory entries
337			 * to prime dd_seek.  This also checks if the
338			 * fd passed to fdopendir() is a directory.
339			 */
340			dirp->dd_size = _getdirentries(dirp->dd_fd,
341			    dirp->dd_buf, dirp->dd_len, &dirp->dd_seek);
342			if (dirp->dd_size < 0) {
343				if (errno == EINVAL)
344					errno = ENOTDIR;
345				goto fail;
346			}
347			dirp->dd_flags |= __DTF_SKIPREAD;
348		} else {
349			dirp->dd_size = 0;
350			dirp->dd_seek = 0;
351		}
352	}
353
354	return (dirp);
355
356fail:
357	saved_errno = errno;
358	free(dirp->dd_buf);
359	free(dirp);
360	errno = saved_errno;
361	return (NULL);
362}
363