1 /*        $NetBSD: initdir.c,v 1.5 2021/07/11 16:30:41 kre Exp $      */
2 
3 /*
4  * Copyright (c) 1983, 1993
5  *        The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #if defined(LIBC_SCCS) && !defined(lint)
34 __RCSID("$NetBSD: initdir.c,v 1.5 2021/07/11 16:30:41 kre Exp $");
35 #endif /* LIBC_SCCS and not lint */
36 
37 #include "namespace.h"
38 #include "reentrant.h"
39 #include "extern.h"
40 
41 #include <sys/param.h>
42 
43 #include <assert.h>
44 #include <dirent.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50 
51 #include "dirent_private.h"
52 
53 #define   MAXITERATIONS       100
54 
55 int
_initdir(DIR * dirp,int fd,const char * name)56 _initdir(DIR *dirp, int fd, const char *name)
57 {
58           int flags = dirp->dd_flags;
59           int pagesz;
60           int incr;
61 
62           /*
63            * If the machine's page size is an exact multiple of DIRBLKSIZ,
64            * use a buffer that is cluster boundary aligned.
65            * Hopefully this can be a big win someday by allowing page trades
66            * to user space to be done by getdents()
67            */
68           if (((pagesz = getpagesize()) % DIRBLKSIZ) == 0)
69                     incr = pagesz;
70           else
71                     incr = DIRBLKSIZ;
72 
73           if ((flags & DTF_REWIND) && name == NULL) {
74                     return EINVAL;
75           }
76           if ((flags & __DTF_READALL) != 0) {
77                     size_t len;
78                     size_t space;
79                     char *buf, *nbuf;
80                     char *ddptr;
81                     char *ddeptr;
82                     int n;
83                     struct dirent **dpv;
84                     int i;
85 
86                     /*
87                      * The strategy here for directories on top of a union stack
88                      * is to read all the directory entries into a buffer, sort
89                      * the buffer, and remove duplicate entries by setting the
90                      * inode number to zero.
91                      *
92                      * For directories on an NFS mounted filesystem, we try
93                      * to get a consistent snapshot by trying until we have
94                      * successfully read all of the directory without errors
95                      * (i.e. 'bad cookie' errors from the server because
96                      * the directory was modified). These errors should not
97                      * happen often, but need to be dealt with.
98                      */
99                     i = 0;
100 retry:
101                     len = 0;
102                     space = 0;
103                     buf = 0;
104                     ddptr = 0;
105 
106                     do {
107                               /*
108                                * Always make at least DIRBLKSIZ bytes
109                                * available to getdents
110                                */
111                               if (space < DIRBLKSIZ) {
112                                         space += incr;
113                                         len += incr;
114                                         nbuf = realloc(buf, len);
115                                         if (nbuf == NULL) {
116                                                   dirp->dd_buf = buf;
117                                                   return errno;
118                                         }
119                                         buf = nbuf;
120                                         ddptr = buf + (len - space);
121                               }
122 
123                               dirp->dd_seek = lseek(fd, (off_t)0, SEEK_CUR);
124                               n = getdents(fd, ddptr, space);
125                               /*
126                                * For NFS: EINVAL means a bad cookie error
127                                * from the server. Keep trying to get a
128                                * consistent view, in this case this means
129                                * starting all over again.
130                                */
131                               if (n == -1 && errno == EINVAL &&
132                                   (flags & __DTF_RETRY_ON_BADCOOKIE) != 0) {
133                                         free(buf);
134                                         lseek(fd, (off_t)0, SEEK_SET);
135                                         if (++i > MAXITERATIONS)
136                                                   return EINVAL;
137                                         goto retry;
138                               }
139                               if (n > 0) {
140                                         ddptr += n;
141                                         space -= n;
142                               }
143                     } while (n > 0);
144 
145                     ddeptr = ddptr;
146 
147                     /*
148                      * Re-open the directory.
149                      * This has the effect of rewinding back to the
150                      * top of the union stack and is needed by
151                      * programs which plan to fchdir to a descriptor
152                      * which has also been read -- see fts.c.
153                      */
154                     if (flags & DTF_REWIND) {
155                               (void) close(fd);
156                               if ((fd = open(name, O_RDONLY | O_CLOEXEC)) == -1) {
157                                         dirp->dd_buf = buf;
158                                         return errno;
159                               }
160                     }
161 
162                     /*
163                      * There is now a buffer full of (possibly) duplicate
164                      * names.
165                      */
166                     dirp->dd_buf = buf;
167 
168                     /*
169                      * Go round this loop twice...
170                      *
171                      * Scan through the buffer, counting entries.
172                      * On the second pass, save pointers to each one.
173                      * Then sort the pointers and remove duplicate names.
174                      */
175                     if ((flags & DTF_NODUP) != 0) {
176                               for (dpv = 0;;) {
177                                         for (n = 0, ddptr = buf; ddptr < ddeptr;) {
178                                                   struct dirent *dp;
179 
180                                                   dp = (struct dirent *)(void *)ddptr;
181                                                   if ((long)dp & _DIRENT_ALIGN(dp))
182                                                             break;
183                                                   /*
184                                                    * d_reclen is unsigned,
185                                                    * so no need to compare <= 0
186                                                    */
187                                                   if (dp->d_reclen > (ddeptr + 1 - ddptr))
188                                                             break;
189                                                   ddptr += dp->d_reclen;
190                                                   if (dp->d_fileno) {
191                                                             if (dpv)
192                                                                       dpv[n] = dp;
193                                                             n++;
194                                                   }
195                                         }
196 
197                                         if (dpv) {
198                                                   struct dirent *xp;
199 
200                                                   /*
201                                                    * This sort must be stable.
202                                                    */
203                                                   mergesort(dpv, (size_t)n, sizeof(*dpv),
204                                                       (int (*)(const void *,
205                                                                  const void *))alphasort);
206 
207                                                   dpv[n] = NULL;
208                                                   xp = NULL;
209 
210                                                   /*
211                                                    * Scan through the buffer in sort
212                                                    * order, zapping the inode number
213                                                    * of any duplicate names.
214                                                    */
215                                                   for (n = 0; dpv[n]; n++) {
216                                                             struct dirent *dp = dpv[n];
217 
218                                                             if ((xp == NULL) ||
219                                                                 strcmp(dp->d_name,
220                                                                   xp->d_name))
221                                                                       xp = dp;
222                                                             else
223                                                                       dp->d_fileno = 0;
224                                                             if (dp->d_type == DT_WHT &&
225                                                                 (flags & DTF_HIDEW))
226                                                                       dp->d_fileno = 0;
227                                                   }
228 
229                                                   free(dpv);
230                                                   break;
231                                         } else {
232                                                   dpv = malloc((n + 1) *
233                                                       sizeof(struct dirent *));
234                                                   if (dpv == NULL)
235                                                             break;
236                                         }
237                               }
238                     }
239 
240                     _DIAGASSERT(__type_fit(int, len));
241                     dirp->dd_len = (int)len;
242                     dirp->dd_size = ddptr - dirp->dd_buf;
243           } else {
244                     dirp->dd_len = incr;
245                     dirp->dd_size = 0;
246                     dirp->dd_buf = malloc((size_t)dirp->dd_len);
247                     if (dirp->dd_buf == NULL)
248                               return errno;
249                     dirp->dd_seek = 0;
250                     flags &= ~DTF_REWIND;
251           }
252           dirp->dd_loc = 0;
253           dirp->dd_fd = fd;
254           dirp->dd_flags = flags;
255           /*
256            * Set up seek point for rewinddir.
257            */
258           (void)_telldir_unlocked(dirp);
259           return 0;
260 }
261 
262 void
_finidir(DIR * dirp)263 _finidir(DIR *dirp)
264 {
265           struct dirpos *poslist;
266 
267           free(dirp->dd_buf);
268 
269           /* free seekdir/telldir storage */
270           for (poslist = dirp->dd_internal; poslist; ) {
271                     struct dirpos *nextpos = poslist->dp_next;
272                     free(poslist);
273                     poslist = nextpos;
274           }
275           dirp->dd_internal = NULL;
276 }
277