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.2 (Berkeley) 06/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 				 * This sort must be stable.
144 				 */
145 				mergesort(dpv, n, sizeof(*dpv), alphasort);
146 
147 				dpv[n] = NULL;
148 				xp = NULL;
149 
150 				/*
151 				 * Scan through the buffer in sort order,
152 				 * zapping the inode number of any
153 				 * duplicate names.
154 				 */
155 				for (n = 0; dpv[n]; n++) {
156 					struct dirent *dp = dpv[n];
157 
158 					if ((xp == NULL) ||
159 					    strcmp(dp->d_name, xp->d_name))
160 						xp = dp;
161 					else
162 						dp->d_fileno = 0;
163 				}
164 
165 				free(dpv);
166 				break;
167 			} else {
168 				dpv = malloc((n+1) * sizeof(struct dirent *));
169 				if (dpv == NULL)
170 					break;
171 			}
172 		}
173 
174 		dirp->dd_len = len;
175 		dirp->dd_size = ddptr - dirp->dd_buf;
176 	} else
177 #endif /* MOUNT_UNION */
178 	{
179 		dirp->dd_len = incr;
180 		dirp->dd_buf = malloc(dirp->dd_len);
181 		if (dirp->dd_buf == NULL) {
182 			free(dirp);
183 			close (fd);
184 			return (NULL);
185 		}
186 		dirp->dd_seek = 0;
187 		dirp->dd_loc = 0;
188 	}
189 
190 	dirp->dd_fd = fd;
191 
192 	/*
193 	 * Set up seek point for rewinddir.
194 	 */
195 	dirp->dd_rewind = telldir(dirp);
196 
197 	return (dirp);
198 }
199