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