]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - lib/libc/gen/opendir.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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  * 4. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #if defined(LIBC_SCCS) && !defined(lint)
31 static char sccsid[] = "@(#)opendir.c   8.8 (Berkeley) 5/1/95";
32 #endif /* LIBC_SCCS and not lint */
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35
36 #include "namespace.h"
37 #include <sys/param.h>
38 #include <sys/mount.h>
39 #include <sys/stat.h>
40
41 #include <dirent.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include "un-namespace.h"
48
49 #include "gen-private.h"
50 #include "telldir.h"
51
52 static DIR * __opendir_common(int, int, bool);
53
54 /*
55  * Open a directory.
56  */
57 DIR *
58 opendir(const char *name)
59 {
60
61         return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
62 }
63
64 /*
65  * Open a directory with existing file descriptor.
66  */
67 DIR *
68 fdopendir(int fd)
69 {
70
71         if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
72                 return (NULL);
73         return (__opendir_common(fd, DTF_HIDEW|DTF_NODUP, true));
74 }
75
76 DIR *
77 __opendir2(const char *name, int flags)
78 {
79         int fd;
80         DIR *dir;
81         int saved_errno;
82
83         if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0)
84                 return (NULL);
85         if ((fd = _open(name,
86             O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
87                 return (NULL);
88
89         dir = __opendir_common(fd, flags, false);
90         if (dir == NULL) {
91                 saved_errno = errno;
92                 _close(fd);
93                 errno = saved_errno;
94         }
95         return (dir);
96 }
97
98 static int
99 opendir_compar(const void *p1, const void *p2)
100 {
101
102         return (strcmp((*(const struct dirent **)p1)->d_name,
103             (*(const struct dirent **)p2)->d_name));
104 }
105
106 /*
107  * For a directory at the top of a unionfs stack, the entire directory's
108  * contents are read and cached locally until the next call to rewinddir().
109  * For the fdopendir() case, the initial seek position must be preserved.
110  * For rewinddir(), the full directory should always be re-read from the
111  * beginning.
112  *
113  * If an error occurs, the existing buffer and state of 'dirp' is left
114  * unchanged.
115  */
116 bool
117 _filldir(DIR *dirp, bool use_current_pos)
118 {
119         struct dirent **dpv;
120         char *buf, *ddptr, *ddeptr;
121         off_t pos;
122         int fd2, incr, len, n, saved_errno, space;
123         
124         len = 0;
125         space = 0;
126         buf = NULL;
127         ddptr = NULL;
128
129         /*
130          * Use the system page size if that is a multiple of DIRBLKSIZ.
131          * Hopefully this can be a big win someday by allowing page
132          * trades to user space to be done by _getdirentries().
133          */
134         incr = getpagesize();
135         if ((incr % DIRBLKSIZ) != 0) 
136                 incr = DIRBLKSIZ;
137
138         /*
139          * The strategy here is to read all the directory
140          * entries into a buffer, sort the buffer, and
141          * remove duplicate entries by setting the inode
142          * number to zero.
143          *
144          * We reopen the directory because _getdirentries()
145          * on a MNT_UNION mount modifies the open directory,
146          * making it refer to the lower directory after the
147          * upper directory's entries are exhausted.
148          * This would otherwise break software that uses
149          * the directory descriptor for fchdir or *at
150          * functions, such as fts.c.
151          */
152         if ((fd2 = _openat(dirp->dd_fd, ".", O_RDONLY | O_CLOEXEC)) == -1)
153                 return (false);
154
155         if (use_current_pos) {
156                 pos = lseek(dirp->dd_fd, 0, SEEK_CUR);
157                 if (pos == -1 || lseek(fd2, pos, SEEK_SET) == -1) {
158                         saved_errno = errno;
159                         _close(fd2);
160                         errno = saved_errno;
161                         return (false);
162                 }
163         }
164
165         do {
166                 /*
167                  * Always make at least DIRBLKSIZ bytes
168                  * available to _getdirentries
169                  */
170                 if (space < DIRBLKSIZ) {
171                         space += incr;
172                         len += incr;
173                         buf = reallocf(buf, len);
174                         if (buf == NULL) {
175                                 saved_errno = errno;
176                                 _close(fd2);
177                                 errno = saved_errno;
178                                 return (false);
179                         }
180                         ddptr = buf + (len - space);
181                 }
182
183                 n = _getdirentries(fd2, ddptr, space, &dirp->dd_seek);
184                 if (n > 0) {
185                         ddptr += n;
186                         space -= n;
187                 }
188                 if (n < 0) {
189                         saved_errno = errno;
190                         _close(fd2);
191                         errno = saved_errno;
192                         return (false);
193                 }
194         } while (n > 0);
195         _close(fd2);
196
197         ddeptr = ddptr;
198
199         /*
200          * There is now a buffer full of (possibly) duplicate
201          * names.
202          */
203         dirp->dd_buf = buf;
204
205         /*
206          * Go round this loop twice...
207          *
208          * Scan through the buffer, counting entries.
209          * On the second pass, save pointers to each one.
210          * Then sort the pointers and remove duplicate names.
211          */
212         for (dpv = 0;;) {
213                 n = 0;
214                 ddptr = buf;
215                 while (ddptr < ddeptr) {
216                         struct dirent *dp;
217
218                         dp = (struct dirent *) ddptr;
219                         if ((long)dp & 03L)
220                                 break;
221                         if ((dp->d_reclen <= 0) ||
222                             (dp->d_reclen > (ddeptr + 1 - ddptr)))
223                                 break;
224                         ddptr += dp->d_reclen;
225                         if (dp->d_fileno) {
226                                 if (dpv)
227                                         dpv[n] = dp;
228                                 n++;
229                         }
230                 }
231
232                 if (dpv) {
233                         struct dirent *xp;
234
235                         /*
236                          * This sort must be stable.
237                          */
238                         mergesort(dpv, n, sizeof(*dpv), opendir_compar);
239
240                         dpv[n] = NULL;
241                         xp = NULL;
242
243                         /*
244                          * Scan through the buffer in sort order,
245                          * zapping the inode number of any
246                          * duplicate names.
247                          */
248                         for (n = 0; dpv[n]; n++) {
249                                 struct dirent *dp = dpv[n];
250
251                                 if ((xp == NULL) ||
252                                     strcmp(dp->d_name, xp->d_name)) {
253                                         xp = dp;
254                                 } else {
255                                         dp->d_fileno = 0;
256                                 }
257                                 if (dp->d_type == DT_WHT &&
258                                     (dirp->dd_flags & DTF_HIDEW))
259                                         dp->d_fileno = 0;
260                         }
261
262                         free(dpv);
263                         break;
264                 } else {
265                         dpv = malloc((n+1) * sizeof(struct dirent *));
266                         if (dpv == NULL)
267                                 break;
268                 }
269         }
270
271         dirp->dd_len = len;
272         dirp->dd_size = ddptr - dirp->dd_buf;
273         return (true);
274 }
275
276
277 /*
278  * Common routine for opendir(3), __opendir2(3) and fdopendir(3).
279  */
280 static DIR *
281 __opendir_common(int fd, int flags, bool use_current_pos)
282 {
283         DIR *dirp;
284         int incr;
285         int saved_errno;
286         int unionstack;
287
288         if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
289                 return (NULL);
290
291         dirp->dd_buf = NULL;
292         dirp->dd_fd = fd;
293         dirp->dd_flags = flags;
294         dirp->dd_loc = 0;
295         dirp->dd_lock = NULL;
296         dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR));
297         LIST_INIT(&dirp->dd_td->td_locq);
298         dirp->dd_td->td_loccnt = 0;
299
300         /*
301          * Use the system page size if that is a multiple of DIRBLKSIZ.
302          * Hopefully this can be a big win someday by allowing page
303          * trades to user space to be done by _getdirentries().
304          */
305         incr = getpagesize();
306         if ((incr % DIRBLKSIZ) != 0) 
307                 incr = DIRBLKSIZ;
308
309         /*
310          * Determine whether this directory is the top of a union stack.
311          */
312         if (flags & DTF_NODUP) {
313                 struct statfs sfb;
314
315                 if (_fstatfs(fd, &sfb) < 0)
316                         goto fail;
317                 unionstack = !strcmp(sfb.f_fstypename, "unionfs")
318                     || (sfb.f_flags & MNT_UNION);
319         } else {
320                 unionstack = 0;
321         }
322
323         if (unionstack) {
324                 if (!_filldir(dirp, use_current_pos))
325                         goto fail;
326                 dirp->dd_flags |= __DTF_READALL;
327         } else {
328                 dirp->dd_len = incr;
329                 dirp->dd_buf = malloc(dirp->dd_len);
330                 if (dirp->dd_buf == NULL)
331                         goto fail;
332                 if (use_current_pos) {
333                         /*
334                          * Read the first batch of directory entries
335                          * to prime dd_seek.  This also checks if the
336                          * fd passed to fdopendir() is a directory.
337                          */
338                         dirp->dd_size = _getdirentries(dirp->dd_fd,
339                             dirp->dd_buf, dirp->dd_len, &dirp->dd_seek);
340                         if (dirp->dd_size < 0) {
341                                 if (errno == EINVAL)
342                                         errno = ENOTDIR;
343                                 goto fail;
344                         }
345                         dirp->dd_flags |= __DTF_SKIPREAD;
346                 } else {
347                         dirp->dd_size = 0;
348                         dirp->dd_seek = 0;
349                 }
350         }
351
352         return (dirp);
353
354 fail:
355         saved_errno = errno;
356         free(dirp->dd_buf);
357         free(dirp);
358         errno = saved_errno;
359         return (NULL);
360 }