/*static char *SCCSID = "@(#)fndmatch.c 6.3 92/01/31";*/
#define BSWAP2 BSWAP2

#define INCL_DOSERRORS
#include <os2def.h>
#include <bse.h>
#include <fsh.h>
#include "heap.h"
#include "api.h"
#include "const.h"
#include "chain.h"
#include "misc.h"
#include "buf.h"
#include "dir.h"
#include "find.h"
#include "storage.h"
#include "fnode.h"
#include "volume.h"
#include "tables.h"
#include "pb_ifs.h"
#include "lw.h"
#include "frame.h"
#include "fsdata.h"
#include "pbmacros.h"
#include "ea.h"
#include "assert.h"
#include "log.h"                                                 // @76193

#include "pr_EA.h"
#include "pr_FIND.h"
#include "pr_DIR.h"
#include "pr_HEAP.h"
#include "pr_MISC.h"
#include "pr_debug.h"


/*
 ***    FindMatch - Search for Next Entries that Match
 *
 *      We have a pointer somewhere into a directory list; it might
 *      be at the top level or it might be at some lower level.
 *      We search linearly from here and fill up the return buffer
 *      with matching entries.
 *
 *      After we have filled up the return buffer, the present state
 *      is saved for use when FindNext is called.  FindNext will either
 *      continue on from the last entry visited or from the last name
 *      returned (if the directory has changed in the mean time).
 *
 *      called by: FS_FINDFIRST, FS_FINDNEXT, FindFromName
 *
 *      ENTRY   pdb = address of directory block
 *              pde = address of next entry to examine
 *              pbuf = buffer for pdb (& pde)
 *              usLevel = level of info returned
 *              usFlags -> CFR_POSITION -> include space for position info
 *                         CFR_DOT -> '.' returned?
 *                         CFR_NEWNAME -> return all names (mixed-case)
 *                         CFR_1STINBUF -> this will be 1st entry in ret buff
 *              cData = length of free space in return buffer
 *              pbData = address of free space in return buffer (if level=3
 *                       then this pointer has already been incremented
 *                       past the eaop struct at the beginning.)
 *              pfw = address of FWORK structure
 *              pcusEnt = ptr to max number of entries
 *              fMatchFirst = skip the first call to MetaMatch
 *              peaop = pointer to EAOP struct from caller's buffer
 *
 *      EXIT    returns error code:
 *                NO_ERROR              - entries returned
 *                ERROR_BUFFER_OVERFLOW - no room for first matching entry
 *                ERROR_NO_MORE_FILES   - no matches found
 *                ERROR_EAS_DIDNT_FIT   - EAs for 1st entry returned in
 *                                        *pbData were too big for the
 *                                        buffer.  Note that this error
 *                                        does bump the find sequence
 *                                        so the next call to FS_FINDNEXT
 *                                        will return the next matching file
 *                                        in the directory.
 *
 *              *pfw = updated FWORK structure
 *              *pcusEnt = number of entries found
 *
 *      HISTORY
 *  @76193  23-Nov-1993 by D.Azzarito - Change holds for LOG support
 *          19-Sep-89 by JR (John Rogers, JohnRo@Microsoft)
 *              - Avoid warnings from Microsoft C 5.2 (on slash-slash comments).
 */

#define FLO_MATCHALL    1
#define FLO_EASTOBIG    2

USHORT
FindMatch (
    PDIRBLK     pdb,            /* ptr to dir block of cur dir entry */
    PDIRENT     pde,            /* ptr to cur dir entry to examine */
    PBUFNODE    pbuf,           /* ptr to buffer header for dir block */
    USHORT      usLevel,        /* level of info returned */
    USHORT      usFlags,        /* should position info be returned? '.'? */
    USHORT      cData,          /* length of return buffer free space */
    PBYTE       pbData,         /* address of return buffer free space */
    PFWORK      pfw,            /* ptr to FWORK structure */
    PUSHORT     pcusEnt,        /* ptr to number of entries found */
    BOOL        fMatchFirst,    /* skip the first call to MetaMatch */
    PEAOP       peaop,          /* eaop from caller's buffer */
    PFRAME      pfrm)
{
    USHORT  usMaxEnt;           /* max number of entries to return */
    USHORT  usRetMatch;         /* return code from MetaMatch */
    PPASCALSTRING pspPattern;   /* ptr to search pattern */
    PDIRENT pdeLast;            /* last returned dir entry */
    PBUFNODE pbufLast;          /* buffer header for pdeLast */
    USHORT   fsFlow;            /* misc flags set as follows:
                                 *   FLO_MATCHALL skip all calls to MetaMatch
                                 *   FLO_EASTOBIG return ERROR_EAS_DIDNT_FIT
                                 */
    PGEALIST pGEAList = NULL; /* ptr to GEA list (info level 3) */
    LHANDLE lhGEAList;
    LHANDLE lhData;
    BOOL    fMatch;              /* True if match, False if no match  */
    USHORT  usError = NO_ERROR;

#ifdef DEBUG2
    dprintf ("FindMatch: pbData=%p\n", pbData);
#endif

    pspPattern = FILE_NAME(&pfw->cpSearchPath);

    /* if the pattern len is 1 then return all dir entries */
    fsFlow = (pspPattern->c == 1) ? FLO_MATCHALL : 0;

    usMaxEnt = *pcusEnt;
    *pcusEnt = 0;

    if (fMatchFirst || (fsFlow & FLO_MATCHALL) ) {
        usRetMatch = METAMATCH_EQUAL;
    } else {
        usRetMatch = MetaMatch (pspPattern, (PPASCALSTRING) &pde->cchName,
                                pfrm->pvol->paiCodePage [pde->bCodePage]);
    }

#ifndef MINIFSD

    if (usLevel == 3) {

        /* For info level 3, the beginning of the user's data is an
         * EAOP and we must extract the GEA list pointer.
         *
         * We have not held the buffer which contains the current dir entry.
         * We must now put a hold on this buffer so that it will remain valid
         * while we are checking the address.
         */
        IncrHoldsBUF (pbuf);  // @76193

        LOCKSWAP(&swapBSWAP0);
        usError = LockFindEAs(peaop,
                              cData + sizeof (EAOP),
                              &lhData,
                              &lhGEAList,pfrm);

        DecrHoldsBUF (pbuf);  // @76193

        if (usError != NO_ERROR) {

            /* User buffer cannot be accessed;  save state and exit.  */
            goto SaveState;
        }

        pGEAList = peaop->fpGEAList;
    }
#endif

    /* keep only position, new name, and 1st in buffer flags */
    usFlags &= CFR_GETPOS | CFR_NEWNAME | CFR_1STINBUF;

    /* save current pde & pbuf, in case we need it for LastName */
    pdeLast = pde;
    pbufLast = pbuf;

    /* loop until:
     *    (1) a match is found and max number have been returned
     *    (2) a match is found and no room in return buffer
     *    (3) no more directory entries
     *    (4) no more matches can occur (entry name > search pattern)
     */

    for (;;) {

        if (usRetMatch == METAMATCH_EQUAL) {

            if (pfw->attr_union.havebits.usMustHave) {
               if ((pde->fAttr &
                   ~(pfw->attr_union.havebits.usMayHave)) == 0) {
                 fMatch = (pfw->attr_union.havebits.usMustHave ==
                  (pde->fAttr & pfw->attr_union.havebits.usMustHave));
               } else {
                 fMatch = FALSE;
               }
            } else {
               fMatch = FATTRFIND (pde->fAttr,
                           pfw->attr_union.wordattr.usSearchAttr);
            }

            /*
             * Skip hidden, system or directory entries
             * unless specified by search attribute.
             */
            if (fMatch) {

                /* Create find record if there is room in return buffer. */
                if (usMaxEnt == 0) {

                    /* Max number of files returned;  exit loop. */
                    usError = ERROR_BUFFER_OVERFLOW;
                    break;
                }

                /* hold pdeLast, in case we need it */
                IncrHoldsBUF (pbufLast);  // @76193

                if ((usError = CFR (pde,
                                    pbuf,
                                    usLevel,
                                    usFlags,
                                    pGEAList,
                                    &cData,
                                    &pbData,
                                    pfrm)) != NO_ERROR) {

                    if( usError != ERROR_EAS_DIDNT_FIT ) {
                        /* No room in return buffer;  exit loop.  */
                        DecrHoldsBUF (pbufLast);  // @76193
                        break;
                    } else {
                        /*
                         * EA's didn't fit, return the level 2 info
                         * and one name only
                         */
                        usMaxEnt = 1;
                        fsFlow |= FLO_EASTOBIG;
                        usError = NO_ERROR;
                    }

                }

                /* not 1st in buffer any more */
                usFlags &= ~CFR_1STINBUF;

                /* unhold pdeLast */
                DecrHoldsBUF (pbufLast);  // @76193

                /* One more entry returned. */
                (*pcusEnt)++;
                usMaxEnt--;

                /*
                 * Save the current pde (and buffer) in case we need to
                 * get the last name.
                 */
                pdeLast = pde;
                pbufLast = pbuf;
            }
        }

        /* hold pdeLast, in case we need it later */
        IncrHoldsBUF (pbufLast);  // @76193

        if (! NextDir(&pdb, &pde, &pbuf, pfrm)) {

            /* No more entries in directory;  exit loop. */
            DecrHoldsBUF (pbufLast);  // @76193
            break;
        }

        ASSERT_DB (pdb);
        DecrHoldsBUF (pbufLast);  // @76193

        if ( !(fsFlow & FLO_MATCHALL) &&
            (usRetMatch = MetaMatch (pspPattern,
                                     (PPASCALSTRING)&pde->cchName,
                                     pfrm->pvol->paiCodePage [pde->bCodePage]))
                                         == METAMATCH_DONE) {

            /* No more entries will match;  exit loop. */
            break;
        }

    } /* for loop */

#ifndef MINIFSD
    if (usLevel == 3) {

        /* hold pdeLast - we'll need it later */
        IncrHoldsBUF (pbufLast);  // @76193

        UnlockEAs(lhData, lhGEAList, pfrm);
        UNLOCKSWAP(&swapBSWAP0);

        DecrHoldsBUF (pbufLast);  // @76193

    }
#endif

    /*
     * At this point in time:
     *      (1) we found a dir entry and max number have been returned, OR
     *      (2) we found a dir entry and no space to return it, OR
     *      (3) we are at the end - the dir entry is the end marker, OR
     *      (4) the dir entry indicates no more matches will occur;
     * and
     *      (A) no entries have been returned, OR
     *      (B) at least one entry has been returned.
     *
     * also
     *      (OFF)   Normal exit
     *      (ON)    Exit caused by ERROR_EAS_DIDNT_FIT
     */

    if (usError == NO_ERROR) {

#ifdef DEBUG2
        dprintf ("FindMatch: case 3 or 4\n");
#endif
        /* Case (3) or (4):  no more entries will be found.  */
        pfw->fFindDone = TRUE;
        if (pfw->pspLastName != NULL) {
            RetHeap (pfw->pspLastName);         /* release last name found */
            pfw->pspLastName = NULL;            /* so we know it's gone */
        }

        return( *pcusEnt == 0 ? ERROR_NO_MORE_FILES :
                ( (fsFlow & FLO_EASTOBIG) ? ERROR_EAS_DIDNT_FIT : NO_ERROR ) );
    }

#ifndef MINIFSD
SaveState:
#endif
    /*
     * Case (1) or (2):  no space to return next dir entry.
     *
     * Save the present state in FWORK structure for use
     * when FindNext is called.
     */

#ifdef DEBUG2
    dprintf ("FindMatch: case 1 or 2\n");
#endif

    pfw->fFindDone = FALSE;

    pfw->offusDirEnt = (PCHAR) pde - (PCHAR) pdb;   /* remember DIRENT */
    pfw->scpDir.pbufHint = pbuf;                    /* remember DIRBLK */
    pfw->scpDir.lsn = pdb->lsnThisDir;

    pfw->culChange = pdb->culChange;        /* used for detecting... */
    pfw->culFreeDB = pfw->psbd->culFreeDB;  /* ...directory changes */

    if (*pcusEnt == 0) {

        /* Case (1A) or (2A) (OFF):not enough room for the first entry found.*/
#ifdef DEBUG2
        dprintf ("FindMatch: case 1A or 2A\n");
#endif
        return usError;
    }

    /*
     * Case (1B) or (2B) either (ON) or (OFF):
     *      no room for next dir entry,
     *      but at least one has been returned.
     *
     * Allocate space for the last name returned and save a pointer
     * to it in FWORK structure.  If the directory is changed between
     * find calls then we use this name to locate the dir entry where
     * we resume searching.
     */
    {
        USHORT cbName;


        if (pfw->pspLastName != NULL)
            RetHeap (pfw->pspLastName);         /* release last name found */

        cbName = pdeLast->cchName + 1;

        /* hold pdeLast - we'll need it later */
        IncrHoldsBUF (pbufLast);  // @76193

        pfw->pspLastName = (PPASCALSTRING)GHS(cbName,pfrm);

        DecrHoldsBUF (pbufLast);  // @76193

        if (pfw->pspLastName == NULL) {
#ifdef DEBUG
                dprintf ("FindMatch: ghs returned NULL\n");
#endif
                return (ERROR_NOT_ENOUGH_MEMORY);
        }
        pfw->iCP = pfrm->pvol->paiCodePage [pdeLast->bCodePage]; //#90495

        pfw->pspLastName->c = (BYTE) cbName;
        BYTE_COPY (pdeLast->bName, pfw->pspLastName->b, cbName - 1);

#ifdef DEBUG2
        dprintf ("FindMatch: case 1B or 2B, pfw=%p, pfw->pspLastName=%p, %s",
                 pfw, pfw->pspLastName,
                 "\n\r\t*pspLastName=");
        {
        USHORT us;
        PCHAR pc = pfw->pspLastName->b;
        for (us = 0; us < pfw->pspLastName->c; us++) {
                dprintf ("%x ", (UCHAR) (0x00ff & *pc++));
        }
        }
        dprintf ("\n");
#endif
    }
    return( (fsFlow & FLO_EASTOBIG) ? ERROR_EAS_DIDNT_FIT : NO_ERROR );

#ifdef MINIFSD
        /*  to keep the compiler happy: */
        peaop;
        lhGEAList;
        lhData;
#endif
}
