xref: /openbsd-src/lib/libc/gen/opendir.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
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  * 3. 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)
31 static char rcsid[] = "$OpenBSD: opendir.c,v 1.9 2003/06/02 20:18:34 millert Exp $";
32 #endif /* LIBC_SCCS and not lint */
33 
34 #include <sys/param.h>
35 #include <sys/mount.h>
36 #include <sys/stat.h>
37 
38 #include <dirent.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <limits.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 static int direntcmp(const void *, const void *);
47 
48 /*
49  * Comparison function for sorting dirent structures that never returns 0;
50  * this causes qsort() to emulate a stable sort.
51  */
52 static int
53 direntcmp(const void *d1, const void *d2)
54 {
55 	int i;
56 
57 	i = strcmp((*(struct dirent **)d1)->d_name,
58 	    (*(struct dirent **)d2)->d_name);
59 	return (i != 0 ? i : (char *)d2 - (char *)d1);
60 }
61 
62 /*
63  * Open a directory.
64  */
65 DIR *
66 opendir(name)
67 	const char *name;
68 {
69 
70 	return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
71 }
72 
73 DIR *
74 __opendir2(name, flags)
75 	const char *name;
76 	int flags;
77 {
78 	DIR *dirp;
79 	int fd;
80 	struct stat sb;
81 	int pagesz;
82 	int incr;
83 	int unionstack;
84 
85 	if ((fd = open(name, O_RDONLY | O_NONBLOCK)) == -1)
86 		return (NULL);
87 	if (fstat(fd, &sb) || !S_ISDIR(sb.st_mode)) {
88 		errno = ENOTDIR;
89 		close(fd);
90 		return (NULL);
91 	}
92 	if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 ||
93 	    (dirp = (DIR *)malloc(sizeof(DIR))) == NULL) {
94 		close(fd);
95 		return (NULL);
96 	}
97 
98 	/*
99 	 * If the machine's page size is an exact multiple of DIRBLKSIZ,
100 	 * use a buffer that is cluster boundary aligned.
101 	 * Hopefully this can be a big win someday by allowing page trades
102 	 * to user space to be done by getdirentries()
103 	 */
104 	if (((pagesz = getpagesize()) % DIRBLKSIZ) == 0)
105 		incr = pagesz;
106 	else
107 		incr = DIRBLKSIZ;
108 
109 	/*
110 	 * Determine whether this directory is the top of a union stack.
111 	 */
112 	if (flags & DTF_NODUP) {
113 		struct statfs sfb;
114 
115 		if (fstatfs(fd, &sfb) < 0) {
116 			free(dirp);
117 			close(fd);
118 			return (NULL);
119 		}
120 		unionstack = strncmp(sfb.f_fstypename, MOUNT_UNION, MFSNAMELEN) == 0 ||
121 			     (sfb.f_flags & MNT_UNION);
122 	} else {
123 		unionstack = 0;
124 	}
125 
126 	if (unionstack) {
127 		int len = 0;
128 		int space = 0;
129 		char *buf = 0;
130 		char *ddptr = 0;
131 		char *ddeptr;
132 		int n;
133 		struct dirent **dpv;
134 
135 		/*
136 		 * The strategy here is to read all the directory
137 		 * entries into a buffer, sort the buffer, and
138 		 * remove duplicate entries by setting the inode
139 		 * number to zero.
140 		 */
141 
142 		do {
143 			/*
144 			 * Always make at least DIRBLKSIZ bytes
145 			 * available to getdirentries
146 			 */
147 			if (space < DIRBLKSIZ) {
148 				char *nbuf;
149 
150 				space += incr;
151 				len += incr;
152 				nbuf = realloc(buf, len);
153 				if (nbuf == NULL) {
154 					if (buf)
155 						free(buf);
156 					free(dirp);
157 					close(fd);
158 					return (NULL);
159 				}
160 				buf = nbuf;
161 				ddptr = buf + (len - space);
162 			}
163 
164 			n = getdirentries(fd, ddptr, space, &dirp->dd_seek);
165 			if (n > 0) {
166 				ddptr += n;
167 				space -= n;
168 			}
169 		} while (n > 0);
170 
171 		ddeptr = ddptr;
172 		flags |= __DTF_READALL;
173 
174 		/*
175 		 * Re-open the directory.
176 		 * This has the effect of rewinding back to the
177 		 * top of the union stack and is needed by
178 		 * programs which plan to fchdir to a descriptor
179 		 * which has also been read -- see fts.c.
180 		 */
181 		if (flags & DTF_REWIND) {
182 			(void) close(fd);
183 			if ((fd = open(name, O_RDONLY)) == -1) {
184 				free(buf);
185 				free(dirp);
186 				return (NULL);
187 			}
188 		}
189 
190 		/*
191 		 * There is now a buffer full of (possibly) duplicate
192 		 * names.
193 		 */
194 		dirp->dd_buf = buf;
195 
196 		/*
197 		 * Go round this loop twice...
198 		 *
199 		 * Scan through the buffer, counting entries.
200 		 * On the second pass, save pointers to each one.
201 		 * Then sort the pointers and remove duplicate names.
202 		 */
203 		for (dpv = 0;;) {
204 			for (n = 0, ddptr = buf; ddptr < ddeptr;) {
205 				struct dirent *dp;
206 
207 				dp = (struct dirent *) ddptr;
208 				if ((long)dp & 03)
209 					break;
210 				if ((dp->d_reclen <= 0) ||
211 				    (dp->d_reclen > (ddeptr + 1 - ddptr)))
212 					break;
213 				ddptr += dp->d_reclen;
214 				if (dp->d_fileno) {
215 					if (dpv)
216 						dpv[n] = dp;
217 					n++;
218 				}
219 			}
220 
221 			if (dpv) {
222 				struct dirent *xp;
223 
224 				/*
225 				 * This sort must be stable.
226 				 */
227 				qsort(dpv, n, sizeof(*dpv), direntcmp);
228 
229 				dpv[n] = NULL;
230 				xp = NULL;
231 
232 				/*
233 				 * Scan through the buffer in sort order,
234 				 * zapping the inode number of any
235 				 * duplicate names.
236 				 */
237 				for (n = 0; dpv[n]; n++) {
238 					struct dirent *dp = dpv[n];
239 
240 					if ((xp == NULL) ||
241 					    strcmp(dp->d_name, xp->d_name))
242 						xp = dp;
243 					else
244 						dp->d_fileno = 0;
245 					if (dp->d_type == DT_WHT &&
246 					    (flags & DTF_HIDEW))
247 						dp->d_fileno = 0;
248 				}
249 
250 				free(dpv);
251 				break;
252 			} else {
253 				if (n+1 > SIZE_T_MAX / sizeof(struct dirent *))
254 					break;
255 				dpv = malloc((n+1) * sizeof(struct dirent *));
256 				if (dpv == NULL)
257 					break;
258 			}
259 		}
260 
261 		dirp->dd_len = len;
262 		dirp->dd_size = ddptr - dirp->dd_buf;
263 	} else {
264 		dirp->dd_len = incr;
265 		dirp->dd_buf = malloc(dirp->dd_len);
266 		if (dirp->dd_buf == NULL) {
267 			free(dirp);
268 			close (fd);
269 			return (NULL);
270 		}
271 		dirp->dd_seek = 0;
272 		flags &= ~DTF_REWIND;
273 	}
274 
275 	dirp->dd_loc = 0;
276 	dirp->dd_fd = fd;
277 	dirp->dd_flags = flags;
278 
279 	/*
280 	 * Set up seek point for rewinddir.
281 	 */
282 	dirp->dd_rewind = telldir(dirp);
283 
284 	return (dirp);
285 }
286