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