1 /*
2  * Copyright (c) 1983, 1993, 1994
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 orig_sccsid[] = "@(#)opendir.c	8.2 (Berkeley) 2/12/94";
10 static char sccsid[] = "@(#)libc.opendir.c	8.1 (Berkeley) 02/15/94";
11 #endif /* LIBC_SCCS and not lint */
12 
13 #include <sys/param.h>
14 #include <sys/mount.h>
15 
16 #include <dirent.h>
17 #include <fcntl.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 
21 /*
22  * open a directory.
23  */
24 DIR *
25 opendir(name)
26 	const char *name;
27 {
28 	DIR *dirp;
29 	int fd;
30 	int incr;
31 	struct statfs sfb;
32 
33 	if ((fd = open(name, 0)) == -1)
34 		return (NULL);
35 	if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 ||
36 	    (dirp = (DIR *)malloc(sizeof(DIR))) == NULL) {
37 		close(fd);
38 		return (NULL);
39 	}
40 
41 	/*
42 	 * If CLBYTES is an exact multiple of DIRBLKSIZ, use a CLBYTES
43 	 * buffer that it cluster boundary aligned.
44 	 * Hopefully this can be a big win someday by allowing page
45 	 * trades trade to user space to be done by getdirentries()
46 	 */
47 	if ((CLBYTES % DIRBLKSIZ) == 0)
48 		incr = CLBYTES;
49 	else
50 		incr = DIRBLKSIZ;
51 
52 #ifdef MOUNT_UNION
53 	/*
54 	 * Determine whether this directory is the top of a union stack.
55 	 */
56 	if (fstatfs(fd, &sfb) < 0) {
57 		free(dirp);
58 		close(fd);
59 		return (NULL);
60 	}
61 
62 	if (sfb.f_type == MOUNT_UNION) {
63 		int len = 0;
64 		int space = 0;
65 		char *buf = 0;
66 		char *ddptr = 0;
67 		int n;
68 		struct dirent **dpv;
69 
70 		/*
71 		 * The strategy here is to read all the directory
72 		 * entries into a buffer, sort the buffer, and
73 		 * remove duplicate entries by setting the inode
74 		 * number to zero.
75 		 */
76 
77 		/*
78 		 * Fixup dd_loc to be non-zero to fake out readdir
79 		 */
80 		dirp->dd_loc = sizeof(void *);
81 
82 		do {
83 			/*
84 			 * Always make at least DIRBLKSIZ bytes
85 			 * available to getdirentries
86 			 */
87 			if (space < DIRBLKSIZ) {
88 				space += incr;
89 				len += incr;
90 				buf = realloc(buf, len);
91 				if (buf == NULL) {
92 					free(dirp);
93 					close(fd);
94 					return (NULL);
95 				}
96 				ddptr = buf + (len - space) + dirp->dd_loc;
97 			}
98 
99 			n = getdirentries(fd, ddptr, space, &dirp->dd_seek);
100 			if (n > 0) {
101 				ddptr += n;
102 				space -= n;
103 			}
104 		} while (n > 0);
105 
106 		/*
107 		 * There is now a buffer full of (possibly) duplicate
108 		 * names.
109 		 */
110 		dirp->dd_buf = buf;
111 
112 		/*
113 		 * Go round this loop twice...
114 		 *
115 		 * Scan through the buffer, counting entries.
116 		 * On the second pass, save pointers to each one.
117 		 * Then sort the pointers and remove duplicate names.
118 		 */
119 		for (dpv = 0;;) {
120 			n = 0;
121 			ddptr = buf + dirp->dd_loc;
122 			while (ddptr < buf + len) {
123 				struct dirent *dp;
124 
125 				dp = (struct dirent *) ddptr;
126 				if ((int)dp & 03)
127 					break;
128 				if ((dp->d_reclen <= 0) ||
129 				    (dp->d_reclen > (buf + len + 1 - ddptr)))
130 					break;
131 				ddptr += dp->d_reclen;
132 				if (dp->d_fileno) {
133 					if (dpv)
134 						dpv[n] = dp;
135 					n++;
136 				}
137 			}
138 
139 			if (dpv) {
140 				struct dirent *xp;
141 
142 				/*
143 				 * If and when whiteouts happen,
144 				 * this sort would need to be stable.
145 				 */
146 				heapsort(dpv, n, sizeof(*dpv), alphasort);
147 
148 				dpv[n] = NULL;
149 				xp = NULL;
150 
151 				/*
152 				 * Scan through the buffer in sort order,
153 				 * zapping the inode number of any
154 				 * duplicate names.
155 				 */
156 				for (n = 0; dpv[n]; n++) {
157 					struct dirent *dp = dpv[n];
158 
159 					if ((xp == NULL) ||
160 					    strcmp(dp->d_name, xp->d_name))
161 						xp = dp;
162 					else
163 						dp->d_fileno = 0;
164 				}
165 
166 				free(dpv);
167 				break;
168 			} else {
169 				dpv = malloc((n+1) * sizeof(struct dirent *));
170 				if (dpv == NULL)
171 					break;
172 			}
173 		}
174 
175 		dirp->dd_len = len;
176 		dirp->dd_size = ddptr - dirp->dd_buf;
177 	} else
178 #endif /* MOUNT_UNION */
179 	{
180 		dirp->dd_len = incr;
181 		dirp->dd_buf = malloc(dirp->dd_len);
182 		if (dirp->dd_buf == NULL) {
183 			free(dirp);
184 			close (fd);
185 			return (NULL);
186 		}
187 		dirp->dd_seek = 0;
188 		dirp->dd_loc = 0;
189 	}
190 
191 	dirp->dd_fd = fd;
192 
193 	/*
194 	 * Set up seek point for rewinddir.
195 	 */
196 	dirp->dd_rewind = telldir(dirp);
197 
198 	return (dirp);
199 }
200