1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* Copyright (c) 1988 AT&T */ 30 /* All Rights Reserved */ 31 32 33 /* 34 * nftw - new file tree walk 35 * 36 * int nftw(char *path, int (*fn)(), int depth, int flags); 37 * 38 * Derived from System V ftw() by David Korn 39 * 40 * nftw visits each file and directory in the tree starting at 41 * path. It uses the generic directory reading library so it works 42 * for any file system type. The flags field is used to specify: 43 * FTW_PHYS Physical walk, does not follow symbolic links 44 * Otherwise, nftw will follow links but will not 45 * walk down any path the crosses itself. 46 * FTW_MOUNT The walk will not cross a mount point. 47 * FTW_DEPTH All subdirectories will be visited before the 48 * directory itself. 49 * FTW_CHDIR The walk will change to each directory before 50 * reading it. This is faster but core dumps 51 * may not get generated. 52 * 53 * The following flags are private, and are used by the find 54 * utility: 55 * FTW_ANYERR Call the callback function and return 56 * FTW_NS on any stat failure, not just 57 * lack of permission. 58 * FTW_HOPTION Use stat the first time the walk 59 * function is called, regardless of 60 * whether or not FTW_PHYS is specified. 61 * FTW_NOLOOP Allow find utility to detect infinite loops created 62 * by both symbolic and hard linked directories. 63 * 64 * fn is called with four arguments at each file and directory. 65 * The first argument is the pathname of the object, the second 66 * is a pointer to the stat buffer and the third is an integer 67 * giving additional information as follows: 68 * 69 * FTW_F The object is a file. 70 * FTW_D The object is a directory. 71 * FTW_DP The object is a directory and subdirectories 72 * have been visited. 73 * FTW_SL The object is a symbolic link. 74 * FTW_SLN The object is a symbolic link pointing at a 75 * non-existing file. 76 * FTW_DNR The object is a directory that cannot be read. 77 * fn will not be called for any of its descendants. 78 * FTW_NS Stat failed on the object because of lack of 79 * appropriate permission. The stat buffer passed to fn 80 * is undefined. Stat failure for any reason is 81 * considered an error and nftw will return -1. 82 * The following value is private, and is used by the find utility: 83 * FTW_DL An infinite loop has been detected. 84 * The fourth argument is a struct FTW* which contains the depth 85 * and the offset into pathname to the base name. 86 * If fn returns nonzero, nftw returns this value to its caller. 87 * 88 * depth limits the number of open directories that ftw uses 89 * before it starts recycling file descriptors. In general, 90 * a file descriptor is used for each level. When FTW_CHDIR isn't set, 91 * in order to descend to arbitrary depths, nftw requires 2 file 92 * descriptors to be open during the call to openat(), therefore if 93 * the depth argument is less than 2 nftw will not use openat(), and 94 * it will fail with ENAMETOOLONG if it descends to a directory that 95 * exceeds PATH_MAX. 96 * 97 */ 98 99 #include <sys/feature_tests.h> 100 101 #if !defined(_LP64) && _FILE_OFFSET_BITS == 64 102 #pragma weak nftw64 = _nftw64 103 #define _nftw _nftw64 104 #define fstat64 _fstat64 105 #define fstatat64 _fstatat64 106 #define lstat64 _lstat64 107 #define openat64 _openat64 108 #define readdir64 _readdir64 109 #define stat64 _stat64 110 #else 111 #pragma weak nftw = _nftw 112 #define fstat _fstat 113 #define fstatat _fstatat 114 #define lstat _lstat 115 #define openat _openat 116 #define readdir _readdir 117 #define stat _stat 118 #endif /* !_LP64 && _FILE_OFFSET_BITS == 64 */ 119 120 #define chdir _chdir 121 #define close _close 122 #define closedir _closedir 123 #define fchdir _fchdir 124 #define fdopendir _fdopendir 125 #define fprintf _fprintf 126 #define getcwd _getcwd 127 #define opendir _opendir 128 #define seekdir _seekdir 129 #define strdup _strdup 130 #define strerror _strerror 131 #define strtok_r _strtok_r 132 #define telldir _telldir 133 134 #include "lint.h" 135 #include <mtlib.h> 136 #include <sys/types.h> 137 #include <sys/stat.h> 138 #include <dirent.h> 139 #include <errno.h> 140 #include <limits.h> 141 #include <ftw.h> 142 #include <stdlib.h> 143 #include <string.h> 144 #include <unistd.h> 145 #include <thread.h> 146 #include <synch.h> 147 #include <stdio.h> 148 #include <strings.h> 149 #include <fcntl.h> 150 151 #ifndef PATH_MAX 152 #define PATH_MAX 1023 153 #endif 154 155 /* 156 * Local variables (used to be static local). 157 * Putting them into a structure that is passed 158 * around makes nftw() MT-safe with no locking required. 159 */ 160 struct Save { 161 struct Save *last; 162 DIR *fd; 163 char *comp; 164 long here; 165 dev_t dev; 166 ino_t inode; 167 }; 168 169 struct Var { 170 char *home; 171 size_t len; 172 char *fullpath; 173 char *tmppath; 174 int curflags; 175 dev_t cur_mount; 176 struct FTW state; 177 int walklevel; 178 int (*statf)(const char *, struct stat *, struct Save *); 179 int (*savedstatf)(const char *, struct stat *, struct Save *); 180 DIR *(*opendirf)(const char *); 181 }; 182 183 static int oldclose(struct Save *); 184 static int cdlstat(const char *, struct stat *, struct Save *); 185 static int cdstat(const char *, struct stat *, struct Save *); 186 static int nocdlstat(const char *, struct stat *, struct Save *); 187 static int nocdstat(const char *, struct stat *, struct Save *); 188 static DIR *cdopendir(const char *); 189 static DIR *nocdopendir(const char *); 190 static const char *get_unrooted(const char *); 191 192 /* 193 * This is the recursive walker. 194 */ 195 static int 196 walk(char *component, 197 int (*fn)(const char *, const struct stat *, int, struct FTW *), 198 int depth, struct Save *last, struct Var *vp) 199 { 200 struct stat statb; 201 char *p, *tmp; 202 int type; 203 char *comp; 204 struct dirent *dir; 205 char *q; 206 int rc = 0; 207 int val = -1; 208 int cdval = -1; 209 int oldbase; 210 int skip; 211 struct Save this; 212 size_t base_comp, base_component, base_this_comp, base_last_comp; 213 size_t base_fullpath, base_tmppath; 214 215 this.last = last; 216 this.fd = 0; 217 if ((vp->curflags & FTW_CHDIR) && last) 218 comp = last->comp; 219 else 220 comp = vp->tmppath; 221 222 if (vp->savedstatf == NULL) 223 vp->savedstatf = vp->statf; 224 225 if ((vp->walklevel++ == 0) && (vp->curflags & FTW_HOPTION)) { 226 if (((vp->curflags & FTW_CHDIR) == 0) && (depth >= 2)) { 227 vp->statf = nocdstat; 228 } else { 229 vp->statf = cdstat; 230 } 231 } else { 232 vp->statf = vp->savedstatf; 233 } 234 235 /* 236 * Determine the type of the component. 237 */ 238 if ((*vp->statf)(comp, &statb, last) >= 0) { 239 if ((statb.st_mode & S_IFMT) == S_IFDIR) { 240 type = FTW_D; 241 if (depth <= 1) 242 (void) oldclose(last); 243 if ((this.fd = (*vp->opendirf)(comp)) == 0) { 244 if (errno == EMFILE && oldclose(last) && 245 (this.fd = (*vp->opendirf)(comp)) != 0) { 246 /* 247 * If opendirf fails because there 248 * are OPEN_MAX fd in the calling 249 * process, and we close the oldest 250 * fd, and another opendirf doesn't 251 * fail, depth is set to 1. 252 */ 253 depth = 1; 254 } else { 255 type = FTW_DNR; 256 goto fail; 257 } 258 } 259 if (statb.st_fstype[0] == 'a' && 260 strcmp(statb.st_fstype, "autofs") == 0) { 261 /* 262 * this dir is on autofs 263 */ 264 if (fstat(this.fd->dd_fd, &statb) < 0) { 265 (void) closedir(this.fd); 266 type = FTW_NS; 267 goto fail; 268 } 269 } 270 } else if ((statb.st_mode & S_IFMT) == S_IFLNK) { 271 type = FTW_SL; 272 } else { 273 type = FTW_F; 274 } 275 } else if ((vp->curflags & FTW_ANYERR) && errno != ENOENT) { 276 /* 277 * If FTW_ANYERR is specified, then a stat error 278 * other than ENOENT automatically results in 279 * failure. This allows the callback function 280 * to properly handle ENAMETOOLONG and ELOOP and 281 * things of that nature, that would be masked 282 * by calling lstat before failing. 283 */ 284 type = FTW_NS; 285 goto fail; 286 } else { 287 /* 288 * Statf has failed. If stat was used instead of lstat, 289 * try using lstat. If lstat doesn't fail, "comp" 290 * must be a symbolic link pointing to a non-existent 291 * file. Such a symbolic link should be ignored. 292 * Also check the file type, if possible, for symbolic 293 * link. 294 */ 295 if (((vp->statf == cdstat) && 296 (cdlstat(comp, &statb, last) >= 0) && 297 ((statb.st_mode & S_IFMT) == S_IFLNK)) || 298 ((vp->statf == nocdstat) && 299 (nocdlstat(comp, &statb, last) >= 0) && 300 ((statb.st_mode & S_IFMT) == S_IFLNK))) { 301 302 /* 303 * Ignore bad symbolic link, let "fn" 304 * report it. 305 */ 306 307 errno = ENOENT; 308 type = FTW_SLN; 309 } else { 310 type = FTW_NS; 311 fail: 312 /* 313 * if FTW_ANYERR is set in flags, we call 314 * the user function with FTW_NS set, regardless 315 * of the reason stat failed. 316 */ 317 if (!(vp->curflags & FTW_ANYERR)) 318 if (errno != EACCES) 319 return (-1); 320 } 321 } 322 323 /* 324 * If the walk is not supposed to cross a mount point, 325 * and it did, get ready to return. 326 */ 327 if ((vp->curflags & FTW_MOUNT) && type != FTW_NS && 328 statb.st_dev != vp->cur_mount) 329 goto quit; 330 vp->state.quit = 0; 331 332 /* 333 * If current component is not a directory, call user 334 * specified function and get ready to return. 335 */ 336 if (type != FTW_D || (vp->curflags & FTW_DEPTH) == 0) 337 rc = (*fn)(vp->tmppath, &statb, type, &vp->state); 338 if (rc > 0) 339 val = rc; 340 skip = (vp->state.quit & FTW_SKD); 341 if (rc != 0 || type != FTW_D || (vp->state.quit & FTW_PRUNE)) 342 goto quit; 343 344 if (vp->tmppath[0] != '\0' && component[-1] != '/') 345 *component++ = '/'; 346 *component = 0; 347 if (vp->curflags & FTW_CHDIR) { 348 struct stat statb2; 349 350 /* 351 * Security check (there is a window between 352 * (*vp->statf)() and opendir() above). 353 */ 354 if ((vp->curflags & FTW_PHYS) && 355 (fstat(this.fd->dd_fd, &statb2) < 0 || 356 statb2.st_ino != statb.st_ino || 357 statb2.st_dev != statb.st_dev)) { 358 errno = EAGAIN; 359 rc = -1; 360 goto quit; 361 } 362 363 if ((cdval = fchdir(this.fd->dd_fd)) >= 0) { 364 this.comp = component; 365 } else { 366 type = FTW_DNR; 367 rc = (*fn)(vp->tmppath, &statb, type, &vp->state); 368 goto quit; 369 } 370 } 371 372 /* 373 * If the walk has followed a symbolic link (FTW_PHYS is not set), 374 * traverse the walk back to make sure there is not a loop. 375 * The find utility (FTW_NOLOOP is set) detects infinite loops 376 * in both symbolic and hard linked directories. 377 */ 378 if ((vp->curflags & FTW_NOLOOP) || 379 ((vp->curflags & FTW_PHYS) == 0)) { 380 struct Save *sp = last; 381 while (sp) { 382 /* 383 * If the same node has already been visited, there 384 * is a loop. Get ready to return. 385 */ 386 if (sp->dev == statb.st_dev && 387 sp->inode == statb.st_ino) { 388 if (vp->curflags & FTW_NOLOOP) { 389 /* private interface for find util */ 390 type = FTW_DL; 391 goto fail; 392 } 393 goto quit; 394 } 395 sp = sp->last; 396 } 397 } 398 this.dev = statb.st_dev; 399 this.inode = statb.st_ino; 400 oldbase = vp->state.base; 401 vp->state.base = (int)(component - vp->tmppath); 402 while (dir = readdir(this.fd)) { 403 if (dir->d_ino == 0) 404 continue; 405 q = dir->d_name; 406 if (*q == '.') { 407 if (q[1] == 0) 408 continue; 409 else if (q[1] == '.' && q[2] == 0) 410 continue; 411 } 412 if (last != NULL && last->comp != NULL) { 413 base_last_comp = last->comp - vp->home; 414 } 415 base_comp = comp - vp->home; 416 base_component = component - vp->home; 417 if ((strlen(q) + strlen(vp->home) + 1) > vp->len) { 418 /* 419 * When the space needed for vp->home has 420 * exceeded the amount of space that has 421 * been allocated, realloc() more space 422 * and adjust pointers to point to the 423 * (possibly moved) new block for vp->home 424 */ 425 base_this_comp = this.comp - vp->home; 426 base_fullpath = vp->fullpath - vp->home; 427 base_tmppath = vp->tmppath - vp->home; 428 vp->len *= 2; 429 tmp = (char *)realloc(vp->home, vp->len); 430 if (tmp == NULL) { 431 rc = -1; 432 goto quit; 433 } 434 vp->home = tmp; 435 comp = vp->home + base_comp; 436 component = vp->home + base_component; 437 this.comp = vp->home + base_this_comp; 438 vp->fullpath = vp->home + base_fullpath; 439 vp->tmppath = vp->home + base_tmppath; 440 if (last != NULL && last->comp != NULL) { 441 last->comp = vp->home + base_last_comp; 442 } 443 } 444 p = component; 445 while (*q != '\0') 446 *p++ = *q++; 447 *p = '\0'; 448 vp->state.level++; 449 450 /* Call walk() recursively. */ 451 rc = walk(p, fn, depth-1, &this, vp); 452 if (last != NULL && last->comp != NULL) { 453 last->comp = vp->home + base_last_comp; 454 } 455 comp = vp->home + base_comp; 456 component = vp->home + base_component; 457 vp->state.level--; 458 if (this.fd == 0) { 459 *component = 0; 460 if (vp->curflags & FTW_CHDIR) { 461 this.fd = opendir("."); 462 } else { 463 this.fd = (*vp->opendirf)(comp); 464 } 465 if (this.fd == 0) { 466 rc = -1; 467 goto quit; 468 } 469 seekdir(this.fd, this.here); 470 } 471 if (rc != 0) { 472 if (errno == ENOENT) { 473 (void) fprintf(stderr, "cannot open %s: %s\n", 474 vp->tmppath, strerror(errno)); 475 val = rc; 476 continue; 477 } 478 goto quit; /* this seems extreme */ 479 } 480 } 481 vp->state.base = oldbase; 482 *--component = 0; 483 type = FTW_DP; 484 if ((vp->tmppath[0] != '\0') && (vp->curflags & FTW_DEPTH) && !skip) 485 rc = (*fn)(vp->tmppath, &statb, type, &vp->state); 486 quit: 487 if (cdval >= 0 && last) { 488 /* try to change back to previous directory */ 489 if (last->fd != NULL) { 490 if (fchdir(last->fd->dd_fd) < 0) { 491 rc = -1; 492 } 493 } else { 494 if ((cdval = chdir("..")) >= 0) { 495 if ((*vp->statf)(".", &statb, last) < 0 || 496 statb.st_ino != last->inode || 497 statb.st_dev != last->dev) 498 cdval = -1; 499 } 500 *comp = 0; 501 if (cdval < 0) { 502 if (chdir(vp->fullpath) < 0) { 503 rc = -1; 504 } else { 505 /* Security check */ 506 if ((vp->curflags & FTW_PHYS) && 507 ((*vp->statf)(".", &statb, last) < 0 || 508 statb.st_ino != last->inode || 509 statb.st_dev != last->dev)) { 510 errno = EAGAIN; 511 rc = -1; 512 } 513 } 514 } 515 } 516 } 517 if (this.fd) 518 (void) closedir(this.fd); 519 if (val > rc) 520 return (val); 521 else 522 return (rc); 523 } 524 525 int 526 _nftw(const char *path, 527 int (*fn)(const char *, const struct stat *, int, struct FTW *), 528 int depth, int flags) 529 { 530 struct Var var; 531 struct stat statb; 532 int rc = -1; 533 char *dp; 534 char *base; 535 char *endhome; 536 const char *savepath = path; 537 int save_errno; 538 539 var.walklevel = 0; 540 var.len = 2*(PATH_MAX+1); 541 var.home = (char *)malloc(var.len); 542 if (var.home == NULL) 543 return (-1); 544 545 var.home[0] = 0; 546 547 /* 548 * If the walk is going to change directory before 549 * reading it, save current working directory. 550 */ 551 if (flags & FTW_CHDIR) { 552 if (getcwd(var.home, PATH_MAX+1) == 0) { 553 free(var.home); 554 return (-1); 555 } 556 } 557 endhome = dp = var.home + strlen(var.home); 558 if (*path == '/') 559 var.fullpath = dp; 560 else { 561 *dp++ = '/'; 562 var.fullpath = var.home; 563 } 564 var.tmppath = dp; 565 base = dp-1; 566 while (*path) { 567 *dp = *path; 568 if (*dp == '/') 569 base = dp; 570 dp++, path++; 571 } 572 *dp = 0; 573 var.state.base = (int)(base + 1 - var.tmppath); 574 if (*path) { 575 free(var.home); 576 errno = ENAMETOOLONG; 577 return (-1); 578 } 579 var.curflags = flags; 580 581 /* 582 * If doing chdir()'s, set var.opendirf to cdopendir. 583 * If not doing chdir()'s and if nftw()'s depth arg >= 2, 584 * set var.opendirf to nocdopendir. In order to 585 * descend to arbitrary depths without doing chdir()'s, nftw() 586 * requires a depth arg >= 2 so that nocdopendir() can use openat() 587 * to traverse the directories. So when not doing 588 * chdir()'s if nftw()'s depth arg <= 1, set var.opendirf to 589 * cdopendir. 590 * If doing a physical walk (not following symbolic link), set 591 * var.statf to cdlstat() or nocdlstat(). Otherwise, set var.statf 592 * to cdstat() or nocdstat(). 593 */ 594 if (((flags & FTW_CHDIR) == 0) && (depth >= 2)) { 595 var.opendirf = nocdopendir; 596 if (flags & FTW_PHYS) 597 var.statf = nocdlstat; 598 else 599 var.statf = nocdstat; 600 } else { 601 var.opendirf = cdopendir; 602 if (flags & FTW_PHYS) 603 var.statf = cdlstat; 604 else 605 var.statf = cdstat; 606 } 607 608 /* 609 * If walk is not going to cross a mount point, 610 * save the current mount point. 611 */ 612 if (flags & FTW_MOUNT) { 613 if ((*var.statf)(savepath, &statb, NULL) >= 0) 614 var.cur_mount = statb.st_dev; 615 else 616 goto done; 617 } 618 var.state.level = 0; 619 620 /* 621 * Call walk() which does most of the work. 622 * walk() uses errno in a rather obtuse way 623 * so we shield any incoming errno. 624 */ 625 save_errno = errno; 626 errno = 0; 627 var.savedstatf = NULL; 628 rc = walk(dp, fn, depth, (struct Save *)0, &var); 629 if (errno == 0) 630 errno = save_errno; 631 done: 632 *endhome = 0; 633 if (flags & FTW_CHDIR) 634 (void) chdir(var.home); 635 free(var.home); 636 return (rc); 637 } 638 639 /* 640 * Get stat info on path when FTW_CHDIR is set. 641 */ 642 /*ARGSUSED1*/ 643 static int 644 cdstat(const char *path, struct stat *statp, struct Save *lp) { 645 return (stat(path, statp)); 646 } 647 648 /* 649 * Get lstat info on path when FTW_CHDIR is set. 650 */ 651 /*ARGSUSED1*/ 652 static int 653 cdlstat(const char *path, struct stat *statp, struct Save *lp) 654 { 655 return (lstat(path, statp)); 656 } 657 658 /* 659 * Get stat info on path when FTW_CHDIR is not set. 660 */ 661 static int 662 nocdstat(const char *path, struct stat *statp, struct Save *lp) 663 { 664 const char *unrootp; 665 666 if (lp && lp->fd) { 667 /* get basename of path */ 668 unrootp = get_unrooted(path); 669 return (fstatat(lp->fd->dd_fd, unrootp, statp, 0)); 670 } else { 671 return (stat(path, statp)); 672 } 673 } 674 675 /* 676 * Get lstat info on path when FTW_CHDIR is not set. 677 */ 678 static int 679 nocdlstat(const char *path, struct stat *statp, struct Save *lp) 680 { 681 const char *unrootp; 682 683 if (lp && lp->fd) { 684 /* get basename of path */ 685 unrootp = get_unrooted(path); 686 return (fstatat(lp->fd->dd_fd, unrootp, statp, 687 AT_SYMLINK_NOFOLLOW)); 688 } else { 689 return (lstat(path, statp)); 690 } 691 } 692 693 /* 694 * Open path directory when FTW_CHDIR is set. 695 * 696 */ 697 static DIR * 698 cdopendir(const char *path) 699 { 700 return (opendir(path)); 701 } 702 703 /* 704 * Open path directory when FTW_CHDIR is not set. 705 */ 706 static DIR * 707 nocdopendir(const char *path) 708 { 709 int fd, cfd; 710 DIR *fdd; 711 char *dirp, *token, *ptr; 712 713 if (((fdd = opendir(path)) == NULL) && (errno == ENAMETOOLONG)) { 714 if ((dirp = strdup(path)) == NULL) { 715 errno = ENAMETOOLONG; 716 return (NULL); 717 } 718 if ((token = strtok_r(dirp, "/", &ptr)) != NULL) { 719 if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) { 720 (void) free(dirp); 721 errno = ENAMETOOLONG; 722 return (NULL); 723 } 724 while ((token = strtok_r(NULL, "/", &ptr)) != NULL) { 725 if ((cfd = openat(fd, token, O_RDONLY)) < 0) { 726 (void) close(fd); 727 (void) free(dirp); 728 errno = ENAMETOOLONG; 729 return (NULL); 730 } 731 (void) close(fd); 732 fd = cfd; 733 } 734 (void) free(dirp); 735 return (fdopendir(fd)); 736 } 737 (void) free(dirp); 738 errno = ENAMETOOLONG; 739 } 740 return (fdd); 741 } 742 743 /* return pointer basename of path, which may contain trailing slashes */ 744 static const char * 745 get_unrooted(const char *path) 746 { 747 const char *ptr; 748 749 if (!path || !*path) 750 return (NULL); 751 752 ptr = path + strlen(path); 753 /* find last char in path before any trailing slashes */ 754 while (ptr != path && *--ptr == '/') 755 ; 756 757 if (ptr == path) /* all slashes */ 758 return (ptr); 759 760 while (ptr != path) 761 if (*--ptr == '/') 762 return (++ptr); 763 764 return (ptr); 765 } 766 767 /* 768 * close the oldest directory. It saves the seek offset. 769 * return value is 0 unless it was unable to close any descriptor 770 */ 771 772 static int 773 oldclose(struct Save *sp) 774 { 775 struct Save *spnext; 776 while (sp) { 777 spnext = sp->last; 778 if (spnext == 0 || spnext->fd == 0) 779 break; 780 sp = spnext; 781 } 782 if (sp == 0 || sp->fd == 0) 783 return (0); 784 sp->here = telldir(sp->fd); 785 (void) closedir(sp->fd); 786 sp->fd = 0; 787 return (1); 788 } 789