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 *, int flags); 179 int (*savedstatf)(const char *, struct stat *, struct Save *, 180 int flags); 181 DIR *(*opendirf)(const char *); 182 }; 183 184 static int oldclose(struct Save *); 185 static int cdlstat(const char *, struct stat *, struct Save *, int flags); 186 static int cdstat(const char *, struct stat *, struct Save *, int flags); 187 static int nocdlstat(const char *, struct stat *, struct Save *, int flags); 188 static int nocdstat(const char *, struct stat *, struct Save *, int flags); 189 static DIR *cdopendir(const char *); 190 static DIR *nocdopendir(const char *); 191 static const char *get_unrooted(const char *); 192 193 /* 194 * This is the recursive walker. 195 */ 196 static int 197 walk(char *component, 198 int (*fn)(const char *, const struct stat *, int, struct FTW *), 199 int depth, struct Save *last, struct Var *vp) 200 { 201 struct stat statb; 202 char *p, *tmp; 203 int type; 204 char *comp; 205 struct dirent *dir; 206 char *q; 207 int rc = 0; 208 int val = -1; 209 int cdval = -1; 210 int oldbase; 211 int skip; 212 struct Save this; 213 size_t base_comp, base_component, base_this_comp, base_last_comp; 214 size_t base_fullpath, base_tmppath; 215 216 this.last = last; 217 this.fd = 0; 218 if ((vp->curflags & FTW_CHDIR) && last) 219 comp = last->comp; 220 else 221 comp = vp->tmppath; 222 223 if (vp->savedstatf == NULL) 224 vp->savedstatf = vp->statf; 225 226 if ((vp->walklevel++ == 0) && (vp->curflags & FTW_HOPTION)) { 227 if (((vp->curflags & FTW_CHDIR) == 0) && (depth >= 2)) { 228 vp->statf = nocdstat; 229 } else { 230 vp->statf = cdstat; 231 } 232 } else { 233 vp->statf = vp->savedstatf; 234 } 235 236 /* 237 * Determine the type of the component. 238 * 239 * Note that if the component is a trigger mount, this 240 * will cause it to load. 241 */ 242 if ((*vp->statf)(comp, &statb, last, _AT_TRIGGER) >= 0) { 243 if ((statb.st_mode & S_IFMT) == S_IFDIR) { 244 type = FTW_D; 245 if (depth <= 1) 246 (void) oldclose(last); 247 if ((this.fd = (*vp->opendirf)(comp)) == 0) { 248 if (errno == EMFILE && oldclose(last) && 249 (this.fd = (*vp->opendirf)(comp)) != 0) { 250 /* 251 * If opendirf fails because there 252 * are OPEN_MAX fd in the calling 253 * process, and we close the oldest 254 * fd, and another opendirf doesn't 255 * fail, depth is set to 1. 256 */ 257 depth = 1; 258 } else { 259 type = FTW_DNR; 260 goto fail; 261 } 262 } 263 } else if ((statb.st_mode & S_IFMT) == S_IFLNK) { 264 type = FTW_SL; 265 } else { 266 type = FTW_F; 267 } 268 } else if ((vp->curflags & FTW_ANYERR) && errno != ENOENT) { 269 /* 270 * If FTW_ANYERR is specified, then a stat error 271 * other than ENOENT automatically results in 272 * failure. This allows the callback function 273 * to properly handle ENAMETOOLONG and ELOOP and 274 * things of that nature, that would be masked 275 * by calling lstat before failing. 276 */ 277 type = FTW_NS; 278 goto fail; 279 } else { 280 /* 281 * Statf has failed. If stat was used instead of lstat, 282 * try using lstat. If lstat doesn't fail, "comp" 283 * must be a symbolic link pointing to a non-existent 284 * file. Such a symbolic link should be ignored. 285 * Also check the file type, if possible, for symbolic 286 * link. 287 */ 288 if (((vp->statf == cdstat) && 289 (cdlstat(comp, &statb, last, 0) >= 0) && 290 ((statb.st_mode & S_IFMT) == S_IFLNK)) || 291 ((vp->statf == nocdstat) && 292 (nocdlstat(comp, &statb, last, 0) >= 0) && 293 ((statb.st_mode & S_IFMT) == S_IFLNK))) { 294 295 /* 296 * Ignore bad symbolic link, let "fn" 297 * report it. 298 */ 299 300 errno = ENOENT; 301 type = FTW_SLN; 302 } else { 303 type = FTW_NS; 304 fail: 305 /* 306 * if FTW_ANYERR is set in flags, we call 307 * the user function with FTW_NS set, regardless 308 * of the reason stat failed. 309 */ 310 if (!(vp->curflags & FTW_ANYERR)) 311 if (errno != EACCES) 312 return (-1); 313 } 314 } 315 316 /* 317 * If the walk is not supposed to cross a mount point, 318 * and it did, get ready to return. 319 */ 320 if ((vp->curflags & FTW_MOUNT) && type != FTW_NS && 321 statb.st_dev != vp->cur_mount) 322 goto quit; 323 vp->state.quit = 0; 324 325 /* 326 * If current component is not a directory, call user 327 * specified function and get ready to return. 328 */ 329 if (type != FTW_D || (vp->curflags & FTW_DEPTH) == 0) 330 rc = (*fn)(vp->tmppath, &statb, type, &vp->state); 331 if (rc > 0) 332 val = rc; 333 skip = (vp->state.quit & FTW_SKD); 334 if (rc != 0 || type != FTW_D || (vp->state.quit & FTW_PRUNE)) 335 goto quit; 336 337 if (vp->tmppath[0] != '\0' && component[-1] != '/') 338 *component++ = '/'; 339 *component = 0; 340 if (vp->curflags & FTW_CHDIR) { 341 struct stat statb2; 342 343 /* 344 * Security check (there is a window between 345 * (*vp->statf)() and opendir() above). 346 */ 347 if ((vp->curflags & FTW_PHYS) && 348 (fstat(this.fd->dd_fd, &statb2) < 0 || 349 statb2.st_ino != statb.st_ino || 350 statb2.st_dev != statb.st_dev)) { 351 errno = EAGAIN; 352 rc = -1; 353 goto quit; 354 } 355 356 if ((cdval = fchdir(this.fd->dd_fd)) >= 0) { 357 this.comp = component; 358 } else { 359 type = FTW_DNR; 360 rc = (*fn)(vp->tmppath, &statb, type, &vp->state); 361 goto quit; 362 } 363 } 364 365 /* 366 * If the walk has followed a symbolic link (FTW_PHYS is not set), 367 * traverse the walk back to make sure there is not a loop. 368 * The find utility (FTW_NOLOOP is set) detects infinite loops 369 * in both symbolic and hard linked directories. 370 */ 371 if ((vp->curflags & FTW_NOLOOP) || 372 ((vp->curflags & FTW_PHYS) == 0)) { 373 struct Save *sp = last; 374 while (sp) { 375 /* 376 * If the same node has already been visited, there 377 * is a loop. Get ready to return. 378 */ 379 if (sp->dev == statb.st_dev && 380 sp->inode == statb.st_ino) { 381 if (vp->curflags & FTW_NOLOOP) { 382 /* private interface for find util */ 383 type = FTW_DL; 384 goto fail; 385 } 386 goto quit; 387 } 388 sp = sp->last; 389 } 390 } 391 this.dev = statb.st_dev; 392 this.inode = statb.st_ino; 393 oldbase = vp->state.base; 394 vp->state.base = (int)(component - vp->tmppath); 395 while (dir = readdir(this.fd)) { 396 if (dir->d_ino == 0) 397 continue; 398 q = dir->d_name; 399 if (*q == '.') { 400 if (q[1] == 0) 401 continue; 402 else if (q[1] == '.' && q[2] == 0) 403 continue; 404 } 405 if (last != NULL && last->comp != NULL) { 406 base_last_comp = last->comp - vp->home; 407 } 408 base_comp = comp - vp->home; 409 base_component = component - vp->home; 410 if ((strlen(q) + strlen(vp->home) + 1) > vp->len) { 411 /* 412 * When the space needed for vp->home has 413 * exceeded the amount of space that has 414 * been allocated, realloc() more space 415 * and adjust pointers to point to the 416 * (possibly moved) new block for vp->home 417 */ 418 base_this_comp = this.comp - vp->home; 419 base_fullpath = vp->fullpath - vp->home; 420 base_tmppath = vp->tmppath - vp->home; 421 vp->len *= 2; 422 tmp = (char *)realloc(vp->home, vp->len); 423 if (tmp == NULL) { 424 rc = -1; 425 goto quit; 426 } 427 vp->home = tmp; 428 comp = vp->home + base_comp; 429 component = vp->home + base_component; 430 this.comp = vp->home + base_this_comp; 431 vp->fullpath = vp->home + base_fullpath; 432 vp->tmppath = vp->home + base_tmppath; 433 if (last != NULL && last->comp != NULL) { 434 last->comp = vp->home + base_last_comp; 435 } 436 } 437 p = component; 438 while (*q != '\0') 439 *p++ = *q++; 440 *p = '\0'; 441 vp->state.level++; 442 443 /* Call walk() recursively. */ 444 rc = walk(p, fn, depth-1, &this, vp); 445 if (last != NULL && last->comp != NULL) { 446 last->comp = vp->home + base_last_comp; 447 } 448 comp = vp->home + base_comp; 449 component = vp->home + base_component; 450 vp->state.level--; 451 if (this.fd == 0) { 452 *component = 0; 453 if (vp->curflags & FTW_CHDIR) { 454 this.fd = opendir("."); 455 } else { 456 this.fd = (*vp->opendirf)(comp); 457 } 458 if (this.fd == 0) { 459 rc = -1; 460 goto quit; 461 } 462 seekdir(this.fd, this.here); 463 } 464 if (rc != 0) { 465 if (errno == ENOENT) { 466 (void) fprintf(stderr, "cannot open %s: %s\n", 467 vp->tmppath, strerror(errno)); 468 val = rc; 469 continue; 470 } 471 goto quit; /* this seems extreme */ 472 } 473 } 474 vp->state.base = oldbase; 475 *--component = 0; 476 type = FTW_DP; 477 if ((vp->tmppath[0] != '\0') && (vp->curflags & FTW_DEPTH) && !skip) 478 rc = (*fn)(vp->tmppath, &statb, type, &vp->state); 479 quit: 480 if (cdval >= 0 && last) { 481 /* try to change back to previous directory */ 482 if (last->fd != NULL) { 483 if (fchdir(last->fd->dd_fd) < 0) { 484 rc = -1; 485 } 486 } else { 487 if ((cdval = chdir("..")) >= 0) { 488 if ((*vp->statf)(".", &statb, last, 0) < 0 || 489 statb.st_ino != last->inode || 490 statb.st_dev != last->dev) 491 cdval = -1; 492 } 493 *comp = 0; 494 if (cdval < 0) { 495 if (chdir(vp->fullpath) < 0) { 496 rc = -1; 497 } else { 498 /* Security check */ 499 if ((vp->curflags & FTW_PHYS) && 500 ((*vp->statf)(".", &statb, 501 last, 0) < 0 || 502 statb.st_ino != last->inode || 503 statb.st_dev != last->dev)) { 504 errno = EAGAIN; 505 rc = -1; 506 } 507 } 508 } 509 } 510 } 511 512 if (this.fd) 513 (void) closedir(this.fd); 514 if (val > rc) 515 return (val); 516 else 517 return (rc); 518 } 519 520 int 521 _nftw(const char *path, 522 int (*fn)(const char *, const struct stat *, int, struct FTW *), 523 int depth, int flags) 524 { 525 struct Var var; 526 struct stat statb; 527 int rc = -1; 528 char *dp; 529 char *base; 530 char *endhome; 531 const char *savepath = path; 532 int save_errno; 533 534 var.walklevel = 0; 535 var.len = 2*(PATH_MAX+1); 536 var.home = (char *)malloc(var.len); 537 if (var.home == NULL) 538 return (-1); 539 540 var.home[0] = 0; 541 542 /* 543 * If the walk is going to change directory before 544 * reading it, save current working directory. 545 */ 546 if (flags & FTW_CHDIR) { 547 if (getcwd(var.home, PATH_MAX+1) == 0) { 548 free(var.home); 549 return (-1); 550 } 551 } 552 endhome = dp = var.home + strlen(var.home); 553 if (*path == '/') 554 var.fullpath = dp; 555 else { 556 *dp++ = '/'; 557 var.fullpath = var.home; 558 } 559 var.tmppath = dp; 560 base = dp-1; 561 while (*path) { 562 *dp = *path; 563 if (*dp == '/') 564 base = dp; 565 dp++, path++; 566 } 567 *dp = 0; 568 var.state.base = (int)(base + 1 - var.tmppath); 569 if (*path) { 570 free(var.home); 571 errno = ENAMETOOLONG; 572 return (-1); 573 } 574 var.curflags = flags; 575 576 /* 577 * If doing chdir()'s, set var.opendirf to cdopendir. 578 * If not doing chdir()'s and if nftw()'s depth arg >= 2, 579 * set var.opendirf to nocdopendir. In order to 580 * descend to arbitrary depths without doing chdir()'s, nftw() 581 * requires a depth arg >= 2 so that nocdopendir() can use openat() 582 * to traverse the directories. So when not doing 583 * chdir()'s if nftw()'s depth arg <= 1, set var.opendirf to 584 * cdopendir. 585 * If doing a physical walk (not following symbolic link), set 586 * var.statf to cdlstat() or nocdlstat(). Otherwise, set var.statf 587 * to cdstat() or nocdstat(). 588 */ 589 if (((flags & FTW_CHDIR) == 0) && (depth >= 2)) { 590 var.opendirf = nocdopendir; 591 if (flags & FTW_PHYS) 592 var.statf = nocdlstat; 593 else 594 var.statf = nocdstat; 595 } else { 596 var.opendirf = cdopendir; 597 if (flags & FTW_PHYS) 598 var.statf = cdlstat; 599 else 600 var.statf = cdstat; 601 } 602 603 /* 604 * If walk is not going to cross a mount point, 605 * save the current mount point. 606 */ 607 if (flags & FTW_MOUNT) { 608 if ((*var.statf)(savepath, &statb, NULL, 0) >= 0) 609 var.cur_mount = statb.st_dev; 610 else 611 goto done; 612 } 613 var.state.level = 0; 614 615 /* 616 * Call walk() which does most of the work. 617 * walk() uses errno in a rather obtuse way 618 * so we shield any incoming errno. 619 */ 620 save_errno = errno; 621 errno = 0; 622 var.savedstatf = NULL; 623 rc = walk(dp, fn, depth, (struct Save *)0, &var); 624 if (errno == 0) 625 errno = save_errno; 626 done: 627 *endhome = 0; 628 if (flags & FTW_CHDIR) 629 (void) chdir(var.home); 630 free(var.home); 631 return (rc); 632 } 633 634 /* 635 * Get stat info on path when FTW_CHDIR is set. 636 */ 637 /*ARGSUSED1*/ 638 static int 639 cdstat(const char *path, struct stat *statp, struct Save *lp, int flags) 640 { 641 return (fstatat(AT_FDCWD, path, statp, flags)); 642 } 643 644 /* 645 * Get lstat info on path when FTW_CHDIR is set. 646 */ 647 /*ARGSUSED1*/ 648 static int 649 cdlstat(const char *path, struct stat *statp, struct Save *lp, int flags) 650 { 651 return (fstatat(AT_FDCWD, path, statp, 652 flags | AT_SYMLINK_NOFOLLOW)); 653 } 654 655 /* 656 * Get stat info on path when FTW_CHDIR is not set. 657 */ 658 static int 659 nocdstat(const char *path, struct stat *statp, struct Save *lp, int flags) 660 { 661 int fd; 662 const char *basepath; 663 664 if (lp && lp->fd) { 665 /* get basename of path */ 666 basepath = get_unrooted(path); 667 668 fd = lp->fd->dd_fd; 669 } else { 670 basepath = path; 671 672 fd = AT_FDCWD; 673 } 674 675 return (fstatat(fd, basepath, statp, flags)); 676 } 677 678 /* 679 * Get lstat info on path when FTW_CHDIR is not set. 680 */ 681 static int 682 nocdlstat(const char *path, struct stat *statp, struct Save *lp, int flags) 683 { 684 int fd; 685 const char *basepath; 686 687 if (lp && lp->fd) { 688 /* get basename of path */ 689 basepath = get_unrooted(path); 690 691 fd = lp->fd->dd_fd; 692 } else { 693 basepath = path; 694 695 fd = AT_FDCWD; 696 } 697 698 return (fstatat(fd, basepath, statp, flags | AT_SYMLINK_NOFOLLOW)); 699 } 700 701 /* 702 * Open path directory when FTW_CHDIR is set. 703 * 704 */ 705 static DIR * 706 cdopendir(const char *path) 707 { 708 return (opendir(path)); 709 } 710 711 /* 712 * Open path directory when FTW_CHDIR is not set. 713 */ 714 static DIR * 715 nocdopendir(const char *path) 716 { 717 int fd, cfd; 718 DIR *fdd; 719 char *dirp, *token, *ptr; 720 721 if (((fdd = opendir(path)) == NULL) && (errno == ENAMETOOLONG)) { 722 if ((dirp = strdup(path)) == NULL) { 723 errno = ENAMETOOLONG; 724 return (NULL); 725 } 726 if ((token = strtok_r(dirp, "/", &ptr)) != NULL) { 727 if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) { 728 (void) free(dirp); 729 errno = ENAMETOOLONG; 730 return (NULL); 731 } 732 while ((token = strtok_r(NULL, "/", &ptr)) != NULL) { 733 if ((cfd = openat(fd, token, O_RDONLY)) < 0) { 734 (void) close(fd); 735 (void) free(dirp); 736 errno = ENAMETOOLONG; 737 return (NULL); 738 } 739 (void) close(fd); 740 fd = cfd; 741 } 742 (void) free(dirp); 743 return (fdopendir(fd)); 744 } 745 (void) free(dirp); 746 errno = ENAMETOOLONG; 747 } 748 return (fdd); 749 } 750 751 /* 752 * return pointer basename of path, which may contain trailing slashes 753 * 754 * We do this when we do not chdir() on the input. 755 */ 756 static const char * 757 get_unrooted(const char *path) 758 { 759 const char *ptr; 760 761 if (!path || !*path) 762 return (NULL); 763 764 ptr = path + strlen(path); 765 /* find last char in path before any trailing slashes */ 766 while (ptr != path && *--ptr == '/') 767 ; 768 769 if (ptr == path) /* all slashes */ 770 return (ptr); 771 772 while (ptr != path) 773 if (*--ptr == '/') 774 return (++ptr); 775 776 return (ptr); 777 } 778 779 /* 780 * close the oldest directory. It saves the seek offset. 781 * return value is 0 unless it was unable to close any descriptor 782 */ 783 784 static int 785 oldclose(struct Save *sp) 786 { 787 struct Save *spnext; 788 while (sp) { 789 spnext = sp->last; 790 if (spnext == 0 || spnext->fd == 0) 791 break; 792 sp = spnext; 793 } 794 if (sp == 0 || sp->fd == 0) 795 return (0); 796 sp->here = telldir(sp->fd); 797 (void) closedir(sp->fd); 798 sp->fd = 0; 799 return (1); 800 } 801