Initial import from FreeBSD RELENG_4:
[dragonfly.git] / lib / libc / gen / opendir.c
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. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * $FreeBSD: src/lib/libc/gen/opendir.c,v 1.10.2.1 2001/06/04 20:59:48 joerg Exp $
34  */
35
36 #if defined(LIBC_SCCS) && !defined(lint)
37 static char sccsid[] = "@(#)opendir.c   8.8 (Berkeley) 5/1/95";
38 #endif /* LIBC_SCCS and not lint */
39
40 #include <sys/param.h>
41 #include <sys/mount.h>
42 #include <sys/stat.h>
43
44 #include <dirent.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <stdlib.h>
48 #include <unistd.h>
49
50 /*
51  * Open a directory.
52  */
53 DIR *
54 opendir(name)
55         const char *name;
56 {
57
58         return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
59 }
60
61 DIR *
62 __opendir2(name, flags)
63         const char *name;
64         int flags;
65 {
66         DIR *dirp;
67         int fd;
68         int incr;
69         int saved_errno;
70         int unionstack;
71         struct stat statb;
72
73         /*
74          * stat() before open() because opening of special files may be
75          * harmful.  fstat() after open because the file may have changed.
76          */
77         if (stat(name, &statb) != 0)
78                 return (NULL);
79         if (!S_ISDIR(statb.st_mode)) {
80                 errno = ENOTDIR;
81                 return (NULL);
82         }
83         if ((fd = _open(name, O_RDONLY | O_NONBLOCK)) == -1)
84                 return (NULL);
85         dirp = NULL;
86         if (fstat(fd, &statb) != 0)
87                 goto fail;
88         if (!S_ISDIR(statb.st_mode)) {
89                 errno = ENOTDIR;
90                 goto fail;
91         }
92         if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 ||
93             (dirp = malloc(sizeof(DIR))) == NULL)
94                 goto fail;
95
96         /*
97          * Use the system page size if that is a multiple of DIRBLKSIZ.
98          * Hopefully this can be a big win someday by allowing page
99          * trades to user space to be done by getdirentries().
100          */
101         incr = getpagesize();
102         if ((incr % DIRBLKSIZ) != 0) 
103                 incr = DIRBLKSIZ;
104
105         /*
106          * Determine whether this directory is the top of a union stack.
107          */
108         if (flags & DTF_NODUP) {
109                 struct statfs sfb;
110
111                 if (fstatfs(fd, &sfb) < 0)
112                         goto fail;
113                 unionstack = !strcmp(sfb.f_fstypename, "union")
114                     || (sfb.f_flags & MNT_UNION);
115         } else {
116                 unionstack = 0;
117         }
118
119         if (unionstack) {
120                 int len = 0;
121                 int space = 0;
122                 char *buf = 0;
123                 char *ddptr = 0;
124                 char *ddeptr;
125                 int n;
126                 struct dirent **dpv;
127
128                 /*
129                  * The strategy here is to read all the directory
130                  * entries into a buffer, sort the buffer, and
131                  * remove duplicate entries by setting the inode
132                  * number to zero.
133                  */
134
135                 do {
136                         /*
137                          * Always make at least DIRBLKSIZ bytes
138                          * available to getdirentries
139                          */
140                         if (space < DIRBLKSIZ) {
141                                 space += incr;
142                                 len += incr;
143                                 buf = reallocf(buf, len);
144                                 if (buf == NULL)
145                                         goto fail;
146                                 ddptr = buf + (len - space);
147                         }
148
149                         n = getdirentries(fd, ddptr, space, &dirp->dd_seek);
150                         if (n > 0) {
151                                 ddptr += n;
152                                 space -= n;
153                         }
154                 } while (n > 0);
155
156                 ddeptr = ddptr;
157                 flags |= __DTF_READALL;
158
159                 /*
160                  * Re-open the directory.
161                  * This has the effect of rewinding back to the
162                  * top of the union stack and is needed by
163                  * programs which plan to fchdir to a descriptor
164                  * which has also been read -- see fts.c.
165                  */
166                 if (flags & DTF_REWIND) {
167                         (void)_close(fd);
168                         if ((fd = _open(name, O_RDONLY)) == -1) {
169                                 saved_errno = errno;
170                                 free(buf);
171                                 free(dirp);
172                                 errno = saved_errno;
173                                 return (NULL);
174                         }
175                 }
176
177                 /*
178                  * There is now a buffer full of (possibly) duplicate
179                  * names.
180                  */
181                 dirp->dd_buf = buf;
182
183                 /*
184                  * Go round this loop twice...
185                  *
186                  * Scan through the buffer, counting entries.
187                  * On the second pass, save pointers to each one.
188                  * Then sort the pointers and remove duplicate names.
189                  */
190                 for (dpv = 0;;) {
191                         n = 0;
192                         ddptr = buf;
193                         while (ddptr < ddeptr) {
194                                 struct dirent *dp;
195
196                                 dp = (struct dirent *) ddptr;
197                                 if ((long)dp & 03L)
198                                         break;
199                                 if ((dp->d_reclen <= 0) ||
200                                     (dp->d_reclen > (ddeptr + 1 - ddptr)))
201                                         break;
202                                 ddptr += dp->d_reclen;
203                                 if (dp->d_fileno) {
204                                         if (dpv)
205                                                 dpv[n] = dp;
206                                         n++;
207                                 }
208                         }
209
210                         if (dpv) {
211                                 struct dirent *xp;
212
213                                 /*
214                                  * This sort must be stable.
215                                  */
216                                 mergesort(dpv, n, sizeof(*dpv), alphasort);
217
218                                 dpv[n] = NULL;
219                                 xp = NULL;
220
221                                 /*
222                                  * Scan through the buffer in sort order,
223                                  * zapping the inode number of any
224                                  * duplicate names.
225                                  */
226                                 for (n = 0; dpv[n]; n++) {
227                                         struct dirent *dp = dpv[n];
228
229                                         if ((xp == NULL) ||
230                                             strcmp(dp->d_name, xp->d_name)) {
231                                                 xp = dp;
232                                         } else {
233                                                 dp->d_fileno = 0;
234                                         }
235                                         if (dp->d_type == DT_WHT &&
236                                             (flags & DTF_HIDEW))
237                                                 dp->d_fileno = 0;
238                                 }
239
240                                 free(dpv);
241                                 break;
242                         } else {
243                                 dpv = malloc((n+1) * sizeof(struct dirent *));
244                                 if (dpv == NULL)
245                                         break;
246                         }
247                 }
248
249                 dirp->dd_len = len;
250                 dirp->dd_size = ddptr - dirp->dd_buf;
251         } else {
252                 dirp->dd_len = incr;
253                 dirp->dd_buf = malloc(dirp->dd_len);
254                 if (dirp->dd_buf == NULL)
255                         goto fail;
256                 dirp->dd_seek = 0;
257                 flags &= ~DTF_REWIND;
258         }
259
260         dirp->dd_loc = 0;
261         dirp->dd_fd = fd;
262         dirp->dd_flags = flags;
263
264         /*
265          * Set up seek point for rewinddir.
266          */
267         dirp->dd_rewind = telldir(dirp);
268
269         return (dirp);
270
271 fail:
272         saved_errno = errno;
273         free(dirp);
274         (void)_close(fd);
275         errno = saved_errno;
276         return (NULL);
277 }