xref: /original-bsd/lib/libc/gen/opendir.c (revision 0842ddeb)
1 /*
2  * Copyright (c) 1983, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #if defined(LIBC_SCCS) && !defined(lint)
9 static char sccsid[] = "@(#)opendir.c	8.8 (Berkeley) 05/01/95";
10 #endif /* LIBC_SCCS and not lint */
11 
12 #include <sys/param.h>
13 #include <sys/mount.h>
14 #include <sys/stat.h>
15 
16 #include <dirent.h>
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 
22 /*
23  * Open a directory.
24  */
25 DIR *
26 opendir(name)
27 	const char *name;
28 {
29 
30 	return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
31 }
32 
33 DIR *
34 __opendir2(name, flags)
35 	const char *name;
36 	int flags;
37 {
38 	DIR *dirp;
39 	int fd;
40 	int incr;
41 	int unionstack;
42 	struct stat statb;
43 
44 	if ((fd = open(name, O_RDONLY)) == -1)
45 		return (NULL);
46 	if (fstat(fd, &statb) || !S_ISDIR(statb.st_mode)) {
47 		errno = ENOTDIR;
48 		close(fd);
49 		return (NULL);
50 	}
51 	if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 ||
52 	    (dirp = (DIR *)malloc(sizeof(DIR))) == NULL) {
53 		close(fd);
54 		return (NULL);
55 	}
56 
57 	/*
58 	 * If CLBYTES is an exact multiple of DIRBLKSIZ, use a CLBYTES
59 	 * buffer that it cluster boundary aligned.
60 	 * Hopefully this can be a big win someday by allowing page
61 	 * trades to user space to be done by getdirentries()
62 	 */
63 	if ((CLBYTES % DIRBLKSIZ) == 0)
64 		incr = CLBYTES;
65 	else
66 		incr = DIRBLKSIZ;
67 
68 	/*
69 	 * Determine whether this directory is the top of a union stack.
70 	 */
71 	if (flags & DTF_NODUP) {
72 		struct statfs sfb;
73 
74 		if (fstatfs(fd, &sfb) < 0) {
75 			free(dirp);
76 			close(fd);
77 			return (NULL);
78 		}
79 		unionstack = !strcmp(sfb.f_fstypename, "union");
80 	} else {
81 		unionstack = 0;
82 	}
83 
84 	if (unionstack) {
85 		int len = 0;
86 		int space = 0;
87 		char *buf = 0;
88 		char *ddptr = 0;
89 		char *ddeptr;
90 		int n;
91 		struct dirent **dpv;
92 
93 		/*
94 		 * The strategy here is to read all the directory
95 		 * entries into a buffer, sort the buffer, and
96 		 * remove duplicate entries by setting the inode
97 		 * number to zero.
98 		 */
99 
100 		do {
101 			/*
102 			 * Always make at least DIRBLKSIZ bytes
103 			 * available to getdirentries
104 			 */
105 			if (space < DIRBLKSIZ) {
106 				space += incr;
107 				len += incr;
108 				buf = realloc(buf, len);
109 				if (buf == NULL) {
110 					free(dirp);
111 					close(fd);
112 					return (NULL);
113 				}
114 				ddptr = buf + (len - space);
115 			}
116 
117 			n = getdirentries(fd, ddptr, space, &dirp->dd_seek);
118 			if (n > 0) {
119 				ddptr += n;
120 				space -= n;
121 			}
122 		} while (n > 0);
123 
124 		ddeptr = ddptr;
125 		flags |= __DTF_READALL;
126 
127 		/*
128 		 * Re-open the directory.
129 		 * This has the effect of rewinding back to the
130 		 * top of the union stack and is needed by
131 		 * programs which plan to fchdir to a descriptor
132 		 * which has also been read -- see fts.c.
133 		 */
134 		if (flags & DTF_REWIND) {
135 			(void) close(fd);
136 			if ((fd = open(name, O_RDONLY)) == -1) {
137 				free(buf);
138 				free(dirp);
139 				return (NULL);
140 			}
141 		}
142 
143 		/*
144 		 * There is now a buffer full of (possibly) duplicate
145 		 * names.
146 		 */
147 		dirp->dd_buf = buf;
148 
149 		/*
150 		 * Go round this loop twice...
151 		 *
152 		 * Scan through the buffer, counting entries.
153 		 * On the second pass, save pointers to each one.
154 		 * Then sort the pointers and remove duplicate names.
155 		 */
156 		for (dpv = 0;;) {
157 			n = 0;
158 			ddptr = buf;
159 			while (ddptr < ddeptr) {
160 				struct dirent *dp;
161 
162 				dp = (struct dirent *) ddptr;
163 				if ((int)dp & 03)
164 					break;
165 				if ((dp->d_reclen <= 0) ||
166 				    (dp->d_reclen > (ddeptr + 1 - ddptr)))
167 					break;
168 				ddptr += dp->d_reclen;
169 				if (dp->d_fileno) {
170 					if (dpv)
171 						dpv[n] = dp;
172 					n++;
173 				}
174 			}
175 
176 			if (dpv) {
177 				struct dirent *xp;
178 
179 				/*
180 				 * This sort must be stable.
181 				 */
182 				mergesort(dpv, n, sizeof(*dpv), alphasort);
183 
184 				dpv[n] = NULL;
185 				xp = NULL;
186 
187 				/*
188 				 * Scan through the buffer in sort order,
189 				 * zapping the inode number of any
190 				 * duplicate names.
191 				 */
192 				for (n = 0; dpv[n]; n++) {
193 					struct dirent *dp = dpv[n];
194 
195 					if ((xp == NULL) ||
196 					    strcmp(dp->d_name, xp->d_name)) {
197 						xp = dp;
198 					} else {
199 						dp->d_fileno = 0;
200 					}
201 					if (dp->d_type == DT_WHT &&
202 					    (flags & DTF_HIDEW))
203 						dp->d_fileno = 0;
204 				}
205 
206 				free(dpv);
207 				break;
208 			} else {
209 				dpv = malloc((n+1) * sizeof(struct dirent *));
210 				if (dpv == NULL)
211 					break;
212 			}
213 		}
214 
215 		dirp->dd_len = len;
216 		dirp->dd_size = ddptr - dirp->dd_buf;
217 	} else {
218 		dirp->dd_len = incr;
219 		dirp->dd_buf = malloc(dirp->dd_len);
220 		if (dirp->dd_buf == NULL) {
221 			free(dirp);
222 			close (fd);
223 			return (NULL);
224 		}
225 		dirp->dd_seek = 0;
226 		flags &= ~DTF_REWIND;
227 	}
228 
229 	dirp->dd_loc = 0;
230 	dirp->dd_fd = fd;
231 	dirp->dd_flags = flags;
232 
233 	/*
234 	 * Set up seek point for rewinddir.
235 	 */
236 	dirp->dd_rewind = telldir(dirp);
237 
238 	return (dirp);
239 }
240