aboutsummaryrefslogblamecommitdiffstats
path: root/util/folder/fts.cpp
blob: 0e6a6f86eb96be0f94b518100b36989e0c40ed05 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
                                 
                                                                        









                                                                           
                                                                   













                                                                             
                                                           
   

                                 
                                 
 
                   
             




                          
     

                           
      


                     
 
                


                   
                                   























                                   
                   
 
                                                        


                                                                        
                                                                 












                                                                  
                                 
                 
                                

                         
                         





                          
                       
 



                                                                        
                                                                 







                                                                  
                                  
                              

                           


                                                                        
                                                                 



                                                                  
                                       
              
               
 
 
                    
 




                                                              

                                                                                    










                                                                  
     
                                           
      








                                                               
 
                                                                   
 

                                            
 
                                                              
                     

                                                
 










                                                             
                                 
 
               
 
                              
                       
                  
     
                  
 
                                                                                             
 
            


                         
 
              




                       

                                    
                       
     
 
                                        
                                                      
                       
     

                               
 
                     
                  
 
                                                                     
                             
                         
     
 


                                                                
                                                               
                  
     
 
                                            
                                                     
                  
     
                                            
 
                                      
                                                               
                                            



                                                                                         
                                                                              
                  
      


                           
 



                                                            
                                                       
 
                                                             
                                     
                                
         
 






                                                             
                                  
                                  
                               
                    



                                  
                               
                                          
     
 



                                                                        
                                                          
                  
                                           
                                     
 





                                                                           
 
                                                                      
                         
     
 
                
 
                    
                 


                       
                   

           
                            
 
               
             
 







                                                                          
                                                                                             




                                                        
 
                       
 
















                                                                          
                        
                                 
                        
                            
     
                       
 
                                                                

                                                          










                                             

  
                                                                    
   


                                                    
 
                    
                    
            
                    
                           
 
                                                          
                                                    
                       
     
 
                                   
 

                                              
 

                                                                  
                                                       










                                                                   
                                                       


                                                          
                    
                                              
             







                                                                     
                                               
                                         
             
                                         
                                        







                                                                
                                    
















                                                                      
                                                             
                                                   
                 
             
                                                                       
                                  
                               
             

                          
                                
                  
 
                                              
            
                                       







                                                                       
                               








                                                                  
                                       
                      
         
                                             
                                                           
                                                              
                                                 
                                          
                        
                                                  
                 

                                       
 
                                                  














                                                                  
                                       











                                                                         
                           





                                              
                           

                                                 

                                                                 

                                                  







                                                                           
                                           
 
                      





                                                             
 
                                 
 
              

                                         
                       
     
 
                                   
 



                                                                  
 
                                 
                          
                       
     
 
                                                       
                                  
                             
     
 



                                                                       
                                                               
                       
     
 
                                          
                        
                                 
     
 

                                
            
                       
     
 






                                                                           
                             
                                                      
     
 
                                      
                       
     
                                         
                       
                       
     
                           
 
                                                                    


                                                                                                  



                                       
                   
 












                                                                               
                            
 
                      

                       
 
               
                     
     
              
      
                  
                                                                  
             
 
                                   
 


                                                                  
                   
                            
                                       
        
                                                   
     
                                                
      
                                                                  


                                    
                       
     
 


                                       
































                                                                        
             
                                                                

                                                     
                                          
                                       
             


                                            
                           
                              
                                
                                  
      
                
                        
            

















                                                                        
                     
     
          
                                   
 
                               
 
                                                                         


                             
                                                                                                                    
                                                      
                     
         
 
                                                                                  
                      
                                                                               
                                   
                                                               



                                                               

                                    
                            
                 
                                     

                                  

                                        
                               


                                                   
                                         
                                            
                 












                                                               

                              

                                    
                           


                                                           
 
                   
                                    
      








                                          
                                                    


                                       
                    
                                       
             
                                              
             
                                                                        
      
           






                                                                                   
                    
                                             
             
                                             
                                                           
 
                                                      
                                                                                  
                         
             
         
 
                                                                      
                              
                              
                            
                



                               
               
                             


                          
 


                                                               
                   
                        
     
 



                                                                   
                                                    
                 
         









                                                                         
                                                                                                                          
                                
                        
                       
     
 
                                               
                            
                                   
         
                        
                       
     
 
                           
                                       
                                          
     
                  

              
                                        
 
              
                         

                                                                 
 
                   

                                 
                                            


                                   
      




                                                                       
                                             




                                              
                                                
                            
         
                                            
                             
                                            
                        
 
                                








                                                                       
 
                                             
                             
         
 
          



                                                                     
 
                                                                    
             
                  
                               
                                                                


                                                         
         
                
                       
                                
                        
                                
                       
     
                         
 
                                           
 








                                                                          
                           
 
                                     

                                                                                           
                                    
             
                                    



                               
                                                            
                  

                                                                                                                    
                                
     
                              
                  
 
                                                 
 
              









                                                                           
                             
                                                
                                                
                       
     
 

                                                              
 

                                                                      




                                      
                             
                           
               

           
                       
 
              
 
                                           
                                   

                              







                                                                               
                                
 
                                  
                                                                           
                                     
 
           
                             
 
                                                              
                                            
                                                     

                                              
                                                                
         
                          
                                                             
                                
 



                                                                            
                    
 







                                                
                                             
                                                 
                        
     
 



                                                           

             
                                
 
                    
 
                                          
                      
         
                     
 




                                                                  
             
          
                                                                
 
                           
                   
 
               
                             
                   
                                                          



                            
     
                                                             
                                            


                        
     
                   
                 
                           
     
                   
 
          
                                                                    
            
                   

                           
                               


                                  
                                            

                  
                           

                 











                                                               
                                            





                           
      
/*-
 * Copyright (c) 1990, 1993, 1994
 *    The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by the University of
 *    California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $OpenBSD: fts.c,v 1.22 1999/10/03 19:22:22 millert Exp $
 */

#include <util/memory/tempbuf.h>
#include <util/system/compat.h>
#include <util/system/compiler.h>
#include <util/system/defaults.h>
#include <util/system/error.h>

#include <stdlib.h>
#ifndef _win_
    #include <inttypes.h>
    #include <sys/param.h>
    #include <dirent.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
#else
    #include <direct.h>
    #include "dirent_win.h"
    #include "lstat_win.h"
#endif

#include <sys/stat.h>

#include <fcntl.h>

#include "fts.h"
#include <limits.h>

#ifndef _win_

static const dird invalidDirD = -1;

dird get_cwdd() {
    return open(".", O_RDONLY, 0);
}

dird get_dird(char* path) {
    return open(path, O_RDONLY, 0);
}

int valid_dird(dird fd) {
    return fd < 0;
}

void close_dird(dird fd) {
    (void)close(fd);
}

int chdir_dird(dird fd) {
    return fchdir(fd);
}

int cmp_dird(dird fd1, dird fd2) {
    return fd1 - fd2;
}

#else // ndef _win_

int stat64UTF(const char* path, struct _stat64* _Stat) {
    int len_converted = MultiByteToWideChar(CP_UTF8, 0, path, -1, 0, 0);
    if (len_converted == 0) {
        return -1;
    }
    WCHAR* buf = (WCHAR*)malloc(sizeof(WCHAR) * (len_converted));
    if (buf == nullptr) {
        return -1;
    }
    MultiByteToWideChar(CP_UTF8, 0, path, -1, buf, len_converted);

    int ret = _wstat64(buf, _Stat);
    free(buf);
    return ret;
}

int stat64UTF(dird path, struct _stat64* _Stat) {
    return _wstat64(path, _Stat);
}

const dird invalidDirD = nullptr;

dird get_cwdd() {
    return _wgetcwd(nullptr, 0);
}

int valid_dird(dird fd) {
    return fd == nullptr;
}

void close_dird(dird fd) {
    free(fd);
}

int chdir_dird(dird fd) {
    return _wchdir(fd);
}

int chdir_dird(const char* path) {
    int len_converted = MultiByteToWideChar(CP_UTF8, 0, path, -1, 0, 0);
    if (len_converted == 0) {
        return -1;
    }
    WCHAR* buf = (WCHAR*)malloc(sizeof(WCHAR) * (len_converted));
    if (buf == nullptr) {
        return -1;
    }
    MultiByteToWideChar(CP_UTF8, 0, path, -1, buf, len_converted);
    int ret = _wchdir(buf);
    free(buf);
    return ret;
}

int cmp_dird(dird fd1, dird fd2) {
    return lstrcmpW(fd1, fd2);
}

dird get_dird(char* path) {
    int len_converted = MultiByteToWideChar(CP_UTF8, 0, path, -1, 0, 0);
    if (len_converted == 0) {
        return nullptr;
    }
    WCHAR* buf = (WCHAR*)malloc(sizeof(WCHAR) * (len_converted));
    if (buf == nullptr) {
        return nullptr;
    }
    MultiByteToWideChar(CP_UTF8, 0, path, -1, buf, len_converted);

    WCHAR* ret = _wfullpath(0, buf, 0);

    free(buf);
    return ret;
}

#endif // ndef _win_

#ifdef _win_
    #define S_ISDIR(st_mode) ((st_mode & _S_IFMT) == _S_IFDIR)
    #define S_ISREG(st_mode) ((st_mode & _S_IFMT) == _S_IFREG)
    #define S_ISLNK(st_mode) ((st_mode & _S_IFMT) == _S_IFLNK)
    #define O_RDONLY _O_RDONLY
static int fts_safe_changedir(FTS*, FTSENT*, int, dird);
#endif

#if defined(__svr4__) || defined(__linux__) || defined(__CYGWIN__) || defined(_win_)
    #ifdef MAX
        #undef MAX
    #endif
    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    #undef ALIGNBYTES
    #undef ALIGN
    #define ALIGNBYTES (sizeof(long long) - 1)
    #define ALIGN(p) (((uintptr_t)(p) + ALIGNBYTES) & ~ALIGNBYTES)
    #if !defined(__linux__) && !defined(__CYGWIN__)
        #define dirfd(dirp) ((dirp)->dd_fd)
    #endif
    #define D_NAMLEN(dirp) (strlen(dirp->d_name))
#else
    #define D_NAMLEN(dirp) (dirp->d_namlen)
#endif

static FTSENT* fts_alloc(FTS*, const char*, int);
static FTSENT* fts_build(FTS*, int);
static void fts_lfree(FTSENT*);
static void fts_load(FTS*, FTSENT*);
static size_t fts_maxarglen(char* const*);
static void fts_padjust(FTS*);
static int fts_palloc(FTS*, size_t);
static FTSENT* fts_sort(FTS*, FTSENT*, int);
static u_short fts_stat(FTS*, FTSENT*, int);
static int fts_safe_changedir(FTS*, FTSENT*, int, const char*);

#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2])))

#define CLR(opt) (sp->fts_options &= ~(opt))
#define ISSET(opt) (sp->fts_options & (opt))
#define SET(opt) (sp->fts_options |= (opt))

#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && chdir_dird(fd))

/* fts_build flags */
#define BCHILD 1 /* yfts_children */
#define BNAMES 2 /* yfts_children, names only */
#define BREAD 3  /* yfts_read */

static u_short
yfts_type_from_info(u_short info) {
    if (info == FTS_D || info == FTS_DP || info == FTS_DOT) {
        return FTS_D;
    } else if (info == FTS_F) {
        return FTS_F;
    } else if (info == FTS_SL || info == FTS_SLNONE) {
        return FTS_SL;
    }
    return FTS_NSOK;
}

static void*
yreallocf(void* ptr, size_t size)
{
    void* nptr;

    nptr = realloc(ptr, size);
    if (!nptr && ptr) {
        free(ptr);
    }
    return (nptr);
}

FTS* yfts_open(char* const* argv, int options, int (*compar)(const FTSENT**, const FTSENT**))
{
    FTS* sp;
    FTSENT *p, *root;
    int nitems;
    FTSENT *parent, *tmp;
    int len;

    errno = 0;

    Y_ASSERT(argv);
    if (!*argv) {
        errno = ENOENT;
        return nullptr;
    }

    /* Options check. */
    if (options & ~FTS_OPTIONMASK) {
        errno = EINVAL;
        return nullptr;
    }

    /* Allocate/initialize the stream */
    if ((sp = (FTS*)malloc(sizeof(FTS))) == nullptr) {
        return nullptr;
    }
    memset(sp, 0, sizeof(FTS));
    sp->fts_compar = compar;
    sp->fts_options = options;

    /* Shush, GCC. */
    tmp = nullptr;

    /* Logical walks turn on NOCHDIR; symbolic links are too hard. */
    if (ISSET(FTS_LOGICAL)) {
        SET(FTS_NOCHDIR);
    }

    /*
     * Start out with 1K of path space, and enough, in any case,
     * to hold the user's paths.
     */
    if (fts_palloc(sp, MAX(fts_maxarglen(argv), MAXPATHLEN))) {
        goto mem1;
    }

    /* Allocate/initialize root's parent. */
    if ((parent = fts_alloc(sp, "", 0)) == nullptr) {
        goto mem2;
    }
    parent->fts_level = FTS_ROOTPARENTLEVEL;

    /* Allocate/initialize root(s). */
    for (root = nullptr, nitems = 0; *argv; ++argv, ++nitems) {
        /* Don't allow zero-length paths. */

        len = strlen(*argv);

//Any subsequent windows call will expect no trailing slashes so we will remove them here
#ifdef _win_
        while (len && ((*argv)[len - 1] == '\\' || (*argv)[len - 1] == '/')) {
            --len;
        }
#endif

        if (len == 0) {
            errno = ENOENT;
            goto mem3;
        }

        p = fts_alloc(sp, *argv, len);
        p->fts_level = FTS_ROOTLEVEL;
        p->fts_parent = parent;
        p->fts_accpath = p->fts_name;
        p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW));
        p->fts_type = yfts_type_from_info(p->fts_info);

        /* Command-line "." and ".." are real directories. */
        if (p->fts_info == FTS_DOT) {
            p->fts_info = FTS_D;
        }

        /*
         * If comparison routine supplied, traverse in sorted
         * order; otherwise traverse in the order specified.
         */
        if (compar) {
            p->fts_link = root;
            root = p;
        } else {
            p->fts_link = nullptr;
            if (root == nullptr) {
                tmp = root = p;
            } else {
                tmp->fts_link = p;
                tmp = p;
            }
        }
    }
    if (compar && nitems > 1) {
        root = fts_sort(sp, root, nitems);
    }

    /*
     * Allocate a dummy pointer and make yfts_read think that we've just
     * finished the node before the root(s); set p->fts_info to FTS_INIT
     * so that everything about the "current" node is ignored.
     */
    if ((sp->fts_cur = fts_alloc(sp, "", 0)) == nullptr) {
        goto mem3;
    }
    sp->fts_cur->fts_level = FTS_ROOTLEVEL;
    sp->fts_cur->fts_link = root;
    sp->fts_cur->fts_info = FTS_INIT;

    /*
     * If using chdir(2), grab a file descriptor pointing to dot to ensure
     * that we can get back here; this could be avoided for some paths,
     * but almost certainly not worth the effort.  Slashes, symbolic links,
     * and ".." are all fairly nasty problems.  Note, if we can't get the
     * descriptor we run anyway, just more slowly.
     */

    if (!ISSET(FTS_NOCHDIR) && valid_dird(sp->fts_rfd = get_cwdd())) {
        SET(FTS_NOCHDIR);
    }

    return (sp);

mem3:
    fts_lfree(root);
    free(parent);
mem2:
    free(sp->fts_path);
mem1:
    free(sp);
    return nullptr;
}

static void
fts_load(FTS* sp, FTSENT* p)
{
    size_t len;
    char* cp;

    /*
     * Load the stream structure for the next traversal.  Since we don't
     * actually enter the directory until after the preorder visit, set
     * the fts_accpath field specially so the chdir gets done to the right
     * place and the user can access the first node.  From yfts_open it's
     * known that the path will fit.
     */
    len = p->fts_pathlen = p->fts_namelen;
    memmove((void*)sp->fts_path, (void*)p->fts_name, len + 1);
    if ((cp = strrchr(p->fts_name, LOCSLASH_C)) != nullptr && (cp != p->fts_name || cp[1])) {
        len = strlen(++cp);
        memmove((void*)p->fts_name, (void*)cp, len + 1);
        p->fts_namelen = (u_short)len;
    }
    p->fts_accpath = p->fts_path = sp->fts_path;
    sp->fts_dev = p->fts_dev;
}

int yfts_close(FTS* sp)
{
    FTSENT *freep, *p;
    int saved_errno;

    /*
     * This still works if we haven't read anything -- the dummy structure
     * points to the root list, so we step through to the end of the root
     * list which has a valid parent pointer.
     */
    if (sp->fts_cur) {
        for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
            freep = p;
            p = p->fts_link ? p->fts_link : p->fts_parent;
            free(freep);
        }
        free(p);
    }

    /* Free up child linked list, sort array, path buffer. */
    if (sp->fts_child) {
        fts_lfree(sp->fts_child);
    }
    if (sp->fts_array) {
        free(sp->fts_array);
    }
    free(sp->fts_path);

    /* Return to original directory, save errno if necessary. */
    if (!ISSET(FTS_NOCHDIR)) {
        saved_errno = chdir_dird(sp->fts_rfd) ? errno : 0;
        close_dird(sp->fts_rfd);

        /* Set errno and return. */
        if (saved_errno != 0) {
            /* Free up the stream pointer. */
            free(sp);
            errno = saved_errno;
            return (-1);
        }
    }

    /* Free up the stream pointer. */
    free(sp);
    return (0);
}

/*
 * Special case of "/" at the end of the path so that slashes aren't
 * appended which would cause paths to be written as "....//foo".
 */
#define NAPPEND(p)                                 \
    (p->fts_path[p->fts_pathlen - 1] == LOCSLASH_C \
         ? p->fts_pathlen - 1                      \
         : p->fts_pathlen)

FTSENT*
yfts_read(FTS* sp) {
    FTSENT *p, *tmp;
    int instr;
    char* t;
    int saved_errno;

    ClearLastSystemError();

    /* If finished or unrecoverable error, return NULL. */
    if (sp->fts_cur == nullptr || ISSET(FTS_STOP)) {
        return nullptr;
    }

    /* Set current node pointer. */
    p = sp->fts_cur;

    /* Save and zero out user instructions. */
    instr = p->fts_instr;
    p->fts_instr = FTS_NOINSTR;

    /* Any type of file may be re-visited; re-stat and re-turn. */
    if (instr == FTS_AGAIN) {
        p->fts_info = fts_stat(sp, p, 0);
        p->fts_type = yfts_type_from_info(p->fts_info);
        return (p);
    }

    /*
     * Following a symlink -- SLNONE test allows application to see
     * SLNONE and recover.  If indirecting through a symlink, have
     * keep a pointer to current location.  If unable to get that
     * pointer, follow fails.
     */
    if (instr == FTS_FOLLOW &&
        (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
        p->fts_info = fts_stat(sp, p, 1);
        p->fts_type = yfts_type_from_info(p->fts_info);
        if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
            if (valid_dird(p->fts_symfd = get_cwdd())) {
                p->fts_errno = errno;
                p->fts_info = FTS_ERR;
            } else {
                p->fts_flags |= FTS_SYMFOLLOW;
            }
        }
        return (p);
    }

    /* Directory in pre-order. */
    if (p->fts_info == FTS_D) {
        /* If skipped or crossed mount point, do post-order visit. */
        if (instr == FTS_SKIP ||
            (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) {
            if (p->fts_flags & FTS_SYMFOLLOW) {
                close_dird(p->fts_symfd);
            }
            if (sp->fts_child) {
                fts_lfree(sp->fts_child);
                sp->fts_child = nullptr;
            }
            p->fts_info = FTS_DP;
            return (p);
        }

        /* Rebuild if only read the names and now traversing. */
        if (sp->fts_child && ISSET(FTS_NAMEONLY)) {
            CLR(FTS_NAMEONLY);
            fts_lfree(sp->fts_child);
            sp->fts_child = nullptr;
        }

        /*
         * Cd to the subdirectory.
         *
         * If have already read and now fail to chdir, whack the list
         * to make the names come out right, and set the parent errno
         * so the application will eventually get an error condition.
         * Set the FTS_DONTCHDIR flag so that when we logically change
         * directories back to the parent we don't do a chdir.
         *
         * If haven't read do so.  If the read fails, fts_build sets
         * FTS_STOP or the fts_info field of the node.
         */
        if (sp->fts_child) {
            if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) {
                p->fts_errno = errno;
                p->fts_flags |= FTS_DONTCHDIR;
                for (p = sp->fts_child; p; p = p->fts_link) {
                    p->fts_accpath =
                        p->fts_parent->fts_accpath;
                }
            }
        } else if ((sp->fts_child = fts_build(sp, BREAD)) == nullptr) {
            if (ISSET(FTS_STOP)) {
                return nullptr;
            }
            return (p);
        }
        p = sp->fts_child;
        sp->fts_child = nullptr;
        goto name;
    }

    /* Move to the next node on this level. */
next:
    tmp = p;
    if ((p = p->fts_link) != nullptr) {
        free(tmp);

        /*
         * If reached the top, return to the original directory (or
         * the root of the tree), and load the paths for the next root.
         */
        if (p->fts_level == FTS_ROOTLEVEL) {
            if (FCHDIR(sp, sp->fts_rfd)) {
                SET(FTS_STOP);
                return nullptr;
            }
            fts_load(sp, p);
            return (sp->fts_cur = p);
        }

        /*
         * User may have called yfts_set on the node.  If skipped,
         * ignore.  If followed, get a file descriptor so we can
         * get back if necessary.
         */
        if (p->fts_instr == FTS_SKIP) {
            goto next;
        }
        if (p->fts_instr == FTS_FOLLOW) {
            p->fts_info = fts_stat(sp, p, 1);
            p->fts_type = yfts_type_from_info(p->fts_info);
            if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
                if (valid_dird(p->fts_symfd =
                                   get_cwdd())) {
                    p->fts_errno = errno;
                    p->fts_info = FTS_ERR;
                } else {
                    p->fts_flags |= FTS_SYMFOLLOW;
                }
            }
            p->fts_instr = FTS_NOINSTR;
        }

    name:
        t = sp->fts_path + NAPPEND(p->fts_parent);
        *t++ = LOCSLASH_C;
        memmove(t, p->fts_name, (size_t)p->fts_namelen + 1);
        return (sp->fts_cur = p);
    }

    /* Move up to the parent node. */
    p = tmp->fts_parent;
    free(tmp);

    if (p->fts_level == FTS_ROOTPARENTLEVEL) {
        /*
         * Done; free everything up and set errno to 0 so the user
         * can distinguish between error and EOF.
         */
        free(p);
        errno = 0;
        return (sp->fts_cur = nullptr);
    }

    /* NUL terminate the pathname. */
    sp->fts_path[p->fts_pathlen] = '\0';

    /*
     * Return to the parent directory.  If at a root node or came through
     * a symlink, go back through the file descriptor.  Otherwise, cd up
     * one directory.
     */
    if (p->fts_level == FTS_ROOTLEVEL) {
        if (FCHDIR(sp, sp->fts_rfd)) {
            SET(FTS_STOP);
            return nullptr;
        }
    } else if (p->fts_flags & FTS_SYMFOLLOW) {
        if (FCHDIR(sp, p->fts_symfd)) {
            saved_errno = errno;
            close_dird(p->fts_symfd);
            errno = saved_errno;
            SET(FTS_STOP);
            return nullptr;
        }
        close_dird(p->fts_symfd);
    } else if (!(p->fts_flags & FTS_DONTCHDIR) &&
               fts_safe_changedir(sp, p->fts_parent, -1, "..")) {
        SET(FTS_STOP);
        return nullptr;
    }
    p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;
    return (sp->fts_cur = p);
}

/*
 * Fts_set takes the stream as an argument although it's not used in this
 * implementation; it would be necessary if anyone wanted to add global
 * semantics to fts using yfts_set.  An error return is allowed for similar
 * reasons.
 */
/* ARGSUSED */
int yfts_set(FTS* sp, FTSENT* p, int instr)
{
    (void)sp; //Unused
    if (instr && instr != FTS_AGAIN && instr != FTS_FOLLOW &&
        instr != FTS_NOINSTR && instr != FTS_SKIP) {
        errno = EINVAL;
        return (1);
    }
    p->fts_instr = (u_short)instr;
    return (0);
}

FTSENT*
yfts_children(FTS* sp, int instr)
{
    FTSENT* p;
    dird fd;
    if (instr && instr != FTS_NAMEONLY) {
        errno = EINVAL;
        return nullptr;
    }

    /* Set current node pointer. */
    p = sp->fts_cur;

    /*
     * Errno set to 0 so user can distinguish empty directory from
     * an error.
     */
    errno = 0;

    /* Fatal errors stop here. */
    if (ISSET(FTS_STOP)) {
        return nullptr;
    }

    /* Return logical hierarchy of user's arguments. */
    if (p->fts_info == FTS_INIT) {
        return (p->fts_link);
    }

    /*
     * If not a directory being visited in pre-order, stop here.  Could
     * allow FTS_DNR, assuming the user has fixed the problem, but the
     * same effect is available with FTS_AGAIN.
     */
    if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */) {
        return nullptr;
    }

    /* Free up any previous child list. */
    if (sp->fts_child) {
        fts_lfree(sp->fts_child);
    }

    if (instr == FTS_NAMEONLY) {
        SET(FTS_NAMEONLY);
        instr = BNAMES;
    } else {
        instr = BCHILD;
    }

    /*
     * If using chdir on a relative path and called BEFORE yfts_read does
     * its chdir to the root of a traversal, we can lose -- we need to
     * chdir into the subdirectory, and we don't know where the current
     * directory is, so we can't get back so that the upcoming chdir by
     * yfts_read will work.
     */
    if (p->fts_level != FTS_ROOTLEVEL || p->fts_accpath[0] == LOCSLASH_C ||
        ISSET(FTS_NOCHDIR)) {
        return (sp->fts_child = fts_build(sp, instr));
    }

    if (valid_dird(fd = get_cwdd())) {
        return nullptr;
    }
    sp->fts_child = fts_build(sp, instr);
    if (chdir_dird(fd)) {
        close_dird(fd);
        return nullptr;
    }
    close_dird(fd);
    return (sp->fts_child);
}

static inline struct dirent* yreaddir(DIR* dir, struct dirent* de) {
    // TODO(yazevnul|IGNIETFERRO-1070): remove these macroses by replacing `readdir_r` with proper
    // alternative
    Y_PRAGMA_DIAGNOSTIC_PUSH
    Y_PRAGMA_NO_DEPRECATED
    if (readdir_r(dir, de, &de) == 0) {
        Y_PRAGMA_DIAGNOSTIC_POP
        return de;
    }

    return nullptr;
}

/*
 * This is the tricky part -- do not casually change *anything* in here.  The
 * idea is to build the linked list of entries that are used by yfts_children
 * and yfts_read.  There are lots of special cases.
 *
 * The real slowdown in walking the tree is the stat calls.  If FTS_NOSTAT is
 * set and it's a physical walk (so that symbolic links can't be directories),
 * we can do things quickly.  First, if it's a 4.4BSD file system, the type
 * of the file is in the directory entry.  Otherwise, we assume that the number
 * of subdirectories in a node is equal to the number of links to the parent.
 * The former skips all stat calls.  The latter skips stat calls in any leaf
 * directories and for any files after the subdirectories in the directory have
 * been found, cutting the stat calls by about 2/3.
 */
static FTSENT*
fts_build(FTS* sp, int type)
{
    struct dirent* dp;
    FTSENT *p, *head;
    int nitems;
    FTSENT *cur, *tail;

#ifdef _win_
    dird dirpd;
    struct DIR* dirp;
#else
    DIR* dirp;
#endif

    void* oldaddr;
    int cderrno, descend, len, level, maxlen, nlinks, saved_errno,
        nostat, doadjust;
    char* cp;

    /* Set current node pointer. */
    cur = sp->fts_cur;

    /*
     * Open the directory for reading.  If this fails, we're done.
     * If being called from yfts_read, set the fts_info field.
     */
#ifdef FTS_WHITEOUT
    if (ISSET(FTS_WHITEOUT))
        oflag = DTF_NODUP | DTF_REWIND;
    else
        oflag = DTF_HIDEW | DTF_NODUP | DTF_REWIND;
#else
    #define __opendir2(path, flag) opendir(path)
#endif
    if ((dirp = __opendir2(cur->fts_accpath, oflag)) == nullptr) {
        if (type == BREAD) {
            cur->fts_info = FTS_DNR;
            cur->fts_errno = errno;
        }
        return nullptr;
    }

#ifdef _win_
    dirpd = get_dird(cur->fts_accpath);
#endif

    /*
     * Nlinks is the number of possible entries of type directory in the
     * directory if we're cheating on stat calls, 0 if we're not doing
     * any stat calls at all, -1 if we're doing stats on everything.
     */
    if (type == BNAMES) {
        nlinks = 0;
        /* Be quiet about nostat, GCC. */
        nostat = 0;
    } else if (ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL)) {
        nlinks = cur->fts_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2);
        nostat = 1;
    } else {
        nlinks = -1;
        nostat = 0;
    }

    /*
     * If we're going to need to stat anything or we want to descend
     * and stay in the directory, chdir.  If this fails we keep going,
     * but set a flag so we don't chdir after the post-order visit.
     * We won't be able to stat anything, but we can still return the
     * names themselves.  Note, that since yfts_read won't be able to
     * chdir into the directory, it will have to return different path
     * names than before, i.e. "a/b" instead of "b".  Since the node
     * has already been visited in pre-order, have to wait until the
     * post-order visit to return the error.  There is a special case
     * here, if there was nothing to stat then it's not an error to
     * not be able to stat.  This is all fairly nasty.  If a program
     * needed sorted entries or stat information, they had better be
     * checking FTS_NS on the returned nodes.
     */
    cderrno = 0;
    if (nlinks || type == BREAD) {
#ifndef _win_
        if (fts_safe_changedir(sp, cur, dirfd(dirp), nullptr)) {
#else
        if (fts_safe_changedir(sp, cur, -1, dirpd)) {
#endif

            if (nlinks && type == BREAD) {
                cur->fts_errno = errno;
            }
            cur->fts_flags |= FTS_DONTCHDIR;
            descend = 0;
            cderrno = errno;
            (void)closedir(dirp);
            dirp = nullptr;
#ifdef _win_
            close_dird(dirpd);
            dirpd = invalidDirD;
#else
            Y_UNUSED(invalidDirD);
#endif
        } else {
            descend = 1;
        }
    } else {
        descend = 0;
    }

    /*
     * Figure out the max file name length that can be stored in the
     * current path -- the inner loop allocates more path as necessary.
     * We really wouldn't have to do the maxlen calculations here, we
     * could do them in yfts_read before returning the path, but it's a
     * lot easier here since the length is part of the dirent structure.
     *
     * If not changing directories set a pointer so that can just append
     * each new name into the path.
     */
    len = NAPPEND(cur);
    if (ISSET(FTS_NOCHDIR)) {
        cp = sp->fts_path + len;
        *cp++ = LOCSLASH_C;
    } else {
        /* GCC, you're too verbose. */
        cp = nullptr;
    }
    ++len;
    maxlen = sp->fts_pathlen - len;

    level = cur->fts_level + 1;

    /* Read the directory, attaching each entry to the `link' pointer. */
    doadjust = 0;

    //to ensure enough buffer
    TTempBuf dpe;

    for (head = tail = nullptr, nitems = 0; dirp && (dp = yreaddir(dirp, (struct dirent*)dpe.Data())) != nullptr;) {
        if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name)) {
            continue;
        }

        if ((p = fts_alloc(sp, dp->d_name, (int)strlen(dp->d_name))) == nullptr) {
            goto mem1;
        }
        if (strlen(dp->d_name) >= (size_t)maxlen) { /* include space for NUL */
            oldaddr = sp->fts_path;
            if (fts_palloc(sp, strlen(dp->d_name) + len + 1)) {
                /*
                 * No more memory for path or structures.  Save
                 * errno, free up the current structure and the
                 * structures already allocated.
                 */
            mem1:
                saved_errno = errno;
                if (p) {
                    free(p);
                }
                fts_lfree(head);
                (void)closedir(dirp);
#ifdef _win_
                close_dird(dirpd);
#endif
                cur->fts_info = FTS_ERR;
                SET(FTS_STOP);
                errno = saved_errno;
                return nullptr;
            }
            /* Did realloc() change the pointer? */
            if (oldaddr != sp->fts_path) {
                doadjust = 1;
                if (ISSET(FTS_NOCHDIR)) {
                    cp = sp->fts_path + len;
                }
            }
            maxlen = sp->fts_pathlen - len;
        }

        if (len + strlen(dp->d_name) >= USHRT_MAX) {
            /*
             * In an FTSENT, fts_pathlen is a u_short so it is
             * possible to wraparound here.  If we do, free up
             * the current structure and the structures already
             * allocated, then error out with ENAMETOOLONG.
             */
            free(p);
            fts_lfree(head);
            (void)closedir(dirp);
#ifdef _win_
            close_dird(dirpd);
#endif
            cur->fts_info = FTS_ERR;
            SET(FTS_STOP);
            errno = ENAMETOOLONG;
            return nullptr;
        }
        p->fts_level = (short)level;
        p->fts_parent = sp->fts_cur;
        p->fts_pathlen = u_short(len + strlen(dp->d_name));

#ifdef FTS_WHITEOUT
        if (dp->d_type == DT_WHT)
            p->fts_flags |= FTS_ISW;
#endif

#ifdef _DIRENT_HAVE_D_TYPE
        if (dp->d_type == DT_DIR) {
            p->fts_type = FTS_D;
        } else if (dp->d_type == DT_REG) {
            p->fts_type = FTS_F;
        } else if (dp->d_type == DT_LNK) {
            p->fts_type = FTS_SL;
        }
#endif

        // coverity[dead_error_line]: false positive
        if (cderrno) {
            if (nlinks) {
                p->fts_info = FTS_NS;
                p->fts_errno = cderrno;
            } else {
                p->fts_info = FTS_NSOK;
            }
            p->fts_accpath = cur->fts_accpath;
        } else if (nlinks == 0
#ifdef DT_DIR
                   || (nostat &&
                       dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)
#endif
        ) {
            p->fts_accpath =
                ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name;
            p->fts_info = FTS_NSOK;
        } else {
            /* Build a file name for fts_stat to stat. */
            if (ISSET(FTS_NOCHDIR)) {
                p->fts_accpath = p->fts_path;
                memmove((void*)cp, (void*)p->fts_name, (size_t)p->fts_namelen + 1);
            } else {
                p->fts_accpath = p->fts_name;
            }
            /* Stat it. */
            p->fts_info = fts_stat(sp, p, 0);
            p->fts_type = yfts_type_from_info(p->fts_info);

            /* Decrement link count if applicable. */
            if (nlinks > 0 && (p->fts_info == FTS_D ||
                               p->fts_info == FTS_DC || p->fts_info == FTS_DOT)) {
                --nlinks;
            }
        }

        /* We walk in directory order so "ls -f" doesn't get upset. */
        p->fts_link = nullptr;
        if (head == nullptr) {
            head = tail = p;
        } else {
            tail->fts_link = p;
            tail = p;
        }
        ++nitems;
    }
    if (dirp) {
        (void)closedir(dirp);
#ifdef _win_
        close_dird(dirpd);
#endif
    }

    /*
     * If realloc() changed the address of the path, adjust the
     * addresses for the rest of the tree and the dir list.
     */
    if (doadjust) {
        fts_padjust(sp);
    }

    /*
     * If not changing directories, reset the path back to original
     * state.
     */
    if (ISSET(FTS_NOCHDIR)) {
        if (len == sp->fts_pathlen || nitems == 0) {
            --cp;
        }
        *cp = '\0';
    }

    /*
     * If descended after called from yfts_children or after called from
     * yfts_read and nothing found, get back.  At the root level we use
     * the saved fd; if one of yfts_open()'s arguments is a relative path
     * to an empty directory, we wind up here with no other way back.  If
     * can't get back, we're done.
     */
    if (descend && (type == BCHILD || !nitems) &&
        (cur->fts_level == FTS_ROOTLEVEL ? FCHDIR(sp, sp->fts_rfd) : fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) {
        cur->fts_info = FTS_ERR;
        SET(FTS_STOP);
        fts_lfree(head);
        return nullptr;
    }

    /* If didn't find anything, return NULL. */
    if (!nitems) {
        if (type == BREAD) {
            cur->fts_info = FTS_DP;
        }
        fts_lfree(head);
        return nullptr;
    }

    /* Sort the entries. */
    if (sp->fts_compar && nitems > 1) {
        head = fts_sort(sp, head, nitems);
    }
    return (head);
}

static u_short
fts_stat(FTS* sp, FTSENT* p, int follow)
{
    dev_t dev;
    ino_t ino;
    stat_struct *sbp, sb;
    int saved_errno;
    /* If user needs stat info, stat buffer already allocated. */
    sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp;

#ifdef FTS_WHITEOUT
    /* check for whiteout */
    if (p->fts_flags & FTS_ISW) {
        if (sbp != &sb) {
            memset(sbp, '\0', sizeof(*sbp));
            sbp->st_mode = S_IFWHT;
        }
        return (FTS_W);
    }
#endif

    /*
     * If doing a logical walk, or application requested FTS_FOLLOW, do
     * a stat(2).  If that fails, check for a non-existent symlink.  If
     * fail, set the errno from the stat call.
     */
    if (ISSET(FTS_LOGICAL) || follow) {
        if (STAT_FUNC(p->fts_accpath, sbp)) {
            saved_errno = errno;
            if (!lstat(p->fts_accpath, sbp)) {
                errno = 0;
                return (FTS_SLNONE);
            }
            p->fts_errno = saved_errno;
            memset(sbp, 0, sizeof(stat_struct));
            return (FTS_NS);
        }
    } else if (lstat(p->fts_accpath, sbp)) {
        p->fts_errno = errno;
        memset(sbp, 0, sizeof(stat_struct));
        return (FTS_NS);
    }

    if (S_ISDIR(sbp->st_mode)) {
        /*
         * Set the device/inode.  Used to find cycles and check for
         * crossing mount points.  Also remember the link count, used
         * in fts_build to limit the number of stat calls.  It is
         * understood that these fields are only referenced if fts_info
         * is set to FTS_D.
         */
        dev = p->fts_dev = sbp->st_dev;
        ino = p->fts_ino = sbp->st_ino;
        p->fts_nlink = sbp->st_nlink;

        const char* fts_name_x = p->fts_name;
        if (ISDOT(fts_name_x)) {
            return (FTS_DOT);
        }

        /*
         * Cycle detection is done by brute force when the directory
         * is first encountered.  If the tree gets deep enough or the
         * number of symbolic links to directories is high enough,
         * something faster might be worthwhile.
         */

        //There is no way to detect symlink or mount cycles on win32

#ifndef _win_
        FTSENT* t;
        for (t = p->fts_parent;
             t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent) {
            if (ino == t->fts_ino && dev == t->fts_dev) {
                p->fts_cycle = t;
                return (FTS_DC);
            }
        }
#endif /*_win_*/
        return (FTS_D);
    }
    if (S_ISLNK(sbp->st_mode)) {
        return (FTS_SL);
    }
    if (S_ISREG(sbp->st_mode)) {
        return (FTS_F);
    }
    return (FTS_DEFAULT);
}

static FTSENT*
fts_sort(FTS* sp, FTSENT* head, int nitems)
{
    FTSENT **ap, *p;

    /*
     * Construct an array of pointers to the structures and call qsort(3).
     * Reassemble the array in the order returned by qsort.  If unable to
     * sort for memory reasons, return the directory entries in their
     * current order.  Allocate enough space for the current needs plus
     * 40 so don't realloc one entry at a time.
     */
    if (nitems > sp->fts_nitems) {
        struct _ftsent** a;

        sp->fts_nitems = nitems + 40;
        if ((a = (struct _ftsent**)realloc(sp->fts_array,
                                           sp->fts_nitems * sizeof(FTSENT*))) == nullptr) {
            if (sp->fts_array) {
                free(sp->fts_array);
            }
            sp->fts_array = nullptr;
            sp->fts_nitems = 0;
            return (head);
        }
        sp->fts_array = a;
    }
    for (ap = sp->fts_array, p = head; p; p = p->fts_link) {
        *ap++ = p;
    }
    qsort((void*)sp->fts_array, (size_t)nitems, sizeof(FTSENT*), (int (*)(const void*, const void*))sp->fts_compar);
    for (head = *(ap = sp->fts_array); --nitems; ++ap) {
        ap[0]->fts_link = ap[1];
    }
    ap[0]->fts_link = nullptr;
    return (head);
}

static FTSENT*
fts_alloc(FTS* sp, const char* name, int namelen)
{
    FTSENT* p;
    size_t len;

    /*
     * The file name is a variable length array and no stat structure is
     * necessary if the user has set the nostat bit.  Allocate the FTSENT
     * structure, the file name and the stat structure in one chunk, but
     * be careful that the stat structure is reasonably aligned.  Since the
     * fts_name field is declared to be of size 1, the fts_name pointer is
     * namelen + 2 before the first possible address of the stat structure.
     */
    len = sizeof(FTSENT) + namelen;
    if (!ISSET(FTS_NOSTAT)) {
        len += sizeof(stat_struct) + ALIGNBYTES;
    }
    if ((p = (FTSENT*)malloc(len)) == nullptr) {
        return nullptr;
    }

    /* Copy the name and guarantee NUL termination. */
    memmove((void*)p->fts_name, (void*)name, (size_t)namelen);
    p->fts_name[namelen] = '\0';

    if (!ISSET(FTS_NOSTAT)) {
        p->fts_statp = (stat_struct*)ALIGN(p->fts_name + namelen + 2);
    }
    p->fts_namelen = (u_short)namelen;
    p->fts_path = sp->fts_path;
    p->fts_errno = 0;
    p->fts_flags = 0;
    p->fts_instr = FTS_NOINSTR;
    p->fts_number = 0;
    p->fts_pointer = nullptr;
    p->fts_type = FTS_NSOK;
    return (p);
}

static void
fts_lfree(FTSENT* head)
{
    FTSENT* p;

    /* Free a linked list of structures. */
    while ((p = head) != nullptr) {
        head = head->fts_link;
        free(p);
    }
}

/*
 * Allow essentially unlimited paths; find, rm, ls should all work on any tree.
 * Most systems will allow creation of paths much longer than MAXPATHLEN, even
 * though the kernel won't resolve them.  Add the size (not just what's needed)
 * plus 256 bytes so don't realloc the path 2 bytes at a time.
 */
static int
fts_palloc(FTS* sp, size_t more)
{
    sp->fts_pathlen += more + 256;
    sp->fts_path = (char*)yreallocf(sp->fts_path, (size_t)sp->fts_pathlen);
    return (sp->fts_path == nullptr);
}

static void
ADJUST(FTSENT* p, void* addr)
{
    if ((p)->fts_accpath >= (p)->fts_path &&
        (p)->fts_accpath < (p)->fts_path + (p)->fts_pathlen) {
        if (p->fts_accpath != p->fts_path) {
            errx(1, "fts ADJUST: accpath %p path %p",
                 p->fts_accpath, p->fts_path);
        }
        if (p->fts_level != 0) {
            errx(1, "fts ADJUST: level %d not 0", p->fts_level);
        }
        (p)->fts_accpath =
            (char*)addr + ((p)->fts_accpath - (p)->fts_path);
    }
    (p)->fts_path = (char*)addr;
}

/*
 * When the path is realloc'd, have to fix all of the pointers in structures
 * already returned.
 */
static void
fts_padjust(FTS* sp)
{
    FTSENT* p;
    char* addr = sp->fts_path;

#define ADJUST1(p)                             \
    {                                          \
        if ((p)->fts_accpath == (p)->fts_path) \
            (p)->fts_accpath = (addr);         \
        (p)->fts_path = addr;                  \
    }
    /* Adjust the current set of children. */
    for (p = sp->fts_child; p; p = p->fts_link) {
        ADJUST(p, addr);
    }

    /* Adjust the rest of the tree. */
    for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
        ADJUST(p, addr);
        p = p->fts_link ? p->fts_link : p->fts_parent;
    }
}

static size_t
fts_maxarglen(char* const* argv)
{
    size_t len, max;

    for (max = 0; *argv; ++argv) {
        if ((len = strlen(*argv)) > max) {
            max = len;
        }
    }
    return (max + 1);
}

/*
 * Change to dir specified by fd or p->fts_accpath without getting
 * tricked by someone changing the world out from underneath us.
 * Assumes p->fts_dev and p->fts_ino are filled in.
 */

#ifndef _win_
static int
fts_safe_changedir(FTS* sp, FTSENT* p, int fd, const char* path)
{
    int ret, oerrno, newfd;
    stat_struct sb;

    newfd = fd;
    if (ISSET(FTS_NOCHDIR)) {
        return (0);
    }
    if (fd < 0 && (newfd = open(path, O_RDONLY, 0)) < 0) {
        return (-1);
    }
    if (fstat(newfd, &sb)) {
        ret = -1;
        goto bail;
    }
    if (p->fts_dev != sb.st_dev || p->fts_ino != sb.st_ino) {
        errno = ENOENT; /* disinformation */
        ret = -1;
        goto bail;
    }
    ret = fchdir(newfd);
bail:
    oerrno = errno;
    if (fd < 0) {
        (void)close(newfd);
    }
    errno = oerrno;
    return (ret);
}
#else
static int
fts_safe_changedir(FTS* sp, FTSENT* p, int /*fd*/, const char* path)
{
    int ret;
    stat_struct sb;

    if (ISSET(FTS_NOCHDIR))
        return (0);
    if (STAT_FUNC(path, &sb)) {
        ret = -1;
        goto bail;
    }
    if (p->fts_dev != sb.st_dev) {
        errno = ENOENT; /* disinformation */
        ret = -1;
        goto bail;
    }
    ret = chdir_dird(path);
bail:
    return (ret);
}

static int
fts_safe_changedir(FTS* sp, FTSENT* p, int /*fd*/, dird path) {
    int ret;
    stat_struct sb;

    if (ISSET(FTS_NOCHDIR))
        return (0);
    if (STAT_FUNC(path, &sb)) {
        ret = -1;
        goto bail;
    }
    if (p->fts_dev != sb.st_dev) {
        errno = ENOENT; /* disinformation */
        ret = -1;
        goto bail;
    }
    ret = chdir_dird(path);
bail:
    return (ret);
}
#endif