|
1 /* $Id: fts.c,v 1.5 2000/11/25 16:33:36 arnd Exp $ */ |
|
2 // |
|
3 // © Portions Copyright (c) Symbian Software Ltd 2007. All rights reserved. |
|
4 // |
|
5 /*- |
|
6 * Copyright (c) 1990, 1993, 1994 |
|
7 * The Regents of the University of California. All rights reserved. |
|
8 * |
|
9 * Redistribution and use in source and binary forms, with or without |
|
10 * modification, are permitted provided that the following conditions |
|
11 * are met: |
|
12 * 1. Redistributions of source code must retain the above copyright |
|
13 * notice, this list of conditions and the following disclaimer. |
|
14 * 2. Redistributions in binary form must reproduce the above copyright |
|
15 * notice, this list of conditions and the following disclaimer in the |
|
16 * documentation and/or other materials provided with the distribution. |
|
17 * 3. All advertising materials mentioning features or use of this software |
|
18 * must display the following acknowledgement: |
|
19 * This product includes software developed by the University of |
|
20 * California, Berkeley and its contributors. |
|
21 * 4. Neither the name of the University nor the names of its contributors |
|
22 * may be used to endorse or promote products derived from this software |
|
23 * without specific prior written permission. |
|
24 * |
|
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
|
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
|
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
35 * SUCH DAMAGE. |
|
36 */ |
|
37 |
|
38 #define __NO_FCHDIR /* Fix me! fchdir fn not yet implemented (AH) */ |
|
39 |
|
40 #define _BSD_SOURCE |
|
41 #include <sys/param.h> |
|
42 #include <sys/stat.h> |
|
43 #include <dirent.h> |
|
44 #ifndef __SYMBIAN32__ |
|
45 #include <sys/direntx.h> |
|
46 #endif |
|
47 #include <errno.h> |
|
48 #include <fcntl.h> |
|
49 #include "fts.h" |
|
50 #include <stdlib.h> |
|
51 #include <string.h> |
|
52 #include <unistd.h> |
|
53 |
|
54 static FTSENT *fts_alloc(FTS *, char *, size_t); |
|
55 static FTSENT *fts_build(FTS *, int); |
|
56 static void fts_lfree(FTSENT *); |
|
57 static void fts_load(FTS *, FTSENT *); |
|
58 static size_t fts_maxarglen(char * const *); |
|
59 static void fts_padjust(FTS *, FTSENT *); |
|
60 static int fts_palloc(FTS *, size_t); |
|
61 static FTSENT *fts_sort(FTS *, FTSENT *, size_t); |
|
62 static u_short fts_stat(FTS *, FTSENT *, int); |
|
63 static int fts_safe_changedir(FTS *, FTSENT *, int); |
|
64 |
|
65 #define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) |
|
66 |
|
67 #define CLR(opt) (sp->fts_options &= ~(opt)) |
|
68 #define ISSET(opt) (sp->fts_options & (opt)) |
|
69 #define SET(opt) (sp->fts_options |= (opt)) |
|
70 |
|
71 #ifndef __NO_FCHDIR |
|
72 #define CHDIR(sp, path) (!ISSET(FTS_NOCHDIR) && chdir(path)) |
|
73 #define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && fchdir(fd)) |
|
74 #else |
|
75 #define CHDIR(sp, path) (0) |
|
76 #define FCHDIR(sp, fd) (0) |
|
77 #endif |
|
78 |
|
79 #ifdef __SYMBIAN32__ |
|
80 #ifdef __WINSCW__ |
|
81 #pragma warn_unusedarg off |
|
82 #endif//__WINSCW__ |
|
83 #ifdef __ARMCC__ |
|
84 // Turn off the 'extended constant initialiser used' warning |
|
85 #pragma diag_remark 1294 |
|
86 #endif |
|
87 #endif//__SYMBIAN32__ |
|
88 |
|
89 /* fts_build flags */ |
|
90 #define BCHILD 1 /* fts_children */ |
|
91 #define BNAMES 2 /* fts_children, names only */ |
|
92 #define BREAD 3 /* fts_read */ |
|
93 |
|
94 static void |
|
95 fts_load(sp, p) |
|
96 FTS *sp; |
|
97 register FTSENT *p; |
|
98 { |
|
99 register size_t len; |
|
100 register char *cp; |
|
101 |
|
102 /* |
|
103 * Load the stream structure for the next traversal. Since we don't |
|
104 * actually enter the directory until after the preorder visit, set |
|
105 * the fts_accpath field specially so the chdir gets done to the right |
|
106 * place and the user can access the first node. From fts_open it's |
|
107 * known that the path will fit. |
|
108 */ |
|
109 len = p->fts_pathlen = p->fts_namelen; |
|
110 memmove(sp->fts_path, p->fts_name, len + 1); |
|
111 |
|
112 if (!(cp = strrchr(p->fts_name, '\\'))) |
|
113 cp = strrchr(p->fts_name, '\\'); |
|
114 if (cp && (cp != p->fts_name || cp[1])) { |
|
115 len = strlen(++cp); |
|
116 memmove(p->fts_name, cp, len + 1); |
|
117 p->fts_namelen = len; |
|
118 } |
|
119 p->fts_accpath = p->fts_path = sp->fts_path; |
|
120 sp->fts_dev = p->fts_dev; |
|
121 } |
|
122 |
|
123 /* |
|
124 * Special case of "/" at the end of the path so that slashes aren't |
|
125 * appended which would cause paths to be written as "....//foo". |
|
126 */ |
|
127 #define NAPPEND(p) \ |
|
128 (p->fts_path[p->fts_pathlen - 1] == '/' \ |
|
129 ? p->fts_pathlen - 1 : p->fts_pathlen) |
|
130 |
|
131 /* |
|
132 * This is the tricky part -- do not casually change *anything* in here. The |
|
133 * idea is to build the linked list of entries that are used by fts_children |
|
134 * and fts_read. There are lots of special cases. |
|
135 * |
|
136 * The real slowdown in walking the tree is the stat calls. If FTS_NOSTAT is |
|
137 * set and it's a physical walk (so that symbolic links can't be directories), |
|
138 * we can do things quickly. First, if it's a 4.4BSD file system, the type |
|
139 * of the file is in the directory entry. Otherwise, we assume that the number |
|
140 * of subdirectories in a node is equal to the number of links to the parent. |
|
141 * The former skips all stat calls. The latter skips stat calls in any leaf |
|
142 * directories and for any files after the subdirectories in the directory have |
|
143 * been found, cutting the stat calls by about 2/3. |
|
144 */ |
|
145 static FTSENT * |
|
146 fts_build(sp, type) |
|
147 register FTS *sp; |
|
148 int type; |
|
149 { |
|
150 register struct dirent *dp; |
|
151 register FTSENT *p, *head; |
|
152 register size_t nitems; |
|
153 FTSENT *cur, *tail; |
|
154 DIR *dirp; |
|
155 void *oldaddr; |
|
156 int cderrno, descend, level, nlinks, /* oflag, */ saved_errno, |
|
157 nostat = 0, doadjust; |
|
158 size_t len, maxlen; |
|
159 char *cp = NULL; |
|
160 |
|
161 /* Set current node pointer. */ |
|
162 cur = sp->fts_cur; |
|
163 |
|
164 /* |
|
165 * Open the directory for reading. If this fails, we're done. |
|
166 * If being called from fts_read, set the fts_info field. |
|
167 */ |
|
168 #ifdef FTS_WHITEOUT |
|
169 if (ISSET(FTS_WHITEOUT)) |
|
170 oflag = DTF_NODUP|DTF_REWIND; |
|
171 else |
|
172 oflag = DTF_HIDEW|DTF_NODUP|DTF_REWIND; |
|
173 #else |
|
174 #define __opendir2(path, flag) opendir(path) |
|
175 #endif |
|
176 if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) { |
|
177 if (type == BREAD) { |
|
178 cur->fts_info = FTS_DNR; |
|
179 cur->fts_errno = errno; |
|
180 } |
|
181 return (NULL); |
|
182 } |
|
183 |
|
184 /* |
|
185 * Nlinks is the number of possible entries of type directory in the |
|
186 * directory if we're cheating on stat calls, 0 if we're not doing |
|
187 * any stat calls at all, -1 if we're doing stats on everything. |
|
188 */ |
|
189 if (type == BNAMES) |
|
190 nlinks = 0; |
|
191 else if (ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL)) { |
|
192 nlinks = cur->fts_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2); |
|
193 nostat = 1; |
|
194 } else { |
|
195 nlinks = -1; |
|
196 nostat = 0; |
|
197 } |
|
198 |
|
199 #ifdef notdef |
|
200 (void)printf("nlinks == %d (cur: %d)\n", nlinks, cur->fts_nlink); |
|
201 (void)printf("NOSTAT %d PHYSICAL %d SEEDOT %d\n", |
|
202 ISSET(FTS_NOSTAT), ISSET(FTS_PHYSICAL), ISSET(FTS_SEEDOT)); |
|
203 #endif |
|
204 /* |
|
205 * If we're going to need to stat anything or we want to descend |
|
206 * and stay in the directory, chdir. If this fails we keep going, |
|
207 * but set a flag so we don't chdir after the post-order visit. |
|
208 * We won't be able to stat anything, but we can still return the |
|
209 * names themselves. Note, that since fts_read won't be able to |
|
210 * chdir into the directory, it will have to return different path |
|
211 * names than before, i.e. "a/b" instead of "b". Since the node |
|
212 * has already been visited in pre-order, have to wait until the |
|
213 * post-order visit to return the error. There is a special case |
|
214 * here, if there was nothing to stat then it's not an error to |
|
215 * not be able to stat. This is all fairly nasty. If a program |
|
216 * needed sorted entries or stat information, they had better be |
|
217 * checking FTS_NS on the returned nodes. |
|
218 */ |
|
219 cderrno = 0; |
|
220 if (nlinks || type == BREAD) { |
|
221 if (fts_safe_changedir(sp, cur, dirfd(dirp))) { |
|
222 if (nlinks && type == BREAD) |
|
223 cur->fts_errno = errno; |
|
224 cur->fts_flags |= FTS_DONTCHDIR; |
|
225 descend = 0; |
|
226 cderrno = errno; |
|
227 (void)closedir(dirp); |
|
228 dirp = NULL; |
|
229 } else |
|
230 descend = 1; |
|
231 } else |
|
232 descend = 0; |
|
233 |
|
234 /* |
|
235 * Figure out the max file name length that can be stored in the |
|
236 * current path -- the inner loop allocates more path as necessary. |
|
237 * We really wouldn't have to do the maxlen calculations here, we |
|
238 * could do them in fts_read before returning the path, but it's a |
|
239 * lot easier here since the length is part of the dirent structure. |
|
240 * |
|
241 * If not changing directories set a pointer so that can just append |
|
242 * each new name into the path. |
|
243 */ |
|
244 len = NAPPEND(cur); |
|
245 if (ISSET(FTS_NOCHDIR)) { |
|
246 cp = sp->fts_path + len; |
|
247 *cp++ = '\\'; |
|
248 } |
|
249 len++; |
|
250 maxlen = sp->fts_pathlen - len; |
|
251 |
|
252 level = cur->fts_level + 1; |
|
253 |
|
254 /* Read the directory, attaching each entry to the `link' pointer. */ |
|
255 doadjust = 0; |
|
256 for (head = tail = NULL, nitems = 0; dirp && (dp = readdir(dirp));) { |
|
257 if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name)) |
|
258 continue; |
|
259 |
|
260 if ((p = fts_alloc(sp, dp->d_name, dp->d_namlen)) == NULL) |
|
261 goto mem1; |
|
262 if (dp->d_namlen >= maxlen) { /* include space for NUL */ |
|
263 oldaddr = sp->fts_path; |
|
264 if (fts_palloc(sp, dp->d_namlen +len + 1)) { |
|
265 /* |
|
266 * No more memory for path or structures. Save |
|
267 * errno, free up the current structure and the |
|
268 * structures already allocated. |
|
269 */ |
|
270 mem1: saved_errno = errno; |
|
271 if (p) |
|
272 free(p); |
|
273 fts_lfree(head); |
|
274 (void)closedir(dirp); |
|
275 cur->fts_info = FTS_ERR; |
|
276 SET(FTS_STOP); |
|
277 errno = saved_errno; |
|
278 return (NULL); |
|
279 } |
|
280 /* Did realloc() change the pointer? */ |
|
281 if (oldaddr != sp->fts_path) { |
|
282 doadjust = 1; |
|
283 if (ISSET(FTS_NOCHDIR)) |
|
284 cp = sp->fts_path + len; |
|
285 } |
|
286 maxlen = sp->fts_pathlen - len; |
|
287 } |
|
288 |
|
289 if (len + dp->d_namlen >= USHRT_MAX) { |
|
290 /* |
|
291 * In an FTSENT, fts_pathlen is a u_short so it is |
|
292 * possible to wraparound here. If we do, free up |
|
293 * the current structure and the structures already |
|
294 * allocated, then error out with ENAMETOOLONG. |
|
295 */ |
|
296 free(p); |
|
297 fts_lfree(head); |
|
298 (void)closedir(dirp); |
|
299 cur->fts_info = FTS_ERR; |
|
300 SET(FTS_STOP); |
|
301 errno = ENAMETOOLONG; |
|
302 return (NULL); |
|
303 } |
|
304 p->fts_level = level; |
|
305 p->fts_parent = sp->fts_cur; |
|
306 p->fts_pathlen = len + dp->d_namlen; |
|
307 |
|
308 #ifdef FTS_WHITEOUT |
|
309 if (dp->d_type == DT_WHT) |
|
310 p->fts_flags |= FTS_ISW; |
|
311 #endif |
|
312 |
|
313 if (nlinks == 0 || (nostat && |
|
314 dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN) |
|
315 ) { |
|
316 p->fts_accpath = |
|
317 ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name; |
|
318 p->fts_info = FTS_NSOK; |
|
319 } else { |
|
320 /* Build a file name for fts_stat to stat. */ |
|
321 if (ISSET(FTS_NOCHDIR)) { |
|
322 p->fts_accpath = p->fts_path; |
|
323 memmove(cp, p->fts_name, p->fts_namelen + 1); |
|
324 } else |
|
325 p->fts_accpath = p->fts_name; |
|
326 /* Stat it. */ |
|
327 p->fts_info = fts_stat(sp, p, 0); |
|
328 |
|
329 /* Decrement link count if applicable. */ |
|
330 if (nlinks > 0 && (p->fts_info == FTS_D || |
|
331 p->fts_info == FTS_DC || p->fts_info == FTS_DOT)) |
|
332 --nlinks; |
|
333 } |
|
334 |
|
335 /* We walk in directory order so "ls -f" doesn't get upset. */ |
|
336 p->fts_link = NULL; |
|
337 if (head == NULL) |
|
338 head = tail = p; |
|
339 else { |
|
340 tail->fts_link = p; |
|
341 tail = p; |
|
342 } |
|
343 ++nitems; |
|
344 } |
|
345 if (dirp) |
|
346 (void)closedir(dirp); |
|
347 |
|
348 /* |
|
349 * If realloc() changed the address of the path, adjust the |
|
350 * addresses for the rest of the tree and the dir list. |
|
351 */ |
|
352 if (doadjust) |
|
353 fts_padjust(sp, head); |
|
354 |
|
355 /* |
|
356 * If not changing directories, reset the path back to original |
|
357 * state. |
|
358 */ |
|
359 if (ISSET(FTS_NOCHDIR)) { |
|
360 if (len == sp->fts_pathlen || nitems == 0) |
|
361 --cp; |
|
362 *cp = '\0'; |
|
363 } |
|
364 |
|
365 /* |
|
366 * If descended after called from fts_children or after called from |
|
367 * fts_read and nothing found, get back. At the root level we use |
|
368 * the saved fd; if one of fts_open()'s arguments is a relative path |
|
369 * to an empty directory, we wind up here with no other way back. If |
|
370 * can't get back, we're done. |
|
371 */ |
|
372 if (descend && (type == BCHILD || !nitems) && |
|
373 (cur->fts_level == FTS_ROOTLEVEL ? |
|
374 FCHDIR(sp, sp->fts_rfd) : CHDIR(sp, ".."))) { |
|
375 cur->fts_info = FTS_ERR; |
|
376 SET(FTS_STOP); |
|
377 return (NULL); |
|
378 } |
|
379 |
|
380 /* If didn't find anything, return NULL. */ |
|
381 if (!nitems && (head == NULL)) { |
|
382 if (type == BREAD) |
|
383 cur->fts_info = FTS_DP; |
|
384 return (NULL); |
|
385 } |
|
386 |
|
387 /* Sort the entries. */ |
|
388 if (sp->fts_compar && nitems > 1) |
|
389 head = fts_sort(sp, head, nitems); |
|
390 return (head); |
|
391 } |
|
392 |
|
393 static u_short |
|
394 fts_stat(sp, p, follow) |
|
395 FTS *sp; |
|
396 register FTSENT *p; |
|
397 int follow; |
|
398 { |
|
399 |
|
400 register dev_t dev; |
|
401 register ino_t ino; |
|
402 struct stat *sbp, sb; |
|
403 int saved_errno; |
|
404 |
|
405 /* If user needs stat info, stat buffer already allocated. */ |
|
406 sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp; |
|
407 |
|
408 #ifdef FTS_WHITEOUT |
|
409 /* check for whiteout */ |
|
410 if (p->fts_flags & FTS_ISW) { |
|
411 if (sbp != &sb) { |
|
412 memset(sbp, '\0', sizeof (*sbp)); |
|
413 sbp->st_mode = S_IFWHT; |
|
414 } |
|
415 return (FTS_W); |
|
416 } |
|
417 #endif |
|
418 |
|
419 /* |
|
420 * If doing a logical walk, or application requested FTS_FOLLOW, do |
|
421 * a stat(2). If that fails, check for a non-existent symlink. If |
|
422 * fail, set the errno from the stat call. |
|
423 */ |
|
424 if (ISSET(FTS_LOGICAL) || follow) { |
|
425 if (stat(p->fts_accpath, sbp)) { |
|
426 saved_errno = errno; |
|
427 if (!lstat(p->fts_accpath, sbp)) { |
|
428 errno = 0; |
|
429 return (FTS_SLNONE); |
|
430 } |
|
431 p->fts_errno = saved_errno; |
|
432 goto err; |
|
433 } |
|
434 } else if (lstat(p->fts_accpath, sbp)) { |
|
435 p->fts_errno = errno; |
|
436 err: memset(sbp, 0, sizeof(struct stat)); |
|
437 return (FTS_NS); |
|
438 } |
|
439 |
|
440 if (S_ISDIR(sbp->st_mode)) { |
|
441 /* |
|
442 * Set the device/inode. Used to find cycles and check for |
|
443 * crossing mount points. Also remember the link count, used |
|
444 * in fts_build to limit the number of stat calls. It is |
|
445 * understood that these fields are only referenced if fts_info |
|
446 * is set to FTS_D. |
|
447 */ |
|
448 dev = p->fts_dev = sbp->st_dev; |
|
449 ino = p->fts_ino = sbp->st_ino; |
|
450 p->fts_nlink = sbp->st_nlink; |
|
451 |
|
452 if (ISDOT(p->fts_name)) |
|
453 return (FTS_DOT); |
|
454 |
|
455 /* |
|
456 * Cycle detection is done by brute force when the directory |
|
457 * is first encountered. If the tree gets deep enough or the |
|
458 * number of symbolic links to directories is high enough, |
|
459 * something faster might be worthwhile. |
|
460 */ |
|
461 #ifndef __SYMBIAN32__ |
|
462 //fts_ino is set |
|
463 for (t = p->fts_parent; |
|
464 t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent) |
|
465 if (ino == t->fts_ino && dev == t->fts_dev) { |
|
466 p->fts_cycle = t; |
|
467 return (FTS_DC); |
|
468 } |
|
469 #endif |
|
470 return (FTS_D); |
|
471 } |
|
472 if (S_ISLNK(sbp->st_mode)) |
|
473 return (FTS_SL); |
|
474 if (S_ISREG(sbp->st_mode)) |
|
475 return (FTS_F); |
|
476 return (FTS_DEFAULT); |
|
477 } |
|
478 |
|
479 typedef int (*qsType)(const void *, const void *); |
|
480 |
|
481 static FTSENT * |
|
482 fts_sort(sp, head, nitems) |
|
483 FTS *sp; |
|
484 FTSENT *head; |
|
485 register size_t nitems; |
|
486 { |
|
487 register FTSENT **ap, *p; |
|
488 |
|
489 /* |
|
490 * Construct an array of pointers to the structures and call qsort(3). |
|
491 * Reassemble the array in the order returned by qsort. If unable to |
|
492 * sort for memory reasons, return the directory entries in their |
|
493 * current order. Allocate enough space for the current needs plus |
|
494 * 40 so don't realloc one entry at a time. |
|
495 */ |
|
496 if (nitems > sp->fts_nitems) { |
|
497 struct _ftsent **a; |
|
498 |
|
499 sp->fts_nitems = nitems + 40; |
|
500 if ((a = realloc(sp->fts_array, |
|
501 sp->fts_nitems * sizeof(FTSENT *))) == NULL) { |
|
502 if (sp->fts_array) |
|
503 free(sp->fts_array); |
|
504 sp->fts_array = NULL; |
|
505 sp->fts_nitems = 0; |
|
506 return (head); |
|
507 } |
|
508 sp->fts_array = a; |
|
509 } |
|
510 for (ap = sp->fts_array, p = head; p; p = p->fts_link) |
|
511 *ap++ = p; |
|
512 qsort((void *)sp->fts_array, nitems, sizeof(FTSENT *), |
|
513 (qsType)sp->fts_compar); |
|
514 for (head = *(ap = sp->fts_array); --nitems; ++ap) |
|
515 ap[0]->fts_link = ap[1]; |
|
516 ap[0]->fts_link = NULL; |
|
517 return (head); |
|
518 } |
|
519 |
|
520 static FTSENT * |
|
521 fts_alloc(sp, name, namelen) |
|
522 FTS *sp; |
|
523 char *name; |
|
524 register size_t namelen; |
|
525 { |
|
526 register FTSENT *p; |
|
527 size_t len; |
|
528 |
|
529 /* |
|
530 * The file name is a variable length array and no stat structure is |
|
531 * necessary if the user has set the nostat bit. Allocate the FTSENT |
|
532 * structure, the file name and the stat structure in one chunk, but |
|
533 * be careful that the stat structure is reasonably aligned. Since the |
|
534 * fts_name field is declared to be of size 1, the fts_name pointer is |
|
535 * namelen + 2 before the first possible address of the stat structure. |
|
536 */ |
|
537 len = sizeof(FTSENT) + namelen; |
|
538 if (!ISSET(FTS_NOSTAT)) |
|
539 len += sizeof(struct stat) + ALIGNBYTES; |
|
540 if ((p = malloc(len)) == NULL) |
|
541 return (NULL); |
|
542 |
|
543 /* Copy the name and guarantee NUL termination. */ |
|
544 memmove(p->fts_name, name, namelen); |
|
545 p->fts_name[namelen] = '\0'; |
|
546 |
|
547 if (!ISSET(FTS_NOSTAT)) |
|
548 p->fts_statp = (struct stat *)ALIGN(p->fts_name + namelen + 2); |
|
549 p->fts_namelen = namelen; |
|
550 p->fts_path = sp->fts_path; |
|
551 p->fts_errno = 0; |
|
552 p->fts_flags = 0; |
|
553 p->fts_instr = FTS_NOINSTR; |
|
554 p->fts_number = 0; |
|
555 p->fts_pointer = NULL; |
|
556 return (p); |
|
557 } |
|
558 |
|
559 static void |
|
560 fts_lfree(head) |
|
561 register FTSENT *head; |
|
562 { |
|
563 register FTSENT *p; |
|
564 |
|
565 /* Free a linked list of structures. */ |
|
566 while ((p = head)) { |
|
567 head = head->fts_link; |
|
568 free(p); |
|
569 } |
|
570 } |
|
571 |
|
572 /* |
|
573 * Allow essentially unlimited paths; find, rm, ls should all work on any tree. |
|
574 * Most systems will allow creation of paths much longer than MAXPATHLEN, even |
|
575 * though the kernel won't resolve them. Add the size (not just what's needed) |
|
576 * plus 256 bytes so don't realloc the path 2 bytes at a time. |
|
577 */ |
|
578 static int |
|
579 fts_palloc(sp, more) |
|
580 FTS *sp; |
|
581 size_t more; |
|
582 { |
|
583 char *p; |
|
584 |
|
585 sp->fts_pathlen += more + 256; |
|
586 /* |
|
587 * Check for possible wraparound. In an FTS, fts_pathlen is |
|
588 * a signed int but in an FTSENT it is an unsigned short. |
|
589 * We limit fts_pathlen to USHRT_MAX to be safe in both cases. |
|
590 */ |
|
591 if (/* sp->fts_pathlen < 0 || */ sp->fts_pathlen >= USHRT_MAX) { |
|
592 if (sp->fts_path) |
|
593 free(sp->fts_path); |
|
594 sp->fts_path = NULL; |
|
595 errno = ENAMETOOLONG; |
|
596 return (1); |
|
597 } |
|
598 p = realloc(sp->fts_path, sp->fts_pathlen); |
|
599 if (p == NULL) { |
|
600 if (sp->fts_path) |
|
601 free(sp->fts_path); |
|
602 sp->fts_path = NULL; |
|
603 return (1); |
|
604 } |
|
605 sp->fts_path = p; |
|
606 return (0); |
|
607 } |
|
608 |
|
609 /* |
|
610 * When the path is realloc'd, have to fix all of the pointers in structures |
|
611 * already returned. |
|
612 */ |
|
613 static void |
|
614 fts_padjust(sp, head) |
|
615 FTS *sp; |
|
616 FTSENT *head; |
|
617 { |
|
618 FTSENT *p; |
|
619 char *addr = sp->fts_path; |
|
620 |
|
621 #define ADJUST(p) { \ |
|
622 if ((p)->fts_accpath != (p)->fts_name) { \ |
|
623 (p)->fts_accpath = \ |
|
624 (char *)addr + ((p)->fts_accpath - (p)->fts_path); \ |
|
625 } \ |
|
626 (p)->fts_path = addr; \ |
|
627 } |
|
628 /* Adjust the current set of children. */ |
|
629 for (p = sp->fts_child; p; p = p->fts_link) |
|
630 ADJUST(p); |
|
631 |
|
632 /* Adjust the rest of the tree, including the current level. */ |
|
633 for (p = head; p->fts_level >= FTS_ROOTLEVEL;) { |
|
634 ADJUST(p); |
|
635 p = p->fts_link ? p->fts_link : p->fts_parent; |
|
636 } |
|
637 } |
|
638 |
|
639 static size_t |
|
640 fts_maxarglen(argv) |
|
641 char * const *argv; |
|
642 { |
|
643 size_t len, max; |
|
644 |
|
645 for (max = 0; *argv; ++argv) |
|
646 if ((len = strlen(*argv)) > max) |
|
647 max = len; |
|
648 return (max + 1); |
|
649 } |
|
650 |
|
651 /* |
|
652 * Change to dir specified by fd or p->fts_accpath without getting |
|
653 * tricked by someone changing the world out from underneath us. |
|
654 * Assumes p->fts_dev and p->fts_ino are filled in. |
|
655 */ |
|
656 static int |
|
657 fts_safe_changedir(sp, p, fd) |
|
658 FTS *sp; |
|
659 FTSENT *p; |
|
660 int fd; |
|
661 { |
|
662 #ifndef __NO_FCHDIR |
|
663 int ret, oerrno, newfd; |
|
664 struct stat sb; |
|
665 |
|
666 newfd = fd; |
|
667 if (ISSET(FTS_NOCHDIR)) |
|
668 return (0); |
|
669 if (fd < 0 && (newfd = open(p->fts_accpath, O_RDONLY, 0)) < 0) |
|
670 return (-1); |
|
671 if (fstat(newfd, &sb)) { |
|
672 ret = -1; |
|
673 goto bail; |
|
674 } |
|
675 if (p->fts_dev != sb.st_dev || p->fts_ino != sb.st_ino) { |
|
676 errno = ENOENT; /* disinformation */ |
|
677 ret = -1; |
|
678 goto bail; |
|
679 } |
|
680 ret = fchdir(newfd); |
|
681 bail: |
|
682 oerrno = errno; |
|
683 if (fd < 0) |
|
684 (void)close(newfd); |
|
685 errno = oerrno; |
|
686 return (ret); |
|
687 #else |
|
688 return (0); |
|
689 #endif |
|
690 } |
|
691 |
|
692 |
|
693 FTS * |
|
694 fts_open(argv, options, compar) |
|
695 char * const *argv; |
|
696 register int options; |
|
697 int (*compar)(const FTSENT **, const FTSENT **); |
|
698 { |
|
699 register FTS *sp; |
|
700 register FTSENT *p, *root; |
|
701 register size_t nitems; |
|
702 FTSENT *parent, *tmp = NULL; |
|
703 size_t len; |
|
704 |
|
705 /* Options check. */ |
|
706 if (options & ~FTS_OPTIONMASK) { |
|
707 errno = EINVAL; |
|
708 return (NULL); |
|
709 } |
|
710 |
|
711 /* Allocate/initialize the stream */ |
|
712 if ((sp = malloc((u_int)sizeof(FTS))) == NULL) |
|
713 return (NULL); |
|
714 memset(sp, 0, sizeof(FTS)); |
|
715 sp->fts_compar = compar; |
|
716 #ifndef __NO_FCHDIR |
|
717 sp->fts_options = options; |
|
718 |
|
719 /* Logical walks turn on NOCHDIR; symbolic links are too hard. */ |
|
720 if (ISSET(FTS_LOGICAL)) |
|
721 SET(FTS_NOCHDIR); |
|
722 #else |
|
723 sp->fts_options = options | FTS_NOCHDIR; |
|
724 #endif |
|
725 /* |
|
726 * Start out with 1K of path space, and enough, in any case, |
|
727 * to hold the user's paths. |
|
728 */ |
|
729 if (fts_palloc(sp, MAX(fts_maxarglen(argv), MAXPATHLEN))) |
|
730 goto mem1; |
|
731 |
|
732 /* Allocate/initialize root's parent. */ |
|
733 if ((parent = fts_alloc(sp, "", 0)) == NULL) |
|
734 goto mem2; |
|
735 parent->fts_level = FTS_ROOTPARENTLEVEL; |
|
736 |
|
737 /* Allocate/initialize root(s). */ |
|
738 for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) { |
|
739 /* Don't allow zero-length paths. */ |
|
740 if ((len = strlen(*argv)) == 0) { |
|
741 errno = ENOENT; |
|
742 goto mem3; |
|
743 } |
|
744 |
|
745 p = fts_alloc(sp, *argv, len); |
|
746 p->fts_level = FTS_ROOTLEVEL; |
|
747 p->fts_parent = parent; |
|
748 p->fts_accpath = p->fts_name; |
|
749 p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW)); |
|
750 |
|
751 /* Command-line "." and ".." are real directories. */ |
|
752 if (p->fts_info == FTS_DOT) |
|
753 p->fts_info = FTS_D; |
|
754 |
|
755 /* |
|
756 * If comparison routine supplied, traverse in sorted |
|
757 * order; otherwise traverse in the order specified. |
|
758 */ |
|
759 if (compar) { |
|
760 p->fts_link = root; |
|
761 root = p; |
|
762 } else { |
|
763 p->fts_link = NULL; |
|
764 if (root == NULL) |
|
765 tmp = root = p; |
|
766 else { |
|
767 tmp->fts_link = p; |
|
768 tmp = p; |
|
769 } |
|
770 } |
|
771 } |
|
772 if (compar && nitems > 1) |
|
773 root = fts_sort(sp, root, nitems); |
|
774 |
|
775 /* |
|
776 * Allocate a dummy pointer and make fts_read think that we've just |
|
777 * finished the node before the root(s); set p->fts_info to FTS_INIT |
|
778 * so that everything about the "current" node is ignored. |
|
779 */ |
|
780 if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL) |
|
781 goto mem3; |
|
782 sp->fts_cur->fts_link = root; |
|
783 sp->fts_cur->fts_info = FTS_INIT; |
|
784 |
|
785 #ifndef __NO_FCHDIR |
|
786 /* |
|
787 * If using chdir(2), grab a file descriptor pointing to dot to ensure |
|
788 * that we can get back here; this could be avoided for some paths, |
|
789 * but almost certainly not worth the effort. Slashes, symbolic links, |
|
790 * and ".." are all fairly nasty problems. Note, if we can't get the |
|
791 * descriptor we run anyway, just more slowly. |
|
792 */ |
|
793 if (!ISSET(FTS_NOCHDIR) && (sp->fts_rfd = open(".", O_RDONLY, 0)) < 0) |
|
794 SET(FTS_NOCHDIR); |
|
795 #endif |
|
796 |
|
797 return (sp); |
|
798 |
|
799 mem3: fts_lfree(root); |
|
800 free(parent); |
|
801 mem2: free(sp->fts_path); |
|
802 mem1: free(sp); |
|
803 return (NULL); |
|
804 } |
|
805 |
|
806 |
|
807 int |
|
808 fts_close(sp) |
|
809 FTS *sp; |
|
810 { |
|
811 register FTSENT *freep, *p; |
|
812 /* int saved_errno = 0; */ |
|
813 |
|
814 /* |
|
815 * This still works if we haven't read anything -- the dummy structure |
|
816 * points to the root list, so we step through to the end of the root |
|
817 * list which has a valid parent pointer. |
|
818 */ |
|
819 if (sp->fts_cur) { |
|
820 for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) { |
|
821 freep = p; |
|
822 p = p->fts_link ? p->fts_link : p->fts_parent; |
|
823 free(freep); |
|
824 } |
|
825 free(p); |
|
826 } |
|
827 |
|
828 /* Free up child linked list, sort array, path buffer. */ |
|
829 if (sp->fts_child) |
|
830 fts_lfree(sp->fts_child); |
|
831 if (sp->fts_array) |
|
832 free(sp->fts_array); |
|
833 free(sp->fts_path); |
|
834 |
|
835 #ifndef __NO_FCHDIR |
|
836 /* Return to original directory, save errno if necessary. */ |
|
837 if (!ISSET(FTS_NOCHDIR)) { |
|
838 saved_errno = fchdir(sp->fts_rfd) ? errno : 0; |
|
839 (void)close(sp->fts_rfd); |
|
840 } |
|
841 /* Set errno and return. */ |
|
842 if (!ISSET(FTS_NOCHDIR) && saved_errno) { |
|
843 /* Free up the stream pointer. */ |
|
844 free(sp); |
|
845 errno = saved_errno; |
|
846 return (-1); |
|
847 } |
|
848 #endif |
|
849 |
|
850 /* Free up the stream pointer. */ |
|
851 free(sp); |
|
852 return (0); |
|
853 } |
|
854 |
|
855 FTSENT * |
|
856 fts_read(sp) |
|
857 register FTS *sp; |
|
858 { |
|
859 register FTSENT *p, *tmp; |
|
860 register int instr; |
|
861 register char *t; |
|
862 /* int saved_errno; */ |
|
863 |
|
864 /* If finished or unrecoverable error, return NULL. */ |
|
865 if (sp->fts_cur == NULL || ISSET(FTS_STOP)) |
|
866 return (NULL); |
|
867 |
|
868 /* Set current node pointer. */ |
|
869 p = sp->fts_cur; |
|
870 |
|
871 /* Save and zero out user instructions. */ |
|
872 instr = p->fts_instr; |
|
873 p->fts_instr = FTS_NOINSTR; |
|
874 |
|
875 /* Any type of file may be re-visited; re-stat and re-turn. */ |
|
876 if (instr == FTS_AGAIN) { |
|
877 p->fts_info = fts_stat(sp, p, 0); |
|
878 return (p); |
|
879 } |
|
880 |
|
881 /* |
|
882 * Following a symlink -- SLNONE test allows application to see |
|
883 * SLNONE and recover. If indirecting through a symlink, have |
|
884 * keep a pointer to current location. If unable to get that |
|
885 * pointer, follow fails. |
|
886 */ |
|
887 if (instr == FTS_FOLLOW && |
|
888 (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) { |
|
889 p->fts_info = fts_stat(sp, p, 1); |
|
890 if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { |
|
891 if ((p->fts_symfd = open(".", O_RDONLY, 0)) < 0) { |
|
892 p->fts_errno = errno; |
|
893 p->fts_info = FTS_ERR; |
|
894 } else |
|
895 p->fts_flags |= FTS_SYMFOLLOW; |
|
896 } |
|
897 return (p); |
|
898 } |
|
899 |
|
900 /* Directory in pre-order. */ |
|
901 if (p->fts_info == FTS_D) { |
|
902 /* If skipped or crossed mount point, do post-order visit. */ |
|
903 if (instr == FTS_SKIP || |
|
904 (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) { |
|
905 if (p->fts_flags & FTS_SYMFOLLOW) |
|
906 (void)close(p->fts_symfd); |
|
907 if (sp->fts_child) { |
|
908 fts_lfree(sp->fts_child); |
|
909 sp->fts_child = NULL; |
|
910 } |
|
911 p->fts_info = FTS_DP; |
|
912 return (p); |
|
913 } |
|
914 |
|
915 /* Rebuild if only read the names and now traversing. */ |
|
916 if (sp->fts_child && ISSET(FTS_NAMEONLY)) { |
|
917 CLR(FTS_NAMEONLY); |
|
918 fts_lfree(sp->fts_child); |
|
919 sp->fts_child = NULL; |
|
920 } |
|
921 |
|
922 /* |
|
923 * Cd to the subdirectory. |
|
924 * |
|
925 * If have already read and now fail to chdir, whack the list |
|
926 * to make the names come out right, and set the parent errno |
|
927 * so the application will eventually get an error condition. |
|
928 * Set the FTS_DONTCHDIR flag so that when we logically change |
|
929 * directories back to the parent we don't do a chdir. |
|
930 * |
|
931 * If haven't read do so. If the read fails, fts_build sets |
|
932 * FTS_STOP or the fts_info field of the node. |
|
933 */ |
|
934 if (sp->fts_child) { |
|
935 if (fts_safe_changedir(sp, p, -1)) { |
|
936 p->fts_errno = errno; |
|
937 p->fts_flags |= FTS_DONTCHDIR; |
|
938 for (p = sp->fts_child; p; p = p->fts_link) |
|
939 p->fts_accpath = |
|
940 p->fts_parent->fts_accpath; |
|
941 } |
|
942 } else if ((sp->fts_child = fts_build(sp, BREAD)) == NULL) { |
|
943 if (ISSET(FTS_STOP)) |
|
944 return (NULL); |
|
945 return (p); |
|
946 } |
|
947 p = sp->fts_child; |
|
948 sp->fts_child = NULL; |
|
949 goto name; |
|
950 } |
|
951 |
|
952 /* Move to the next node on this level. */ |
|
953 next: tmp = p; |
|
954 if ((p = p->fts_link)) { |
|
955 free(tmp); |
|
956 |
|
957 /* |
|
958 * If reached the top, return to the original directory (or |
|
959 * the root of the tree), and load the paths for the next root. |
|
960 */ |
|
961 if (p->fts_level == FTS_ROOTLEVEL) { |
|
962 if ((sp->fts_options & FTS_CHDIRROOT)) { |
|
963 if (chdir(p->fts_accpath)) { |
|
964 SET(FTS_STOP); |
|
965 return (NULL); |
|
966 } |
|
967 } else { |
|
968 if (FCHDIR(sp, sp->fts_rfd)) { |
|
969 SET(FTS_STOP); |
|
970 return (NULL); |
|
971 } |
|
972 } |
|
973 fts_load(sp, p); |
|
974 return (sp->fts_cur = p); |
|
975 } |
|
976 |
|
977 /* |
|
978 * User may have called fts_set on the node. If skipped, |
|
979 * ignore. If followed, get a file descriptor so we can |
|
980 * get back if necessary. |
|
981 */ |
|
982 if (p->fts_instr == FTS_SKIP) |
|
983 goto next; |
|
984 if (p->fts_instr == FTS_FOLLOW) { |
|
985 p->fts_info = fts_stat(sp, p, 1); |
|
986 if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { |
|
987 if ((p->fts_symfd = |
|
988 open(".", O_RDONLY, 0)) < 0) { |
|
989 p->fts_errno = errno; |
|
990 p->fts_info = FTS_ERR; |
|
991 } else |
|
992 p->fts_flags |= FTS_SYMFOLLOW; |
|
993 } |
|
994 p->fts_instr = FTS_NOINSTR; |
|
995 } |
|
996 |
|
997 name: t = sp->fts_path + NAPPEND(p->fts_parent); |
|
998 *t++ = '\\'; |
|
999 memmove(t, p->fts_name, p->fts_namelen + 1); |
|
1000 return (sp->fts_cur = p); |
|
1001 } |
|
1002 |
|
1003 /* Move up to the parent node. */ |
|
1004 p = tmp->fts_parent; |
|
1005 free(tmp); |
|
1006 |
|
1007 if (p->fts_level == FTS_ROOTPARENTLEVEL) { |
|
1008 /* |
|
1009 * Done; free everything up and set errno to 0 so the user |
|
1010 * can distinguish between error and EOF. |
|
1011 */ |
|
1012 free(p); |
|
1013 errno = 0; |
|
1014 return (sp->fts_cur = NULL); |
|
1015 } |
|
1016 |
|
1017 /* NUL terminate the pathname. */ |
|
1018 sp->fts_path[p->fts_pathlen] = '\0'; |
|
1019 |
|
1020 /* |
|
1021 * Return to the parent directory. If at a root node or came through |
|
1022 * a symlink, go back through the file descriptor. Otherwise, cd up |
|
1023 * one directory. |
|
1024 */ |
|
1025 if (p->fts_level == FTS_ROOTLEVEL) { |
|
1026 if ((sp->fts_options & FTS_CHDIRROOT)) { |
|
1027 if (chdir(p->fts_accpath)) { |
|
1028 SET(FTS_STOP); |
|
1029 return (NULL); |
|
1030 } |
|
1031 } else { |
|
1032 if (FCHDIR(sp, sp->fts_rfd)) { |
|
1033 SET(FTS_STOP); |
|
1034 return (NULL); |
|
1035 } |
|
1036 } |
|
1037 } else if (p->fts_flags & FTS_SYMFOLLOW) { |
|
1038 #ifndef __NO_FCHDIR |
|
1039 if (FCHDIR(sp, p->fts_symfd)) { |
|
1040 saved_errno = errno; |
|
1041 (void)close(p->fts_symfd); |
|
1042 errno = saved_errno; |
|
1043 SET(FTS_STOP); |
|
1044 return (NULL); |
|
1045 } |
|
1046 #endif |
|
1047 (void)close(p->fts_symfd); |
|
1048 } else if (!(p->fts_flags & FTS_DONTCHDIR)) { |
|
1049 if (CHDIR(sp, "..")) { |
|
1050 SET(FTS_STOP); |
|
1051 return (NULL); |
|
1052 } |
|
1053 } |
|
1054 p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP; |
|
1055 return (sp->fts_cur = p); |
|
1056 } |
|
1057 |
|
1058 /* |
|
1059 * Fts_set takes the stream as an argument although it's not used in this |
|
1060 * implementation; it would be necessary if anyone wanted to add global |
|
1061 * semantics to fts using fts_set. An error return is allowed for similar |
|
1062 * reasons. |
|
1063 */ |
|
1064 /* ARGSUSED */ |
|
1065 int |
|
1066 fts_set(FTS *sp_dummy, FTSENT *p, int instr) |
|
1067 { |
|
1068 if (instr && instr != FTS_AGAIN && instr != FTS_FOLLOW && |
|
1069 instr != FTS_NOINSTR && instr != FTS_SKIP) { |
|
1070 errno = EINVAL; |
|
1071 return (1); |
|
1072 } |
|
1073 p->fts_instr = instr; |
|
1074 return (0); |
|
1075 } |
|
1076 |
|
1077 FTSENT * |
|
1078 fts_children(sp, instr) |
|
1079 register FTS *sp; |
|
1080 int instr; |
|
1081 { |
|
1082 register FTSENT *p; |
|
1083 /* int fd; */ |
|
1084 |
|
1085 if (instr && instr != FTS_NAMEONLY) { |
|
1086 errno = EINVAL; |
|
1087 return (NULL); |
|
1088 } |
|
1089 |
|
1090 /* Set current node pointer. */ |
|
1091 p = sp->fts_cur; |
|
1092 |
|
1093 /* |
|
1094 * Errno set to 0 so user can distinguish empty directory from |
|
1095 * an error. |
|
1096 */ |
|
1097 errno = 0; |
|
1098 |
|
1099 /* Fatal errors stop here. */ |
|
1100 if (ISSET(FTS_STOP)) |
|
1101 return (NULL); |
|
1102 |
|
1103 /* Return logical hierarchy of user's arguments. */ |
|
1104 if (p->fts_info == FTS_INIT) |
|
1105 return (p->fts_link); |
|
1106 |
|
1107 /* |
|
1108 * If not a directory being visited in pre-order, stop here. Could |
|
1109 * allow FTS_DNR, assuming the user has fixed the problem, but the |
|
1110 * same effect is available with FTS_AGAIN. |
|
1111 */ |
|
1112 if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */) |
|
1113 return (NULL); |
|
1114 |
|
1115 /* Free up any previous child list. */ |
|
1116 if (sp->fts_child) |
|
1117 fts_lfree(sp->fts_child); |
|
1118 |
|
1119 if (instr == FTS_NAMEONLY) { |
|
1120 SET(FTS_NAMEONLY); |
|
1121 instr = BNAMES; |
|
1122 } else |
|
1123 instr = BCHILD; |
|
1124 |
|
1125 #ifndef __NO_FCHDIR |
|
1126 /* |
|
1127 * If using chdir on a relative path and called BEFORE fts_read does |
|
1128 * its chdir to the root of a traversal, we can lose -- we need to |
|
1129 * chdir into the subdirectory, and we don't know where the current |
|
1130 * directory is, so we can't get back so that the upcoming chdir by |
|
1131 * fts_read will work. |
|
1132 */ |
|
1133 if (p->fts_level != FTS_ROOTLEVEL || p->fts_accpath[0] == '\\' || |
|
1134 p->fts_accpath[0] == '\\' || ISSET(FTS_NOCHDIR)) |
|
1135 return (sp->fts_child = fts_build(sp, instr)); |
|
1136 |
|
1137 if ((fd = open(".", O_RDONLY, 0)) < 0) |
|
1138 return (NULL); |
|
1139 sp->fts_child = fts_build(sp, instr); |
|
1140 if (fchdir(fd)) |
|
1141 return (NULL); |
|
1142 (void)close(fd); |
|
1143 return (sp->fts_child); |
|
1144 #else |
|
1145 return (sp->fts_child = fts_build(sp, instr)); |
|
1146 #endif |
|
1147 |
|
1148 } |
|
1149 |