1 /* $NetBSD: opendir.c,v 1.22 2003/05/28 20:03:37 christos 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. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 #if defined(LIBC_SCCS) && !defined(lint) 38 #if 0 39 static char sccsid[] = "@(#)opendir.c 8.7 (Berkeley) 12/10/94"; 40 #else 41 __RCSID("$NetBSD: opendir.c,v 1.22 2003/05/28 20:03:37 christos Exp $"); 42 #endif 43 #endif /* LIBC_SCCS and not lint */ 44 45 #include "namespace.h" 46 #include "reentrant.h" 47 #include <sys/param.h> 48 #include <sys/mount.h> 49 #include <sys/stat.h> 50 51 #include <assert.h> 52 #include <dirent.h> 53 #include <errno.h> 54 #include <fcntl.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <unistd.h> 58 59 #ifdef __weak_alias 60 __weak_alias(opendir,_opendir) 61 #endif 62 63 /* 64 * Open a directory. 65 */ 66 DIR * 67 opendir(name) 68 const char *name; 69 { 70 71 _DIAGASSERT(name != NULL); 72 73 return (__opendir2(name, DTF_HIDEW|DTF_NODUP)); 74 } 75 76 DIR * 77 __opendir2(name, flags) 78 const char *name; 79 int flags; 80 { 81 DIR *dirp = NULL; 82 int fd; 83 int serrno; 84 struct stat sb; 85 int pagesz; 86 int incr; 87 int unionstack, nfsdir; 88 struct statfs sfb; 89 90 _DIAGASSERT(name != NULL); 91 92 if ((fd = open(name, O_RDONLY | O_NONBLOCK)) == -1) 93 return (NULL); 94 if (fstat(fd, &sb) || !S_ISDIR(sb.st_mode)) { 95 close(fd); 96 errno = ENOTDIR; 97 return (NULL); 98 } 99 if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 || 100 (dirp = (DIR *)malloc(sizeof(DIR))) == NULL) { 101 goto error; 102 } 103 dirp->dd_buf = NULL; 104 105 /* 106 * If the machine's page size is an exact multiple of DIRBLKSIZ, 107 * use a buffer that is cluster boundary aligned. 108 * Hopefully this can be a big win someday by allowing page trades 109 * to user space to be done by getdirentries() 110 */ 111 if (((pagesz = getpagesize()) % DIRBLKSIZ) == 0) 112 incr = pagesz; 113 else 114 incr = DIRBLKSIZ; 115 116 /* 117 * Determine whether this directory is the top of a union stack. 118 */ 119 120 if (fstatfs(fd, &sfb) < 0) 121 goto error; 122 123 if (flags & DTF_NODUP) 124 unionstack = !(strncmp(sfb.f_fstypename, MOUNT_UNION, 125 MFSNAMELEN)) || (sfb.f_flags & MNT_UNION); 126 else 127 unionstack = 0; 128 129 nfsdir = !(strncmp(sfb.f_fstypename, MOUNT_NFS, MFSNAMELEN)); 130 131 if (unionstack || nfsdir) { 132 size_t len; 133 size_t space; 134 char *buf, *nbuf; 135 char *ddptr; 136 char *ddeptr; 137 int n; 138 struct dirent **dpv; 139 140 /* 141 * The strategy here for directories on top of a union stack 142 * is to read all the directory entries into a buffer, sort 143 * the buffer, and remove duplicate entries by setting the 144 * inode number to zero. 145 * 146 * For directories on an NFS mounted filesystem, we try 147 * to get a consistent snapshot by trying until we have 148 * successfully read all of the directory without errors 149 * (i.e. 'bad cookie' errors from the server because 150 * the directory was modified). These errors should not 151 * happen often, but need to be dealt with. 152 */ 153 retry: 154 len = 0; 155 space = 0; 156 buf = 0; 157 ddptr = 0; 158 159 do { 160 /* 161 * Always make at least DIRBLKSIZ bytes 162 * available to getdirentries 163 */ 164 if (space < DIRBLKSIZ) { 165 space += incr; 166 len += incr; 167 nbuf = realloc(buf, len); 168 if (nbuf == NULL) { 169 dirp->dd_buf = buf; 170 goto error; 171 } 172 buf = nbuf; 173 ddptr = buf + (len - space); 174 } 175 176 dirp->dd_seek = lseek(fd, (off_t)0, SEEK_CUR); 177 n = getdents(fd, ddptr, space); 178 /* 179 * For NFS: EINVAL means a bad cookie error 180 * from the server. Keep trying to get a 181 * consistent view, in this case this means 182 * starting all over again. 183 */ 184 if (n == -1 && errno == EINVAL && nfsdir) { 185 free(buf); 186 lseek(fd, (off_t)0, SEEK_SET); 187 goto retry; 188 } 189 if (n > 0) { 190 ddptr += n; 191 space -= n; 192 } 193 } while (n > 0); 194 195 ddeptr = ddptr; 196 flags |= __DTF_READALL; 197 198 /* 199 * Re-open the directory. 200 * This has the effect of rewinding back to the 201 * top of the union stack and is needed by 202 * programs which plan to fchdir to a descriptor 203 * which has also been read -- see fts.c. 204 */ 205 if (flags & DTF_REWIND) { 206 (void) close(fd); 207 if ((fd = open(name, O_RDONLY)) == -1) { 208 dirp->dd_buf = buf; 209 goto error; 210 } 211 } 212 213 /* 214 * There is now a buffer full of (possibly) duplicate 215 * names. 216 */ 217 dirp->dd_buf = buf; 218 219 /* 220 * Go round this loop twice... 221 * 222 * Scan through the buffer, counting entries. 223 * On the second pass, save pointers to each one. 224 * Then sort the pointers and remove duplicate names. 225 */ 226 if (!nfsdir) { 227 for (dpv = 0;;) { 228 for (n = 0, ddptr = buf; ddptr < ddeptr;) { 229 struct dirent *dp; 230 231 dp = (struct dirent *)(void *)ddptr; 232 if ((long)dp & 03) 233 break; 234 /* 235 * d_reclen is unsigned, 236 * so no need to compare <= 0 237 */ 238 if (dp->d_reclen > (ddeptr + 1 - ddptr)) 239 break; 240 ddptr += dp->d_reclen; 241 if (dp->d_fileno) { 242 if (dpv) 243 dpv[n] = dp; 244 n++; 245 } 246 } 247 248 if (dpv) { 249 struct dirent *xp; 250 251 /* 252 * This sort must be stable. 253 */ 254 mergesort(dpv, (size_t)n, sizeof(*dpv), 255 alphasort); 256 257 dpv[n] = NULL; 258 xp = NULL; 259 260 /* 261 * Scan through the buffer in sort 262 * order, zapping the inode number 263 * of any duplicate names. 264 */ 265 for (n = 0; dpv[n]; n++) { 266 struct dirent *dp = dpv[n]; 267 268 if ((xp == NULL) || 269 strcmp(dp->d_name, 270 xp->d_name)) 271 xp = dp; 272 else 273 dp->d_fileno = 0; 274 if (dp->d_type == DT_WHT && 275 (flags & DTF_HIDEW)) 276 dp->d_fileno = 0; 277 } 278 279 free(dpv); 280 break; 281 } else { 282 dpv = malloc((n + 1) * 283 sizeof(struct dirent *)); 284 if (dpv == NULL) 285 break; 286 } 287 } 288 } 289 290 dirp->dd_len = len; 291 dirp->dd_size = ddptr - dirp->dd_buf; 292 } else { 293 dirp->dd_len = incr; 294 dirp->dd_buf = malloc((size_t)dirp->dd_len); 295 if (dirp->dd_buf == NULL) 296 goto error; 297 dirp->dd_seek = 0; 298 flags &= ~DTF_REWIND; 299 } 300 301 dirp->dd_loc = 0; 302 dirp->dd_fd = fd; 303 dirp->dd_flags = flags; 304 305 /* 306 * Set up seek point for rewinddir. 307 */ 308 dirp->dd_rewind = telldir(dirp); 309 #ifdef _REENTRANT 310 if (__isthreaded) { 311 if ((dirp->dd_lock = malloc(sizeof(mutex_t))) == NULL) 312 goto error; 313 mutex_init((mutex_t *)dirp->dd_lock, NULL); 314 } 315 #endif 316 return (dirp); 317 error: 318 serrno = errno; 319 if (dirp && dirp->dd_buf) 320 free(dirp->dd_buf); 321 if (dirp) 322 free(dirp); 323 if (fd != -1) 324 (void)close(fd); 325 errno = serrno; 326 return NULL; 327 } 328