]>
Commit | Line | Data |
---|---|---|
e9ce8d39 | 1 | /* |
974e3884 | 2 | * Copyright (c) 1999, 2000, 2003, 2005, 2008, 2012, 2016 Apple Inc. All rights reserved. |
e9ce8d39 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
734aad71 A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
e9ce8d39 A |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
734aad71 A |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
e9ce8d39 A |
20 | * |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | /* | |
24 | * Copyright (c) 1990, 1993, 1994 | |
25 | * The Regents of the University of California. All rights reserved. | |
26 | * | |
27 | * Redistribution and use in source and binary forms, with or without | |
28 | * modification, are permitted provided that the following conditions | |
29 | * are met: | |
30 | * 1. Redistributions of source code must retain the above copyright | |
31 | * notice, this list of conditions and the following disclaimer. | |
32 | * 2. Redistributions in binary form must reproduce the above copyright | |
33 | * notice, this list of conditions and the following disclaimer in the | |
34 | * documentation and/or other materials provided with the distribution. | |
974e3884 | 35 | * 3. Neither the name of the University nor the names of its contributors |
e9ce8d39 A |
36 | * may be used to endorse or promote products derived from this software |
37 | * without specific prior written permission. | |
38 | * | |
39 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
40 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
41 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
42 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
43 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
44 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
45 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
46 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
47 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
48 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
49 | * SUCH DAMAGE. | |
50 | */ | |
51 | ||
974e3884 | 52 | /* $OpenBSD: fts.c,v 1.51 2015/09/12 13:32:24 guenther Exp $ */ |
e9ce8d39 A |
53 | |
54 | #include <sys/param.h> | |
55 | #include <sys/stat.h> | |
56 | ||
974e3884 | 57 | #include <assert.h> |
e9ce8d39 A |
58 | #include <dirent.h> |
59 | #include <errno.h> | |
60 | #include <fcntl.h> | |
61 | #include <fts.h> | |
974e3884 | 62 | #include <limits.h> |
e9ce8d39 A |
63 | #include <stdlib.h> |
64 | #include <string.h> | |
65 | #include <unistd.h> | |
974e3884 A |
66 | #include <stdbool.h> |
67 | #include <sys/vnode.h> | |
68 | #include <sys/attr.h> | |
69 | ||
34e8f829 A |
70 | #ifdef __BLOCKS__ |
71 | #include <Block.h> | |
72 | #endif /* __BLOCKS__ */ | |
974e3884 | 73 | #include <malloc_private.h> |
e9ce8d39 | 74 | |
9385eb3d A |
75 | static FTSENT *fts_alloc(FTS *, char *, int); |
76 | static FTSENT *fts_build(FTS *, int); | |
77 | static void fts_lfree(FTSENT *); | |
78 | static void fts_load(FTS *, FTSENT *); | |
79 | static size_t fts_maxarglen(char * const *); | |
974e3884 | 80 | static void fts_padjust(FTS *, FTSENT *); |
9385eb3d A |
81 | static int fts_palloc(FTS *, size_t); |
82 | static FTSENT *fts_sort(FTS *, FTSENT *, int); | |
974e3884 A |
83 | static u_short fts_stat(FTS *, FTSENT *, int, int); |
84 | static u_short fts_stat2(FTS *, FTSENT *, int, int, struct stat *); | |
85 | static int fts_safe_changedir(FTS *, FTSENT *, int, char *); | |
e9ce8d39 | 86 | |
974e3884 | 87 | #define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) |
e9ce8d39 | 88 | |
974e3884 A |
89 | #define CLR(opt) (sp->fts_options &= ~(opt)) |
90 | #define ISSET(opt) (sp->fts_options & (opt)) | |
91 | #define SET(opt) (sp->fts_options |= (opt)) | |
e9ce8d39 | 92 | |
e9ce8d39 A |
93 | #define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && fchdir(fd)) |
94 | ||
95 | /* fts_build flags */ | |
96 | #define BCHILD 1 /* fts_children */ | |
97 | #define BNAMES 2 /* fts_children, names only */ | |
98 | #define BREAD 3 /* fts_read */ | |
99 | ||
34e8f829 A |
100 | /* 5653270 |
101 | * For directories containing > 64k subdirectories (or HFS+ with > 64k files | |
102 | * and subdirectories), struct stat's st_nlink (16 bits) will overflow. This | |
103 | * causes the case with FTS_NOSTAT and FTS_PHYSICAL set to prematurely stop | |
104 | * recursing into subdirectories, because of an optimization that expects | |
105 | * st_nlink to be the number of subdirectories (once that number has been | |
106 | * encountered, no further calls to stat should be needed). | |
107 | * | |
108 | * However, on Mac OS X, another optimization largely nullifies the st_nlink | |
109 | * optimization. struct dirent contains d_type, which can distinguish | |
110 | * directories from files without initially calling stat. So stat is only | |
111 | * called on known directories, rather than on other files. With this | |
112 | * optimization, the difference in also using the st_nlink optimization is | |
113 | * pretty minimal (tests show an improvement of a percent or two, probably | |
114 | * due to additional if statement clauses that need to be evaluated). | |
115 | * | |
116 | * So removing the st_nlink optimization code will fix the > 64k subdirectories | |
117 | * problem. And if we replace the multiple if clause logic with a single | |
118 | * switch statement, we can recover the minimal performance lose. We can | |
119 | * go even further and for the case of FTS_NOSTAT and FTS_LOGICAL set, we | |
120 | * can use d_type to also distinguish symbolic links, and so we only need to | |
121 | * call stat on directories and symlinks, not on all files. This provides | |
122 | * a significant performance boost in that special case. | |
123 | */ | |
124 | /* | |
125 | * The following macros defines values of the dostat variable, which is or-ed | |
126 | * with the value of d_type, and the result used in a switch statement to | |
127 | * determine whether to call stat or not. (We order the macros to minimize | |
128 | * the size of any jump table that the compiler may generate.) | |
129 | */ | |
130 | #define F_SHIFT 4 /* shift to leave space for d_type */ | |
131 | #define F_NOSTAT (0 << F_SHIFT) /* don't do any stat's */ | |
132 | #define F_STATDIRSYM (1 << F_SHIFT) /* only stat directories and symlinks (and unknowns) */ | |
133 | #define F_ALWAYSSTAT (2 << F_SHIFT) /* always stat */ | |
134 | #define F_STATDIR (3 << F_SHIFT) /* only stat directories (and unknowns) */ | |
6465356a A |
135 | #define F_D_TYPE (4 << F_SHIFT) /* only stat directories but use d_type */ |
136 | #define F_D_TYPESYM (5 << F_SHIFT) /* only stat directories and symlinks but use d_type */ | |
34e8f829 A |
137 | |
138 | static FTS * | |
974e3884 | 139 | __fts_open(char * const *argv, FTS *sp) |
34e8f829 | 140 | { |
974e3884 A |
141 | FTSENT *p, *root; |
142 | int nitems; | |
143 | FTSENT *parent, *tmp; | |
e9ce8d39 A |
144 | int len; |
145 | ||
e9ce8d39 A |
146 | /* Logical walks turn on NOCHDIR; symbolic links are too hard. */ |
147 | if (ISSET(FTS_LOGICAL)) | |
148 | SET(FTS_NOCHDIR); | |
149 | ||
150 | /* | |
151 | * Start out with 1K of path space, and enough, in any case, | |
152 | * to hold the user's paths. | |
153 | */ | |
974e3884 | 154 | if (fts_palloc(sp, MAX(fts_maxarglen(argv), PATH_MAX))) |
e9ce8d39 A |
155 | goto mem1; |
156 | ||
157 | /* Allocate/initialize root's parent. */ | |
158 | if ((parent = fts_alloc(sp, "", 0)) == NULL) | |
159 | goto mem2; | |
160 | parent->fts_level = FTS_ROOTPARENTLEVEL; | |
161 | ||
162 | /* Allocate/initialize root(s). */ | |
163 | for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) { | |
164 | /* Don't allow zero-length paths. */ | |
165 | if ((len = strlen(*argv)) == 0) { | |
166 | errno = ENOENT; | |
167 | goto mem3; | |
168 | } | |
169 | ||
974e3884 A |
170 | if ((p = fts_alloc(sp, *argv, len)) == NULL) |
171 | goto mem3; | |
e9ce8d39 A |
172 | p->fts_level = FTS_ROOTLEVEL; |
173 | p->fts_parent = parent; | |
174 | p->fts_accpath = p->fts_name; | |
974e3884 | 175 | p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOWDIR) ? -1 : ISSET(FTS_COMFOLLOW), -1); |
e9ce8d39 A |
176 | |
177 | /* Command-line "." and ".." are real directories. */ | |
178 | if (p->fts_info == FTS_DOT) | |
179 | p->fts_info = FTS_D; | |
180 | ||
181 | /* | |
182 | * If comparison routine supplied, traverse in sorted | |
183 | * order; otherwise traverse in the order specified. | |
184 | */ | |
34e8f829 | 185 | if (sp->fts_compar) { |
e9ce8d39 A |
186 | p->fts_link = root; |
187 | root = p; | |
188 | } else { | |
189 | p->fts_link = NULL; | |
190 | if (root == NULL) | |
191 | tmp = root = p; | |
192 | else { | |
193 | tmp->fts_link = p; | |
194 | tmp = p; | |
195 | } | |
196 | } | |
197 | } | |
34e8f829 | 198 | if (sp->fts_compar && nitems > 1) |
e9ce8d39 A |
199 | root = fts_sort(sp, root, nitems); |
200 | ||
201 | /* | |
202 | * Allocate a dummy pointer and make fts_read think that we've just | |
203 | * finished the node before the root(s); set p->fts_info to FTS_INIT | |
204 | * so that everything about the "current" node is ignored. | |
205 | */ | |
206 | if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL) | |
207 | goto mem3; | |
208 | sp->fts_cur->fts_link = root; | |
209 | sp->fts_cur->fts_info = FTS_INIT; | |
210 | ||
211 | /* | |
974e3884 | 212 | * If using chdir(2), grab a file descriptor pointing to dot to ensure |
e9ce8d39 A |
213 | * that we can get back here; this could be avoided for some paths, |
214 | * but almost certainly not worth the effort. Slashes, symbolic links, | |
215 | * and ".." are all fairly nasty problems. Note, if we can't get the | |
216 | * descriptor we run anyway, just more slowly. | |
217 | */ | |
974e3884 A |
218 | if (!ISSET(FTS_NOCHDIR) && |
219 | (sp->fts_rfd = open(".", O_RDONLY | O_CLOEXEC)) < 0) | |
e9ce8d39 A |
220 | SET(FTS_NOCHDIR); |
221 | ||
974e3884 A |
222 | if (nitems == 0) |
223 | free(parent); | |
224 | ||
e9ce8d39 A |
225 | return (sp); |
226 | ||
227 | mem3: fts_lfree(root); | |
228 | free(parent); | |
229 | mem2: free(sp->fts_path); | |
230 | mem1: free(sp); | |
231 | return (NULL); | |
232 | } | |
233 | ||
34e8f829 | 234 | FTS * |
974e3884 | 235 | fts_open(char * const *argv, int options, int (*compar)()) |
34e8f829 | 236 | { |
974e3884 | 237 | FTS *sp; |
34e8f829 A |
238 | |
239 | /* Options check. */ | |
240 | if (options & ~FTS_OPTIONMASK) { | |
241 | errno = EINVAL; | |
242 | return (NULL); | |
243 | } | |
6465356a | 244 | if (options & FTS_NOSTAT_TYPE) options |= FTS_NOSTAT; |
34e8f829 A |
245 | |
246 | /* Allocate/initialize the stream */ | |
974e3884 | 247 | if ((sp = calloc(1, sizeof(FTS))) == NULL) |
34e8f829 | 248 | return (NULL); |
34e8f829 A |
249 | sp->fts_compar = compar; |
250 | sp->fts_options = options; | |
251 | ||
252 | return __fts_open(argv, sp); | |
253 | } | |
254 | ||
255 | #ifdef __BLOCKS__ | |
256 | FTS * | |
974e3884 | 257 | fts_open_b(char * const *argv, int options, int (^compar)(const FTSENT **, const FTSENT **)) |
34e8f829 | 258 | { |
974e3884 | 259 | FTS *sp; |
34e8f829 A |
260 | |
261 | /* Options check. */ | |
262 | if (options & ~FTS_OPTIONMASK) { | |
263 | errno = EINVAL; | |
264 | return (NULL); | |
265 | } | |
6465356a | 266 | if (options & FTS_NOSTAT_TYPE) options |= FTS_NOSTAT; |
34e8f829 A |
267 | |
268 | /* Allocate/initialize the stream */ | |
974e3884 | 269 | if ((sp = calloc(1, sizeof(FTS))) == NULL) |
34e8f829 | 270 | return (NULL); |
34e8f829 A |
271 | sp->fts_compar_b = (int (^)())Block_copy(compar); |
272 | sp->fts_options = options | FTS_BLOCK_COMPAR; | |
273 | ||
274 | return __fts_open(argv, sp); | |
275 | } | |
276 | #endif /* __BLOCKS__ */ | |
277 | ||
e9ce8d39 | 278 | static void |
974e3884 | 279 | fts_load(FTS *sp, FTSENT *p) |
e9ce8d39 | 280 | { |
974e3884 A |
281 | int len; |
282 | char *cp; | |
e9ce8d39 A |
283 | |
284 | /* | |
285 | * Load the stream structure for the next traversal. Since we don't | |
286 | * actually enter the directory until after the preorder visit, set | |
287 | * the fts_accpath field specially so the chdir gets done to the right | |
288 | * place and the user can access the first node. From fts_open it's | |
289 | * known that the path will fit. | |
290 | */ | |
291 | len = p->fts_pathlen = p->fts_namelen; | |
292 | memmove(sp->fts_path, p->fts_name, len + 1); | |
293 | if ((cp = strrchr(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) { | |
294 | len = strlen(++cp); | |
295 | memmove(p->fts_name, cp, len + 1); | |
296 | p->fts_namelen = len; | |
297 | } | |
298 | p->fts_accpath = p->fts_path = sp->fts_path; | |
299 | sp->fts_dev = p->fts_dev; | |
300 | } | |
301 | ||
302 | int | |
974e3884 | 303 | fts_close(FTS *sp) |
e9ce8d39 | 304 | { |
974e3884 A |
305 | FTSENT *freep, *p; |
306 | int rfd, error = 0; | |
e9ce8d39 A |
307 | |
308 | /* | |
309 | * This still works if we haven't read anything -- the dummy structure | |
310 | * points to the root list, so we step through to the end of the root | |
311 | * list which has a valid parent pointer. | |
312 | */ | |
313 | if (sp->fts_cur) { | |
314 | for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) { | |
315 | freep = p; | |
316 | p = p->fts_link ? p->fts_link : p->fts_parent; | |
317 | free(freep); | |
318 | } | |
319 | free(p); | |
320 | } | |
321 | ||
974e3884 A |
322 | /* Stash the original directory fd if needed. */ |
323 | rfd = ISSET(FTS_NOCHDIR) ? -1 : sp->fts_rfd; | |
324 | ||
325 | /* Free up child linked list, sort array, path buffer, stream ptr.*/ | |
326 | if (sp->fts_child){ | |
e9ce8d39 | 327 | fts_lfree(sp->fts_child); |
e9ce8d39 | 328 | } |
974e3884 A |
329 | free(sp->fts_array); sp->fts_array = NULL; |
330 | free(sp->fts_path); sp->fts_path = NULL; | |
e9ce8d39 | 331 | |
34e8f829 A |
332 | #ifdef __BLOCKS__ |
333 | /* Free up any block pointer. */ | |
334 | if (ISSET(FTS_BLOCK_COMPAR) && sp->fts_compar_b != NULL) | |
335 | Block_release(sp->fts_compar_b); | |
336 | #endif /* __BLOCKS__ */ | |
337 | ||
e9ce8d39 A |
338 | /* Free up the stream pointer. */ |
339 | free(sp); | |
340 | ||
974e3884 A |
341 | /* Return to original directory, checking for error. */ |
342 | if (rfd != -1) { | |
343 | int saved_errno = errno; | |
344 | if (fchdir(rfd) != 0){ | |
345 | error = -1; | |
346 | saved_errno = errno; | |
347 | } | |
348 | if (close(rfd) != 0){ | |
349 | error = -1; | |
350 | saved_errno = errno; | |
351 | } | |
e9ce8d39 | 352 | errno = saved_errno; |
e9ce8d39 | 353 | } |
974e3884 A |
354 | |
355 | return (error); | |
e9ce8d39 A |
356 | } |
357 | ||
358 | /* | |
359 | * Special case a root of "/" so that slashes aren't appended which would | |
360 | * cause paths to be written as "//foo". | |
361 | */ | |
362 | #define NAPPEND(p) \ | |
363 | (p->fts_level == FTS_ROOTLEVEL && p->fts_pathlen == 1 && \ | |
364 | p->fts_path[0] == '/' ? 0 : p->fts_pathlen) | |
365 | ||
366 | FTSENT * | |
974e3884 | 367 | fts_read(FTS *sp) |
e9ce8d39 | 368 | { |
974e3884 A |
369 | FTSENT *p, *tmp; |
370 | int instr; | |
371 | char *t; | |
e9ce8d39 A |
372 | int saved_errno; |
373 | ||
374 | /* If finished or unrecoverable error, return NULL. */ | |
375 | if (sp->fts_cur == NULL || ISSET(FTS_STOP)) | |
376 | return (NULL); | |
377 | ||
378 | /* Set current node pointer. */ | |
379 | p = sp->fts_cur; | |
380 | ||
381 | /* Save and zero out user instructions. */ | |
382 | instr = p->fts_instr; | |
383 | p->fts_instr = FTS_NOINSTR; | |
384 | ||
385 | /* Any type of file may be re-visited; re-stat and re-turn. */ | |
386 | if (instr == FTS_AGAIN) { | |
974e3884 | 387 | p->fts_info = fts_stat(sp, p, 0, -1); |
e9ce8d39 A |
388 | return (p); |
389 | } | |
390 | ||
391 | /* | |
392 | * Following a symlink -- SLNONE test allows application to see | |
393 | * SLNONE and recover. If indirecting through a symlink, have | |
394 | * keep a pointer to current location. If unable to get that | |
395 | * pointer, follow fails. | |
396 | */ | |
397 | if (instr == FTS_FOLLOW && | |
398 | (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) { | |
974e3884 | 399 | p->fts_info = fts_stat(sp, p, 1, -1); |
6465356a | 400 | if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { |
974e3884 A |
401 | if ((p->fts_symfd = |
402 | open(".", O_RDONLY | O_CLOEXEC)) < 0) { | |
e9ce8d39 A |
403 | p->fts_errno = errno; |
404 | p->fts_info = FTS_ERR; | |
6465356a | 405 | } else { |
e9ce8d39 | 406 | p->fts_flags |= FTS_SYMFOLLOW; |
6465356a A |
407 | } |
408 | } | |
e9ce8d39 A |
409 | return (p); |
410 | } | |
411 | ||
412 | /* Directory in pre-order. */ | |
413 | if (p->fts_info == FTS_D) { | |
414 | /* If skipped or crossed mount point, do post-order visit. */ | |
415 | if (instr == FTS_SKIP || | |
6465356a | 416 | (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) { |
e9ce8d39 A |
417 | if (p->fts_flags & FTS_SYMFOLLOW) |
418 | (void)close(p->fts_symfd); | |
419 | if (sp->fts_child) { | |
420 | fts_lfree(sp->fts_child); | |
421 | sp->fts_child = NULL; | |
422 | } | |
423 | p->fts_info = FTS_DP; | |
424 | return (p); | |
6465356a | 425 | } |
e9ce8d39 A |
426 | |
427 | /* Rebuild if only read the names and now traversing. */ | |
974e3884 A |
428 | if (sp->fts_child && ISSET(FTS_NAMEONLY)) { |
429 | CLR(FTS_NAMEONLY); | |
e9ce8d39 A |
430 | fts_lfree(sp->fts_child); |
431 | sp->fts_child = NULL; | |
432 | } | |
433 | ||
434 | /* | |
435 | * Cd to the subdirectory. | |
436 | * | |
437 | * If have already read and now fail to chdir, whack the list | |
438 | * to make the names come out right, and set the parent errno | |
439 | * so the application will eventually get an error condition. | |
440 | * Set the FTS_DONTCHDIR flag so that when we logically change | |
441 | * directories back to the parent we don't do a chdir. | |
442 | * | |
443 | * If haven't read do so. If the read fails, fts_build sets | |
444 | * FTS_STOP or the fts_info field of the node. | |
445 | */ | |
446 | if (sp->fts_child) { | |
974e3884 | 447 | if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) { |
e9ce8d39 A |
448 | p->fts_errno = errno; |
449 | p->fts_flags |= FTS_DONTCHDIR; | |
450 | for (p = sp->fts_child; p; p = p->fts_link) | |
451 | p->fts_accpath = | |
452 | p->fts_parent->fts_accpath; | |
453 | } | |
454 | } else if ((sp->fts_child = fts_build(sp, BREAD)) == NULL) { | |
455 | if (ISSET(FTS_STOP)) | |
456 | return (NULL); | |
457 | return (p); | |
458 | } | |
459 | p = sp->fts_child; | |
460 | sp->fts_child = NULL; | |
461 | goto name; | |
462 | } | |
463 | ||
464 | /* Move to the next node on this level. */ | |
465 | next: tmp = p; | |
6465356a | 466 | if ((p = p->fts_link)) { |
974e3884 A |
467 | free(tmp); |
468 | ||
e9ce8d39 | 469 | /* |
974e3884 A |
470 | * If reached the top, return to the original directory (or |
471 | * the root of the tree), and load the paths for the next root. | |
e9ce8d39 A |
472 | */ |
473 | if (p->fts_level == FTS_ROOTLEVEL) { | |
974e3884 | 474 | if (FCHDIR(sp, sp->fts_rfd)) { |
e9ce8d39 A |
475 | SET(FTS_STOP); |
476 | return (NULL); | |
477 | } | |
478 | fts_load(sp, p); | |
479 | return (sp->fts_cur = p); | |
480 | } | |
481 | ||
482 | /* | |
483 | * User may have called fts_set on the node. If skipped, | |
484 | * ignore. If followed, get a file descriptor so we can | |
485 | * get back if necessary. | |
486 | */ | |
1f2f436a | 487 | if (p->fts_instr == FTS_SKIP) { |
e9ce8d39 | 488 | goto next; |
1f2f436a | 489 | } |
e9ce8d39 | 490 | if (p->fts_instr == FTS_FOLLOW) { |
974e3884 | 491 | p->fts_info = fts_stat(sp, p, 1, -1); |
6465356a | 492 | if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { |
e9ce8d39 | 493 | if ((p->fts_symfd = |
974e3884 | 494 | open(".", O_RDONLY | O_CLOEXEC, 0)) < 0) { |
e9ce8d39 A |
495 | p->fts_errno = errno; |
496 | p->fts_info = FTS_ERR; | |
6465356a | 497 | } else { |
e9ce8d39 | 498 | p->fts_flags |= FTS_SYMFOLLOW; |
6465356a A |
499 | } |
500 | } | |
e9ce8d39 A |
501 | p->fts_instr = FTS_NOINSTR; |
502 | } | |
503 | ||
504 | name: t = sp->fts_path + NAPPEND(p->fts_parent); | |
505 | *t++ = '/'; | |
506 | memmove(t, p->fts_name, p->fts_namelen + 1); | |
507 | return (sp->fts_cur = p); | |
508 | } | |
509 | ||
510 | /* Move up to the parent node. */ | |
511 | p = tmp->fts_parent; | |
974e3884 | 512 | free(tmp); |
e9ce8d39 A |
513 | |
514 | if (p->fts_level == FTS_ROOTPARENTLEVEL) { | |
515 | /* | |
516 | * Done; free everything up and set errno to 0 so the user | |
517 | * can distinguish between error and EOF. | |
518 | */ | |
519 | free(p); | |
520 | errno = 0; | |
521 | return (sp->fts_cur = NULL); | |
522 | } | |
523 | ||
974e3884 | 524 | /* NUL terminate the pathname. */ |
e9ce8d39 A |
525 | sp->fts_path[p->fts_pathlen] = '\0'; |
526 | ||
527 | /* | |
528 | * Return to the parent directory. If at a root node or came through | |
529 | * a symlink, go back through the file descriptor. Otherwise, cd up | |
530 | * one directory. | |
531 | */ | |
532 | if (p->fts_level == FTS_ROOTLEVEL) { | |
974e3884 | 533 | if (FCHDIR(sp, sp->fts_rfd)) { |
e9ce8d39 | 534 | SET(FTS_STOP); |
974e3884 | 535 | sp->fts_cur = p; |
e9ce8d39 A |
536 | return (NULL); |
537 | } | |
538 | } else if (p->fts_flags & FTS_SYMFOLLOW) { | |
539 | if (FCHDIR(sp, p->fts_symfd)) { | |
540 | saved_errno = errno; | |
541 | (void)close(p->fts_symfd); | |
542 | errno = saved_errno; | |
543 | SET(FTS_STOP); | |
974e3884 | 544 | sp->fts_cur = p; |
e9ce8d39 A |
545 | return (NULL); |
546 | } | |
547 | (void)close(p->fts_symfd); | |
974e3884 A |
548 | } else if (!(p->fts_flags & FTS_DONTCHDIR) && |
549 | fts_safe_changedir(sp, p->fts_parent, -1, "..")) { | |
550 | SET(FTS_STOP); | |
551 | sp->fts_cur = p; | |
552 | return (NULL); | |
e9ce8d39 A |
553 | } |
554 | p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP; | |
555 | return (sp->fts_cur = p); | |
556 | } | |
557 | ||
558 | /* | |
559 | * Fts_set takes the stream as an argument although it's not used in this | |
560 | * implementation; it would be necessary if anyone wanted to add global | |
561 | * semantics to fts using fts_set. An error return is allowed for similar | |
562 | * reasons. | |
563 | */ | |
564 | /* ARGSUSED */ | |
565 | int | |
974e3884 | 566 | fts_set(FTS __unused *sp, FTSENT *p, int instr) |
e9ce8d39 A |
567 | { |
568 | if (instr && instr != FTS_AGAIN && instr != FTS_FOLLOW && | |
569 | instr != FTS_NOINSTR && instr != FTS_SKIP) { | |
570 | errno = EINVAL; | |
571 | return (1); | |
572 | } | |
573 | p->fts_instr = instr; | |
574 | return (0); | |
575 | } | |
576 | ||
577 | FTSENT * | |
974e3884 | 578 | fts_children(FTS *sp, int instr) |
e9ce8d39 | 579 | { |
974e3884 | 580 | FTSENT *p; |
e9ce8d39 A |
581 | int fd; |
582 | ||
583 | if (instr && instr != FTS_NAMEONLY) { | |
584 | errno = EINVAL; | |
585 | return (NULL); | |
586 | } | |
587 | ||
588 | /* Set current node pointer. */ | |
589 | p = sp->fts_cur; | |
590 | ||
591 | /* | |
592 | * Errno set to 0 so user can distinguish empty directory from | |
593 | * an error. | |
594 | */ | |
595 | errno = 0; | |
596 | ||
597 | /* Fatal errors stop here. */ | |
598 | if (ISSET(FTS_STOP)) | |
599 | return (NULL); | |
600 | ||
601 | /* Return logical hierarchy of user's arguments. */ | |
602 | if (p->fts_info == FTS_INIT) | |
603 | return (p->fts_link); | |
604 | ||
605 | /* | |
606 | * If not a directory being visited in pre-order, stop here. Could | |
607 | * allow FTS_DNR, assuming the user has fixed the problem, but the | |
608 | * same effect is available with FTS_AGAIN. | |
609 | */ | |
610 | if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */) | |
611 | return (NULL); | |
612 | ||
613 | /* Free up any previous child list. */ | |
614 | if (sp->fts_child) | |
615 | fts_lfree(sp->fts_child); | |
616 | ||
617 | if (instr == FTS_NAMEONLY) { | |
974e3884 | 618 | SET(FTS_NAMEONLY); |
e9ce8d39 | 619 | instr = BNAMES; |
974e3884 | 620 | } else |
e9ce8d39 A |
621 | instr = BCHILD; |
622 | ||
623 | /* | |
624 | * If using chdir on a relative path and called BEFORE fts_read does | |
625 | * its chdir to the root of a traversal, we can lose -- we need to | |
626 | * chdir into the subdirectory, and we don't know where the current | |
627 | * directory is, so we can't get back so that the upcoming chdir by | |
628 | * fts_read will work. | |
629 | */ | |
630 | if (p->fts_level != FTS_ROOTLEVEL || p->fts_accpath[0] == '/' || | |
631 | ISSET(FTS_NOCHDIR)) | |
632 | return (sp->fts_child = fts_build(sp, instr)); | |
633 | ||
974e3884 | 634 | if ((fd = open(".", O_RDONLY | O_CLOEXEC, 0)) < 0) |
e9ce8d39 A |
635 | return (NULL); |
636 | sp->fts_child = fts_build(sp, instr); | |
974e3884 A |
637 | if (fchdir(fd)) { |
638 | (void)close(fd); | |
e9ce8d39 | 639 | return (NULL); |
974e3884 | 640 | } |
e9ce8d39 A |
641 | (void)close(fd); |
642 | return (sp->fts_child); | |
643 | } | |
644 | ||
974e3884 A |
645 | typedef struct __attribute__((packed)) { |
646 | uint32_t length; | |
647 | ||
648 | /* common attributes */ | |
649 | attribute_set_t attrset; | |
650 | attrreference_t name; | |
651 | dev_t st_dev; | |
652 | fsobj_type_t objtype; | |
653 | struct timespec st_birthtimespec; | |
654 | struct timespec st_mtimespec; | |
655 | struct timespec st_ctimespec; | |
656 | struct timespec st_atimespec; | |
657 | uid_t st_uid; | |
658 | gid_t st_gid; | |
659 | uint32_t accessmask; | |
660 | uint32_t st_flags; | |
661 | uint64_t st_ino; | |
662 | ||
663 | /* non-directory attributes */ | |
664 | uint32_t st_nlink; | |
665 | off_t allocsize; | |
666 | uint32_t st_blksize; | |
667 | uint32_t st_rdev; | |
668 | off_t st_size; | |
669 | } attrListAttributes; | |
670 | ||
671 | typedef struct __attribute__((packed)) { | |
672 | uint32_t length; | |
673 | ||
674 | /* common attributes */ | |
675 | attribute_set_t attrset; | |
676 | attrreference_t name; | |
677 | dev_t st_dev; | |
678 | fsobj_type_t objtype; | |
679 | uint64_t st_ino; | |
680 | ||
681 | /* non-directory attributes */ | |
682 | uint32_t st_nlink; | |
683 | uint32_t st_rdev; | |
684 | } attrListAttributes_nostat; | |
685 | ||
686 | #define ATTR_BUF_SIZE (32*1024) | |
687 | ||
688 | typedef struct { | |
689 | DIR *dirp; | |
690 | ||
691 | struct attrlist requested_attrs; | |
692 | void *attrbuf; | |
693 | union { | |
694 | attrListAttributes *curattr; | |
695 | attrListAttributes_nostat *curattr_nostat; | |
696 | }; | |
697 | int dirfd; | |
698 | bool done; | |
699 | bool nostat; | |
700 | bool needs_dot; | |
701 | bool needs_dotdot; | |
702 | ||
703 | int entry_count; | |
704 | int cur_entry; | |
705 | } dir_handle; | |
706 | ||
707 | typedef struct { | |
708 | char *d_name; | |
709 | size_t d_namlen; | |
710 | struct stat sb; | |
711 | int d_type; | |
712 | bool stat_valid; | |
713 | } dir_entry; | |
714 | ||
715 | static bool | |
716 | advance_directory(dir_handle *handle) | |
717 | { | |
718 | if (handle->done) return true; | |
719 | ||
720 | assert(handle->dirfd != -1); | |
721 | handle->entry_count = getattrlistbulk(handle->dirfd, &handle->requested_attrs, | |
722 | handle->attrbuf, ATTR_BUF_SIZE, FSOPT_PACK_INVAL_ATTRS); | |
723 | if (handle->entry_count == -1) { | |
724 | goto error; | |
725 | } else if (handle->entry_count == 0) { | |
726 | /* No more entries. */ | |
727 | handle->done = true; | |
728 | } | |
729 | handle->cur_entry = 0; | |
730 | handle->curattr = handle->attrbuf; | |
731 | return true; | |
732 | ||
733 | error: { | |
734 | int saved_errno = errno; | |
735 | close(handle->dirfd); | |
736 | handle->dirfd = -1; | |
737 | errno = saved_errno; | |
738 | return false; | |
739 | } | |
740 | } | |
741 | ||
742 | static bool | |
743 | open_directory(FTS *sp, dir_handle *handle, const char *path) | |
744 | { | |
745 | memset(handle, 0, sizeof(*handle)); | |
746 | ||
747 | handle->nostat = ISSET(FTS_NOSTAT); | |
748 | handle->needs_dot = handle->needs_dotdot = ISSET(FTS_SEEDOT); | |
749 | ||
750 | handle->dirfd = open(path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC); | |
751 | if (handle->dirfd == -1) goto fallback; | |
752 | ||
753 | handle->attrbuf = malloc(ATTR_BUF_SIZE); | |
754 | if (!handle->attrbuf) goto fallback; | |
755 | ||
756 | handle->requested_attrs.bitmapcount = ATTR_BIT_MAP_COUNT; | |
757 | if (!handle->nostat) { | |
758 | handle->requested_attrs.commonattr = | |
759 | ATTR_CMN_RETURNED_ATTRS| | |
760 | ATTR_CMN_NAME|ATTR_CMN_DEVID|ATTR_CMN_OBJTYPE| | |
761 | ATTR_CMN_CRTIME|ATTR_CMN_MODTIME|ATTR_CMN_CHGTIME|ATTR_CMN_ACCTIME| | |
762 | ATTR_CMN_OWNERID|ATTR_CMN_GRPID|ATTR_CMN_ACCESSMASK|ATTR_CMN_FLAGS| | |
763 | ATTR_CMN_FILEID; | |
764 | handle->requested_attrs.fileattr = ATTR_FILE_LINKCOUNT|ATTR_FILE_ALLOCSIZE| | |
765 | ATTR_FILE_IOBLOCKSIZE|ATTR_FILE_DEVTYPE|ATTR_FILE_DATALENGTH; | |
766 | } else { | |
767 | handle->requested_attrs.commonattr = ATTR_CMN_RETURNED_ATTRS| | |
768 | ATTR_CMN_NAME|ATTR_CMN_DEVID|ATTR_CMN_OBJTYPE| | |
769 | ATTR_CMN_FILEID; | |
770 | handle->requested_attrs.fileattr = ATTR_FILE_LINKCOUNT; | |
771 | } | |
772 | ||
773 | if (advance_directory(handle)) { | |
774 | /* | |
775 | * We successfully read the first attribute buffer, | |
776 | * so we'll use getdirentriesattr/getattrlistbulk. | |
777 | */ | |
778 | return true; | |
779 | } | |
780 | ||
781 | fallback: | |
782 | if (handle->dirfd != -1) close(handle->dirfd); | |
783 | handle->dirfd = -1; | |
784 | free(handle->attrbuf); | |
785 | handle->attrbuf = NULL; | |
786 | handle->dirp = opendir(path); | |
787 | return (handle->dirp != NULL); | |
788 | } | |
789 | ||
790 | static bool | |
791 | read_dirent(dir_handle *handle, dir_entry *entry) | |
792 | { | |
793 | if (handle->dirp) { | |
794 | struct dirent *de = readdir(handle->dirp); | |
795 | if (de == NULL) return false; | |
796 | entry->d_name = de->d_name; | |
797 | entry->d_namlen = de->d_namlen; | |
798 | entry->d_type = de->d_type; | |
799 | entry->stat_valid = false; | |
800 | return true; | |
801 | } | |
802 | ||
803 | /* | |
804 | * There are three states that our dir_handle can be in: | |
805 | * - real getattrlistbulk use (entry_count >= 0) - the rest of this function | |
806 | * - fallback - handled above with dirp | |
807 | * - closed fallback (dirp == NULL) - handled with this check | |
808 | */ | |
809 | if (handle->dirfd == -1) { | |
810 | return false; | |
811 | } | |
812 | ||
813 | if (handle->needs_dot) { | |
814 | handle->needs_dot = false; | |
815 | entry->d_name = "."; | |
816 | entry->d_namlen = strlen("."); | |
817 | entry->d_type = DT_DIR; | |
818 | entry->stat_valid = false; | |
819 | return true; | |
820 | } else if (handle->needs_dotdot) { | |
821 | handle->needs_dotdot = false; | |
822 | entry->d_name = ".."; | |
823 | entry->d_namlen = strlen(".."); | |
824 | entry->d_type = DT_DIR; | |
825 | entry->stat_valid = false; | |
826 | return true; | |
827 | } | |
828 | ||
829 | if (handle->cur_entry == handle->entry_count) { | |
830 | if (handle->done) return false; /* Already done with the directory. */ | |
831 | if (!advance_directory(handle)) return false; /* Reading the next buffer failed. */ | |
832 | if (handle->done) return false; /* We're now done with the directory. */ | |
833 | } | |
834 | ||
835 | bzero(entry, sizeof(*entry)); | |
836 | ||
837 | attrListAttributes *curattr_stat = NULL; | |
838 | attrListAttributes_nostat *curattr_nostat = NULL; | |
839 | if (!handle->nostat) { | |
840 | curattr_stat = handle->curattr; | |
841 | handle->cur_entry++; | |
842 | handle->curattr = (attrListAttributes*)(((char*)curattr_stat) + curattr_stat->length); | |
843 | ||
844 | entry->d_name = ((char*)&curattr_stat->name) + curattr_stat->name.attr_dataoffset; | |
845 | /* attr_length includes the null terminator, but readdir's d_namlen doesn't. */ | |
846 | entry->d_namlen = curattr_stat->name.attr_length - 1; | |
847 | } else { | |
848 | curattr_nostat = handle->curattr_nostat; | |
849 | handle->cur_entry++; | |
850 | handle->curattr_nostat = (attrListAttributes_nostat*)(((char*)curattr_nostat) + curattr_nostat->length); | |
851 | ||
852 | entry->d_name = ((char*)&curattr_nostat->name) + curattr_nostat->name.attr_dataoffset; | |
853 | /* attr_length includes the null terminator, but readdir's d_namlen doesn't. */ | |
854 | entry->d_namlen = curattr_nostat->name.attr_length - 1; | |
855 | } | |
856 | ||
857 | int stat_type = 0; | |
858 | ||
859 | switch (handle->nostat ? curattr_nostat->objtype : curattr_stat->objtype) { | |
860 | case VREG: | |
861 | entry->d_type = DT_REG; | |
862 | stat_type = S_IFREG; | |
863 | break; | |
864 | case VDIR: | |
865 | entry->d_type = DT_DIR; | |
866 | /* Force a stat call so we don't have to guess on st_size, st_blocks, etc. */ | |
867 | // stat_type = S_IFDIR; | |
868 | break; | |
869 | case VBLK: | |
870 | entry->d_type = DT_BLK; | |
871 | stat_type = S_IFBLK; | |
872 | break; | |
873 | case VCHR: | |
874 | entry->d_type = DT_CHR; | |
875 | stat_type = S_IFCHR; | |
876 | break; | |
877 | case VLNK: | |
878 | entry->d_type = DT_LNK; | |
879 | stat_type = S_IFLNK; | |
880 | break; | |
881 | case VSOCK: | |
882 | entry->d_type = DT_SOCK; | |
883 | stat_type = S_IFSOCK; | |
884 | break; | |
885 | case VFIFO: | |
886 | entry->d_type = DT_FIFO; | |
887 | stat_type = S_IFIFO; | |
888 | break; | |
889 | default: | |
890 | entry->d_type = DT_UNKNOWN; | |
891 | break; | |
892 | } | |
893 | ||
894 | if (!handle->nostat && stat_type) { | |
895 | entry->stat_valid = true; | |
896 | ||
897 | /* | |
898 | * Make sure we got all the attributes we need to fill out a stat structure. | |
899 | */ | |
900 | ||
901 | attrgroup_t requiredCommon = handle->requested_attrs.commonattr; | |
902 | attrgroup_t requiredFile = handle->requested_attrs.fileattr; | |
903 | ||
904 | if ((entry->d_type != DT_BLK) && (entry->d_type != DT_CHR)) { | |
905 | /* It's okay for ATTR_FILE_DEVTYPE to be missing if the entry isn't a block or character device. */ | |
906 | curattr_stat->st_rdev = 0; | |
907 | requiredFile &= ~ATTR_FILE_DEVTYPE; | |
908 | } | |
909 | ||
910 | if ((curattr_stat->attrset.commonattr & requiredCommon) != requiredCommon || | |
911 | (curattr_stat->attrset.fileattr & requiredFile) != requiredFile) { | |
912 | /* Some of our required attributes are missing. */ | |
913 | entry->stat_valid = false; | |
914 | } | |
915 | } else { | |
916 | entry->stat_valid = false; | |
917 | } | |
918 | ||
919 | if (entry->stat_valid) { | |
920 | ||
921 | #define COPY_FIELD(fld) entry->sb.fld = curattr_stat->fld | |
922 | COPY_FIELD(st_dev); | |
923 | /* COPY_FIELD(st_mode); Handled below. */ | |
924 | COPY_FIELD(st_nlink); | |
925 | COPY_FIELD(st_ino); | |
926 | COPY_FIELD(st_uid); | |
927 | COPY_FIELD(st_gid); | |
928 | COPY_FIELD(st_rdev); | |
929 | COPY_FIELD(st_atimespec); | |
930 | COPY_FIELD(st_mtimespec); | |
931 | COPY_FIELD(st_ctimespec); | |
932 | #if __DARWIN_64_BIT_INO_T | |
933 | COPY_FIELD(st_birthtimespec); | |
934 | #endif /* __DARWIN_64_BIT_INO_T */ | |
935 | COPY_FIELD(st_size); | |
936 | /* COPY_FIELD(st_blocks); Handled below. */ | |
937 | COPY_FIELD(st_blksize); | |
938 | COPY_FIELD(st_flags); | |
939 | #undef COPY_FIELD | |
940 | ||
941 | /* We have to handle some fields specially. */ | |
942 | entry->sb.st_mode = (curattr_stat->accessmask & ~S_IFMT) | stat_type; | |
943 | entry->sb.st_blocks = howmany(curattr_stat->allocsize, 512); /* Matches vn_stat implementation in xnu. */ | |
944 | } | |
945 | return true; | |
946 | } | |
947 | ||
948 | static int | |
949 | dir_fd(dir_handle *handle) | |
950 | { | |
951 | return handle->dirp ? dirfd(handle->dirp) : handle->dirfd; | |
952 | } | |
953 | ||
954 | static void | |
955 | close_directory(dir_handle *handle) | |
956 | { | |
957 | if (handle->dirp) { | |
958 | closedir(handle->dirp); | |
959 | handle->dirp = NULL; | |
960 | } | |
961 | if (handle->dirfd != -1) { | |
962 | close(handle->dirfd); | |
963 | handle->dirfd = -1; | |
964 | } | |
965 | free(handle->attrbuf); | |
966 | handle->attrbuf = NULL; | |
967 | } | |
968 | ||
e9ce8d39 A |
969 | /* |
970 | * This is the tricky part -- do not casually change *anything* in here. The | |
971 | * idea is to build the linked list of entries that are used by fts_children | |
972 | * and fts_read. There are lots of special cases. | |
973 | * | |
974 | * The real slowdown in walking the tree is the stat calls. If FTS_NOSTAT is | |
34e8f829 A |
975 | * set, we can use d_type to determine if the entry is a directory (or for |
976 | * logical walks, a directory or symlink) and not call stat for other file | |
977 | * types. This cuts the number of stat calls significantly. | |
e9ce8d39 A |
978 | */ |
979 | static FTSENT * | |
974e3884 | 980 | fts_build(FTS *sp, int type) |
e9ce8d39 | 981 | { |
974e3884 A |
982 | dir_entry de = { 0 }; |
983 | dir_entry *dp = &de; | |
984 | FTSENT *p, *head; | |
985 | int nitems; | |
e9ce8d39 | 986 | FTSENT *cur, *tail; |
974e3884 A |
987 | dir_handle dirp; |
988 | void *oldaddr; | |
989 | int len, maxlen; | |
990 | int cderrno, descend, level, dostat, doadjust; | |
991 | int saved_errno; | |
992 | char *cp; | |
e9ce8d39 A |
993 | |
994 | /* Set current node pointer. */ | |
995 | cur = sp->fts_cur; | |
996 | ||
997 | /* | |
998 | * Open the directory for reading. If this fails, we're done. | |
999 | * If being called from fts_read, set the fts_info field. | |
1000 | */ | |
974e3884 | 1001 | if (!open_directory(sp, &dirp, cur->fts_accpath)) { |
e9ce8d39 A |
1002 | if (type == BREAD) { |
1003 | cur->fts_info = FTS_DNR; | |
1004 | cur->fts_errno = errno; | |
1005 | } | |
1006 | return (NULL); | |
1007 | } | |
1008 | ||
e9ce8d39 | 1009 | if (type == BNAMES) |
34e8f829 | 1010 | dostat = F_NOSTAT; |
6465356a A |
1011 | else if (ISSET(FTS_NOSTAT_TYPE)) |
1012 | dostat = ISSET(FTS_PHYSICAL) ? F_D_TYPE : F_D_TYPESYM; | |
34e8f829 A |
1013 | else if (ISSET(FTS_NOSTAT)) |
1014 | dostat = ISSET(FTS_PHYSICAL) ? F_STATDIR : F_STATDIRSYM; | |
e9ce8d39 | 1015 | else |
34e8f829 | 1016 | dostat = F_ALWAYSSTAT; |
e9ce8d39 A |
1017 | |
1018 | #ifdef notdef | |
34e8f829 | 1019 | (void)printf("dostat == %d\n", dostat); |
e9ce8d39 A |
1020 | (void)printf("NOSTAT %d PHYSICAL %d SEEDOT %d\n", |
1021 | ISSET(FTS_NOSTAT), ISSET(FTS_PHYSICAL), ISSET(FTS_SEEDOT)); | |
1022 | #endif | |
1023 | /* | |
1024 | * If we're going to need to stat anything or we want to descend | |
1025 | * and stay in the directory, chdir. If this fails we keep going, | |
1026 | * but set a flag so we don't chdir after the post-order visit. | |
1027 | * We won't be able to stat anything, but we can still return the | |
1028 | * names themselves. Note, that since fts_read won't be able to | |
1029 | * chdir into the directory, it will have to return different path | |
1030 | * names than before, i.e. "a/b" instead of "b". Since the node | |
1031 | * has already been visited in pre-order, have to wait until the | |
1032 | * post-order visit to return the error. There is a special case | |
1033 | * here, if there was nothing to stat then it's not an error to | |
1034 | * not be able to stat. This is all fairly nasty. If a program | |
1035 | * needed sorted entries or stat information, they had better be | |
1036 | * checking FTS_NS on the returned nodes. | |
1037 | */ | |
1038 | cderrno = 0; | |
974e3884 A |
1039 | if (dostat || type == BREAD) { |
1040 | if (fts_safe_changedir(sp, cur, dir_fd(&dirp), NULL)) { | |
1041 | if (dostat && type == BREAD) { | |
e9ce8d39 | 1042 | cur->fts_errno = errno; |
974e3884 | 1043 | } |
e9ce8d39 A |
1044 | cur->fts_flags |= FTS_DONTCHDIR; |
1045 | descend = 0; | |
1046 | cderrno = errno; | |
974e3884 A |
1047 | close_directory(&dirp); |
1048 | } else { | |
e9ce8d39 | 1049 | descend = 1; |
974e3884 A |
1050 | } |
1051 | } else { | |
e9ce8d39 | 1052 | descend = 0; |
974e3884 | 1053 | } |
e9ce8d39 A |
1054 | |
1055 | /* | |
1056 | * Figure out the max file name length that can be stored in the | |
1057 | * current path -- the inner loop allocates more path as necessary. | |
1058 | * We really wouldn't have to do the maxlen calculations here, we | |
1059 | * could do them in fts_read before returning the path, but it's a | |
1060 | * lot easier here since the length is part of the dirent structure. | |
1061 | * | |
1062 | * If not changing directories set a pointer so that can just append | |
1063 | * each new name into the path. | |
1064 | */ | |
e9ce8d39 A |
1065 | len = NAPPEND(cur); |
1066 | if (ISSET(FTS_NOCHDIR)) { | |
1067 | cp = sp->fts_path + len; | |
1068 | *cp++ = '/'; | |
1069 | } | |
2be56ee9 A |
1070 | len++; |
1071 | maxlen = sp->fts_pathlen - len; | |
e9ce8d39 | 1072 | |
974e3884 A |
1073 | /* |
1074 | * fts_level is signed so we must prevent it from wrapping | |
1075 | * around to FTS_ROOTLEVEL and FTS_ROOTPARENTLEVEL. | |
1076 | */ | |
1077 | level = cur->fts_level; | |
1078 | if (level < FTS_MAXLEVEL) | |
1079 | level++; | |
e9ce8d39 A |
1080 | |
1081 | /* Read the directory, attaching each entry to the `link' pointer. */ | |
974e3884 A |
1082 | doadjust = 0; |
1083 | for (head = tail = NULL, nitems = 0; read_dirent(&dirp, dp) ; ) { | |
e9ce8d39 A |
1084 | if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name)) |
1085 | continue; | |
1086 | ||
1087 | if ((p = fts_alloc(sp, dp->d_name, (int)dp->d_namlen)) == NULL) | |
1088 | goto mem1; | |
974e3884 A |
1089 | if (dp->d_namlen >= (size_t)maxlen) { /* include space for NUL */ |
1090 | oldaddr = sp->fts_path; | |
1091 | if (fts_palloc(sp, dp->d_namlen +len + 1)) { | |
e9ce8d39 A |
1092 | /* |
1093 | * No more memory for path or structures. Save | |
1094 | * errno, free up the current structure and the | |
1095 | * structures already allocated. | |
1096 | */ | |
1097 | mem1: saved_errno = errno; | |
974e3884 | 1098 | free(p); |
e9ce8d39 | 1099 | fts_lfree(head); |
974e3884 | 1100 | close_directory(&dirp); |
e9ce8d39 A |
1101 | cur->fts_info = FTS_ERR; |
1102 | SET(FTS_STOP); | |
2be56ee9 | 1103 | errno = saved_errno; |
e9ce8d39 A |
1104 | return (NULL); |
1105 | } | |
974e3884 A |
1106 | /* Did realloc() change the pointer? */ |
1107 | if (oldaddr != sp->fts_path) { | |
1108 | doadjust = 1; | |
1109 | if (ISSET(FTS_NOCHDIR)) | |
1110 | cp = sp->fts_path + len; | |
1111 | } | |
1112 | maxlen = sp->fts_pathlen - len; | |
e9ce8d39 A |
1113 | } |
1114 | ||
974e3884 A |
1115 | if (len + dp->d_namlen >= USHRT_MAX) { |
1116 | /* | |
1117 | * In an FTSENT, fts_pathlen is a u_short so it is | |
1118 | * possible to wraparound here. If we do, free up | |
1119 | * the current structure and the structures already | |
1120 | * allocated, then error out with ENAMETOOLONG. | |
1121 | */ | |
1122 | free(p); | |
1123 | fts_lfree(head); | |
1124 | close_directory(&dirp); | |
1125 | cur->fts_info = FTS_ERR; | |
1126 | SET(FTS_STOP); | |
1127 | errno = ENAMETOOLONG; | |
1128 | return (NULL); | |
1129 | } | |
e9ce8d39 | 1130 | p->fts_level = level; |
2be56ee9 A |
1131 | p->fts_parent = sp->fts_cur; |
1132 | p->fts_pathlen = len + dp->d_namlen; | |
e9ce8d39 | 1133 | |
e9ce8d39 | 1134 | if (cderrno) { |
34e8f829 | 1135 | if (dostat) { |
e9ce8d39 A |
1136 | p->fts_info = FTS_NS; |
1137 | p->fts_errno = cderrno; | |
1138 | } else | |
1139 | p->fts_info = FTS_NSOK; | |
1140 | p->fts_accpath = cur->fts_accpath; | |
e9ce8d39 | 1141 | } else { |
34e8f829 A |
1142 | /* |
1143 | * We need to know all file types values that d_type may | |
1144 | * be set to. So if that changes, the following needs | |
1145 | * to be modified appropriately. | |
1146 | */ | |
1147 | switch(dostat | dp->d_type) { | |
1148 | case (F_STATDIR | DT_UNKNOWN): | |
1149 | case (F_STATDIR | DT_DIR): | |
1150 | case (F_STATDIRSYM | DT_UNKNOWN): | |
1151 | case (F_STATDIRSYM | DT_DIR): | |
1152 | case (F_STATDIRSYM | DT_LNK): | |
1153 | case (F_ALWAYSSTAT | DT_UNKNOWN): | |
1154 | case (F_ALWAYSSTAT | DT_FIFO): | |
1155 | case (F_ALWAYSSTAT | DT_CHR): | |
1156 | case (F_ALWAYSSTAT | DT_DIR): | |
1157 | case (F_ALWAYSSTAT | DT_BLK): | |
1158 | case (F_ALWAYSSTAT | DT_REG): | |
1159 | case (F_ALWAYSSTAT | DT_LNK): | |
1160 | case (F_ALWAYSSTAT | DT_SOCK): | |
1161 | case (F_ALWAYSSTAT | DT_WHT): | |
6465356a A |
1162 | case (F_D_TYPE | DT_UNKNOWN): |
1163 | case (F_D_TYPE | DT_DIR): | |
1164 | case (F_D_TYPESYM | DT_UNKNOWN): | |
1165 | case (F_D_TYPESYM | DT_DIR): | |
1166 | case (F_D_TYPESYM | DT_LNK): | |
34e8f829 A |
1167 | /* Build a file name for fts_stat to stat. */ |
1168 | if (ISSET(FTS_NOCHDIR)) { | |
1169 | p->fts_accpath = p->fts_path; | |
1170 | memmove(cp, p->fts_name, p->fts_namelen + 1); | |
974e3884 A |
1171 | p->fts_info = fts_stat2(sp, p, 0, dir_fd(&dirp), dp->stat_valid ? &dp->sb : NULL); |
1172 | } else { | |
34e8f829 | 1173 | p->fts_accpath = p->fts_name; |
974e3884 A |
1174 | p->fts_info = fts_stat2(sp, p, 0, -1, dp->stat_valid ? &dp->sb : NULL); |
1175 | } | |
34e8f829 | 1176 | break; |
6465356a A |
1177 | case (F_D_TYPE | DT_FIFO): |
1178 | case (F_D_TYPE | DT_CHR): | |
1179 | case (F_D_TYPE | DT_BLK): | |
1180 | case (F_D_TYPE | DT_SOCK): | |
1181 | case (F_D_TYPESYM | DT_FIFO): | |
1182 | case (F_D_TYPESYM | DT_CHR): | |
1183 | case (F_D_TYPESYM | DT_BLK): | |
1184 | case (F_D_TYPESYM | DT_SOCK): | |
1185 | p->fts_info = FTS_DEFAULT; | |
1186 | goto common_no_stat; | |
1187 | case (F_D_TYPE | DT_REG): | |
1188 | case (F_D_TYPESYM | DT_REG): | |
1189 | p->fts_info = FTS_F; | |
1190 | goto common_no_stat; | |
1191 | case (F_D_TYPE | DT_LNK): | |
1192 | p->fts_info = FTS_SL; | |
1193 | goto common_no_stat; | |
1194 | case (F_D_TYPE | DT_WHT): | |
1195 | case (F_D_TYPESYM | DT_WHT): | |
1196 | p->fts_info = FTS_W; | |
1197 | goto common_no_stat; | |
34e8f829 A |
1198 | default: |
1199 | /* No stat necessary */ | |
6465356a A |
1200 | p->fts_info = FTS_NSOK; |
1201 | common_no_stat: | |
34e8f829 A |
1202 | p->fts_accpath = |
1203 | ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name; | |
34e8f829 A |
1204 | break; |
1205 | } | |
e9ce8d39 A |
1206 | } |
1207 | ||
1208 | /* We walk in directory order so "ls -f" doesn't get upset. */ | |
1209 | p->fts_link = NULL; | |
1210 | if (head == NULL) | |
1211 | head = tail = p; | |
1212 | else { | |
1213 | tail->fts_link = p; | |
1214 | tail = p; | |
1215 | } | |
1216 | ++nitems; | |
1217 | } | |
974e3884 | 1218 | close_directory(&dirp); |
e9ce8d39 A |
1219 | |
1220 | /* | |
974e3884 A |
1221 | * If realloc() changed the address of the path, adjust the |
1222 | * addresses for the rest of the tree and the dir list. | |
e9ce8d39 | 1223 | */ |
974e3884 A |
1224 | if (doadjust) |
1225 | fts_padjust(sp, head); | |
e9ce8d39 A |
1226 | |
1227 | /* | |
1228 | * If not changing directories, reset the path back to original | |
1229 | * state. | |
1230 | */ | |
1231 | if (ISSET(FTS_NOCHDIR)) { | |
974e3884 | 1232 | if (len == sp->fts_pathlen || nitems == 0) |
e9ce8d39 A |
1233 | --cp; |
1234 | *cp = '\0'; | |
1235 | } | |
1236 | ||
1237 | /* | |
1238 | * If descended after called from fts_children or after called from | |
1239 | * fts_read and nothing found, get back. At the root level we use | |
1240 | * the saved fd; if one of fts_open()'s arguments is a relative path | |
1241 | * to an empty directory, we wind up here with no other way back. If | |
1242 | * can't get back, we're done. | |
1243 | */ | |
1244 | if (descend && (type == BCHILD || !nitems) && | |
974e3884 A |
1245 | (cur->fts_level == FTS_ROOTLEVEL ? FCHDIR(sp, sp->fts_rfd) : |
1246 | fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) { | |
e9ce8d39 A |
1247 | cur->fts_info = FTS_ERR; |
1248 | SET(FTS_STOP); | |
1249 | return (NULL); | |
1250 | } | |
1251 | ||
1252 | /* If didn't find anything, return NULL. */ | |
1253 | if (!nitems) { | |
1254 | if (type == BREAD) | |
1255 | cur->fts_info = FTS_DP; | |
1256 | return (NULL); | |
1257 | } | |
1258 | ||
1259 | /* Sort the entries. */ | |
1260 | if (sp->fts_compar && nitems > 1) | |
1261 | head = fts_sort(sp, head, nitems); | |
1262 | return (head); | |
1263 | } | |
1264 | ||
1265 | static u_short | |
974e3884 A |
1266 | fts_stat(FTS *sp, FTSENT *p, int follow, int dfd) |
1267 | { | |
1268 | return fts_stat2(sp, p, follow, dfd, NULL); | |
1269 | } | |
1270 | ||
1271 | static u_short | |
1272 | fts_stat2(FTS *sp, FTSENT *p, int follow, int dfd, struct stat *insb) | |
e9ce8d39 | 1273 | { |
974e3884 A |
1274 | FTSENT *t; |
1275 | dev_t dev; | |
1276 | ino_t ino; | |
e9ce8d39 A |
1277 | struct stat *sbp, sb; |
1278 | int saved_errno; | |
974e3884 A |
1279 | const char *path; |
1280 | ||
1281 | if (dfd == -1) { | |
1282 | path = p->fts_accpath; | |
1283 | dfd = AT_FDCWD; | |
1284 | } else { | |
1285 | path = p->fts_name; | |
1286 | } | |
e9ce8d39 A |
1287 | |
1288 | /* If user needs stat info, stat buffer already allocated. */ | |
1289 | sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp; | |
1290 | ||
e9ce8d39 | 1291 | /* |
974e3884 A |
1292 | * getattrlistbulk always returns information about the link, not its |
1293 | * destination, so if we are doing a logical walk we need to detect links | |
1294 | * and do a stat anyway. | |
e9ce8d39 | 1295 | */ |
974e3884 A |
1296 | if (follow) { |
1297 | insb = NULL; | |
1298 | } else if (insb && ISSET(FTS_LOGICAL) && ((insb->st_mode & S_IFLNK) == S_IFLNK)) { | |
1299 | insb = NULL; | |
1300 | } | |
1301 | ||
1302 | if (insb) { | |
1303 | memcpy(sbp, insb, sizeof(*insb)); | |
1304 | } else if (ISSET(FTS_LOGICAL) || follow) { | |
1305 | /* | |
1306 | * If doing a logical walk, or application requested FTS_FOLLOW, do | |
1307 | * a stat(2). If that fails, check for a non-existent symlink. If | |
1308 | * fail, set the errno from the stat call. | |
1309 | */ | |
1310 | if (fstatat(dfd, path, sbp, 0)) { | |
e9ce8d39 | 1311 | saved_errno = errno; |
974e3884 | 1312 | if (!fstatat(dfd, path, sbp, AT_SYMLINK_NOFOLLOW)) { |
224c7076 A |
1313 | if (saved_errno == ELOOP) |
1314 | p->fts_errno = ELOOP; | |
e9ce8d39 A |
1315 | errno = 0; |
1316 | return (FTS_SLNONE); | |
974e3884 | 1317 | } |
e9ce8d39 A |
1318 | p->fts_errno = saved_errno; |
1319 | goto err; | |
1320 | } | |
224c7076 A |
1321 | /* |
1322 | * For FTS_COMFOLLOWDIR, drop back to lstat unless we have | |
1323 | * a directory | |
1324 | */ | |
1325 | if (follow == -1 && !S_ISDIR(sbp->st_mode)) { | |
974e3884 | 1326 | if (fstatat(dfd, path, sbp, AT_SYMLINK_NOFOLLOW)) { |
224c7076 A |
1327 | p->fts_errno = errno; |
1328 | goto err; | |
1329 | } | |
1330 | } | |
974e3884 | 1331 | } else if (fstatat(dfd, path, sbp, AT_SYMLINK_NOFOLLOW)) { |
e9ce8d39 A |
1332 | p->fts_errno = errno; |
1333 | err: memset(sbp, 0, sizeof(struct stat)); | |
1334 | return (FTS_NS); | |
1335 | } | |
1336 | ||
1337 | if (S_ISDIR(sbp->st_mode)) { | |
1338 | /* | |
1339 | * Set the device/inode. Used to find cycles and check for | |
1340 | * crossing mount points. Also remember the link count, used | |
1341 | * in fts_build to limit the number of stat calls. It is | |
1342 | * understood that these fields are only referenced if fts_info | |
1343 | * is set to FTS_D. | |
1344 | */ | |
1345 | dev = p->fts_dev = sbp->st_dev; | |
1346 | ino = p->fts_ino = sbp->st_ino; | |
1347 | p->fts_nlink = sbp->st_nlink; | |
1348 | ||
1349 | if (ISDOT(p->fts_name)) | |
1350 | return (FTS_DOT); | |
1351 | ||
1352 | /* | |
1353 | * Cycle detection is done by brute force when the directory | |
1354 | * is first encountered. If the tree gets deep enough or the | |
1355 | * number of symbolic links to directories is high enough, | |
1356 | * something faster might be worthwhile. | |
1357 | */ | |
1358 | for (t = p->fts_parent; | |
1359 | t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent) | |
1360 | if (ino == t->fts_ino && dev == t->fts_dev) { | |
1361 | p->fts_cycle = t; | |
1362 | return (FTS_DC); | |
1363 | } | |
1364 | return (FTS_D); | |
1365 | } | |
1366 | if (S_ISLNK(sbp->st_mode)) | |
1367 | return (FTS_SL); | |
1368 | if (S_ISREG(sbp->st_mode)) | |
1369 | return (FTS_F); | |
1370 | return (FTS_DEFAULT); | |
1371 | } | |
1372 | ||
1373 | static FTSENT * | |
974e3884 | 1374 | fts_sort(FTS *sp, FTSENT *head, int nitems) |
e9ce8d39 | 1375 | { |
974e3884 | 1376 | FTSENT **ap, *p; |
e9ce8d39 A |
1377 | |
1378 | /* | |
1379 | * Construct an array of pointers to the structures and call qsort(3). | |
1380 | * Reassemble the array in the order returned by qsort. If unable to | |
1381 | * sort for memory reasons, return the directory entries in their | |
1382 | * current order. Allocate enough space for the current needs plus | |
1383 | * 40 so don't realloc one entry at a time. | |
1384 | */ | |
1385 | if (nitems > sp->fts_nitems) { | |
974e3884 A |
1386 | struct _ftsent **a; |
1387 | ||
e9ce8d39 | 1388 | sp->fts_nitems = nitems + 40; |
974e3884 A |
1389 | if ((a = reallocarray(sp->fts_array, |
1390 | sp->fts_nitems, sizeof(FTSENT *))) == NULL) { | |
1391 | free(sp->fts_array); | |
1392 | sp->fts_array = NULL; | |
e9ce8d39 A |
1393 | sp->fts_nitems = 0; |
1394 | return (head); | |
1395 | } | |
974e3884 | 1396 | sp->fts_array = a; |
e9ce8d39 A |
1397 | } |
1398 | for (ap = sp->fts_array, p = head; p; p = p->fts_link) | |
1399 | *ap++ = p; | |
34e8f829 A |
1400 | #ifdef __BLOCKS__ |
1401 | if (ISSET(FTS_BLOCK_COMPAR)) | |
1402 | qsort_b((void *)sp->fts_array, nitems, sizeof(FTSENT *), (int (^)(const void *, const void *))sp->fts_compar_b); | |
1403 | else | |
1404 | #endif /* __BLOCKS__ */ | |
1405 | qsort((void *)sp->fts_array, nitems, sizeof(FTSENT *), sp->fts_compar); | |
e9ce8d39 A |
1406 | for (head = *(ap = sp->fts_array); --nitems; ++ap) |
1407 | ap[0]->fts_link = ap[1]; | |
1408 | ap[0]->fts_link = NULL; | |
1409 | return (head); | |
1410 | } | |
1411 | ||
1412 | static FTSENT * | |
974e3884 | 1413 | fts_alloc(FTS *sp, char *name, int namelen) |
e9ce8d39 | 1414 | { |
974e3884 | 1415 | FTSENT *p; |
e9ce8d39 A |
1416 | size_t len; |
1417 | ||
1418 | /* | |
1419 | * The file name is a variable length array and no stat structure is | |
1420 | * necessary if the user has set the nostat bit. Allocate the FTSENT | |
1421 | * structure, the file name and the stat structure in one chunk, but | |
1422 | * be careful that the stat structure is reasonably aligned. Since the | |
1423 | * fts_name field is declared to be of size 1, the fts_name pointer is | |
1424 | * namelen + 2 before the first possible address of the stat structure. | |
1425 | */ | |
1426 | len = sizeof(FTSENT) + namelen; | |
1427 | if (!ISSET(FTS_NOSTAT)) | |
1428 | len += sizeof(struct stat) + ALIGNBYTES; | |
974e3884 | 1429 | if ((p = calloc(1, len)) == NULL) |
e9ce8d39 A |
1430 | return (NULL); |
1431 | ||
e9ce8d39 | 1432 | p->fts_path = sp->fts_path; |
974e3884 | 1433 | p->fts_namelen = namelen; |
e9ce8d39 | 1434 | p->fts_instr = FTS_NOINSTR; |
974e3884 A |
1435 | if (!ISSET(FTS_NOSTAT)) |
1436 | p->fts_statp = (struct stat *)ALIGN(p->fts_name + namelen + 2); | |
1437 | memcpy(p->fts_name, name, namelen); | |
1438 | ||
e9ce8d39 A |
1439 | return (p); |
1440 | } | |
1441 | ||
1442 | static void | |
974e3884 | 1443 | fts_lfree(FTSENT *head) |
e9ce8d39 | 1444 | { |
974e3884 | 1445 | FTSENT *p; |
e9ce8d39 A |
1446 | |
1447 | /* Free a linked list of structures. */ | |
6465356a | 1448 | while ((p = head)) { |
e9ce8d39 A |
1449 | head = head->fts_link; |
1450 | free(p); | |
1451 | } | |
1452 | } | |
1453 | ||
1454 | /* | |
1455 | * Allow essentially unlimited paths; find, rm, ls should all work on any tree. | |
974e3884 | 1456 | * Most systems will allow creation of paths much longer than PATH_MAX, even |
e9ce8d39 | 1457 | * though the kernel won't resolve them. Add the size (not just what's needed) |
6465356a | 1458 | * plus 256 bytes so don't realloc the path 2 bytes at a time. |
e9ce8d39 A |
1459 | */ |
1460 | static int | |
974e3884 | 1461 | fts_palloc(FTS *sp, size_t more) |
e9ce8d39 | 1462 | { |
974e3884 A |
1463 | char *p; |
1464 | ||
e9ce8d39 | 1465 | sp->fts_pathlen += more + 256; |
974e3884 A |
1466 | /* |
1467 | * Check for possible wraparound. In an FTS, fts_pathlen is | |
1468 | * a signed int but in an FTSENT it is an unsigned short. | |
1469 | * We limit fts_pathlen to USHRT_MAX to be safe in both cases. | |
1470 | */ | |
1471 | if (sp->fts_pathlen < 0 || sp->fts_pathlen >= USHRT_MAX) { | |
1472 | free(sp->fts_path); | |
1473 | sp->fts_path = NULL; | |
1474 | errno = ENAMETOOLONG; | |
1475 | return (1); | |
1476 | } | |
1477 | p = realloc(sp->fts_path, sp->fts_pathlen); | |
1478 | if (p == NULL) { | |
1479 | free(sp->fts_path); | |
1480 | sp->fts_path = NULL; | |
1481 | return (1); | |
1482 | } | |
1483 | sp->fts_path = p; | |
1484 | return (0); | |
e9ce8d39 A |
1485 | } |
1486 | ||
1487 | /* | |
1488 | * When the path is realloc'd, have to fix all of the pointers in structures | |
1489 | * already returned. | |
1490 | */ | |
1491 | static void | |
974e3884 | 1492 | fts_padjust(FTS *sp, FTSENT *head) |
e9ce8d39 A |
1493 | { |
1494 | FTSENT *p; | |
974e3884 | 1495 | char *addr = sp->fts_path; |
e9ce8d39 | 1496 | |
974e3884 | 1497 | #define ADJUST(p) { \ |
2be56ee9 A |
1498 | if ((p)->fts_accpath != (p)->fts_name) { \ |
1499 | (p)->fts_accpath = \ | |
1500 | (char *)addr + ((p)->fts_accpath - (p)->fts_path); \ | |
1501 | } \ | |
e9ce8d39 | 1502 | (p)->fts_path = addr; \ |
974e3884 | 1503 | } |
e9ce8d39 A |
1504 | /* Adjust the current set of children. */ |
1505 | for (p = sp->fts_child; p; p = p->fts_link) | |
1506 | ADJUST(p); | |
1507 | ||
974e3884 A |
1508 | /* Adjust the rest of the tree, including the current level. */ |
1509 | for (p = head; p->fts_level >= FTS_ROOTLEVEL;) { | |
e9ce8d39 A |
1510 | ADJUST(p); |
1511 | p = p->fts_link ? p->fts_link : p->fts_parent; | |
1512 | } | |
1513 | } | |
1514 | ||
1515 | static size_t | |
974e3884 | 1516 | fts_maxarglen(char * const *argv) |
e9ce8d39 A |
1517 | { |
1518 | size_t len, max; | |
1519 | ||
1520 | for (max = 0; *argv; ++argv) | |
1521 | if ((len = strlen(*argv)) > max) | |
1522 | max = len; | |
2be56ee9 | 1523 | return (max + 1); |
e9ce8d39 | 1524 | } |
974e3884 A |
1525 | |
1526 | /* | |
1527 | * Change to dir specified by fd or p->fts_accpath without getting | |
1528 | * tricked by someone changing the world out from underneath us. | |
1529 | * Assumes p->fts_dev and p->fts_ino are filled in. | |
1530 | */ | |
1531 | static int | |
1532 | fts_safe_changedir(FTS *sp, FTSENT *p, int fd, char *path) | |
1533 | { | |
1534 | int ret, oerrno, newfd; | |
1535 | struct stat sb; | |
1536 | ||
1537 | newfd = fd; | |
1538 | if (ISSET(FTS_NOCHDIR)) | |
1539 | return (0); | |
1540 | if (fd < 0 && (newfd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC)) < 0) | |
1541 | return (-1); | |
1542 | if (fstat(newfd, &sb)) { | |
1543 | ret = -1; | |
1544 | goto bail; | |
1545 | } | |
1546 | if (p->fts_dev != sb.st_dev || p->fts_ino != sb.st_ino) { | |
1547 | errno = ENOENT; /* disinformation */ | |
1548 | ret = -1; | |
1549 | goto bail; | |
1550 | } | |
1551 | ret = fchdir(newfd); | |
1552 | bail: | |
1553 | oerrno = errno; | |
1554 | if (fd < 0) | |
1555 | (void)close(newfd); | |
1556 | errno = oerrno; | |
1557 | return (ret); | |
1558 | } |