fts-compat.c revision 54770
1220137Strasz/*- 2220137Strasz * Copyright (c) 1990, 1993, 1994 3220137Strasz * The Regents of the University of California. All rights reserved. 4220137Strasz * 5220137Strasz * Redistribution and use in source and binary forms, with or without 6220137Strasz * modification, are permitted provided that the following conditions 7220137Strasz * are met: 8220137Strasz * 1. Redistributions of source code must retain the above copyright 9220137Strasz * notice, this list of conditions and the following disclaimer. 10220137Strasz * 2. Redistributions in binary form must reproduce the above copyright 11220137Strasz * notice, this list of conditions and the following disclaimer in the 12220137Strasz * documentation and/or other materials provided with the distribution. 13220137Strasz * 3. All advertising materials mentioning features or use of this software 14220137Strasz * must display the following acknowledgement: 15220137Strasz * This product includes software developed by the University of 16220137Strasz * California, Berkeley and its contributors. 17220137Strasz * 4. Neither the name of the University nor the names of its contributors 18220137Strasz * may be used to endorse or promote products derived from this software 19220137Strasz * without specific prior written permission. 20220137Strasz * 21220137Strasz * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22220137Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23220137Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24220137Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25220137Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26220137Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27220137Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28220137Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29220137Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30220137Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31220137Strasz * SUCH DAMAGE. 32220137Strasz * 33220137Strasz * $OpenBSD: fts.c,v 1.22 1999/10/03 19:22:22 millert Exp $ 34220137Strasz */ 35220137Strasz 36220137Strasz#if defined(LIBC_SCCS) && !defined(lint) 37220137Strasz#if 0 38220137Straszstatic char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94"; 39220137Strasz#else 40220137Straszstatic char rcsid[] = "$FreeBSD: head/lib/libc/gen/fts-compat.c 54770 1999-12-18 04:36:14Z green $"; 41220137Strasz#endif 42220137Strasz#endif /* LIBC_SCCS and not lint */ 43220137Strasz 44220137Strasz#include <sys/param.h> 45220137Strasz#include <sys/stat.h> 46220137Strasz 47220137Strasz#include <dirent.h> 48220137Strasz#include <errno.h> 49220137Strasz#include <fcntl.h> 50220137Strasz#include <fts.h> 51220137Strasz#include <stdlib.h> 52220137Strasz#include <string.h> 53220137Strasz#include <unistd.h> 54220137Strasz 55220137Straszstatic FTSENT *fts_alloc __P((FTS *, char *, int)); 56220137Straszstatic FTSENT *fts_build __P((FTS *, int)); 57220137Straszstatic void fts_lfree __P((FTSENT *)); 58220137Straszstatic void fts_load __P((FTS *, FTSENT *)); 59220137Straszstatic size_t fts_maxarglen __P((char * const *)); 60220137Straszstatic void fts_padjust __P((FTS *, FTSENT *)); 61220137Straszstatic int fts_palloc __P((FTS *, size_t)); 62220137Straszstatic FTSENT *fts_sort __P((FTS *, FTSENT *, int)); 63220137Straszstatic u_short fts_stat __P((FTS *, FTSENT *, int)); 64220137Straszstatic int fts_safe_changedir __P((FTS *, FTSENT *, int)); 65220137Strasz 66220137Strasz#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) 67220137Strasz 68220137Strasz#define CLR(opt) (sp->fts_options &= ~(opt)) 69220137Strasz#define ISSET(opt) (sp->fts_options & (opt)) 70220137Strasz#define SET(opt) (sp->fts_options |= (opt)) 71220137Strasz 72220137Strasz#define CHDIR(sp, path) (!ISSET(FTS_NOCHDIR) && chdir(path)) 73220137Strasz#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && fchdir(fd)) 74220137Strasz 75220137Strasz/* fts_build flags */ 76220137Strasz#define BCHILD 1 /* fts_children */ 77220137Strasz#define BNAMES 2 /* fts_children, names only */ 78220137Strasz#define BREAD 3 /* fts_read */ 79220137Strasz 80220137StraszFTS * 81220137Straszfts_open(argv, options, compar) 82220137Strasz char * const *argv; 83220137Strasz register int options; 84220137Strasz int (*compar) __P((const FTSENT **, const FTSENT **)); 85220137Strasz{ 86220137Strasz register FTS *sp; 87220137Strasz register FTSENT *p, *root; 88220137Strasz register int nitems; 89220137Strasz FTSENT *parent, *tmp; 90220137Strasz int len; 91220137Strasz 92220137Strasz /* Options check. */ 93220137Strasz if (options & ~FTS_OPTIONMASK) { 94220137Strasz errno = EINVAL; 95220137Strasz return (NULL); 96220137Strasz } 97220137Strasz 98220137Strasz /* Allocate/initialize the stream */ 99220137Strasz if ((sp = malloc((u_int)sizeof(FTS))) == NULL) 100220137Strasz return (NULL); 101220137Strasz memset(sp, 0, sizeof(FTS)); 102220137Strasz sp->fts_compar = compar; 103220137Strasz sp->fts_options = options; 104220137Strasz 105220137Strasz /* Shush, GCC. */ 106224036Strasz tmp = NULL; 107220137Strasz 108220137Strasz /* Logical walks turn on NOCHDIR; symbolic links are too hard. */ 109220137Strasz if (ISSET(FTS_LOGICAL)) 110220137Strasz SET(FTS_NOCHDIR); 111220137Strasz 112220137Strasz /* 113220137Strasz * Start out with 1K of path space, and enough, in any case, 114220137Strasz * to hold the user's paths. 115220137Strasz */ 116220137Strasz if (fts_palloc(sp, MAX(fts_maxarglen(argv), MAXPATHLEN))) 117220137Strasz goto mem1; 118220137Strasz 119220137Strasz /* Allocate/initialize root's parent. */ 120220137Strasz if ((parent = fts_alloc(sp, "", 0)) == NULL) 121220137Strasz goto mem2; 122220137Strasz parent->fts_level = FTS_ROOTPARENTLEVEL; 123220137Strasz 124220137Strasz /* Allocate/initialize root(s). */ 125220137Strasz for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) { 126220137Strasz /* Don't allow zero-length paths. */ 127220137Strasz if ((len = strlen(*argv)) == 0) { 128220137Strasz errno = ENOENT; 129220137Strasz goto mem3; 130220137Strasz } 131220137Strasz 132220137Strasz p = fts_alloc(sp, *argv, len); 133220137Strasz p->fts_level = FTS_ROOTLEVEL; 134220137Strasz p->fts_parent = parent; 135220137Strasz p->fts_accpath = p->fts_name; 136220137Strasz p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW)); 137220137Strasz 138220137Strasz /* Command-line "." and ".." are real directories. */ 139220137Strasz if (p->fts_info == FTS_DOT) 140220137Strasz p->fts_info = FTS_D; 141220137Strasz 142220137Strasz /* 143220137Strasz * If comparison routine supplied, traverse in sorted 144224036Strasz * order; otherwise traverse in the order specified. 145220137Strasz */ 146220137Strasz if (compar) { 147220137Strasz p->fts_link = root; 148220137Strasz root = p; 149220137Strasz } else { 150220137Strasz p->fts_link = NULL; 151220137Strasz if (root == NULL) 152220137Strasz tmp = root = p; 153220137Strasz else { 154220137Strasz tmp->fts_link = p; 155220137Strasz tmp = p; 156220137Strasz } 157220137Strasz } 158220137Strasz } 159220137Strasz if (compar && nitems > 1) 160220137Strasz root = fts_sort(sp, root, nitems); 161220137Strasz 162220137Strasz /* 163220137Strasz * Allocate a dummy pointer and make fts_read think that we've just 164220137Strasz * finished the node before the root(s); set p->fts_info to FTS_INIT 165220137Strasz * so that everything about the "current" node is ignored. 166220137Strasz */ 167220137Strasz if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL) 168220137Strasz goto mem3; 169220137Strasz sp->fts_cur->fts_link = root; 170220137Strasz sp->fts_cur->fts_info = FTS_INIT; 171220137Strasz 172220137Strasz /* 173220137Strasz * If using chdir(2), grab a file descriptor pointing to dot to ensure 174220137Strasz * that we can get back here; this could be avoided for some paths, 175220137Strasz * but almost certainly not worth the effort. Slashes, symbolic links, 176223844Strasz * and ".." are all fairly nasty problems. Note, if we can't get the 177220137Strasz * descriptor we run anyway, just more slowly. 178220137Strasz */ 179220137Strasz if (!ISSET(FTS_NOCHDIR) && (sp->fts_rfd = open(".", O_RDONLY, 0)) < 0) 180220137Strasz SET(FTS_NOCHDIR); 181220137Strasz 182220137Strasz return (sp); 183220137Strasz 184223844Straszmem3: fts_lfree(root); 185220137Strasz free(parent); 186220137Straszmem2: free(sp->fts_path); 187223844Straszmem1: free(sp); 188220137Strasz return (NULL); 189220137Strasz} 190220137Strasz 191220137Straszstatic void 192220137Straszfts_load(sp, p) 193220137Strasz FTS *sp; 194220137Strasz register FTSENT *p; 195220137Strasz{ 196220137Strasz register int len; 197220137Strasz register char *cp; 198220137Strasz 199220137Strasz /* 200220137Strasz * Load the stream structure for the next traversal. Since we don't 201220137Strasz * actually enter the directory until after the preorder visit, set 202220137Strasz * the fts_accpath field specially so the chdir gets done to the right 203220137Strasz * place and the user can access the first node. From fts_open it's 204220137Strasz * known that the path will fit. 205220137Strasz */ 206220137Strasz len = p->fts_pathlen = p->fts_namelen; 207220137Strasz memmove(sp->fts_path, p->fts_name, len + 1); 208220137Strasz if ((cp = strrchr(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) { 209220137Strasz len = strlen(++cp); 210220137Strasz memmove(p->fts_name, cp, len + 1); 211220137Strasz p->fts_namelen = len; 212220137Strasz } 213220137Strasz p->fts_accpath = p->fts_path = sp->fts_path; 214220137Strasz sp->fts_dev = p->fts_dev; 215220137Strasz} 216220137Strasz 217220137Straszint 218220137Straszfts_close(sp) 219220137Strasz FTS *sp; 220220137Strasz{ 221223844Strasz register FTSENT *freep, *p; 222220137Strasz int saved_errno; 223223844Strasz 224220137Strasz /* 225220137Strasz * This still works if we haven't read anything -- the dummy structure 226220137Strasz * points to the root list, so we step through to the end of the root 227220137Strasz * list which has a valid parent pointer. 228220137Strasz */ 229220137Strasz if (sp->fts_cur) { 230220137Strasz for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) { 231220137Strasz freep = p; 232220137Strasz p = p->fts_link ? p->fts_link : p->fts_parent; 233220137Strasz free(freep); 234220137Strasz } 235220137Strasz free(p); 236220137Strasz } 237220137Strasz 238220137Strasz /* Free up child linked list, sort array, path buffer. */ 239220137Strasz if (sp->fts_child) 240220137Strasz fts_lfree(sp->fts_child); 241220137Strasz if (sp->fts_array) 242220137Strasz free(sp->fts_array); 243220137Strasz free(sp->fts_path); 244220137Strasz 245220137Strasz /* Return to original directory, save errno if necessary. */ 246220137Strasz if (!ISSET(FTS_NOCHDIR)) { 247220137Strasz saved_errno = fchdir(sp->fts_rfd) ? errno : 0; 248220137Strasz (void)close(sp->fts_rfd); 249220137Strasz 250220137Strasz /* Set errno and return. */ 251220137Strasz if (saved_errno != 0) { 252220137Strasz /* Free up the stream pointer. */ 253220137Strasz free(sp); 254220137Strasz errno = saved_errno; 255220137Strasz return (-1); 256220137Strasz } 257220137Strasz } 258223844Strasz 259220137Strasz /* Free up the stream pointer. */ 260220137Strasz free(sp); 261220137Strasz return (0); 262220137Strasz} 263220137Strasz 264225944Strasz/* 265225944Strasz * Special case of "/" at the end of the path so that slashes aren't 266220137Strasz * appended which would cause paths to be written as "....//foo". 267220137Strasz */ 268220137Strasz#define NAPPEND(p) \ 269220137Strasz (p->fts_path[p->fts_pathlen - 1] == '/' \ 270220137Strasz ? p->fts_pathlen - 1 : p->fts_pathlen) 271220137Strasz 272220137StraszFTSENT * 273220137Straszfts_read(sp) 274220137Strasz register FTS *sp; 275220137Strasz{ 276220137Strasz register FTSENT *p, *tmp; 277220137Strasz register int instr; 278220137Strasz register char *t; 279220137Strasz int saved_errno; 280220137Strasz 281220137Strasz /* If finished or unrecoverable error, return NULL. */ 282220137Strasz if (sp->fts_cur == NULL || ISSET(FTS_STOP)) 283223844Strasz return (NULL); 284220137Strasz 285220137Strasz /* Set current node pointer. */ 286220137Strasz p = sp->fts_cur; 287220137Strasz 288220137Strasz /* Save and zero out user instructions. */ 289220137Strasz instr = p->fts_instr; 290220137Strasz p->fts_instr = FTS_NOINSTR; 291220137Strasz 292220137Strasz /* Any type of file may be re-visited; re-stat and re-turn. */ 293220137Strasz if (instr == FTS_AGAIN) { 294220137Strasz p->fts_info = fts_stat(sp, p, 0); 295225944Strasz return (p); 296225944Strasz } 297225944Strasz 298225944Strasz /* 299225944Strasz * Following a symlink -- SLNONE test allows application to see 300225944Strasz * SLNONE and recover. If indirecting through a symlink, have 301225944Strasz * keep a pointer to current location. If unable to get that 302225944Strasz * pointer, follow fails. 303225944Strasz */ 304225944Strasz if (instr == FTS_FOLLOW && 305225944Strasz (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) { 306225944Strasz p->fts_info = fts_stat(sp, p, 1); 307225944Strasz if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { 308225944Strasz if ((p->fts_symfd = open(".", O_RDONLY, 0)) < 0) { 309225944Strasz p->fts_errno = errno; 310220137Strasz p->fts_info = FTS_ERR; 311220137Strasz } else 312220137Strasz p->fts_flags |= FTS_SYMFOLLOW; 313220137Strasz } 314220137Strasz return (p); 315220137Strasz } 316220137Strasz 317220137Strasz /* Directory in pre-order. */ 318220137Strasz if (p->fts_info == FTS_D) { 319220137Strasz /* If skipped or crossed mount point, do post-order visit. */ 320221362Strasz if (instr == FTS_SKIP || 321221362Strasz (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) { 322220137Strasz if (p->fts_flags & FTS_SYMFOLLOW) 323220137Strasz (void)close(p->fts_symfd); 324220137Strasz if (sp->fts_child) { 325220137Strasz fts_lfree(sp->fts_child); 326220137Strasz sp->fts_child = NULL; 327220137Strasz } 328220137Strasz p->fts_info = FTS_DP; 329220137Strasz return (p); 330220137Strasz } 331220137Strasz 332220137Strasz /* Rebuild if only read the names and now traversing. */ 333220137Strasz if (sp->fts_child && ISSET(FTS_NAMEONLY)) { 334220137Strasz CLR(FTS_NAMEONLY); 335220137Strasz fts_lfree(sp->fts_child); 336220137Strasz sp->fts_child = NULL; 337220137Strasz } 338220137Strasz 339220137Strasz /* 340220137Strasz * Cd to the subdirectory. 341220137Strasz * 342220137Strasz * If have already read and now fail to chdir, whack the list 343220137Strasz * to make the names come out right, and set the parent errno 344220137Strasz * so the application will eventually get an error condition. 345220137Strasz * Set the FTS_DONTCHDIR flag so that when we logically change 346220137Strasz * directories back to the parent we don't do a chdir. 347220137Strasz * 348220137Strasz * If haven't read do so. If the read fails, fts_build sets 349220137Strasz * FTS_STOP or the fts_info field of the node. 350220137Strasz */ 351220137Strasz if (sp->fts_child) { 352220137Strasz if (fts_safe_changedir(sp, p, -1)) { 353220137Strasz p->fts_errno = errno; 354220137Strasz p->fts_flags |= FTS_DONTCHDIR; 355220137Strasz for (p = sp->fts_child; p; p = p->fts_link) 356220137Strasz p->fts_accpath = 357220137Strasz p->fts_parent->fts_accpath; 358220137Strasz } 359220137Strasz } else if ((sp->fts_child = fts_build(sp, BREAD)) == NULL) { 360220137Strasz if (ISSET(FTS_STOP)) 361220137Strasz return (NULL); 362220137Strasz return (p); 363220137Strasz } 364220137Strasz p = sp->fts_child; 365220137Strasz sp->fts_child = NULL; 366220137Strasz goto name; 367220137Strasz } 368220137Strasz 369220137Strasz /* Move to the next node on this level. */ 370220137Strasznext: tmp = p; 371220137Strasz if ((p = p->fts_link)) { 372220137Strasz free(tmp); 373220137Strasz 374220137Strasz /* 375220137Strasz * If reached the top, return to the original directory (or 376220137Strasz * the root of the tree), and load the paths for the next root. 377220137Strasz */ 378220137Strasz if (p->fts_level == FTS_ROOTLEVEL) { 379220137Strasz if (FCHDIR(sp, sp->fts_rfd)) { 380220137Strasz SET(FTS_STOP); 381220137Strasz return (NULL); 382220137Strasz } 383220137Strasz fts_load(sp, p); 384223844Strasz return (sp->fts_cur = p); 385220137Strasz } 386220137Strasz 387220137Strasz /* 388220137Strasz * User may have called fts_set on the node. If skipped, 389220137Strasz * ignore. If followed, get a file descriptor so we can 390220137Strasz * get back if necessary. 391223844Strasz */ 392220137Strasz if (p->fts_instr == FTS_SKIP) 393220137Strasz goto next; 394220137Strasz if (p->fts_instr == FTS_FOLLOW) { 395220137Strasz p->fts_info = fts_stat(sp, p, 1); 396220137Strasz if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { 397220137Strasz if ((p->fts_symfd = 398220137Strasz open(".", O_RDONLY, 0)) < 0) { 399220137Strasz p->fts_errno = errno; 400220137Strasz p->fts_info = FTS_ERR; 401220137Strasz } else 402220137Strasz p->fts_flags |= FTS_SYMFOLLOW; 403220137Strasz } 404220137Strasz p->fts_instr = FTS_NOINSTR; 405220137Strasz } 406220137Strasz 407220137Straszname: t = sp->fts_path + NAPPEND(p->fts_parent); 408220137Strasz *t++ = '/'; 409220137Strasz memmove(t, p->fts_name, p->fts_namelen + 1); 410220137Strasz return (sp->fts_cur = p); 411220137Strasz } 412220137Strasz 413220137Strasz /* Move up to the parent node. */ 414220137Strasz p = tmp->fts_parent; 415220137Strasz free(tmp); 416220137Strasz 417220137Strasz if (p->fts_level == FTS_ROOTPARENTLEVEL) { 418220137Strasz /* 419220137Strasz * Done; free everything up and set errno to 0 so the user 420220137Strasz * can distinguish between error and EOF. 421220137Strasz */ 422220137Strasz free(p); 423220137Strasz errno = 0; 424220137Strasz return (sp->fts_cur = NULL); 425220137Strasz } 426220137Strasz 427220137Strasz /* NUL terminate the pathname. */ 428220137Strasz sp->fts_path[p->fts_pathlen] = '\0'; 429220137Strasz 430220137Strasz /* 431220137Strasz * Return to the parent directory. If at a root node or came through 432220137Strasz * a symlink, go back through the file descriptor. Otherwise, cd up 433220137Strasz * one directory. 434220137Strasz */ 435220137Strasz if (p->fts_level == FTS_ROOTLEVEL) { 436220137Strasz if (FCHDIR(sp, sp->fts_rfd)) { 437220137Strasz SET(FTS_STOP); 438220137Strasz return (NULL); 439220137Strasz } 440220137Strasz } else if (p->fts_flags & FTS_SYMFOLLOW) { 441220137Strasz if (FCHDIR(sp, p->fts_symfd)) { 442220137Strasz saved_errno = errno; 443220137Strasz (void)close(p->fts_symfd); 444220137Strasz errno = saved_errno; 445220137Strasz SET(FTS_STOP); 446220137Strasz return (NULL); 447220137Strasz } 448220137Strasz (void)close(p->fts_symfd); 449220137Strasz } else if (!(p->fts_flags & FTS_DONTCHDIR)) { 450220137Strasz if (CHDIR(sp, "..")) { 451220137Strasz SET(FTS_STOP); 452220137Strasz return (NULL); 453220137Strasz } 454220137Strasz } 455220137Strasz p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP; 456220137Strasz return (sp->fts_cur = p); 457220137Strasz} 458220137Strasz 459220137Strasz/* 460220137Strasz * Fts_set takes the stream as an argument although it's not used in this 461220137Strasz * implementation; it would be necessary if anyone wanted to add global 462220137Strasz * semantics to fts using fts_set. An error return is allowed for similar 463220137Strasz * reasons. 464220137Strasz */ 465220137Strasz/* ARGSUSED */ 466220137Straszint 467220137Straszfts_set(sp, p, instr) 468220137Strasz FTS *sp; 469220137Strasz FTSENT *p; 470220137Strasz int instr; 471220137Strasz{ 472220137Strasz if (instr && instr != FTS_AGAIN && instr != FTS_FOLLOW && 473220137Strasz instr != FTS_NOINSTR && instr != FTS_SKIP) { 474220137Strasz errno = EINVAL; 475220137Strasz return (1); 476220137Strasz } 477220137Strasz p->fts_instr = instr; 478220137Strasz return (0); 479220137Strasz} 480220137Strasz 481220137StraszFTSENT * 482220137Straszfts_children(sp, instr) 483220137Strasz register FTS *sp; 484220137Strasz int instr; 485220137Strasz{ 486220137Strasz register FTSENT *p; 487220137Strasz int fd; 488220137Strasz 489220137Strasz if (instr && instr != FTS_NAMEONLY) { 490220137Strasz errno = EINVAL; 491220137Strasz return (NULL); 492220137Strasz } 493220137Strasz 494220137Strasz /* Set current node pointer. */ 495220137Strasz p = sp->fts_cur; 496220137Strasz 497220137Strasz /* 498220137Strasz * Errno set to 0 so user can distinguish empty directory from 499220137Strasz * an error. 500223844Strasz */ 501220137Strasz errno = 0; 502220137Strasz 503220137Strasz /* Fatal errors stop here. */ 504220137Strasz if (ISSET(FTS_STOP)) 505220137Strasz return (NULL); 506220137Strasz 507220137Strasz /* Return logical hierarchy of user's arguments. */ 508220137Strasz if (p->fts_info == FTS_INIT) 509220137Strasz return (p->fts_link); 510220137Strasz 511220137Strasz /* 512220137Strasz * If not a directory being visited in pre-order, stop here. Could 513220137Strasz * allow FTS_DNR, assuming the user has fixed the problem, but the 514220137Strasz * same effect is available with FTS_AGAIN. 515220137Strasz */ 516220137Strasz if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */) 517220137Strasz return (NULL); 518220137Strasz 519220137Strasz /* Free up any previous child list. */ 520220137Strasz if (sp->fts_child) 521220137Strasz fts_lfree(sp->fts_child); 522220137Strasz 523223844Strasz if (instr == FTS_NAMEONLY) { 524220137Strasz SET(FTS_NAMEONLY); 525220137Strasz instr = BNAMES; 526220137Strasz } else 527220137Strasz instr = BCHILD; 528220137Strasz 529220137Strasz /* 530221362Strasz * If using chdir on a relative path and called BEFORE fts_read does 531221362Strasz * its chdir to the root of a traversal, we can lose -- we need to 532220137Strasz * chdir into the subdirectory, and we don't know where the current 533220137Strasz * directory is, so we can't get back so that the upcoming chdir by 534220137Strasz * fts_read will work. 535220137Strasz */ 536220137Strasz if (p->fts_level != FTS_ROOTLEVEL || p->fts_accpath[0] == '/' || 537220137Strasz ISSET(FTS_NOCHDIR)) 538220137Strasz return (sp->fts_child = fts_build(sp, instr)); 539220137Strasz 540220137Strasz if ((fd = open(".", O_RDONLY, 0)) < 0) 541220137Strasz return (NULL); 542220137Strasz sp->fts_child = fts_build(sp, instr); 543220137Strasz if (fchdir(fd)) 544220137Strasz return (NULL); 545220137Strasz (void)close(fd); 546220137Strasz return (sp->fts_child); 547220137Strasz} 548220137Strasz 549220137Strasz/* 550220137Strasz * This is the tricky part -- do not casually change *anything* in here. The 551220137Strasz * idea is to build the linked list of entries that are used by fts_children 552220137Strasz * and fts_read. There are lots of special cases. 553220137Strasz * 554220137Strasz * The real slowdown in walking the tree is the stat calls. If FTS_NOSTAT is 555220137Strasz * set and it's a physical walk (so that symbolic links can't be directories), 556220137Strasz * we can do things quickly. First, if it's a 4.4BSD file system, the type 557220137Strasz * of the file is in the directory entry. Otherwise, we assume that the number 558220137Strasz * of subdirectories in a node is equal to the number of links to the parent. 559220137Strasz * The former skips all stat calls. The latter skips stat calls in any leaf 560220137Strasz * directories and for any files after the subdirectories in the directory have 561220137Strasz * been found, cutting the stat calls by about 2/3. 562220137Strasz */ 563220137Straszstatic FTSENT * 564220137Straszfts_build(sp, type) 565220137Strasz register FTS *sp; 566220137Strasz int type; 567220137Strasz{ 568220137Strasz register struct dirent *dp; 569220137Strasz register FTSENT *p, *head; 570220137Strasz register int nitems; 571220137Strasz FTSENT *cur, *tail; 572220137Strasz DIR *dirp; 573220137Strasz void *oldaddr; 574220137Strasz int cderrno, descend, len, level, maxlen, nlinks, oflag, saved_errno, 575223844Strasz nostat, doadjust; 576220137Strasz char *cp; 577220137Strasz 578220137Strasz /* Set current node pointer. */ 579220137Strasz cur = sp->fts_cur; 580225938Strasz 581220137Strasz /* 582220137Strasz * Open the directory for reading. If this fails, we're done. 583220137Strasz * If being called from fts_read, set the fts_info field. 584220137Strasz */ 585220137Strasz#ifdef FTS_WHITEOUT 586225944Strasz if (ISSET(FTS_WHITEOUT)) 587225944Strasz oflag = DTF_NODUP|DTF_REWIND; 588220137Strasz else 589220137Strasz oflag = DTF_HIDEW|DTF_NODUP|DTF_REWIND; 590225944Strasz#else 591225944Strasz#define __opendir2(path, flag) opendir(path) 592225944Strasz#endif 593220137Strasz if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) { 594220137Strasz if (type == BREAD) { 595220137Strasz cur->fts_info = FTS_DNR; 596220137Strasz cur->fts_errno = errno; 597220137Strasz } 598220137Strasz return (NULL); 599220137Strasz } 600220137Strasz 601225940Strasz /* 602225940Strasz * Nlinks is the number of possible entries of type directory in the 603225940Strasz * directory if we're cheating on stat calls, 0 if we're not doing 604225940Strasz * any stat calls at all, -1 if we're doing stats on everything. 605220137Strasz */ 606225940Strasz if (type == BNAMES) { 607225940Strasz nlinks = 0; 608225940Strasz /* Be quiet about nostat, GCC. */ 609225940Strasz nostat = 0; 610225940Strasz } else if (ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL)) { 611225940Strasz nlinks = cur->fts_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2); 612225940Strasz nostat = 1; 613225940Strasz } else { 614225940Strasz nlinks = -1; 615225940Strasz nostat = 0; 616225940Strasz } 617225940Strasz 618225940Strasz#ifdef notdef 619225940Strasz (void)printf("nlinks == %d (cur: %d)\n", nlinks, cur->fts_nlink); 620220137Strasz (void)printf("NOSTAT %d PHYSICAL %d SEEDOT %d\n", 621220137Strasz ISSET(FTS_NOSTAT), ISSET(FTS_PHYSICAL), ISSET(FTS_SEEDOT)); 622225364Strasz#endif 623220137Strasz /* 624220137Strasz * If we're going to need to stat anything or we want to descend 625220137Strasz * and stay in the directory, chdir. If this fails we keep going, 626220137Strasz * but set a flag so we don't chdir after the post-order visit. 627220137Strasz * We won't be able to stat anything, but we can still return the 628220137Strasz * names themselves. Note, that since fts_read won't be able to 629220137Strasz * chdir into the directory, it will have to return different path 630220137Strasz * names than before, i.e. "a/b" instead of "b". Since the node 631220137Strasz * has already been visited in pre-order, have to wait until the 632220137Strasz * post-order visit to return the error. There is a special case 633220137Strasz * here, if there was nothing to stat then it's not an error to 634220137Strasz * not be able to stat. This is all fairly nasty. If a program 635220137Strasz * needed sorted entries or stat information, they had better be 636225364Strasz * checking FTS_NS on the returned nodes. 637225364Strasz */ 638220137Strasz cderrno = 0; 639225364Strasz if (nlinks || type == BREAD) { 640225364Strasz if (fts_safe_changedir(sp, cur, dirfd(dirp))) { 641225364Strasz if (nlinks && type == BREAD) 642225364Strasz cur->fts_errno = errno; 643225364Strasz cur->fts_flags |= FTS_DONTCHDIR; 644225364Strasz descend = 0; 645225364Strasz cderrno = errno; 646225364Strasz (void)closedir(dirp); 647225364Strasz dirp = NULL; 648220137Strasz } else 649220137Strasz descend = 1; 650220137Strasz } else 651220137Strasz descend = 0; 652220137Strasz 653220137Strasz /* 654220137Strasz * Figure out the max file name length that can be stored in the 655220137Strasz * current path -- the inner loop allocates more path as necessary. 656220137Strasz * We really wouldn't have to do the maxlen calculations here, we 657220137Strasz * could do them in fts_read before returning the path, but it's a 658220137Strasz * lot easier here since the length is part of the dirent structure. 659220137Strasz * 660220137Strasz * If not changing directories set a pointer so that can just append 661220137Strasz * each new name into the path. 662220137Strasz */ 663220137Strasz len = NAPPEND(cur); 664220137Strasz if (ISSET(FTS_NOCHDIR)) { 665220137Strasz cp = sp->fts_path + len; 666220137Strasz *cp++ = '/'; 667220137Strasz } else { 668220137Strasz /* GCC, you're too verbose. */ 669220137Strasz cp = NULL; 670220137Strasz } 671220137Strasz len++; 672220137Strasz maxlen = sp->fts_pathlen - len; 673220137Strasz 674220137Strasz level = cur->fts_level + 1; 675220137Strasz 676220137Strasz /* Read the directory, attaching each entry to the `link' pointer. */ 677220137Strasz doadjust = 0; 678220137Strasz for (head = tail = NULL, nitems = 0; dirp && (dp = readdir(dirp));) { 679220137Strasz if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name)) 680220137Strasz continue; 681220137Strasz 682220137Strasz if ((p = fts_alloc(sp, dp->d_name, (int)dp->d_namlen)) == NULL) 683220137Strasz goto mem1; 684220137Strasz if (dp->d_namlen >= maxlen) { /* include space for NUL */ 685220137Strasz oldaddr = sp->fts_path; 686220137Strasz if (fts_palloc(sp, dp->d_namlen +len + 1)) { 687220137Strasz /* 688221362Strasz * No more memory for path or structures. Save 689221362Strasz * errno, free up the current structure and the 690220137Strasz * structures already allocated. 691221362Strasz */ 692221362Straszmem1: saved_errno = errno; 693220137Strasz if (p) 694220137Strasz free(p); 695220137Strasz fts_lfree(head); 696220137Strasz (void)closedir(dirp); 697220137Strasz cur->fts_info = FTS_ERR; 698220137Strasz SET(FTS_STOP); 699220137Strasz errno = saved_errno; 700220137Strasz return (NULL); 701220137Strasz } 702220137Strasz /* Did realloc() change the pointer? */ 703220137Strasz if (oldaddr != sp->fts_path) { 704220137Strasz doadjust = 1; 705220137Strasz if (ISSET(FTS_NOCHDIR)) 706220137Strasz cp = sp->fts_path + len; 707220137Strasz } 708220137Strasz maxlen = sp->fts_pathlen - len; 709220137Strasz } 710220137Strasz 711220137Strasz if (len + dp->d_namlen >= USHRT_MAX) { 712220137Strasz /* 713220137Strasz * In an FTSENT, fts_pathlen is a u_short so it is 714220137Strasz * possible to wraparound here. If we do, free up 715220137Strasz * the current structure and the structures already 716220137Strasz * allocated, then error out with ENAMETOOLONG. 717220137Strasz */ 718220137Strasz free(p); 719220137Strasz fts_lfree(head); 720220137Strasz (void)closedir(dirp); 721220137Strasz cur->fts_info = FTS_ERR; 722220137Strasz SET(FTS_STOP); 723220137Strasz errno = ENAMETOOLONG; 724220137Strasz return (NULL); 725220137Strasz } 726220137Strasz p->fts_level = level; 727220137Strasz p->fts_parent = sp->fts_cur; 728220137Strasz p->fts_pathlen = len + dp->d_namlen; 729220137Strasz 730220137Strasz#ifdef FTS_WHITEOUT 731220137Strasz if (dp->d_type == DT_WHT) 732220137Strasz p->fts_flags |= FTS_ISW; 733220137Strasz#endif 734220137Strasz 735220137Strasz if (cderrno) { 736220137Strasz if (nlinks) { 737220137Strasz p->fts_info = FTS_NS; 738220137Strasz p->fts_errno = cderrno; 739220137Strasz } else 740220137Strasz p->fts_info = FTS_NSOK; 741220137Strasz p->fts_accpath = cur->fts_accpath; 742220137Strasz } else if (nlinks == 0 743220137Strasz#ifdef DT_DIR 744220137Strasz || (nostat && 745220137Strasz dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN) 746220137Strasz#endif 747220137Strasz ) { 748220137Strasz p->fts_accpath = 749220137Strasz ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name; 750220137Strasz p->fts_info = FTS_NSOK; 751220137Strasz } else { 752220137Strasz /* Build a file name for fts_stat to stat. */ 753220137Strasz if (ISSET(FTS_NOCHDIR)) { 754220137Strasz p->fts_accpath = p->fts_path; 755220137Strasz memmove(cp, p->fts_name, p->fts_namelen + 1); 756220137Strasz } else 757220137Strasz p->fts_accpath = p->fts_name; 758220137Strasz /* Stat it. */ 759220137Strasz p->fts_info = fts_stat(sp, p, 0); 760220137Strasz 761220137Strasz /* Decrement link count if applicable. */ 762220137Strasz if (nlinks > 0 && (p->fts_info == FTS_D || 763220137Strasz p->fts_info == FTS_DC || p->fts_info == FTS_DOT)) 764220137Strasz --nlinks; 765221362Strasz } 766220137Strasz 767220137Strasz /* We walk in directory order so "ls -f" doesn't get upset. */ 768220137Strasz p->fts_link = NULL; 769220137Strasz if (head == NULL) 770220137Strasz head = tail = p; 771220137Strasz else { 772220137Strasz tail->fts_link = p; 773220137Strasz tail = p; 774220137Strasz } 775220137Strasz ++nitems; 776220137Strasz } 777220137Strasz if (dirp) 778220137Strasz (void)closedir(dirp); 779220137Strasz 780220137Strasz /* 781220137Strasz * If realloc() changed the address of the path, adjust the 782220137Strasz * addresses for the rest of the tree and the dir list. 783220137Strasz */ 784220137Strasz if (doadjust) 785220137Strasz fts_padjust(sp, head); 786220137Strasz 787220137Strasz /* 788220137Strasz * If not changing directories, reset the path back to original 789220137Strasz * state. 790220137Strasz */ 791220137Strasz if (ISSET(FTS_NOCHDIR)) { 792220137Strasz if (len == sp->fts_pathlen || nitems == 0) 793220137Strasz --cp; 794220137Strasz *cp = '\0'; 795220137Strasz } 796220137Strasz 797220137Strasz /* 798220372Strasz * If descended after called from fts_children or after called from 799220372Strasz * fts_read and nothing found, get back. At the root level we use 800220372Strasz * the saved fd; if one of fts_open()'s arguments is a relative path 801220372Strasz * to an empty directory, we wind up here with no other way back. If 802220372Strasz * can't get back, we're done. 803220137Strasz */ 804220137Strasz if (descend && (type == BCHILD || !nitems) && 805220137Strasz (cur->fts_level == FTS_ROOTLEVEL ? 806220137Strasz FCHDIR(sp, sp->fts_rfd) : CHDIR(sp, ".."))) { 807220137Strasz cur->fts_info = FTS_ERR; 808220137Strasz SET(FTS_STOP); 809220137Strasz return (NULL); 810220137Strasz } 811220137Strasz 812220137Strasz /* If didn't find anything, return NULL. */ 813220137Strasz if (!nitems) { 814220137Strasz if (type == BREAD) 815220137Strasz cur->fts_info = FTS_DP; 816220137Strasz return (NULL); 817220137Strasz } 818220137Strasz 819220372Strasz /* Sort the entries. */ 820220372Strasz if (sp->fts_compar && nitems > 1) 821220372Strasz head = fts_sort(sp, head, nitems); 822220372Strasz return (head); 823220372Strasz} 824220372Strasz 825220372Straszstatic u_short 826220137Straszfts_stat(sp, p, follow) 827220137Strasz FTS *sp; 828220137Strasz register FTSENT *p; 829220137Strasz int follow; 830220137Strasz{ 831220137Strasz register FTSENT *t; 832220137Strasz register dev_t dev; 833220137Strasz register ino_t ino; 834220137Strasz struct stat *sbp, sb; 835220137Strasz int saved_errno; 836220137Strasz 837220137Strasz /* If user needs stat info, stat buffer already allocated. */ 838220137Strasz sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp; 839220137Strasz 840220137Strasz#ifdef FTS_WHITEOUT 841220137Strasz /* check for whiteout */ 842220137Strasz if (p->fts_flags & FTS_ISW) { 843220137Strasz if (sbp != &sb) { 844225940Strasz memset(sbp, '\0', sizeof (*sbp)); 845225940Strasz sbp->st_mode = S_IFWHT; 846225940Strasz } 847225940Strasz return (FTS_W); 848225940Strasz } 849220137Strasz#endif 850220137Strasz 851220137Strasz /* 852220137Strasz * If doing a logical walk, or application requested FTS_FOLLOW, do 853220137Strasz * a stat(2). If that fails, check for a non-existent symlink. If 854 * fail, set the errno from the stat call. 855 */ 856 if (ISSET(FTS_LOGICAL) || follow) { 857 if (stat(p->fts_accpath, sbp)) { 858 saved_errno = errno; 859 if (!lstat(p->fts_accpath, sbp)) { 860 errno = 0; 861 return (FTS_SLNONE); 862 } 863 p->fts_errno = saved_errno; 864 goto err; 865 } 866 } else if (lstat(p->fts_accpath, sbp)) { 867 p->fts_errno = errno; 868err: memset(sbp, 0, sizeof(struct stat)); 869 return (FTS_NS); 870 } 871 872 if (S_ISDIR(sbp->st_mode)) { 873 /* 874 * Set the device/inode. Used to find cycles and check for 875 * crossing mount points. Also remember the link count, used 876 * in fts_build to limit the number of stat calls. It is 877 * understood that these fields are only referenced if fts_info 878 * is set to FTS_D. 879 */ 880 dev = p->fts_dev = sbp->st_dev; 881 ino = p->fts_ino = sbp->st_ino; 882 p->fts_nlink = sbp->st_nlink; 883 884 if (ISDOT(p->fts_name)) 885 return (FTS_DOT); 886 887 /* 888 * Cycle detection is done by brute force when the directory 889 * is first encountered. If the tree gets deep enough or the 890 * number of symbolic links to directories is high enough, 891 * something faster might be worthwhile. 892 */ 893 for (t = p->fts_parent; 894 t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent) 895 if (ino == t->fts_ino && dev == t->fts_dev) { 896 p->fts_cycle = t; 897 return (FTS_DC); 898 } 899 return (FTS_D); 900 } 901 if (S_ISLNK(sbp->st_mode)) 902 return (FTS_SL); 903 if (S_ISREG(sbp->st_mode)) 904 return (FTS_F); 905 return (FTS_DEFAULT); 906} 907 908static FTSENT * 909fts_sort(sp, head, nitems) 910 FTS *sp; 911 FTSENT *head; 912 register int nitems; 913{ 914 register FTSENT **ap, *p; 915 916 /* 917 * Construct an array of pointers to the structures and call qsort(3). 918 * Reassemble the array in the order returned by qsort. If unable to 919 * sort for memory reasons, return the directory entries in their 920 * current order. Allocate enough space for the current needs plus 921 * 40 so don't realloc one entry at a time. 922 */ 923 if (nitems > sp->fts_nitems) { 924 struct _ftsent **a; 925 926 sp->fts_nitems = nitems + 40; 927 if ((a = realloc(sp->fts_array, 928 sp->fts_nitems * sizeof(FTSENT *))) == NULL) { 929 if (sp->fts_array) 930 free(sp->fts_array); 931 sp->fts_array = NULL; 932 sp->fts_nitems = 0; 933 return (head); 934 } 935 sp->fts_array = a; 936 } 937 for (ap = sp->fts_array, p = head; p; p = p->fts_link) 938 *ap++ = p; 939 qsort((void *)sp->fts_array, nitems, sizeof(FTSENT *), sp->fts_compar); 940 for (head = *(ap = sp->fts_array); --nitems; ++ap) 941 ap[0]->fts_link = ap[1]; 942 ap[0]->fts_link = NULL; 943 return (head); 944} 945 946static FTSENT * 947fts_alloc(sp, name, namelen) 948 FTS *sp; 949 char *name; 950 register int namelen; 951{ 952 register FTSENT *p; 953 size_t len; 954 955 /* 956 * The file name is a variable length array and no stat structure is 957 * necessary if the user has set the nostat bit. Allocate the FTSENT 958 * structure, the file name and the stat structure in one chunk, but 959 * be careful that the stat structure is reasonably aligned. Since the 960 * fts_name field is declared to be of size 1, the fts_name pointer is 961 * namelen + 2 before the first possible address of the stat structure. 962 */ 963 len = sizeof(FTSENT) + namelen; 964 if (!ISSET(FTS_NOSTAT)) 965 len += sizeof(struct stat) + ALIGNBYTES; 966 if ((p = malloc(len)) == NULL) 967 return (NULL); 968 969 /* Copy the name and guarantee NUL termination. */ 970 memmove(p->fts_name, name, namelen); 971 p->fts_name[namelen] = '\0'; 972 973 if (!ISSET(FTS_NOSTAT)) 974 p->fts_statp = (struct stat *)ALIGN(p->fts_name + namelen + 2); 975 p->fts_namelen = namelen; 976 p->fts_path = sp->fts_path; 977 p->fts_errno = 0; 978 p->fts_flags = 0; 979 p->fts_instr = FTS_NOINSTR; 980 p->fts_number = 0; 981 p->fts_pointer = NULL; 982 return (p); 983} 984 985static void 986fts_lfree(head) 987 register FTSENT *head; 988{ 989 register FTSENT *p; 990 991 /* Free a linked list of structures. */ 992 while ((p = head)) { 993 head = head->fts_link; 994 free(p); 995 } 996} 997 998/* 999 * Allow essentially unlimited paths; find, rm, ls should all work on any tree. 1000 * Most systems will allow creation of paths much longer than MAXPATHLEN, even 1001 * though the kernel won't resolve them. Add the size (not just what's needed) 1002 * plus 256 bytes so don't realloc the path 2 bytes at a time. 1003 */ 1004static int 1005fts_palloc(sp, more) 1006 FTS *sp; 1007 size_t more; 1008{ 1009 char *p; 1010 1011 sp->fts_pathlen += more + 256; 1012 /* 1013 * Check for possible wraparound. In an FTS, fts_pathlen is 1014 * a signed int but in an FTSENT it is an unsigned short. 1015 * We limit fts_pathlen to USHRT_MAX to be safe in both cases. 1016 */ 1017 if (sp->fts_pathlen < 0 || sp->fts_pathlen >= USHRT_MAX) { 1018 if (sp->fts_path) 1019 free(sp->fts_path); 1020 sp->fts_path = NULL; 1021 errno = ENAMETOOLONG; 1022 return (1); 1023 } 1024 p = realloc(sp->fts_path, sp->fts_pathlen); 1025 if (p == NULL) { 1026 if (sp->fts_path) 1027 free(sp->fts_path); 1028 sp->fts_path = NULL; 1029 return (1); 1030 } 1031 sp->fts_path = p; 1032 return (0); 1033} 1034 1035/* 1036 * When the path is realloc'd, have to fix all of the pointers in structures 1037 * already returned. 1038 */ 1039static void 1040fts_padjust(sp, head) 1041 FTS *sp; 1042 FTSENT *head; 1043{ 1044 FTSENT *p; 1045 char *addr = sp->fts_path; 1046 1047#define ADJUST(p) { \ 1048 if ((p)->fts_accpath != (p)->fts_name) { \ 1049 (p)->fts_accpath = \ 1050 (char *)addr + ((p)->fts_accpath - (p)->fts_path); \ 1051 } \ 1052 (p)->fts_path = addr; \ 1053} 1054 /* Adjust the current set of children. */ 1055 for (p = sp->fts_child; p; p = p->fts_link) 1056 ADJUST(p); 1057 1058 /* Adjust the rest of the tree, including the current level. */ 1059 for (p = head; p->fts_level >= FTS_ROOTLEVEL;) { 1060 ADJUST(p); 1061 p = p->fts_link ? p->fts_link : p->fts_parent; 1062 } 1063} 1064 1065static size_t 1066fts_maxarglen(argv) 1067 char * const *argv; 1068{ 1069 size_t len, max; 1070 1071 for (max = 0; *argv; ++argv) 1072 if ((len = strlen(*argv)) > max) 1073 max = len; 1074 return (max + 1); 1075} 1076 1077/* 1078 * Change to dir specified by fd or p->fts_accpath without getting 1079 * tricked by someone changing the world out from underneath us. 1080 * Assumes p->fts_dev and p->fts_ino are filled in. 1081 */ 1082static int 1083fts_safe_changedir(sp, p, fd) 1084 FTS *sp; 1085 FTSENT *p; 1086 int fd; 1087{ 1088 int ret, oerrno, newfd; 1089 struct stat sb; 1090 1091 newfd = fd; 1092 if (ISSET(FTS_NOCHDIR)) 1093 return (0); 1094 if (fd < 0 && (newfd = open(p->fts_accpath, O_RDONLY, 0)) < 0) 1095 return (-1); 1096 if (fstat(newfd, &sb)) { 1097 ret = -1; 1098 goto bail; 1099 } 1100 if (p->fts_dev != sb.st_dev || p->fts_ino != sb.st_ino) { 1101 errno = ENOENT; /* disinformation */ 1102 ret = -1; 1103 goto bail; 1104 } 1105 ret = fchdir(newfd); 1106bail: 1107 oerrno = errno; 1108 if (fd < 0) 1109 (void)close(newfd); 1110 errno = oerrno; 1111 return (ret); 1112} 1113