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