/*++

Copyright (c) 1988-1999  Microsoft Corporation

Module Name:

    tree.c

Abstract:

    Tree walking

--*/

#include "cmd.h"

extern   TCHAR CurDrvDir[] ;
extern   TCHAR *SaveDir ;
extern   DWORD DosErr ;
extern   BOOL CtrlCSeen;

PTCHAR   SetWildCards( PTCHAR, BOOLEAN );
BOOLEAN  IsFATDrive( PTCHAR );
VOID     SortFileList( PFS, PSORTDESC, ULONG);
BOOLEAN  FindFirstNt( PTCHAR, PWIN32_FIND_DATA, PHANDLE );
BOOLEAN  FindNextNt ( PWIN32_FIND_DATA, HANDLE );

STATUS
BuildFSFromPatterns (
    IN  PDRP     pdpr,
    IN  BOOLEAN  fPrintErrors,
    IN  BOOLEAN  fAddWild,
    OUT PFS *    ppfs
    )
{

    PCPYINFO    pcisFile;
    TCHAR               szCurDir[MAX_PATH + 2];
    TCHAR               szFilePattern[MAX_PATH + 2];
    PTCHAR              pszPatternCur;
    PPATDSC             ppatdscCur;
    PFS                 pfsFirst;
    PFS                 pfsCur;
    ULONG               cbPath;
    BOOLEAN             fFatDrive;
    ULONG               i;
    PTCHAR              pszT;

    //
    // determine FAT drive from original pattern.
    // Used in several places to control name format etc.
    //
    DosErr = 0;

    //
    // Run through each pattern making all sorts of FAT etc. specific
    // changes to it and creating the directory list for it. Then
    // combine groups of patterns into common directories and recurse
    // for each directory group.
    //

    *ppfs = pfsFirst = (PFS)gmkstr(sizeof(FS));
    pfsFirst->pfsNext = NULL;
    pfsFirst->pszDir = NULL;
    pfsCur = pfsFirst;
    pfsCur->cpatdsc = 1;

    for(i = 1, ppatdscCur = &(pdpr->patdscFirst);
        i <= pdpr->cpatdsc;
        i++, ppatdscCur = ppatdscCur->ppatdscNext) {

        pszPatternCur = ppatdscCur->pszPattern;

        if (!(fFatDrive = IsFATDrive(pszPatternCur)) && DosErr) {

            //
            // Error in determining file system type so get out.
            //
            if (fPrintErrors) PutStdErr(DosErr, NOARGS);
            return( FAILURE );

        }
        ppatdscCur->fIsFat = fFatDrive;

        //
        // Do any alterations that require wild cards for searching
        // such as change .xxx to *.xxx for FAT file system requests
        //
        // Note that if the return values is a different buffer then
        // the input the input will be freed when we are done with the
        // Dir command.
        //
        //
        // Note that though SetWildCards will allocate heap for the
        // modified pattern this will get freed when FreeStack is
        // called at the end of the Dir call.
        //
        // An out of memory is the only reason to fail and we would not
        // return from that but go through the abort call in gmstr
        //

        if (fAddWild) {

            pszT = SetWildCards(pszPatternCur, fFatDrive);
            FreeStr(pszPatternCur);
            pszPatternCur = pszT;

        }

        //
        // Convert the current pattern into a path and file part
        //
        // Save the current directory in SaveDir, change to new directory
        // and parse pattern into a copy information structure. This also
        // converts pszPatternCur into the current directory which also produces
        // a fully qualified name.
        //

        DosErr = 0;

        DEBUG((ICGRP, DILVL, "PrintPattern pattern `%ws'", pszPatternCur));
        if ((pcisFile = SetFsSetSaveDir(pszPatternCur)) == (PCPYINFO) FAILURE) {

            //
            // DosErr is set in SetFs.. from GetLastError
            //
            if (fPrintErrors) 
                PutStdErr(DosErr, NOARGS);
            return( FAILURE );
        }

        DEBUG((ICGRP, DILVL, "PrintPattern fullname `%ws'", pcisFile->fnptr));

        //
        // CurDrvDir ends in '\' (old code also and a DOT but I do not
        // understand where this would come from I will leave it in for now.
        // Remove the final '\' from a copy of the current directory and
        // print that version out.
        //

        mystrcpy(szCurDir,CurDrvDir);

        //
        // SetFsSetSaveDir changes directories as a side effect. Since all
        // work will be in fully qualified paths we do not need this. Also
        // since we will change directories for each pattern that is examined
        // we will force the directory back to the original each time.
        //
        // This can not be done until after all use of the current directory
        // is made.
        //
        RestoreSavedDirectory( );

        DEBUG((ICGRP, DILVL, "PrintPattern Current Drive `%ws'", szCurDir));

        cbPath = mystrlen(szCurDir);
        
        if (cbPath > 3) {
            if (fFatDrive && *penulc(szCurDir) == DOT) {
                szCurDir[cbPath-2] = NULLC;
            } else {
                szCurDir[cbPath-1] = NULLC;
            }
        }

        //
        // If no room for filename then return
        //
        if (cbPath >= MAX_PATH -1) {

            if (fPrintErrors) PutStdErr( ERROR_FILE_NOT_FOUND, NOARGS );
            return(FAILURE);

        }

        //
        // Add filename and possibly ext to szSearchPath
        // if no filename or ext, use "*"
        //
        // If pattern was just extension the SetWildCard had already
        // added * to front of extension.
        //
        if (*(pcisFile->fnptr) == NULLC) {

            mystrcpy(szFilePattern, TEXT("*"));

        } else {

            mystrcpy(szFilePattern, pcisFile->fnptr);

        }

        DEBUG((ICGRP, DILVL, "DIR:PrintPattern  Pattern to search for `%ws'", szFilePattern));

        //
        // Is name too long
        //
        if ((cbPath + mystrlen(szFilePattern) + 1) > MAX_PATH ) {

            if (fPrintErrors) PutStdErr(ERROR_BUFFER_OVERFLOW, NOARGS);
            return( FAILURE );

        } else {

            //
            // If this is a FAT drive and there was a filename with
            // no extension then add '.*' (and there is room)
            //
            if (*pcisFile->fnptr && (!pcisFile->extptr || !*pcisFile->extptr) &&
                ((mystrlen(szFilePattern) + 2) < MAX_PATH) && fFatDrive && fAddWild) {
                mystrcat(szFilePattern, TEXT(".*")) ;
            }
        }

        //
        // ppatdscCur->pszPattern will be freed at end of command when everything
        // else is freed.
        //
        ppatdscCur->pszPattern = (PTCHAR)gmkstr(_tcslen(szFilePattern)*sizeof(TCHAR) + sizeof(TCHAR));
        mystrcpy(ppatdscCur->pszPattern, szFilePattern);
        ppatdscCur->pszDir = (PTCHAR)gmkstr(_tcslen(szCurDir)*sizeof(TCHAR) + sizeof(TCHAR));
        mystrcpy(ppatdscCur->pszDir, szCurDir);

        if (pfsCur->pszDir) {

            //
            // changing directories so change directory grouping.
            //
            if (_tcsicmp(pfsCur->pszDir, ppatdscCur->pszDir)) {

                pfsCur->pfsNext = (PFS)gmkstr(sizeof(FS));
                pfsCur = pfsCur->pfsNext;
                pfsCur->pszDir = (PTCHAR)gmkstr(_tcslen(ppatdscCur->pszDir)*sizeof(TCHAR) + sizeof(TCHAR));
                mystrcpy(pfsCur->pszDir, ppatdscCur->pszDir);
                pfsCur->pfsNext = NULL;
                pfsCur->fIsFat = ppatdscCur->fIsFat;
                pfsCur->ppatdsc = ppatdscCur;
                pfsCur->cpatdsc = 1;

            } else {

                pfsCur->cpatdsc++;

            }

        } else {

            //
            // Have not filled in current fs descriptor yet.
            //
            pfsCur->pszDir = (PTCHAR)gmkstr(_tcslen(ppatdscCur->pszDir)*sizeof(TCHAR) + 2*sizeof(TCHAR));
            mystrcpy(pfsCur->pszDir, ppatdscCur->pszDir);
            pfsCur->fIsFat = ppatdscCur->fIsFat;
            pfsCur->ppatdsc = ppatdscCur;

        }

    } // while for running through pattern list

    return( SUCCESS );

}

STATUS
AppendPath(
    OUT PTCHAR Buffer,
    IN ULONG BufferCount,
    IN PTCHAR Prefix,
    IN PTCHAR Suffix
    )
{
    if (mystrlen( Prefix ) + 1 + mystrlen( Suffix ) + 1 > BufferCount) {
        return(ERROR_BUFFER_OVERFLOW);
    }

    mystrcpy( Buffer, Prefix );

    //
    //  Append a \ if there isn't one already at the end of the prefix
    //

    if (*lastc( Buffer ) != TEXT('\\')) {
        mystrcat( Buffer, TEXT( "\\" ));
    }

    mystrcat( Buffer, Suffix );

    return( SUCCESS );
}


STATUS
ExpandAndApplyToFS(
    IN  PFS     FileSpec,
    IN  PSCREEN pscr,
    IN  ULONG   AttribMask,
    IN  ULONG   AttribValues,

    IN  PVOID   Data OPTIONAL,
    IN  VOID    (*ErrorFunction) (STATUS, PTCHAR, PVOID) OPTIONAL,
    IN  STATUS  (*PreScanFunction) (PFS, PSCREEN, PVOID) OPTIONAL,
    IN  STATUS  (*ScanFunction) (PFS, PFF, PSCREEN, PVOID) OPTIONAL,
    IN  STATUS  (*PostScanFunction) (PFS, PSCREEN, PVOID) OPTIONAL
    )
/*++

Routine Description:
    Expand a given FS and apply the dispatch functions to it.  The pff field is
    set to point to the packed set of Win32 find records and the array of pointers
    is set up as well.

Arguments:

    FileSpec - FS pointer to expand.

    AttribMask - mask for attributes we care about when matching

    AttribValues - attributes that must match to satisfy the enumeration

    Data - pointer to caller data passed to functions

    ErrorFunction - routine to call on pattern matching errors

    PreScanFunction - routine to call before enumeration begins

    ScanFunction - routine to call during enumeration

    PostScanFunction - routine to call after enumeration is complete

Return Value:

    If any of the applied functions returns a non-SUCCESS status, we return that.

    If no matching file is ever found then ERROR_FILE_NOT_FOUND.

    Otherwise SUCCESS.

--*/
{
    PFF     CurrentFF;                          //  Pointer to FF begin built
    HANDLE  FindHandle;                         //  Find handle
    ULONG   MaxFFSize = CBFILEINC;              //  Current max size of FF buffer
    ULONG   CurrentFFSize = 0;                  //  Bytes in use in FF
    PPATDSC CurrentPattern;                     //  Current pattern being expanded
#define SEARCHBUFFERLENGTH  (MAX_PATH + 2)
    TCHAR   szSearchPath[SEARCHBUFFERLENGTH];
    ULONG   i;                                  //  loop count for patterns
    PTCHAR  p;
    STATUS Status;
    BOOL    FoundAnyFile = FALSE;

    DosErr = SUCCESS;

    //
    //  Initialize the FS structure:
    //      Allocate default size FF array
    //      Indicate no files stored
    //

    FileSpec->pff = CurrentFF = (PFF)gmkstr( MaxFFSize );
    FileSpec->cff = 0;
    FileSpec->FileCount = 0;
    FileSpec->DirectoryCount = 0;
    FileSpec->cbFileTotal.QuadPart = 0;

    for(i = 1, CurrentPattern = FileSpec->ppatdsc;
        i <= FileSpec->cpatdsc;
        i++, CurrentPattern = CurrentPattern->ppatdscNext ) {

        //
        //  Check immediately if a control-c was hit before
        //  doing file I/O (which may take a long time on a slow link)
        //

        if (CtrlCSeen) {
            return FAILURE;
        }

        //
        //  Build enumeration path.  Handle buffer overflow.
        //

        if (AppendPath( szSearchPath, SEARCHBUFFERLENGTH,
                        FileSpec->pszDir, CurrentPattern->pszPattern) != SUCCESS) {
            return ERROR_BUFFER_OVERFLOW;
        }

        if (PreScanFunction != 0) {
            Status = PreScanFunction( FileSpec, pscr, Data );
            if (Status != SUCCESS) {
                return Status;
            }
        }

        //
        //  Fetch all files since we may looking for a file with a attribute that
        //  is not set (a non-directory etc.
        //

        if (!FindFirstNt(szSearchPath, &(CurrentFF->data), &FindHandle)) {

            if (DosErr) {

                //
                //  Map NO_MORE_FILES/ACCESS_DENIED into FILE_NOT_FOUND
                //

                if (DosErr == ERROR_NO_MORE_FILES
                    || DosErr == ERROR_ACCESS_DENIED) {

                    DosErr = ERROR_FILE_NOT_FOUND;

                }

                if (DosErr == ERROR_FILE_NOT_FOUND || DosErr == ERROR_PATH_NOT_FOUND) {

                    if (ErrorFunction != NULL) {
                        ErrorFunction( DosErr, szSearchPath, Data );
                    }

                }

            }

        } else {


            do {

                //
                // Check immediately if a control-c was hit before
                // doing file I/O (which may take a long time on a slow link)
                //

                if (CtrlCSeen) {
                    findclose( FindHandle );
                    return(FAILURE);
                }

                //
                //  Before allowing this entry to be put in the list check it
                //  for the proper attribs
                //
                //  AttribMask is a bit mask of attribs we care to look at
                //  AttribValues is the state these selected bits must be in
                //  for them to be selected.
                //
                // IMPORTANT: both of these must be in the same bit order
                //

                DEBUG((ICGRP, DILVL, " found %ws", CurrentFF->data.cFileName)) ;
                DEBUG((ICGRP, DILVL, " attribs %x", CurrentFF->data.dwFileAttributes)) ;

                if ((CurrentFF->data.dwFileAttributes & AttribMask) !=
                    (AttribValues & AttribMask) ) {
                    continue;
                }

                //
                //  We have an entry that matches.  Set up FF
                //
                //  Compute the true size of the ff entry and don't forget the zero
                //  and the DWORD alignment factor.
                //  Note that CurrentFF->cb is a USHORT to save space. The
                //  assumption is that MAX_PATH is quite a bit less then 32k
                //
                //  To compute remove the size of the filename field since it is at MAX_PATH.
                //  also take out the size of the alternative name field
                //  then add back in the actual size of the field plus 1 byte termination
                //

                FoundAnyFile = TRUE;

                p = (PTCHAR)CurrentFF->data.cFileName;
                p += mystrlen( p ) + 1;

                mystrcpy( p, CurrentFF->data.cAlternateFileName );
                if (*p == TEXT('\0')) {
                    CurrentFF->obAlternate = 0;
                } else {
                    CurrentFF->obAlternate = (USHORT)(p - CurrentFF->data.cFileName);
                }

                p += mystrlen( p ) + 1;

                CurrentFF->cb = (USHORT)((PBYTE)p - (PBYTE)CurrentFF);

                //
                // Adjust count to align on DWORD boundaries for mips and risc
                // machines
                //

                CurrentFF->cb = (CurrentFF->cb + sizeof( DWORD ) - 1) & (-(int)sizeof( DWORD ));

                if ((CurrentFF->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
                    FileSpec->FileCount++;
                } else {
                    FileSpec->DirectoryCount++;
                }

                //
                //  The FF is built.  Call the enumeration function
                //

                if (ScanFunction != NULL) {
                    Status = ScanFunction( FileSpec, CurrentFF, pscr, Data );
                    if (Status != SUCCESS) {
                        findclose( FindHandle );
                        return Status;
                    }
                }

                //
                //  If there's no scanning function, save the results
                //

                if (ScanFunction == NULL) {

                    FileSpec->cff ++;

                    //
                    // Update the accounting information for file buffer info.
                    //

                    CurrentFFSize += CurrentFF->cb;
                    CurrentFF = (PFF) ((PBYTE)CurrentFF + CurrentFF->cb);

                    //
                    //  Make sure we can handle a max sized entry.  If not, resize the buffer.
                    //

                    if (CurrentFFSize + sizeof( FF ) >= MaxFFSize ) {

                        MaxFFSize += 64 * 1024;

                        DEBUG((ICGRP, DILVL, "\t size of new pff %d", MaxFFSize ));

                        FileSpec->pff = (PFF)resize( FileSpec->pff, MaxFFSize );
                        if (FileSpec->pff == NULL) {
                            DEBUG((ICGRP, DILVL, "\t Could not resize pff" ));
                            return MSG_NO_MEMORY;
                        }

                        CurrentFF = (PFF)((PBYTE)FileSpec->pff + CurrentFFSize);
                        DEBUG((ICGRP, DILVL, "\t resized CurrentFF new value %lx", CurrentFF)) ;

                    }
                }

            } while (FindNextNt(&CurrentFF->data, FindHandle));

            findclose( FindHandle );
        }

        //
        //  We have an error left over from the FindNext.  If it is NO_MORE_FILES then we
        //  continue only if we have more than one directory.
        //

        if (DosErr != SUCCESS && DosErr != ERROR_NO_MORE_FILES) {

            //
            // If not doing multiple file list then error
            // If multiple have failed but still have files from previous pattern
            //
            if (FileSpec->cpatdsc <= 1) {

                return DosErr ;
            }

        }
    
    }

    //
    //  If we did no scan processing, then we must create the pointers since
    //  SOMEONE is interested in this data.
    //
    if (ScanFunction == NULL && FileSpec->cff != 0) {
        FileSpec->prgpff = (PPFF)gmkstr( sizeof(PFF) * FileSpec->cff );

        CurrentFF = FileSpec->pff;

        for (i = 0; i < FileSpec->cff; i++) {
            FileSpec->prgpff[i] = CurrentFF;
            CurrentFF = (PFF) ((PBYTE)CurrentFF + CurrentFF->cb);
        }
    }

    //
    //  Perform post processing
    //

    Status = SUCCESS;

    if (PostScanFunction != NULL) {
        Status = PostScanFunction( FileSpec, pscr, Data );
    }

    if (Status == SUCCESS && !FoundAnyFile) {
        return ERROR_FILE_NOT_FOUND;
    } else {
        return Status;
    }
}


STATUS
WalkTree(
    IN  PFS     FileSpec,
    IN  PSCREEN pscr,
    IN  ULONG   AttribMask,
    IN  ULONG   AttribValues,
    IN  BOOL    Recurse,

    IN  PVOID   Data OPTIONAL,
    IN  VOID    (*ErrorFunction) (STATUS, PTCHAR, PVOID) OPTIONAL,
    IN  STATUS  (*PreScanFunction) (PFS, PSCREEN, PVOID) OPTIONAL,
    IN  STATUS  (*ScanFunction) (PFS, PFF, PSCREEN, PVOID) OPTIONAL,
    IN  STATUS  (*PostScanFunction) (PFS, PSCREEN, PVOID) OPTIONAL
)

/*++

Routine Description:
    Expand a given FS and apply the dispatch functions to it.  The pff field is
    set to point to the packed set of Win32 find records and the array of pointers
    is set up as well.  Recurse if necessary.

Arguments:

    FileSpec - FS pointer to expand.

    pscr - screen for output

    AttribMask - mask for attributes we care about when matching

    AttribValues - attributes that must match to satisfy the enumeration

    Recurse - TRUE => perform the operation in a directory and then descend to
        children

    Data - pointer to caller data passed to functions

    ErrorFunction - routine to call on pattern matching errors

    PreScanFunction - routine to call before enumeration begins

    ScanFunction - routine to call during enumeration

    PostScanFunction - routine to call after enumeration is complete

Return Value:

    If any of the applied functions returns a non-SUCCESS status, we return that.

    If no matching file is ever found then ERROR_FILE_NOT_FOUND.

    Otherwise SUCCESS.

--*/
{
    STATUS Status;
    FS DirectorySpec;
    FS ChildFileSpec;
    ULONG i;
    BOOL FoundAnyFile = FALSE;

    //
    //  Check for ^C often
    //

    if (CtrlCSeen) {
        return FAILURE;
    }

    Status = ExpandAndApplyToFS( FileSpec,
                                 pscr,
                                 AttribMask,
                                 AttribValues,
                                 Data,
                                 ErrorFunction,
                                 PreScanFunction,
                                 ScanFunction,
                                 PostScanFunction );

    //
    //  If we succeeded, remember that we did some work
    //

    if (Status == SUCCESS) {

        FoundAnyFile = TRUE;

    //
    //  If we got an unknown error, or we got FILE_NOT_FOUND and we're not
    //  recursing, return that error
    //

    } else if ((Status != ERROR_FILE_NOT_FOUND && Status != ERROR_PATH_NOT_FOUND) 
               || !Recurse) {

        return Status;
    }

    //
    //  Free up buffer holding files since we no longer need these.
    //  Move on to determine if we needed to go to another directory
    //

    FreeStr((PTCHAR)(FileSpec->pff));
    FileSpec->pff = NULL;

    if (CtrlCSeen) {
        return FAILURE;
    }

    if (!Recurse)
        return SUCCESS;

    //
    //  Build up a copy of the FileSpec and build a list of all
    //  immediate child directories
    //

    DirectorySpec.pszDir = (PTCHAR)gmkstr( (_tcslen( FileSpec->pszDir ) + 1 ) * sizeof( TCHAR ));
    mystrcpy( DirectorySpec.pszDir, FileSpec->pszDir );
    DirectorySpec.ppatdsc = (PPATDSC)gmkstr( sizeof( PATDSC ) );
    DirectorySpec.cpatdsc = 1;
    DirectorySpec.fIsFat = FileSpec->fIsFat;
    DirectorySpec.pfsNext = NULL;

    if (FileSpec->fIsFat) {
        DirectorySpec.ppatdsc->pszPattern = TEXT("*.*");
    } else {
        DirectorySpec.ppatdsc->pszPattern = TEXT("*");
    }

    DirectorySpec.ppatdsc->pszDir = (PTCHAR)gmkstr( (_tcslen( FileSpec->pszDir ) + 1) * sizeof(TCHAR));
    mystrcpy( DirectorySpec.ppatdsc->pszDir, DirectorySpec.pszDir );
    DirectorySpec.ppatdsc->ppatdscNext = NULL;

    Status = ExpandAndApplyToFS( &DirectorySpec,
                             pscr,
                             FILE_ATTRIBUTE_DIRECTORY,
                             FILE_ATTRIBUTE_DIRECTORY,
                             NULL,
                             NULL,
                             NULL,
                             NULL,
                             NULL );

    //
    //  If we got an error enumerating the directories, pretend that
    //  we just didn't find any at all.
    //

    if (Status != SUCCESS) {
        DirectorySpec.cff = 0;
        Status = SUCCESS;
    }


    //
    // Check for CtrlC again after calling GetFS because
    // GetFS may have returned failure because CtrlC was hit
    // inside the GetFS function call
    //

    if (CtrlCSeen) {
        return( FAILURE );
    }

    //
    //  Walk the list of found directories, processing each one
    //

    for (i = 0; i < DirectorySpec.cff; i++) {

        PTCHAR DirectoryName;
        ULONG NameLength;

        //
        //  Skip recursing on . and ..
        //

        DirectoryName = DirectorySpec.prgpff[i]->data.cFileName;

        if (!_tcscmp( DirectoryName, TEXT(".") )
            || !_tcscmp( DirectoryName, TEXT("..") )) {
            continue;
        }

        //
        //  Form the new name we will descend into
        //

        NameLength = _tcslen( FileSpec->pszDir ) + 1 +
                     _tcslen( DirectoryName ) + 1;

        if (NameLength > MAX_PATH) {
            PutStdErr( MSG_DIR_TOO_LONG, TWOARGS, FileSpec->pszDir, DirectoryName );
            return ERROR_BUFFER_OVERFLOW;
        }

        memset( &ChildFileSpec, 0, sizeof( ChildFileSpec ));
        ChildFileSpec.pszDir = (PTCHAR)gmkstr( NameLength * sizeof( TCHAR ));

        AppendPath( ChildFileSpec.pszDir, NameLength, FileSpec->pszDir, DirectoryName );

        ChildFileSpec.ppatdsc = FileSpec->ppatdsc;
        ChildFileSpec.cpatdsc = FileSpec->cpatdsc;
        ChildFileSpec.fIsFat = FileSpec->fIsFat;

        Status = WalkTree( &ChildFileSpec,
                            pscr,
                            AttribMask,
                            AttribValues,
                            Recurse,
                            Data,
                            ErrorFunction,
                            PreScanFunction,
                            ScanFunction,
                            PostScanFunction );

        FreeStr( (PTCHAR) ChildFileSpec.pff );
        ChildFileSpec.pff = NULL;
        FreeStr( (PTCHAR) ChildFileSpec.prgpff );
        ChildFileSpec.prgpff = NULL;
        FreeStr( ChildFileSpec.pszDir );
        ChildFileSpec.pszDir = NULL;

        //
        //  If we succeeded, then remember that we actually did something
        //

        if (Status == SUCCESS) {
            FoundAnyFile = TRUE;

        //
        //  If we just couldn't find what we wanted, keep on working
        //

        } else if (Status == ERROR_BUFFER_OVERFLOW 
                   || Status == ERROR_FILE_NOT_FOUND 
                   || Status == ERROR_PATH_NOT_FOUND) {
            Status = SUCCESS;

        } else if ((DirectorySpec.prgpff[i]->data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
            Status = SUCCESS;
        } else {
            break;
        }

    }

    //
    // At bottom of directory tree, free buffer holding
    // list of directories.
    //
    FreeStr( (PTCHAR)DirectorySpec.pszDir );
    FreeStr( (PTCHAR)DirectorySpec.pff );
    FreeStr( (PTCHAR)DirectorySpec.prgpff );

    if (Status == SUCCESS && !FoundAnyFile) {
        return ERROR_FILE_NOT_FOUND;
    } else {
        return Status;
    }
}