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