/* demsrch.c - SVC handlers for calls to search files
 *
 * demFindFirst
 * demFindNext
 * demFindFirstFCB
 * demFindNextFCB
 *
 * Modification History:
 *
 * Sudeepb 06-Apr-1991 Created
 *
 */

#include "dem.h"
#include "demmsg.h"
#include "winbasep.h"
#include <vdm.h>
#include <softpc.h>
#include <mvdm.h>
#include <memory.h>
#include <nt_vdd.h>

extern BOOL IsFirstCall;



/*
 *  Internal globals, function prototypes
 */

#define FINDFILE_DEVICE (HANDLE)0xffffffff

typedef struct _PSP_FILEFINDLIST {
    LIST_ENTRY PspFFindEntry;      // Next psp
    LIST_ENTRY FFindHeadList;      // File Find list for this psp
    ULONG      usPsp;              // PSP id
} PSP_FFINDLIST, *PPSP_FFINDLIST;

typedef struct _FFINDDOSDATA {
    ULONG    FileIndex;
    ULONG    FileNameLength;
    WCHAR    FileName[MAXIMUM_FILENAME_LENGTH + 1];
    FILETIME ftLastWriteTime;
    DWORD    dwFileSizeLow;
    UCHAR    uchFileAttributes;
    CHAR     cFileName[14];
} FFINDDOSDATA, *PFFINDDOSDATA;

typedef struct _FILEFINDLIST {
    LIST_ENTRY     FFindEntry;
    ULONG          FFindId;
    NTSTATUS       LastQueryStatus;
    LARGE_INTEGER  FindFileTics;
    HANDLE         DirectoryHandle;
    PVOID          FindBufferBase;
    PVOID          FindBufferNext;
    ULONG          FindBufferLength;
    FFINDDOSDATA   DosData;
    USHORT         usSrchAttr;
    BOOLEAN        SupportReset;
    UNICODE_STRING PathName;
    UNICODE_STRING FileName;
}FFINDLIST, *PFFINDLIST;

LIST_ENTRY PspFFindHeadList= {&PspFFindHeadList, &PspFFindHeadList};


#define FFINDID_BASE 0x80000000
ULONG NextFFindId = FFINDID_BASE;
BOOLEAN FFindIdWrap = FALSE;
#define MAX_DIRECTORYHANDLE 64
#define MAX_FINDBUFFER 128
ULONG NumDirectoryHandle = 0;
ULONG NumFindBuffer=0;
LARGE_INTEGER FindFileTics = {0,0};
LARGE_INTEGER NextFindFileTics = {0,0};

char szStartDotStar[]="????????.???";


PFFINDLIST
SearchFile(
    PWCHAR pwcFile,
    USHORT SearchAttr,
    PFFINDLIST pFFindEntry,
    PFFINDDOSDATA pFFindDDOut
    );


NTSTATUS
FileFindNext(
    PFFINDDOSDATA pFFindDD,
    PFFINDLIST pFFindEntry
    );

NTSTATUS
FileFindLast(
    PFFINDLIST pFFindEntry
    );

VOID
FileFindClose(
    PFFINDLIST pFFindEntry
    );


NTSTATUS
FileFindOpen(
    PWCHAR pwcFile,
    PFFINDLIST pFFindEntry,
    ULONG BufferSize
    );

NTSTATUS
FileFindReset(
   PFFINDLIST pFFindEntry
   );


HANDLE
FileFindFirstDevice(
    PWCHAR FileName,
    PFILE_BOTH_DIR_INFORMATION DirectoryInfo
    );

void
CloseOldestFileFindBuffer(
   void
   );


BOOL
CopyDirInfoToDosData(
    PFFINDDOSDATA pFFindDD,
    PFILE_BOTH_DIR_INFORMATION DirectoryInfo,
    USHORT SearchAttr
    );

BOOL
DemOemToUni(
    PUNICODE_STRING pUnicode,
    LPSTR lpstr
    );

VOID
FillFcbVolume(
    PSRCHBUF pSrchBuf,
    CHAR *pFileName,
    USHORT SearchAttr
    );

BOOL
FillDtaVolume(
    CHAR *pFileName,
    PSRCHDTA  pDta,
    USHORT SearchAttr
    );

BOOL
MatchVolLabel(
    CHAR * pVolLabel,
    CHAR * pBaseName
    );

VOID
NtVolumeNameToDosVolumeName(
    CHAR * pDosName,
    CHAR * pNtName
    );

VOID
FillFCBSrchBuf(
     PFFINDDOSDATA pFFindDD,
     PSRCHBUF pSrchBuf
     );

VOID
FillSrchDta(
     PFFINDDOSDATA pFFindDD,
     PSRCHDTA pDta
     );

PFFINDLIST
AddFFindEntry(
     PWCHAR     pwcFile,
     PFFINDLIST pFFindEntrySrc
     );

PPSP_FFINDLIST
GetPspFFindList(
     USHORT CurrPsp
     );

PFFINDLIST
GetFFindEntryByFindId(
     ULONG NextFFindId
     );

VOID
FreeFFindEntry(
     PFFINDLIST pFFindEntry
     );

VOID
FreeFFindList(
     PLIST_ENTRY pFFindHeadList
     );


/* demFindFirst - Path-Style Find First File
 *
 * Entry -  Client (DS:DX) - File Path with wildcard
 *      Client (CX)    - Search Attributes
 *
 * Exit  - Success
 *      Client (CF) = 0
 *      DTA updated
 *
 *     Failure
 *      Client (CF) = 1
 *      Client (AX) = Error Code
 *
 * NOTES
 *    Search Rules: Ignore Read_only and Archive bits.
 *          If CX == ATTR_NORMAL Search only for normal files
 *          If CX == ATTR_HIDDEN Search Hidden or normal files
 *          If CX == ATTR_SYSTEM Search System or normal files
 *          If CX == ATTR_DIRECTORY Search directory or normal files
 *                  If CX == ATTR_VOLUME_ID Search Volume_ID
 *                  if CX == -1 return everytiing you find
 *
 *   Limitations - 21-Sep-1992 Jonle
 *     cannot return label from a UNC name,just like dos.
 *     Apps which keep many find handles open can cause
 *     serious trouble, we must rewrite so that we can
 *     close the handles
 *
 */

VOID demFindFirst (VOID)
{
    DWORD dwRet;
    PVOID pDta;


    LPSTR lpFile = (LPSTR) GetVDMAddr (getDS(),getDX());

    pDta = (PVOID) GetVDMAddr (*((PUSHORT)pulDTALocation + 1),
                               *((PUSHORT)pulDTALocation));

    dwRet = demFileFindFirst (pDta, lpFile, getCX());

    if (dwRet == -1) {
        dwRet = GetLastError();
        demClientError(INVALID_HANDLE_VALUE, *lpFile);
        return;
    }

    if (dwRet != 0) {
        setAX((USHORT) dwRet);
        setCF (1);
    } else {
        setCF (0);
    }
    return;

}


DWORD demFileFindFirst (
    PVOID pvDTA,
    LPSTR lpFile,
    USHORT SearchAttr)
{
    PSRCHDTA       pDta = (PSRCHDTA)pvDTA;
    PFFINDLIST     pFFindEntry;
    FFINDDOSDATA   FFindDD;
    UNICODE_STRING FileUni;
    WCHAR          wcFile[MAX_PATH + sizeof(WCHAR)];


#if DBG
    if (SIZEOF_DOSSRCHDTA != sizeof(SRCHDTA)) {
        sprintf(demDebugBuffer,
                "demsrch: FFirst SIZEOF_DOSSRCHDTA %ld != sizeof(SRCHDTA) %ld\n",
                SIZEOF_DOSSRCHDTA,
                sizeof(SRCHDTA));
        OutputDebugStringOem(demDebugBuffer);
        }

    if (fShowSVCMsg & DEMFILIO){
        sprintf(demDebugBuffer,"demsrch: FindFirst<%s>\n", lpFile);
        OutputDebugStringOem(demDebugBuffer);
        }
#endif

    STOREDWORD(pDta->FFindId,0);
    STOREDWORD(pDta->pFFindEntry,0);

    FileUni.Buffer = wcFile;
    FileUni.MaximumLength = sizeof(wcFile);
    DemOemToUni(&FileUni, lpFile);


    //
    //  Do volume label first.
    //
    if (SearchAttr & ATTR_VOLUME_ID) {
        if (FillDtaVolume(lpFile, pDta, SearchAttr)) {

            // got vol label match
            // do look ahead before returning
            if (SearchAttr != ATTR_VOLUME_ID) {
                pFFindEntry = SearchFile(wcFile, SearchAttr, NULL, NULL);
                if (pFFindEntry) {
                    STOREDWORD(pDta->pFFindEntry,pFFindEntry);
                    STOREDWORD(pDta->FFindId,pFFindEntry->FFindId);
                    }
                }
            return 0;
            }

           // no vol match, if asking for more than vol label
           // fall thru to file search code, otherwise ret error
        else if (SearchAttr == ATTR_VOLUME_ID) {
            return GetLastError();
            }
        }

    //
    // Search the dir
    //
    pFFindEntry = SearchFile(wcFile, SearchAttr, NULL, &FFindDD);

    if (!FFindDD.cFileName[0]) {

        // search.asm in doskrnl never returns ERROR_FILE_NOT_FOUND
        // only ERROR_PATH_NOT_FOUND, ERROR_NO_MORE_FILES
        DWORD dw;

        dw = GetLastError();
        if (dw == ERROR_FILE_NOT_FOUND) {
            SetLastError(ERROR_NO_MORE_FILES);
            }
        else if (dw == ERROR_BAD_PATHNAME || dw == ERROR_DIRECTORY ) {
            SetLastError(ERROR_PATH_NOT_FOUND);
            }
        return (DWORD)-1;
        }

    FillSrchDta(&FFindDD, pDta);

    if (pFFindEntry) {
        STOREDWORD(pDta->pFFindEntry,pFFindEntry);
        STOREDWORD(pDta->FFindId,pFFindEntry->FFindId);
        }

    return 0;
}


/*
 * DemOemToUni
 *
 * returns TRUE\FALSE for success, sets last error if fail
 *
 */
BOOL DemOemToUni(PUNICODE_STRING pUnicode, LPSTR lpstr)
{
    NTSTATUS   Status;
    OEM_STRING OemString;

    RtlInitString(&OemString,lpstr);
    Status = RtlOemStringToUnicodeString(pUnicode,&OemString,FALSE);
    if (!NT_SUCCESS(Status)) {
        if (Status == STATUS_BUFFER_OVERFLOW) {
            SetLastError(ERROR_FILENAME_EXCED_RANGE);
            }
        else {
            SetLastError(RtlNtStatusToDosError(Status));
            }
        return FALSE;
        }

    pUnicode->Buffer[pUnicode->Length] = UNICODE_NULL;

    return TRUE;
}





/* demFindNext - Path-Style Find Next File
 *
 * Entry -  None
 *
 * Exit  - Success
 *      Client (CF) = 0
 *      DTA updated
 *
 *     Failure
 *      Client (CF) = 1
 *      Client (AX) = Error Code
 */
VOID demFindNext (VOID)
{
    DWORD dwRet;
    PVOID pDta;

    pDta = (PVOID) GetVDMAddr(*((PUSHORT)pulDTALocation + 1),
                              *((PUSHORT)pulDTALocation));

    dwRet = demFileFindNext (pDta);

    if (dwRet != 0) {
        setAX((USHORT) dwRet);
        setCF (1);
        return;
        }

    setCF (0);
    return;

}


DWORD demFileFindNext (
    PVOID pvDta)
{
    PSRCHDTA pDta = (PSRCHDTA)pvDta;
    USHORT   SearchAttr;
    PFFINDLIST   pFFindEntry;
    FFINDDOSDATA FFindDD;

    pFFindEntry = GetFFindEntryByFindId(FETCHDWORD(pDta->FFindId));
    if (!pFFindEntry ||
        FETCHDWORD(pDta->pFFindEntry) != (DWORD)pFFindEntry )
      {
        STOREDWORD(pDta->FFindId,0);
        STOREDWORD(pDta->pFFindEntry,0);

        // DOS has only one error (no_more_files) for all causes.
        return(ERROR_NO_MORE_FILES);
        }

#if DBG
    if (fShowSVCMsg & DEMFILIO) {
        sprintf(demDebugBuffer, "demFileFindNext<%ws>\n", pFFindEntry->PathName.Buffer);
        OutputDebugStringOem(demDebugBuffer);
        }
#endif

    SearchAttr = pFFindEntry->usSrchAttr;

    //
    // Search the dir
    //
    pFFindEntry = SearchFile(NULL,
                             SearchAttr,
                             pFFindEntry,
                             &FFindDD
                             );

    if (!FFindDD.cFileName[0]) {
        STOREDWORD(pDta->FFindId,0);
        STOREDWORD(pDta->pFFindEntry,0);
        return GetLastError();
        }

    FillSrchDta(&FFindDD, pDta);

    if (!pFFindEntry) {
        STOREDWORD(pDta->FFindId,0);
        STOREDWORD(pDta->pFFindEntry,0);
        }
     return 0;
}



/* demFindFirstFCB - FCB based Find First file
 *
 * Entry -  Client (DS:SI) - SRCHBUF where the information will be returned
 *      Client (ES:DI) - Full path file name with possibly wild cards
 *      Client (Al)    - 0 if not an extended FCB
 *      Client (DL)    - Search Attributes
 *
 * Exit  - Success
 *      Client (CF) = 0
 *      SRCHBUF is filled in
 *
 *     Failure
 *      Client (AL) = -1
 *
 * NOTES
 *    Search Rules: Ignore Read_only and Archive bits.
 *          If DL == ATTR_NORMAL Search only for normal files
 *          If DL == ATTR_HIDDEN Search Hidden or normal files
 *          If DL == ATTR_SYSTEM Search System or normal files
 *          If DL == ATTR_DIRECTORY Search directory or normal files
 *          If DL == ATTR_VOLUME_ID Search only Volume_ID
 *          if DL == -1 return everytiing you find
 */

VOID demFindFirstFCB (VOID)
{
    LPSTR   lpFile;
    USHORT  SearchAttr;
    PSRCHBUF        pFCBSrchBuf;
    PDIRENT         pDirEnt;
    PFFINDLIST      pFFindEntry;
    FFINDDOSDATA    FFindDD;
    UNICODE_STRING  FileUni;
    WCHAR           wcFile[MAX_PATH];


    lpFile = (LPSTR) GetVDMAddr (getES(),getDI());

#if DBG
    if (fShowSVCMsg & DEMFILIO) {
        sprintf(demDebugBuffer, "demFindFirstFCB<%s>\n", lpFile);
        OutputDebugStringOem(demDebugBuffer);
        }
#endif

    pFCBSrchBuf = (PSRCHBUF) GetVDMAddr (getDS(),getSI());
    pDirEnt = &pFCBSrchBuf->DirEnt;

    STOREDWORD(pDirEnt->pFFindEntry,0);
    STOREDWORD(pDirEnt->FFindId,0);


    if (getDL() == ATTR_VOLUME_ID) {
        FillFcbVolume(pFCBSrchBuf,lpFile, ATTR_VOLUME_ID);
        return;
        }


    FileUni.Buffer = wcFile;
    FileUni.MaximumLength = sizeof(wcFile);
    if (!DemOemToUni(&FileUni ,lpFile)) {
         setCF(1);
         return;
         }

    SearchAttr = getAL() ? getDL() : 0;
    pFFindEntry = SearchFile(wcFile, SearchAttr, NULL, &FFindDD);
    if (!FFindDD.cFileName[0]){
        demClientError(INVALID_HANDLE_VALUE, *lpFile);
        return;
        }

    FillFCBSrchBuf(&FFindDD, pFCBSrchBuf);

    if (pFFindEntry) {
        STOREDWORD(pDirEnt->pFFindEntry,pFFindEntry);
        STOREDWORD(pDirEnt->FFindId,pFFindEntry->FFindId);
        }

    setCF(0);
    return;
}



/* demFindNextFCB - FCB based Find Next file
 *
 * Entry -  Client (DS:SI) - SRCHBUF where the information will be returned
 *      Client (Al)    - 0 if not an extended FCB
 *      Client (DL)    - Search Attributes
 *
 * Exit  - Success
 *      Client (CF) = 0
 *      SRCHBUF is filled in
 *
 *     Failure
 *      Client (AL) = -1
 *
 * NOTES
 *    Search Rules: Ignore Read_only and Archive bits.
 *          If DL == ATTR_NORMAL Search only for normal files
 *          If DL == ATTR_HIDDEN Search Hidden or normal files
 *          If DL == ATTR_SYSTEM Search System or normal files
 *          If DL == ATTR_DIRECTORY Search directory or normal files
 *          If DL == ATTR_VOLUME_ID Search only Volume_ID
 */

VOID demFindNextFCB (VOID)
{
    USHORT          SearchAttr;
    PSRCHBUF        pSrchBuf;
    PDIRENT         pDirEnt;
    PFFINDLIST      pFFindEntry;
    FFINDDOSDATA    FFindDD;


    pSrchBuf = (PSRCHBUF) GetVDMAddr (getDS(),getSI());
    pDirEnt  = &pSrchBuf->DirEnt;

    pFFindEntry = GetFFindEntryByFindId(FETCHDWORD(pDirEnt->FFindId));
    if (!pFFindEntry ||
        FETCHDWORD(pDirEnt->pFFindEntry) != (DWORD)pFFindEntry ||
        getDL() == ATTR_VOLUME_ID )
      {
        if (pFFindEntry &&
            FETCHDWORD(pDirEnt->pFFindEntry) != (DWORD)pFFindEntry)
          {
            FreeFFindEntry(pFFindEntry);
            }

        STOREDWORD(pDirEnt->pFFindEntry,0);
        STOREDWORD(pDirEnt->FFindId,0);

        // DOS has only one error (no_more_files) for all causes.
        setAX(ERROR_NO_MORE_FILES);
        setCF(1);
        return;
        }

#if DBG
    if (fShowSVCMsg & DEMFILIO) {
        sprintf(demDebugBuffer, "demFindNextFCB<%ws>\n", pFFindEntry->PathName.Buffer);
        OutputDebugStringOem(demDebugBuffer);
        }
#endif

    SearchAttr = getAL() ? getDL() : 0;

    //
    // Search the dir
    //
    pFFindEntry = SearchFile(NULL,
                             SearchAttr,
                             pFFindEntry,
                             &FFindDD
                             );

    if (!FFindDD.cFileName[0]) {
        STOREDWORD(pDirEnt->pFFindEntry,0);
        STOREDWORD(pDirEnt->FFindId,0);
        setAX((USHORT) GetLastError());
        setCF(1);
        }

    FillFCBSrchBuf(&FFindDD, pSrchBuf);

    if (!pFFindEntry) {
        STOREDWORD(pDirEnt->FFindId,0);
        STOREDWORD(pDirEnt->pFFindEntry,0);
        }

    setCF(0);
    return;
}



/* demTerminatePDB - PDB Terminate Notification
 *
 * Entry -  Client (BX) - Terminating PDB
 *
 * Exit  -  None
 *
 */

VOID demTerminatePDB (VOID)
{
    PPSP_FFINDLIST pPspFFindEntry;
    USHORT     PSP;

    PSP = getBX ();

    if(!IsFirstCall)
        VDDTerminateUserHook(PSP);
    /* let host knows a process is terminating */

    HostTerminatePDB(PSP);

    pPspFFindEntry = GetPspFFindList(PSP);
    if (!pPspFFindEntry)
         return;

    if (!IsListEmpty(&pPspFFindEntry->FFindHeadList)) {
        FreeFFindList( &pPspFFindEntry->FFindHeadList);
        }

    RemoveEntryList(&pPspFFindEntry->PspFFindEntry);
    free(pPspFFindEntry);

    return;
}


/* SearchFile - Common routine for FIND_FRST and FIND_NEXT
 *
 * Entry -
 * PCHAR  pwcFile              file name to search for
 * USHORT SearchAttr           file attributes to match
 * PFFINDLIST pFFindEntry,     current list entry
 *                             if new search FFindId is expected to be zero
 * PFFINDDOSDATA pFFindDDOut,  filled with the next file in search
 *
 * Exit - if no more files pFFindDDOut is filled with zeros
 *        returns PFFINDLIST if buffered entries exist, else NULL
 */
PFFINDLIST
SearchFile(
    PWCHAR pwcFile,
    USHORT SearchAttr,
    PFFINDLIST pFFindEntry,
    PFFINDDOSDATA pFFindDDOut)
{
    NTSTATUS Status;
    ULONG    BufferSize;
    FFINDLIST  FFindEntry;
    PFFINDLIST pFFEntry = NULL;


    SearchAttr &= ~(ATTR_READ_ONLY | ATTR_ARCHIVE | ATTR_DEVICE);
    Status = STATUS_NO_MORE_FILES;

    if (pFFindDDOut) {
        memset(pFFindDDOut, 0, sizeof(FFINDDOSDATA));
        }

    try {
       if (pFFindEntry) {
           pFFEntry = pFFindEntry;
           Status = pFFindEntry->LastQueryStatus;

           if (pFFindDDOut) {
               *pFFindDDOut = pFFEntry->DosData;
               pFFEntry->DosData.cFileName[0] = '\0';
               }
           else {
               return pFFEntry;
               }

           if (pFFEntry->FindBufferNext || pFFEntry->DirectoryHandle) {
               NTSTATUS st;

               st = FileFindNext(&pFFEntry->DosData,
                                 pFFEntry
                                 );

               if (NT_SUCCESS(st)) {
                   return pFFEntry;
                   }

               if (pFFEntry->DirectoryHandle) {
                   Status = st;
                   }
               }

              //
              // Check Last Known Status before retrying
              //
           if (!NT_SUCCESS(Status)) {
               return NULL;
               }


              //
              // Reopen the FileFind Handle with a large buffer size
              //
           Status = FileFindOpen(NULL,
                                 pFFEntry,
                                 4096
                                 );
           if (!NT_SUCCESS(Status)) {
               return NULL;
               }

              //
              // reset the search to the last known search pos
              //
           Status = FileFindReset(pFFEntry);
           if (!NT_SUCCESS(Status)) {
               return NULL;
               }
           }
       else {
           pFFEntry = &FFindEntry;
           memset(pFFEntry, 0, sizeof(FFINDLIST));
           pFFEntry->SupportReset = TRUE;
           pFFEntry->usSrchAttr = SearchAttr;


           Status = FileFindOpen(pwcFile,
                                 pFFEntry,
                                 1024
                                 );

           if (!NT_SUCCESS(Status)) {
               return NULL;
               }

           //
           // Fill up pFFindDDOut
           //
           if (pFFindDDOut) {
               Status = FileFindNext(pFFindDDOut, pFFEntry);
               if (!NT_SUCCESS(Status)) {
                   return NULL;
                   }
               }
           }

        //
        // Fill up pFFEntry->DosData
        //
        Status = FileFindNext(&pFFEntry->DosData, pFFEntry);
        if (!NT_SUCCESS(Status)) {
            return NULL;
            }


       //
       // if findfirst, fill in the static entries, and add the find entry
       //
       if (!pFFindEntry) {
           pFFEntry->FFindId = NextFFindId++;
           if (NextFFindId == 0xffffffff) {
               NextFFindId = FFINDID_BASE;
               FFindIdWrap = TRUE;
               }

           if (FFindIdWrap) {
               pFFindEntry = GetFFindEntryByFindId(NextFFindId);
               if (pFFindEntry) {
                   FreeFFindEntry(pFFindEntry);
                   pFFindEntry = NULL;
                   }
               }

           pFFEntry = AddFFindEntry(pwcFile, pFFEntry);
           if (!pFFEntry) {
               pFFEntry = &FFindEntry;
               pFFEntry->DosData.cFileName[0] = '\0';
               Status = STATUS_NO_MEMORY;
               return NULL;
               }
           }


       //
       // Try to fill one more entry. If the NtQuery for this search
       // is complete we can set the LastQueryStatus, and close dir handles.
       //
       Status = FileFindLast(pFFEntry);


       }
    finally {

       if (pFFEntry) {

           pFFEntry->LastQueryStatus = Status;

               //
               // if nothing is buffered, cleanup look aheads
               //
           if (!pFFEntry->DosData.cFileName[0] ||
                pFFEntry->DirectoryHandle == FINDFILE_DEVICE)
              {
               if (pFFEntry == &FFindEntry) {
                   FileFindClose(pFFEntry);
                   RtlFreeUnicodeString(&pFFEntry->FileName);
                   RtlFreeUnicodeString(&pFFEntry->PathName);
                   }
               else {
                   FreeFFindEntry(pFFEntry);
                   }
               SetLastError(RtlNtStatusToDosError(Status));
               pFFEntry = NULL;
               }
           }



       if (pFFEntry) {

           if (pFFEntry->DirectoryHandle) {
               if (!pFFindEntry || !NT_SUCCESS(pFFEntry->LastQueryStatus)) {
                   NumDirectoryHandle--;
                   NtClose(pFFEntry->DirectoryHandle);
                   pFFEntry->DirectoryHandle = 0;
                   }
               }

           if (NumFindBuffer > MAX_FINDBUFFER ||
               NumDirectoryHandle > MAX_DIRECTORYHANDLE)
             {
               CloseOldestFileFindBuffer();
               }

           //
           // Set HeartBeat timer to close find buffers, directory handle
           //   Tics  = 8(min) * 60(sec/min) * 18(tic/sec)
           //
           pFFEntry->FindFileTics.QuadPart = 8640 + FindFileTics.QuadPart;
           if (!FindFileTics.QuadPart) {
                NextFindFileTics.QuadPart = pFFEntry->FindFileTics.QuadPart;
                }
           }


       }

     return pFFEntry;
}



NTSTATUS
FileFindOpen(
    PWCHAR pwcFile,
    PFFINDLIST pFFindEntry,
    ULONG BufferSize
    )
{
    NTSTATUS Status;
    BOOLEAN bStatus;
    BOOLEAN bReturnSingleEntry;
    PWCHAR  pwc;
    OBJECT_ATTRIBUTES Obja;
    PUNICODE_STRING FileName;
    PUNICODE_STRING PathName;
    IO_STATUS_BLOCK IoStatusBlock;
    PFILE_BOTH_DIR_INFORMATION DirectoryInfo;


    Status = STATUS_SUCCESS;
    PathName = &pFFindEntry->PathName;
    FileName = &pFFindEntry->FileName;

    try {

         if (pFFindEntry->DirectoryHandle == FINDFILE_DEVICE) {
             Status = STATUS_NO_MORE_FILES;
             goto FFOFinallyExit;
             }


         if (BufferSize <=  sizeof(FILE_BOTH_DIR_INFORMATION) +
                            MAXIMUM_FILENAME_LENGTH*sizeof(WCHAR))
           {
             Status = STATUS_BUFFER_TOO_SMALL;
             goto FFOFinallyExit;
             }



         if (pwcFile) {
             bStatus = RtlDosPathNameToNtPathName_U(pwcFile,
                                                    PathName,
                                                    &pwc,
                                                    NULL
                                                    );

             if (!bStatus ) {
                 Status = STATUS_OBJECT_PATH_NOT_FOUND;
                 goto FFOFinallyExit;
                 }

             //
             // Copy out the PathName, FileName
             //
             if (pwc) {
                 bStatus = RtlCreateUnicodeString(FileName,
                                                  pwc
                                                  );
                 if (!bStatus) {
                     Status = STATUS_NO_MEMORY;
                     goto FFOFinallyExit;
                     }

                 PathName->Length = (USHORT)((ULONG)pwc - (ULONG)PathName->Buffer);
                 if (PathName->Buffer[(PathName->Length>>1)-2] != (WCHAR)':' ) {
                     PathName->Length -= sizeof(UNICODE_NULL);
                     }
                 }
             else {
                 FileName->Length = 0;
                 FileName->MaximumLength = 0;
                 }

             bReturnSingleEntry = FALSE;
             }
         else {
             bReturnSingleEntry = pFFindEntry->SupportReset;
             }



         //
         // Prepare Find Buffer for NtQueryDirectory
         //
         if (BufferSize != pFFindEntry->FindBufferLength) {
             if (pFFindEntry->FindBufferBase) {
                 RtlFreeHeap(RtlProcessHeap(), 0, pFFindEntry->FindBufferBase);
                 }
             else {
                 NumFindBuffer++;
                 }

             pFFindEntry->FindBufferBase = RtlAllocateHeap(RtlProcessHeap(),
                                                           0,
                                                           BufferSize
                                                           );
             if (!pFFindEntry->FindBufferBase) {
                 Status = STATUS_NO_MEMORY;
                 goto FFOFinallyExit;
                 }
             }

         pFFindEntry->FindBufferNext = NULL;
         pFFindEntry->FindBufferLength = BufferSize;
         DirectoryInfo = pFFindEntry->FindBufferBase;

         //
         // Open the directory for list access
         //
         if (!pFFindEntry->DirectoryHandle) {

             InitializeObjectAttributes(
                 &Obja,
                 PathName,
                 OBJ_CASE_INSENSITIVE,
                 NULL,
                 NULL
                 );

             Status = NtOpenFile(
                         &pFFindEntry->DirectoryHandle,
                         FILE_LIST_DIRECTORY | SYNCHRONIZE,
                         &Obja,
                         &IoStatusBlock,
                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                         FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT
                         );

             if (!NT_SUCCESS(Status)) {

                 if (pwcFile) {
                     pFFindEntry->DirectoryHandle = FileFindFirstDevice(pwcFile,
                                                                        DirectoryInfo
                                                                        );
                     }
                 else {
                     pFFindEntry->DirectoryHandle = NULL;
                     }

                 if (pFFindEntry->DirectoryHandle) {
                     Status = STATUS_SUCCESS;
                     goto FFOFinallyExit;
                     }

                 if (Status == STATUS_OBJECT_NAME_NOT_FOUND ||
                     Status == STATUS_OBJECT_TYPE_MISMATCH )
                    {
                     Status = STATUS_OBJECT_PATH_NOT_FOUND;
                     }
                 goto FFOFinallyExit;
                 }

             NumDirectoryHandle++;
             }


         //
         // Prepare the filename for NtQueryDirectory
         //

         if (pwcFile) {
             WCHAR wchCurr, wchPrev;

             int Len = FileName->Length/sizeof(WCHAR);

             //
             // If there is no file part, but we are not looking at a device exit
             //
             if (!Len) {
                 Status = STATUS_OBJECT_PATH_NOT_FOUND;
                 goto FFOFinallyExit;
                 }


             //
             //  ntio expects the following transmogrifications:
             //
             //  - Change all ? to DOS_QM
             //  - Change all . followed by ? or * to DOS_DOT
             //  - Change all * followed by a . into DOS_STAR
             //
             //  However, the doskrnl and wow32 have expanded '*'s to '?'s
             //  so the * rules can be ignored.
             //
             pwc = FileName->Buffer;
             wchPrev = 0;
             while (Len--) {
                wchCurr = *pwc;

                if (wchCurr == L'?') {
                    if (wchPrev == L'.') {
                        *(pwc - 1) = DOS_DOT;
                        }

                    *pwc = DOS_QM;
                    }

                wchPrev = wchCurr;
                pwc++;
                }

             }

#if DBG
         if (fShowSVCMsg & DEMFILIO) {
             sprintf(demDebugBuffer,
                     "FFOpen %x %ws (%ws)\n",
                     pFFindEntry->DirectoryHandle,
                     FileName->Buffer,
                     pwcFile
                     );
             OutputDebugStringOem(demDebugBuffer);
             }
#endif


         //
         // Do an initial query to fill the buffers, and verify everything is ok
         //

         Status = NtQueryDirectoryFile(
                         pFFindEntry->DirectoryHandle,
                         NULL,
                         NULL,
                         NULL,
                         &IoStatusBlock,
                         DirectoryInfo,
                         BufferSize,
                         FileBothDirectoryInformation,
                         bReturnSingleEntry,
                         FileName,
                         FALSE
                         );

FFOFinallyExit:;

         }
    finally {
         if (!NT_SUCCESS(Status)) {
#if DBG
             if ((fShowSVCMsg & DEMFILIO) && !NT_SUCCESS(Status)) {
                 sprintf(demDebugBuffer, "FFOpen Status %x\n", Status);
                 OutputDebugStringOem(demDebugBuffer);
                 }
#endif

             FileFindClose(pFFindEntry);
             RtlFreeUnicodeString(PathName);
             PathName->Buffer = NULL;
             RtlFreeUnicodeString(FileName);
             FileName->Buffer = NULL;
             }
          else {
             pFFindEntry->FindBufferNext = pFFindEntry->FindBufferBase;
             }
         }

    return Status;
}



/*
 *  Closes a FileFindHandle
 */
VOID
FileFindClose(
    PFFINDLIST pFFindEntry
    )
{
    NTSTATUS Status;
    HANDLE DirectoryHandle;

    DirectoryHandle = pFFindEntry->DirectoryHandle;
    if (DirectoryHandle &&
        DirectoryHandle != FINDFILE_DEVICE)
      {
        NtClose(DirectoryHandle);
        --NumDirectoryHandle;
        }

    pFFindEntry->DirectoryHandle = 0;

    if (pFFindEntry->FindBufferBase) {
        RtlFreeHeap(RtlProcessHeap(), 0, pFFindEntry->FindBufferBase);
        --NumFindBuffer;
        }

    pFFindEntry->FindBufferBase = NULL;
    pFFindEntry->FindBufferNext = NULL;
    pFFindEntry->FindBufferLength = 0;
    pFFindEntry->FindFileTics.QuadPart = 0;

    if (!NumDirectoryHandle && !NumFindBuffer) {
        FindFileTics.QuadPart = 0;
        NextFindFileTics.QuadPart = 0;
        }
}



/*
 *  FileFindReset
 *
 *   Resets search pos according to FileName, FileIndex.
 *   The FindBuffers will point to the next file in the search
 *   order. Assumes that the remembered search pos, has not been
 *   reached yet for the current search.
 *
 */
NTSTATUS
FileFindReset(
   PFFINDLIST pFFindEntry
   )
{
    NTSTATUS Status;
    IO_STATUS_BLOCK IoStatusBlock;
    PFILE_BOTH_DIR_INFORMATION DirectoryInfo;
    UNICODE_STRING LastFileName;
    UNICODE_STRING CurrFileName;
    BOOLEAN bSlowReset;


    if (pFFindEntry->DirectoryHandle == FINDFILE_DEVICE) {
        return STATUS_NO_MORE_FILES;
        }

    Status = STATUS_UNSUCCESSFUL;

    LastFileName.Length = (USHORT)pFFindEntry->DosData.FileNameLength;
    LastFileName.MaximumLength = (USHORT)pFFindEntry->DosData.FileNameLength;
    LastFileName.Buffer = pFFindEntry->DosData.FileName;

    RtlInitUnicodeString(&CurrFileName, L".");
    if (!RtlCompareUnicodeString(&LastFileName, &CurrFileName, TRUE)) {
        bSlowReset = TRUE;
        }
    else {
        RtlInitUnicodeString(&CurrFileName, L"..");
        if (!RtlCompareUnicodeString(&LastFileName, &CurrFileName, TRUE)) {
            bSlowReset = TRUE;
            }
        else {
            bSlowReset = FALSE;
            }
        }

    //
    // if the last file name, wasn't Dots and the volume supports reset
    // functionality call nt file sysetm to do the reset.
    //
    if (!bSlowReset && pFFindEntry->SupportReset) {
        VDMQUERYDIRINFO VdmQueryDirInfo;
        UNICODE_STRING  UnicodeString;

        DirectoryInfo = (PFILE_BOTH_DIR_INFORMATION) pFFindEntry->FindBufferBase;

        VdmQueryDirInfo.FileHandle = pFFindEntry->DirectoryHandle;
        VdmQueryDirInfo.FileInformation = DirectoryInfo;
        VdmQueryDirInfo.Length = pFFindEntry->FindBufferLength;
        VdmQueryDirInfo.FileIndex = pFFindEntry->DosData.FileIndex;

        UnicodeString.Length = (USHORT)pFFindEntry->DosData.FileNameLength;
        UnicodeString.MaximumLength = UnicodeString.Length;
        UnicodeString.Buffer = pFFindEntry->DosData.FileName;
        VdmQueryDirInfo.FileName = &UnicodeString;

        Status = NtVdmControl(VdmQueryDir, &VdmQueryDirInfo);
        if (NT_SUCCESS(Status) ||
            Status == STATUS_NO_MORE_FILES || Status == STATUS_NO_SUCH_FILE)
           {
            return Status;
            }

        pFFindEntry->SupportReset = TRUE;

        }

   //
   // Reset the slow way by comparing FileName directly.
   //
   // WARNING: if the "remembered" File has been deleted we will
   //          fail, is there something else we can do ?
   //

    Status = STATUS_NO_MORE_FILES;
    while (TRUE) {

       //
       // If there is no data in the find file buffer, call NtQueryDir
       //

       DirectoryInfo = pFFindEntry->FindBufferNext;
       if (!DirectoryInfo) {
            DirectoryInfo = pFFindEntry->FindBufferBase;

            Status = NtQueryDirectoryFile(
                            pFFindEntry->DirectoryHandle,
                            NULL,                          // no event
                            NULL,                          // no apcRoutine
                            NULL,                          // no apcContext
                            &IoStatusBlock,
                            DirectoryInfo,
                            pFFindEntry->FindBufferLength,
                            FileBothDirectoryInformation,
                            FALSE,                         // single entry
                            NULL,                          // no file name
                            FALSE
                            );

           if (!NT_SUCCESS(Status)) {
#if DBG
               if (fShowSVCMsg & DEMFILIO) {
                   sprintf(demDebugBuffer, "FFReset Status %x\n", Status);
                   OutputDebugStringOem(demDebugBuffer);
                   }
#endif
               return Status;
               }
           }

       if ( DirectoryInfo->NextEntryOffset ) {
           pFFindEntry->FindBufferNext = (PVOID)((ULONG)DirectoryInfo +
                                                DirectoryInfo->NextEntryOffset);
           }
       else {
           pFFindEntry->FindBufferNext = NULL;
           }


       if (DirectoryInfo->FileIndex == pFFindEntry->DosData.FileIndex) {
           CurrFileName.Length = (USHORT)DirectoryInfo->FileNameLength;
           CurrFileName.MaximumLength = (USHORT)DirectoryInfo->FileNameLength;
           CurrFileName.Buffer = DirectoryInfo->FileName;

	   if (!RtlCompareUnicodeString(&LastFileName, &CurrFileName, TRUE)) {
	       return STATUS_SUCCESS;
               }
           }

       }

    return Status;

}




/*
 * FileFindLast - Attempts to fill the FindFile buffer completely.
 *
 *
 * PFFINDLIST pFFindEntry -
 *
 * Returns - Status of NtQueryDir operation if invoked, otherwise
 *           STATUS_SUCCESS.
 *
 */
NTSTATUS
FileFindLast(
    PFFINDLIST pFFindEntry
    )
{
    NTSTATUS Status;
    IO_STATUS_BLOCK IoStatusBlock;
    PFILE_BOTH_DIR_INFORMATION DirInfo, LastDirInfo;
    LONG BytesLeft;

    if (pFFindEntry->DirectoryHandle == FINDFILE_DEVICE) {
        return STATUS_NO_MORE_FILES;
        }

    if (pFFindEntry->FindBufferNext) {
        ULONG BytesOffset;

        BytesOffset = (ULONG)pFFindEntry->FindBufferNext -
                      (ULONG)pFFindEntry->FindBufferBase;

        if (BytesOffset) {
            RtlMoveMemory(pFFindEntry->FindBufferBase,
                          pFFindEntry->FindBufferNext,
                          pFFindEntry->FindBufferLength - BytesOffset
                          );
            }

        pFFindEntry->FindBufferNext = pFFindEntry->FindBufferBase;
        DirInfo = pFFindEntry->FindBufferBase;

        while (DirInfo->NextEntryOffset) {
            DirInfo = (PVOID)((ULONG)DirInfo + DirInfo->NextEntryOffset);
            }
        LastDirInfo = DirInfo;

        DirInfo = (PVOID)&DirInfo->FileName[DirInfo->FileNameLength>>1];

        DirInfo = (PVOID) (((ULONG) DirInfo + sizeof(LONGLONG) - 1) &
            ~(sizeof(LONGLONG) - 1));

        BytesLeft = pFFindEntry->FindBufferLength -
                     ((ULONG)DirInfo - (ULONG)pFFindEntry->FindBufferBase);
        }
    else {
        DirInfo = pFFindEntry->FindBufferBase;
        LastDirInfo = NULL;
        BytesLeft = pFFindEntry->FindBufferLength;
        }


    // the size of the dirinfo structure including the name must be a longlong.
    while (BytesLeft > sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(LONGLONG)) {

       Status = NtQueryDirectoryFile(
                       pFFindEntry->DirectoryHandle,
                       NULL,                          // no event
                       NULL,                          // no apcRoutine
                       NULL,                          // no apcContext
                       &IoStatusBlock,
                       DirInfo,
                       BytesLeft,
                       FileBothDirectoryInformation,
                       FALSE,                          // single entry ?
                       NULL,                          // no file name
                       FALSE
                       );

       if (Status == STATUS_NO_MORE_FILES || Status == STATUS_NO_SUCH_FILE) {
#if DBG
           if ((fShowSVCMsg & DEMFILIO)) {
               sprintf(demDebugBuffer, "FFLast Status %x\n", Status);
               OutputDebugStringOem(demDebugBuffer);
               }
#endif
           return Status;
           }


       if (!NT_SUCCESS(Status)) {
           break;
           }

       if (LastDirInfo) {
           LastDirInfo->NextEntryOffset =(ULONG)DirInfo - (ULONG)LastDirInfo;
           }
       else {
           pFFindEntry->FindBufferNext = pFFindEntry->FindBufferBase;
           }

       while (DirInfo->NextEntryOffset) {
           DirInfo = (PVOID)((ULONG)DirInfo + DirInfo->NextEntryOffset);
           }
       LastDirInfo = DirInfo;
       DirInfo = (PVOID)&DirInfo->FileName[DirInfo->FileNameLength>>1];

        DirInfo = (PVOID) (((ULONG) DirInfo + sizeof(LONGLONG) - 1) &
            ~(sizeof(LONGLONG) - 1));

       BytesLeft = pFFindEntry->FindBufferLength -
                    ((ULONG)DirInfo - (ULONG)pFFindEntry->FindBufferBase);
       }

   return STATUS_SUCCESS;
}






/*
 * FileFindNext - retrieves the next file in the current search order,
 *
 * PFFINDDOSDATA pFFindDD
 *    Receives File info returned by the nt FileSystem
 *
 * PFFINDLIST pFFindEntry -
 *    Contains the DirectoryInfo (FileName,FileIndex) necessary to reset a
 *    search pos. For operations other than QDIR_RESET_SCAN, this is ignored.
 *
 * Returns -
 *    If Got a DirectoryInformation Entry, STATUS_SUCCESS
 *    If no Open Directory handle and is unknown if there are more files
 *    returns STATUS_IN`VALID_HANDLE
 *
 */
NTSTATUS
FileFindNext(
    PFFINDDOSDATA pFFindDD,
    PFFINDLIST pFFindEntry
    )
{
    NTSTATUS Status;
    IO_STATUS_BLOCK IoStatusBlock;

    PFILE_BOTH_DIR_INFORMATION DirectoryInfo;

    if (pFFindEntry->DirectoryHandle == FINDFILE_DEVICE) {
        return STATUS_NO_MORE_FILES;
        }

    Status = STATUS_UNSUCCESSFUL;

    do {

       //
       // If there is no data in the find file buffer, call NtQueryDir
       //

       DirectoryInfo = pFFindEntry->FindBufferNext;
       if (!DirectoryInfo) {
           if (!pFFindEntry->DirectoryHandle) {
               return STATUS_INVALID_HANDLE;
               }

           DirectoryInfo = pFFindEntry->FindBufferBase;

           Status = NtQueryDirectoryFile(
                            pFFindEntry->DirectoryHandle,
                            NULL,                          // no event
                            NULL,                          // no apcRoutine
                            NULL,                          // no apcContext
                            &IoStatusBlock,
                            DirectoryInfo,
                            pFFindEntry->FindBufferLength,
                            FileBothDirectoryInformation,
                            FALSE,                         // single entry ?
                            NULL,                          // no file name
                            FALSE
                            );

           if (!NT_SUCCESS(Status)) {
#if DBG
               if (fShowSVCMsg & DEMFILIO) {
                   sprintf(demDebugBuffer, "FFNext Status %x\n", Status);
                   OutputDebugStringOem(demDebugBuffer);
                   }
#endif
               return Status;
               }
           }


       if ( DirectoryInfo->NextEntryOffset ) {
           pFFindEntry->FindBufferNext = (PVOID)((ULONG)DirectoryInfo +
                                                DirectoryInfo->NextEntryOffset);
           }
       else {
           pFFindEntry->FindBufferNext = NULL;
           }

       } while (!CopyDirInfoToDosData(pFFindDD,
                                      DirectoryInfo,
                                      pFFindEntry->usSrchAttr
                                      ));

    return STATUS_SUCCESS;
}




/*
 *  CopyDirInfoToDosData
 *
 */
BOOL
CopyDirInfoToDosData(
    PFFINDDOSDATA pFFindDD,
    PFILE_BOTH_DIR_INFORMATION DirInfo,
    USHORT SearchAttr
    )
{
    NTSTATUS Status;
    OEM_STRING OemString;
    UNICODE_STRING UnicodeString;
    DWORD   dwAttr;
    BOOLEAN SpacesInName = FALSE;
    BOOLEAN NameValid8Dot3;

    //
    // match the attributes
    // See DOS5.0 sources (dir2.asm, MatchAttributes)
    // ignores READONLY and ARCHIVE bits
    //
    if (FILE_ATTRIBUTE_NORMAL == DirInfo->FileAttributes) {
        DirInfo->FileAttributes = 0;
        }
    else {
        DirInfo->FileAttributes &= DOS_ATTR_MASK;
        }


    dwAttr = DirInfo->FileAttributes;
    dwAttr &= ~(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY);
    if (((~(ULONG)SearchAttr) & dwAttr) & ATTR_ALL)
        return FALSE;


    //
    // set up the destination oem string buffer
    //
    OemString.Buffer        = pFFindDD->cFileName;
    OemString.MaximumLength = 14;

    //
    // see if the name is legal fat
    //

    UnicodeString.Buffer = DirInfo->FileName;
    UnicodeString.Length = (USHORT)DirInfo->FileNameLength;
    UnicodeString.MaximumLength = (USHORT)DirInfo->FileNameLength;

    NameValid8Dot3 = RtlIsNameLegalDOS8Dot3( &UnicodeString,
                                             &OemString,
                                             &SpacesInName );

    //
    // if failed (incompatible codepage or illegal FAT name),
    //    use the short name
    //
    if (!NameValid8Dot3 ||
        (SpacesInName && (DirInfo->ShortName[0] != UNICODE_NULL))) {

        if (DirInfo->ShortName[0] == UNICODE_NULL) {
            pFFindDD->cFileName[0] = '\0';
            return FALSE;
            }

        UnicodeString.Buffer = DirInfo->ShortName;
        UnicodeString.Length = (USHORT)DirInfo->ShortNameLength;
        UnicodeString.MaximumLength = (USHORT)DirInfo->ShortNameLength;

        if (!NT_SUCCESS(RtlUpcaseUnicodeStringToCountedOemString(&OemString, &UnicodeString, FALSE))) {
            pFFindDD->cFileName[0] = '\0';
            return FALSE;
            }
        }

    OemString.Buffer[OemString.Length] = '\0';

    // fill in time, size and attributes
    pFFindDD->ftLastWriteTime   = *(LPFILETIME)&DirInfo->LastWriteTime;
    pFFindDD->dwFileSizeLow     = DirInfo->EndOfFile.LowPart;
    pFFindDD->uchFileAttributes = (UCHAR)DirInfo->FileAttributes;

    // Save File Name, Index for restarting searches
    pFFindDD->FileIndex = DirInfo->FileIndex;
    pFFindDD->FileNameLength = DirInfo->FileNameLength;

    RtlCopyMemory(pFFindDD->FileName,
                  DirInfo->FileName,
                  DirInfo->FileNameLength
                  );

    pFFindDD->FileName[DirInfo->FileNameLength >> 1] = UNICODE_NULL;

    return TRUE;
}




HANDLE
FileFindFirstDevice(
    PWCHAR FileName,
    PFILE_BOTH_DIR_INFORMATION DirectoryInfo
    )

/*++

Routine Description:

    Determines if the FileName is a device, and copies out the
    device name found if it is.

Arguments:

    FileName - Supplies the device name of the file to find.

    pQueryDirInfo - On a successful find, this parameter returns information
                    about the located file.

Return Value:


--*/

{
    ULONG DeviceNameData;
    PWSTR DeviceName;

    DeviceNameData = RtlIsDosDeviceName_U(FileName);
    if (DeviceNameData) {
        RtlZeroMemory(DirectoryInfo, sizeof(FILE_BOTH_DIR_INFORMATION));

        DirectoryInfo->FileAttributes = FILE_ATTRIBUTE_ARCHIVE;
        DeviceName = (PWSTR)((ULONG)FileName + (DeviceNameData >> 16));

        DeviceNameData &= 0xffff;

        DirectoryInfo->FileNameLength = DeviceNameData;
        DirectoryInfo->ShortNameLength = (CCHAR)DeviceNameData;


        RtlCopyMemory(DirectoryInfo->FileName,
                      DeviceName,
                      DeviceNameData
                      );

        RtlCopyMemory(DirectoryInfo->ShortName,
                      DeviceName,
                      DeviceNameData
                      );

        return FINDFILE_DEVICE;
        }

    return NULL;
}




/* FillFcbVolume - fill Volume info in the FCB
 *
 * Entry -  pSrchBuf    FCB Search buffer to be filled in
 *          FileName  File Name (interesting part is the drive letter)
 *
 * Exit -  SUCCESS
 *      Client (CF) - 0
 *      pSrchBuf is filled with volume info
 *
 *     FAILURE
 *      Client (CF) - 1
 *      Client (AX) = Error Code
 */
VOID
FillFcbVolume(
     PSRCHBUF pSrchBuf,
     CHAR *pFileName,
     USHORT SearchAttr
     )
{
    CHAR    *pch;
    PDIRENT pDirEnt = &pSrchBuf->DirEnt;
    CHAR    FullPathBuffer[MAX_PATH];
    CHAR    achBaseName[DOS_VOLUME_NAME_SIZE + 2];  // 11 chars, '.', and null
    CHAR    achVolumeName[NT_VOLUME_NAME_SIZE];

    //
    // form a path without base name
    // this makes sure only on root directory will get the
    // volume label(the GetVolumeInformationOem will fail
    // if the given path is not root directory)
    //

    strcpy(FullPathBuffer, pFileName);
    pch = strrchr(FullPathBuffer, '\\');
    if (pch)  {
        pch++;
        // truncate to dos file name length (including period)
        pch[DOS_VOLUME_NAME_SIZE + 1] = '\0';
        strcpy(achBaseName, pch);
        _strupr(achBaseName);
        *pch = '\0';
        }
    else {
        achBaseName[0] = '\0';
        }


    //
    // if searching for volume only the DOS uses first 3 letters for
    // root drive path ignoring the rest of the path
    // as long as the full pathname is valid.
    //
    if (SearchAttr == ATTR_VOLUME_ID &&
        (pch = strchr(FullPathBuffer, '\\')) &&
        GetFileAttributes(FullPathBuffer) != 0xffffffff )
      {
        pch++;
        *pch = '\0';
        strcpy(achBaseName, szStartDotStar);
        }


    if (GetVolumeInformationOem(FullPathBuffer,
                                achVolumeName,
                                NT_VOLUME_NAME_SIZE,
                                NULL,
                                NULL,
                                NULL,
                                NULL,
                                0) == FALSE)
       {

        demClientError(INVALID_HANDLE_VALUE, *pFileName);
        return;
        }

    // truncate to dos volumen max size (no period)
    achVolumeName[DOS_VOLUME_NAME_SIZE] = '\0';

    if (!achVolumeName[0] || !MatchVolLabel(achVolumeName, achBaseName)) {
        SetLastError(ERROR_NO_MORE_FILES);
        demClientError(INVALID_HANDLE_VALUE, *pFileName);
        return;
        }

    // warning !!! this assumes the FileExt follows FileName immediately
    memset(pSrchBuf->FileName, ' ', DOS_VOLUME_NAME_SIZE);
    strncpy(pSrchBuf->FileName, achVolumeName, strlen(achVolumeName));

    // Now copy the directory entry
    strncpy(pDirEnt->FileName,pSrchBuf->FileName,8);
    strncpy(pDirEnt->FileExt,pSrchBuf->FileExt,3);
    setCF (0);
    return;
}


/* FillDtaVolume - fill Volume info in the DTA
 *
 * Entry - CHAR lpSearchName - Optional name to match with volume name
 *
 *
 * Exit -  SUCCESS
 *      Returns - 0
 *      pSrchBuf is filled with volume info
 *
 *     FAILURE
 *      Returns Error Code
 */

BOOL FillDtaVolume(
     CHAR *pFileName,
     PSRCHDTA  pDta,
     USHORT SearchAttr
     )
{
    CHAR    *pch;
    CHAR    FullPathBuffer[MAX_PATH];
    CHAR    achBaseName[DOS_VOLUME_NAME_SIZE + 2];  // 11 chars, '.' and null
    CHAR    achVolumeName[NT_VOLUME_NAME_SIZE];

    //
    // form a path without base name
    // this makes sure only on root directory will get the
    // volume label(the GetVolumeInformationOem will fail
    // if the given path is not root directory)
    //
    strcpy(FullPathBuffer, pFileName);
    pch = strrchr(FullPathBuffer, '\\');
    if (pch)  {
        pch++;
        pch[DOS_VOLUME_NAME_SIZE + 1] = '\0'; // max len (including period)
        strcpy(achBaseName, pch);
        _strupr(achBaseName);
        *pch = '\0';
        }
    else {
        achBaseName[0] = '\0';
        }


    //
    // if searching for volume only the DOS uses first 3 letters for
    // root drive path ignoring the rest of the path
    // as long as the full path name is valid.
    //
    if (SearchAttr == ATTR_VOLUME_ID &&
        (pch = strchr(FullPathBuffer, '\\')) &&
        GetFileAttributes(FullPathBuffer) != 0xffffffff )
      {
        pch++;
        *pch = '\0';
        strcpy(achBaseName, szStartDotStar);
        }

    if (GetVolumeInformationOem(FullPathBuffer,
                                achVolumeName,
                                NT_VOLUME_NAME_SIZE,
                                NULL,
                                NULL,
                                NULL,
                                NULL,
                                0) == FALSE)
       {
        return FALSE;
        }

    // truncate to dos file name length (no period)
    achVolumeName[DOS_VOLUME_NAME_SIZE] = '\0';

    if  (!achVolumeName[0] || !MatchVolLabel(achVolumeName, achBaseName)) {
        SetLastError(ERROR_NO_MORE_FILES);
        return FALSE;
        }

    //
    // DOS Dta search returns volume label in 8.3 format. But if label is
    // more than 8 characters long than NT just returns that as it is
    // without adding a ".". So here we have to add a "." in volume
    // labels, if needed. But note that FCB based volume search does'nt
    // add the "." So nothing need to be done there.
    //
    NtVolumeNameToDosVolumeName(pDta->achFileName, achVolumeName);
    pDta->uchFileAttr =  ATTR_VOLUME_ID;
    STOREWORD(pDta->usLowSize,0);
    STOREWORD(pDta->usHighSize,0);

    // Zero out dates as we can not fetch dates for volume labels.
    STOREWORD(pDta->usTimeLastWrite,0);
    STOREWORD(pDta->usDateLastWrite,0);

    return TRUE;
}



/*
 *  MatchVolLabel
 *  Does a string compare to see if the vol label matches
 *  a FAT search string. The search string is expected to
 *  have the '*' character already expanded into '?' characters.
 *
 *  WARNING: maintanes dos5.0 quirk of not caring about characters past
 *  the defined len of each part of the vol label.
 *  12345678.123
 *  ^       ^
 *
 *        foovol      foovol1  (srch string)
 *        foo.vol     foo.vol1 (srch string)
 *
 *  entry: CHAR *pVol   -- NT volume name
 *     CHAR *pSrch  -- dos volume name
 *
 *  exit: TRUE for a match
 */
BOOL MatchVolLabel(CHAR *pVol, CHAR *pSrch )
{
    WORD w;
    CHAR  achDosVolumeName[DOS_VOLUME_NAME_SIZE + 2]; // 11 chars, '.' and null

    NtVolumeNameToDosVolumeName(achDosVolumeName, pVol);
    pVol = achDosVolumeName;

    w = 8;
    while (w--) {
        if (*pVol == *pSrch)  {
            if (!*pVol && !*pSrch)
                return TRUE;
            }
        else if (*pSrch == '.') {
            if (*pVol)
                return FALSE;
            }
        else if (*pSrch != '?') {
            return FALSE;
            }

           // move on to the next character
           // but not past second component part
        if (*pVol && *pVol != '.')
            pVol++;
        if (*pSrch && *pSrch != '.')
            pSrch++;
        }

      // skip trailing part of search string, in the first comp
    while (*pSrch && *pSrch != '.')
         pSrch++;


    w = 4;
    while (w--) {
        if (*pVol == *pSrch)  {
            if (!*pVol && !*pSrch)
                return TRUE;
            }
        else if (*pSrch == '.') {
            if (*pVol)
                return FALSE;
            }
        else if (*pSrch != '?') {
            return FALSE;
            }

           // move on to the next character
        if (*pVol)
            pVol++;
        if (*pSrch)
            pSrch++;
        }

     return TRUE;
}


VOID NtVolumeNameToDosVolumeName(CHAR * pDosName, CHAR * pNtName)
{

    char    NtNameBuffer[NT_VOLUME_NAME_SIZE];
    int     i;
    char    char8, char9, char10;

    // make a local copy so that the caller can use the same
    // buffer
    strcpy(NtNameBuffer, pNtName);

    if (strlen(NtNameBuffer) > 8) {
    char8 = NtNameBuffer[8];
    char9 = NtNameBuffer[9];
    char10 = NtNameBuffer[10];
        // eat spaces from first 8 characters
        i = 7;
    while (NtNameBuffer[i] == ' ')
            i--;
    NtNameBuffer[i+1] = '.';
    NtNameBuffer[i+2] = char8;
    NtNameBuffer[i+3] = char9;
    NtNameBuffer[i+4] = char10;
    NtNameBuffer[i+5] = '\0';
    }
    strcpy(pDosName, NtNameBuffer);
}





/* FillFCBSrchBuf - Fill the FCB Search buffer.
 *
 * Entry -  pSrchBuf FCB Search buffer to be filled in
 *      hFind Search Handle
 *      fFirst TRUE if call from FindFirstFCB
 *
 * Exit  - None (pSrchBuf filled in)
 *
 */

VOID FillFCBSrchBuf(
     PFFINDDOSDATA pFFindDD,
     PSRCHBUF pSrchBuf)
{
    PDIRENT     pDirEnt = &pSrchBuf->DirEnt;
    PCHAR       pDot;
    USHORT      usDate,usTime,i;
    FILETIME    ftLocal;

#if DBG
    if (fShowSVCMsg & DEMFILIO) {
        sprintf(demDebugBuffer, "FillFCBSrchBuf<%s>\n", pFFindDD->cFileName);
        OutputDebugStringOem(demDebugBuffer);
        }
#endif

    // Copy file name (Max Name = 8 and Max ext = 3)
    if ((pDot = strchr(pFFindDD->cFileName,'.')) == NULL) {
        strncpy(pSrchBuf->FileName,pFFindDD->cFileName,8);
        _strnset(pSrchBuf->FileExt,'\x020',3);
        }
    else if (pDot == pFFindDD->cFileName) {
        strncpy(pSrchBuf->FileName,pFFindDD->cFileName,8);
        _strnset(pSrchBuf->FileExt,'\x020',3);
        }
    else {
        *pDot = '\0';
        strncpy(pSrchBuf->FileName,pFFindDD->cFileName,8);
        *pDot++ = '\0';
        strncpy(pSrchBuf->FileExt,pDot,3);
        }


    for (i=0;i<8;i++) {
      if (pSrchBuf->FileName[i] == '\0')
          pSrchBuf->FileName[i]='\x020';
      }

    for (i=0;i<3;i++) {
      if (pSrchBuf->FileExt[i] == '\0')
          pSrchBuf->FileExt[i]='\x020';
      }

    STOREWORD(pSrchBuf->usCurBlkNumber,0);
    STOREWORD(pSrchBuf->usRecordSize,0);
    STOREDWORD(pSrchBuf->ulFileSize, pFFindDD->dwFileSizeLow);

    // Convert NT File time/date to DOS time/date
    FileTimeToLocalFileTime (&pFFindDD->ftLastWriteTime,&ftLocal);
    FileTimeToDosDateTime (&ftLocal,
                           &usDate,
                           &usTime);

    // Now copy the directory entry
    strncpy(pDirEnt->FileName,pSrchBuf->FileName,8);
    strncpy(pDirEnt->FileExt,pSrchBuf->FileExt,3);

    pDirEnt->uchAttributes = pFFindDD->uchFileAttributes;

    STOREWORD(pDirEnt->usTime,usTime);
    STOREWORD(pDirEnt->usDate,usDate);
    STOREDWORD(pDirEnt->ulFileSize,pFFindDD->dwFileSizeLow);

    return;
}



/* FillSrchDta - Fill DTA for FIND_FIRST,FIND_NEXT operations.
 *
 * Entry - pW32FindData Buffer containing file data
 *         hFind - Handle returned by FindFirstFile
 *         PSRCHDTA pDta
 *
 * Exit  - None
 *
 * Note : It is guranteed that file name adhers to 8:3 convention.
 *    demSrchFile makes sure of that condition.
 *
 */
VOID
FillSrchDta(
     PFFINDDOSDATA pFFindDD,
     PSRCHDTA pDta)
{
    USHORT   usDate,usTime;
    FILETIME ftLocal;

    pDta->uchFileAttr = pFFindDD->uchFileAttributes;

    // Convert NT File time/date to DOS time/date
    FileTimeToLocalFileTime (&pFFindDD->ftLastWriteTime,&ftLocal);
    FileTimeToDosDateTime (&ftLocal,
                           &usDate,
                           &usTime);

    STOREWORD(pDta->usTimeLastWrite,usTime);
    STOREWORD(pDta->usDateLastWrite,usDate);
    STOREWORD(pDta->usLowSize,(USHORT)pFFindDD->dwFileSizeLow);
    STOREWORD(pDta->usHighSize,(USHORT)(pFFindDD->dwFileSizeLow >> 16));

#if DBG
    if (fShowSVCMsg & DEMFILIO) {
        sprintf(demDebugBuffer, "FillSrchDta<%s>\n", pFFindDD->cFileName);
        OutputDebugStringOem(demDebugBuffer);
        }
#endif

    strncpy(pDta->achFileName,pFFindDD->cFileName, 13);

    return;
}





VOID demCloseAllPSPRecords (VOID)
{
   PLIST_ENTRY Next;
   PPSP_FFINDLIST pPspFFindEntry;

   Next = PspFFindHeadList.Flink;
   while (Next != &PspFFindHeadList) {
       pPspFFindEntry = CONTAINING_RECORD(Next,PSP_FFINDLIST,PspFFindEntry);
       FreeFFindList( &pPspFFindEntry->FFindHeadList);
       Next= Next->Flink;
       RemoveEntryList(&pPspFFindEntry->PspFFindEntry);
       free(pPspFFindEntry);
       }
}


void
DemHeartBeat(void)
{

   PLIST_ENTRY    Next;
   PLIST_ENTRY    pFFindHeadList;
   PPSP_FFINDLIST pPspFFindEntry;
   PFFINDLIST  pFFindEntry;

   if (!NumFindBuffer ||
       NextFindFileTics.QuadPart > ++FindFileTics.QuadPart)
     {
       return;
       }

   pPspFFindEntry = GetPspFFindList(FETCHWORD(pusCurrentPDB[0]));
   if (!pPspFFindEntry) {
       return;
       }
   pFFindHeadList = &pPspFFindEntry->FFindHeadList;
   Next = pFFindHeadList->Blink;
   while (Next != pFFindHeadList) {
        pFFindEntry = CONTAINING_RECORD(Next,FFINDLIST, FFindEntry);

        if (pFFindEntry->FindFileTics.QuadPart) {
            if (pFFindEntry->FindFileTics.QuadPart <= FindFileTics.QuadPart) {
                FileFindClose(pFFindEntry);
                }
            else {
                NextFindFileTics.QuadPart = pFFindEntry->FindFileTics.QuadPart;
                return;
                }
            }

        Next = Next->Blink;
        }

   NextFindFileTics.QuadPart = 0;
   FindFileTics.QuadPart = 0;
}





//
// CloseOldestFileFindBuffer
// walks the psp file find list backwards to find the oldest
// entry with FindBuffers, directory handles and closes it.
//
void
CloseOldestFileFindBuffer(
   void
   )
{
   PLIST_ENTRY    Next, NextPsp;
   PLIST_ENTRY    pFFindHeadList;
   PPSP_FFINDLIST pPspFFindEntry;
   PFFINDLIST     pFFEntry;

   NextPsp = PspFFindHeadList.Blink;
   while (NextPsp != &PspFFindHeadList) {
       pPspFFindEntry = CONTAINING_RECORD(NextPsp,PSP_FFINDLIST,PspFFindEntry);

       pFFindHeadList = &pPspFFindEntry->FFindHeadList;
       Next = pFFindHeadList->Blink;
       while (Next != pFFindHeadList) {
            pFFEntry = CONTAINING_RECORD(Next,FFINDLIST, FFindEntry);
            if (NumFindBuffer >= MAX_FINDBUFFER) {
                FileFindClose(pFFEntry);
                }
            else if (pFFEntry->DirectoryHandle &&
                     NumDirectoryHandle >= MAX_DIRECTORYHANDLE)
               {
                NumDirectoryHandle--;
                NtClose(pFFEntry->DirectoryHandle);
                pFFEntry->DirectoryHandle = 0;
                }

            if (NumFindBuffer < MAX_FINDBUFFER &&
                NumDirectoryHandle < MAX_DIRECTORYHANDLE)
               {
                return;
                }
            Next = Next->Blink;
            }

       NextPsp= NextPsp->Blink;
       }
}





/*
 * GetFFindEntryByFindId
 */
PFFINDLIST GetFFindEntryByFindId(ULONG NextFFindId)
{
   PLIST_ENTRY NextPsp;
   PLIST_ENTRY Next;
   PPSP_FFINDLIST pPspFFindEntry;
   PFFINDLIST     pFFindEntry;
   PLIST_ENTRY    pFFindHeadList;

   NextPsp = PspFFindHeadList.Flink;
   while (NextPsp != &PspFFindHeadList) {
       pPspFFindEntry = CONTAINING_RECORD(NextPsp,PSP_FFINDLIST,PspFFindEntry);

       pFFindHeadList = &pPspFFindEntry->FFindHeadList;
       Next = pFFindHeadList->Flink;
       while (Next != pFFindHeadList) {
            pFFindEntry = CONTAINING_RECORD(Next, FFINDLIST, FFindEntry);
            if (pFFindEntry->FFindId == NextFFindId) {
                return pFFindEntry;
                }
            Next= Next->Flink;
            }

       NextPsp= NextPsp->Flink;
       }

   return NULL;
}



/* AddFFindEntry - Adds a new File Find entry to the current
 *                    PSP's PspFileFindList
 *
 * Entry -
 *
 * Exit  -  PFFINDLIST  pFFindList;
 */
PFFINDLIST
AddFFindEntry(
        PWCHAR pwcFile,
        PFFINDLIST pFFindEntrySrc
        )

{
    PPSP_FFINDLIST pPspFFindEntry;
    PFFINDLIST     pFFindEntry;
    ULONG          Len;

    pPspFFindEntry = GetPspFFindList(FETCHWORD(pusCurrentPDB[0]));

        //
        // if a Psp entry doesn't exist
        //    Allocate one, initialize it and insert it into the list
        //
    if (!pPspFFindEntry) {
        pPspFFindEntry = (PPSP_FFINDLIST) malloc(sizeof(PSP_FFINDLIST));
        if (!pPspFFindEntry)
            return NULL;

        pPspFFindEntry->usPsp = FETCHWORD(pusCurrentPDB[0]);
        InitializeListHead(&pPspFFindEntry->FFindHeadList);
        InsertHeadList(&PspFFindHeadList, &pPspFFindEntry->PspFFindEntry);
        }

    //
    // Create the FileFindEntry and add to the FileFind list
    //
    pFFindEntry = (PFFINDLIST) malloc(sizeof(FFINDLIST));
    if (!pFFindEntry) {
        return pFFindEntry;
        }

    //
    // Fill in FFindList
    //
    *pFFindEntry = *pFFindEntrySrc;

    //
    //  Insert at  the head of this psp list
    //
    InsertHeadList(&pPspFFindEntry->FFindHeadList, &pFFindEntry->FFindEntry);

    return pFFindEntry;
}





/* FreeFFindEntry
 *
 * Entry -  PFFINDLIST pFFindEntry
 *
 * Exit  -  None
 *
 */
VOID FreeFFindEntry(PFFINDLIST pFFindEntry)
{
    RemoveEntryList(&pFFindEntry->FFindEntry);
    FileFindClose(pFFindEntry);
    RtlFreeUnicodeString(&pFFindEntry->FileName);
    RtlFreeUnicodeString(&pFFindEntry->PathName);
    free(pFFindEntry);
    return;
}



/* FreeFFindList
 *
 * Entry -  Frees the entire list
 *
 * Exit  -  None
 *
 */
VOID FreeFFindList(PLIST_ENTRY pFFindHeadList)
{
    PLIST_ENTRY  Next;
    PFFINDLIST  pFFindEntry;

    Next = pFFindHeadList->Flink;
    while (Next != pFFindHeadList) {
         pFFindEntry = CONTAINING_RECORD(Next,FFINDLIST, FFindEntry);
         Next= Next->Flink;
         FreeFFindEntry(pFFindEntry);
         }

    return;
}


/* GetPspFFindList
 *
 * Entry -  USHORT CurrPsp
 *
 * Exit  -  Success - PPSP_FFINDLIST
 *      Failure - NULL
 *
 */
PPSP_FFINDLIST GetPspFFindList(USHORT CurrPsp)
{
   PLIST_ENTRY    Next;
   PPSP_FFINDLIST pPspFFindEntry;

   Next = PspFFindHeadList.Flink;
   while (Next != &PspFFindHeadList) {
       pPspFFindEntry = CONTAINING_RECORD(Next,PSP_FFINDLIST,PspFFindEntry);
       if (CurrPsp == pPspFFindEntry->usPsp) {
           return pPspFFindEntry;
           }
       Next= Next->Flink;
       }

   return NULL;
}
