/***************************************************************************
 *                                                                         *
 *   DFAAPI.C                                                              *
 *                                                                         *
 *   Copyright (c) 1987-1997 Galacticomm, Inc.   All Rights Reserved.      *
 *                                                                         *
 *   This file contains a library of routines for accessing BTRIEVE files  *
 *   with fixed or variable length records, from Windows NT and Large      *
 *   Model DOS.                                                            *
 *                                                                         *
 *                                            - T. Stryker 9/30/86         *
 *   Dup Ins/Upd, File Create, Misc others    - S. Brinker/R. Skurnick     *
 *   Step Based Btrieve Support Added         - R. Skurnick                *
 *   Windows DLL interface                    - T. Stryker                 *
 *   Locking/transaction support              - TJS/CLR/RLS 6/18/94        *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"

#define FILREV "$Revision: 21 $"

static struct dfablk *dfa;         /* current btvu file pointer set        */
static struct dfablk *dfastk[DFSTSZ];/* dfaSetBlk()/dfaRstBlk() stack      */
static USHORT status;              /* status code returned by btvu()       */
static SHORT dfaomode=PRIMBV;      /* dfaOpen() file-open mode             */
static USHORT lastlen;             /* length of last record read           */

#define ANOSEG  0x10               /* keyspc.flags for key has another seg */

struct filspc {                    /* Btrieve STAT command file spec       */
     USHORT reclen;                /*   record length                      */
     USHORT pagsiz;                /*   page size                          */
     SHORT numofx;
     ULONG numofr;
     SHORT flags;
     SHORT reserved;
     SHORT unupag;
};

struct keyspc {                    /* Btrieve STAT command key specs       */
     SHORT keypos;
     SHORT keylen;
     SHORT flags;
     LONG numofk;
     CHAR extype;
     CHAR nullvalue;
     LONG reserved;
};

#define ACSSIZ 265                 /* alternate collating sequence size    */

struct statbf {                    /* STAT command ret buf layout          */
     struct filspc fs;
     struct keyspc ks[SEGMAX];
     CHAR altcol[ACSSIZ];
};

                                   /* internal file creation flags         */
#define DFACF_PREALLOC   4         /*   pre-allocate pages                 */
#define DFACF_USERVALID  (DFACF_VARIABLE|DFACF_BLANKTRUNC|DFACF_COMPRESS   \
                         |DFACF_KEYONLY|DFACF_FREESPACE1|DFACF_FREESPACE2)

                                   /* internal segment specification flags */
#define DFASF_SEGMENT    16        /*   another segment follows            */
#define DFASF_EXTENDED   256       /*   segment uses extended type         */
#define DFASF_USERVALID  (DFASF_ALTCOLLATE|DFASF_DESCENDING)
#define DFASF_KEYVALID   (DFAKF_DUPLICATE|DFAKF_MODIFYABLE|DFAKF_MANUAL|DFAKF_NULL)

#ifdef GCWINNT
struct flddef filspcFDA[] = {

     {CVTFLD_SHORT,3,fldoff(filspc,reclen),NULL},
     {CVTFLD_LONG ,1,fldoff(filspc,numofr),NULL},
     {CVTFLD_SHORT,3,fldoff(filspc,flags) ,NULL},
     {CVTFLD_END  ,0,0                    ,NULL}
};

struct flddef keyspcFDA[] = {
     {CVTFLD_SHORT,3,fldoff(keyspc,keypos),  NULL},
     {CVTFLD_LONG, 1,fldoff(keyspc,numofk),  NULL},
     {CVTFLD_CHAR, 2,fldoff(keyspc,extype),  NULL},
     {CVTFLD_LONG, 1,fldoff(keyspc,reserved),NULL},
     {CVTFLD_END  ,0,0                    ,  NULL}
};

struct flddef statbfFDA[] = {
     {CVTFLD_STRUCT,1,     fldoff(statbf,fs),filspcFDA},
     {CVTFLD_STRUCT,SEGMAX,fldoff(statbf,ks),keyspcFDA},
     {CVTFLD_CHAR,  ACSSIZ,fldoff(statbf,altcol),NULL},
     {CVTFLD_END,   0,     0,                     NULL}
};

#define EXTYPE_INT   1             /* extended key segment type INT        */
#define EXTYPE_UINT  14            /* extended key segment type UINT       */

static CHAR tmpData[0xFFF0];       /* temp. buffer for insert/update       */

#endif // GCWINNT


static USHORT clckln(struct statbf *statInfo);
static VOID dfaPosError(CHAR *recptr,const CHAR *stg);
static VOID dfaError(const CHAR *who);
static GBOOL dfaCopyFile(const CHAR *src,const CHAR *dst);
static USHORT btvu(USHORT funcno,VOID *datbuf,VOID *key,
                                   SHORT keyno,USHORT rlen);

VOID (*dfaErrPtr)(const CHAR *who)=dfaError;

#ifdef GCWINNT
static USHORT buildFDA(struct statbf *statInfo,USHORT keyLength[],
                       struct flddef *fdaList[],USHORT unpackKeySiz[]);
static USHORT getFDAfld(USHORT keytype,USHORT keylen,struct flddef *flddef,
                        USHORT *fldidx);
#endif // GCWINNT

#ifdef GCDOSL
struct btvdat {                    /* btrieve parameter block structure    */
     CHAR *datbuf;                 /* for use with INT 0x7B                */
     USHORT dbflen;
     CHAR *posp38;
     CHAR *posblk;
     USHORT funcno;
     CHAR *key;
     CHAR keylen;
     CHAR keyno;
     USHORT *statpt;
     USHORT magic;
};
#endif // GCDOSL

DFAFILE *                          /*   pointer to a dfa file structure    */
dfaOpen(                           /* open data file                       */
const CHAR *filnam,                /*   filename                           */
USHORT maxlen,                     /*   maximum record length              */
const CHAR *owner)                 /*   owner of file (NULL if none)       */
{
     struct statbf statInfo;
     USHORT ownlen,stt,cntr;

     dfa=alcmem(sizeof(struct dfablk));
     dfa->filnam=alcdup(filnam);
     dfa->reclen=maxlen;
     dfa->data=alcmem(maxlen);
     if (owner == NULL) {
          ownlen=0;
     }
     else {
          ownlen=1+strlen(owner);
          stlcpy(dfa->data,owner,maxlen);
     }
     cntr=0;
     while (1) {
          stt=btvu(0,dfa->data,dfa->filnam,dfaomode,ownlen);
          if (stt == 20) {
               if (++cntr > 100) { // retries for a while if the
                    break;         // btrieve engine isn't ready
               }
          }
          else if (stt != 85) {    /* file is locked by another process -  */
               break;              /* loop until file is unlocked          */
          }
     }
     if (status != 0) {
          (*dfaErrPtr)("OPEN");
     }
#ifdef GCDOS
     dfa->key=alcmem(clckln(&statInfo));
#else
     clckln(&statInfo);
     dfa->key=alcmem(buildFDA(&statInfo,dfa->keylns,dfa->flddefList,
                              dfa->unpackKeySiz));
#endif // GCDOS
     dfaSetBlk(dfa);
     return(dfa);
}

VOID                               /*   Btrieve-specific function          */
dfaMode(                           /* set data file mode                   */
SHORT mode)                        /*   access mode                        */
{
     dfaomode=mode;
}

VOID
dfaSetBlk(                         /* set current dfablk to dfaptr         */
struct dfablk *dfaptr)             /*   datafile block pointer             */
{
     movmem(dfastk,dfastk+1,sizeof(struct dfablk *)*(DFSTSZ-1));
     *dfastk=dfa=dfaptr;
}

VOID
dfaRstBlk(VOID)                    /* restore last dfablk pointer          */
{
     movmem(dfastk+1,dfastk,sizeof(struct dfablk *)*(DFSTSZ-1));
     dfa=*dfastk;
}

VOID
dfaBegTrans(                       /* begin datafile transaction           */
USHORT loktyp)                     /*   lock bias                          */
{
     ASSERT(loktyp == WAITBV || loktyp == NOWTBV);
     if (btvu(19+loktyp,NULL,NULL,0,0) != 0) {
          (*dfaErrPtr)("BEGIN-XACTION");
     }
}

VOID
dfaAbtTrans(VOID)                  /* abort current datafile transaction   */
{
     if (btvu(21,NULL,NULL,0,0) != 0) {
          (*dfaErrPtr)("ABORT-XACTION");
     }
}

VOID
dfaEndTrans(VOID)                  /* end datafile transaction             */
{
     if (btvu(20,NULL,NULL,0,0) != 0) {
          (*dfaErrPtr)("END-XACTION");
     }
}

GBOOL                              /*   return TRUE=success                */
dfaQuery(                          /* datafile query operation             */
const VOID *key,                   /*   key to lookup                      */
SHORT keynum,                      /*   key number or -1                   */
USHORT qryopt)                     /*   query operation to perform         */
{
     GBOOL retval;

     if (dfa == NULL) {
          ASSERT(dfa != NULL);
          return(FALSE);
     }
     if (keynum < 0) {
          ASSERT(keynum == -1);
          keynum=dfa->lastkn;
     }
     else {
          dfa->lastkn=keynum;
     }
#ifdef GCWINNT
     if (key != NULL) {
          memmove(dfa->key,key,goodblk(key,dfa->unpackKeySiz[keynum]));
     }
     if (dfa->flddefList[keynum] != NULL) {
          cvtDataIP(dfa->key,dfa->unpackKeySiz[keynum],dfa->keylns[keynum],
                    dfa->flddefList[keynum],CVTSERVER,CVTDFA,CHAN_NUL);
     }
#else
     if (key != NULL) {
          memmove(dfa->key,key,goodblk(key,dfa->keylns[keynum]));
     }
#endif // GCWINNT
     retval=TRUE;
     if (btvu(qryopt,NULL,dfa->key,keynum,dfa->reclen) != 0) {
          if (status == 4 || status == 9) {
               retval=FALSE;
          }
          else {
               (*dfaErrPtr)("QUERY");
          }
     }
#ifdef GCWINNT
     if (dfa->flddefList[keynum] != NULL) {
          cvtDataIP(dfa->key,dfa->keylns[keynum],dfa->unpackKeySiz[keynum],
                    dfa->flddefList[keynum],CVTDFA,CVTSERVER,CHAN_NUL);
     }
#endif // GCWINNT
     return(retval);
}

GBOOL                              /*   return TRUE=success                */
dfaQueryNP(                        /* datafile query next/previous         */
USHORT qryopt)                     /*   operation to perform               */
{
     GBOOL retval;

     if (dfa == NULL) {
          ASSERT(dfa != NULL);
          return(FALSE);
     }
#ifdef GCWINNT
     if (dfa->flddefList[dfa->lastkn] != NULL) {
          cvtDataIP(dfa->key,dfa->unpackKeySiz[dfa->lastkn],
                    dfa->keylns[dfa->lastkn],dfa->flddefList[dfa->lastkn],
                    CVTSERVER,CVTDFA,CHAN_NUL);
     }
#endif // GCWINNT
     retval=TRUE;
     ASSERT(qryopt >= 55 && qryopt <= 63);
     if (btvu(qryopt-50,dfa->data,dfa->key,dfa->lastkn,dfa->reclen) != 0) {
          if (status == 4 || status == 9) {
               retval=FALSE;
          }
          else {
               dfaPosError(NULL,"QUERY-NP");
          }
     }
#ifdef GCWINNT
     if (dfa->flddefList[dfa->lastkn] != NULL) {
          cvtDataIP(dfa->key,dfa->keylns[dfa->lastkn],
                    dfa->unpackKeySiz[dfa->lastkn],dfa->flddefList[dfa->lastkn],
                    CVTDFA,CVTSERVER,CHAN_NUL);
     }
#endif // GCWINNT
     return(retval);
}

VOID
dfaGetLock(                        /* datafile get operation               */
VOID *recptr,                      /*   pointer for data record            */
const VOID *key,                   /*   key to lookup                      */
SHORT keynum,                      /*   key number or -1                   */
USHORT getopt,                     /*   operation to perform               */
USHORT loktyp)                     /*   lock bias (Btrieve)                */
{
     if (dfa == NULL) {
          ASSERT(dfa != NULL);
          return;
     }
     if (recptr == NULL) {
          recptr=dfa->data;
     }
     if (keynum < 0) {
          ASSERT(keynum == -1);
          keynum=dfa->lastkn;
     }
     else {
          dfa->lastkn=keynum;
     }
#ifdef GCWINNT
     if (key != NULL) {
          memmove(dfa->key,key,goodblk(key,dfa->unpackKeySiz[keynum]));
     }
     if (dfa->flddefList[keynum] != NULL) {
          cvtDataIP(dfa->key,dfa->unpackKeySiz[keynum],dfa->keylns[keynum],
                    dfa->flddefList[keynum],CVTSERVER,CVTDFA,CHAN_NUL);
     }
#else
     if (key != NULL) {
          memmove(dfa->key,key,goodblk(key,dfa->keylns[keynum]));
     }
#endif // GCWINNT
     ASSERT(getopt >= 5 && getopt <= 13);
     ASSERT(loktyp == 0 || loktyp == SLWTBV || loktyp == SLNWBV
         || loktyp == MLWTBV || loktyp == MLNWBV);
     if (btvu(getopt+loktyp,recptr,dfa->key,keynum,dfa->reclen) != 0) {
          dfaPosError(recptr,"GET");
     }
#ifdef GCWINNT
     if (dfa->flddefList[keynum] != NULL) {
          cvtDataIP(dfa->key,dfa->keylns[keynum],dfa->unpackKeySiz[keynum],
                    dfa->flddefList[keynum],CVTDFA,CVTSERVER,CHAN_NUL);
     }
#endif // GCWINNT
}

GBOOL                              /*   return TRUE=success                */
dfaAcqLock(                        /* datafile acquire operation           */
VOID *recptr,                      /*   pointer for data record            */
const VOID *key,                   /*   key to lookup                      */
SHORT keynum,                      /*   key number or -1                   */
USHORT obtopt,                     /*   operation to perform               */
USHORT loktyp)                     /*   lock bias (Btrieve)                */
{
     GBOOL retval;

     if (dfa == NULL) {
          ASSERT(dfa != NULL);
          return(FALSE);
     }
     if (recptr == NULL) {
          recptr=dfa->data;
     }
     if (keynum < 0) {
          ASSERT(keynum == -1);
          keynum=dfa->lastkn;
     }
     else {
          dfa->lastkn=keynum;
     }
#ifdef GCWINNT
     if (key != NULL) {
          memmove(dfa->key,key,goodblk(key,dfa->unpackKeySiz[keynum]));
     }
     if (dfa->flddefList[keynum] != NULL) {
          cvtDataIP(dfa->key,dfa->unpackKeySiz[keynum],dfa->keylns[keynum],
                    dfa->flddefList[keynum],CVTSERVER,CVTDFA,CHAN_NUL);
     }
#else
     if (key != NULL) {
          memmove(dfa->key,key,goodblk(key,dfa->keylns[keynum]));
     }
#endif // GCWINNT
     retval=TRUE;
     ASSERT(obtopt >= 5 && obtopt <= 13);
     ASSERT(loktyp == 0 || loktyp == SLWTBV || loktyp == SLNWBV
         || loktyp == MLWTBV || loktyp == MLNWBV);
     if (btvu(obtopt+loktyp,recptr,dfa->key,keynum,dfa->reclen) != 0) {
          if (status == 4 || status == 9 || dfaWasLocked()) {
               retval=FALSE;
          }
          else {
               dfaPosError(recptr,"OBTAIN");
          }
     }
#ifdef GCWINNT
     if (dfa->flddefList[keynum] != NULL) {
          cvtDataIP(dfa->key,dfa->keylns[keynum],dfa->unpackKeySiz[keynum],
                    dfa->flddefList[keynum],CVTDFA,CVTSERVER,CHAN_NUL);
     }
#endif // GCWINNT
     return(retval);
}

GBOOL                              /*   return TRUE=success                */
dfaAcqNPLock(                      /* datafile acquire nxt/prv op          */
VOID *recptr,                      /*   pointer for data record            */
GBOOL chkcas,                      /*   case sensitive?                    */
USHORT anpopt,                     /*   operation to perform               */
USHORT loktyp)                     /*   lock bias (Btrieve)                */
{
     if (dfa == NULL) {
          ASSERT(dfa != NULL);
          return(FALSE);
     }
     movmem(dfa->key,dfa->data,dfa->keylns[dfa->lastkn]);
     if (dfaAcqLock(recptr,NULL,-1,anpopt,loktyp)) {
          if (chkcas) {
               return(strcmp(dfa->data,dfa->key) == 0);
          }
          return(stricmp(dfa->data,dfa->key) == 0);
     }
     return(FALSE);
}

USHORT                             /*   returns length of last record      */
dfaLastLen(VOID)                   /* get length of last record            */
{
     return(lastlen);
}

LONG                               /*   returns absolute position          */
dfaAbs(VOID)                       /* get absolute position of current rec */
{
     LONG abspos;

     if (btvu(22,&abspos,NULL,0,sizeof(LONG)) != 0) {
          (*dfaErrPtr)("ABSOLUTE");
     }
     return(abspos);
}

VOID
dfaGetAbsLock(                     /* datafile get absolute operation      */
VOID *recptr,                      /*   pointer for data record            */
LONG abspos,                       /*   absolute datafile position         */
SHORT keynum,                      /*   key number                         */
USHORT loktyp)                     /*   lock bias (Btrieve)                */
{
     ASSERT(keynum >= 0);
     if (!dfaAcqAbsLock(recptr,abspos,keynum,loktyp)) {
          dfaPosError(recptr,"GET-ABSOLUTE");
     }
}

GBOOL                              /*   return TRUE=success                */
dfaAcqAbsLock(                     /* datafile acquire absolute operation  */
VOID *recptr,                      /*   pointer for data record            */
LONG abspos,                       /*   absolute datafile position         */
SHORT keynum,                      /*   key number                         */
USHORT loktyp)                     /*   lock bias (Btrieve)                */
{
     ASSERT(dfa != NULL);
     if (recptr == NULL) {
          recptr=dfa->data;
     }
     *(LONG *)recptr=abspos;
     ASSERT(keynum >= 0);
     dfa->lastkn=keynum;
     ASSERT(loktyp == 0 || loktyp == SLWTBV || loktyp == SLNWBV
         || loktyp == MLWTBV || loktyp == MLNWBV);
     btvu(23+loktyp,recptr,dfa->key,keynum,dfa->reclen);
     if (status == 22) {
          if (recptr == NULL) {
               dfa->data[dfa->reclen-1]='\0';
          }
          else {
               ((CHAR *)recptr)[dfa->reclen-1]='\0';
          }
          status=0;
     }
#ifdef GCWINNT
     if (dfa->flddefList[keynum] != NULL) {
          cvtDataIP(dfa->key,dfa->keylns[keynum],dfa->unpackKeySiz[keynum],
                    dfa->flddefList[keynum],CVTDFA,CVTSERVER,CHAN_NUL);
     }
#endif // GCWINNT
     return(status == 0);
}

GBOOL                              /*   return TRUE=success                */
dfaStepLock(                       /* datafile step operation              */
VOID *recptr,                      /*   pointer for data record            */
USHORT stpopt,                     /*   operation to perform               */
USHORT loktyp)                     /*   lock bias (Btrieve)                */
{
     if (dfa == NULL) {
          ASSERT(dfa != NULL);
          return(FALSE);
     }
     if (recptr == NULL) {
          recptr=dfa->data;
     }
     ASSERT(stpopt == 24 || (stpopt >= 33 && stpopt <= 35));
     ASSERT(loktyp == 0 || loktyp == SLWTBV || loktyp == SLNWBV
         || loktyp == MLWTBV || loktyp == MLNWBV);
     if (btvu(stpopt+loktyp,recptr,NULL,0,dfa->reclen) != 0) {
          if (status == 9 || dfaWasLocked()) {
               return(FALSE);
          }
          else {
               dfaPosError(recptr,"STEP");
          }
     }
     return(TRUE);
}

VOID
dfaUpdate(                         /* datafile update record               */
VOID *recptr)                      /*   pointer to record to update        */
{
     ASSERT(dfa != NULL);
     dfaUpdateV(recptr,dfa->reclen);
}

VOID
dfaUpdateV(                        /* datafile update var len record       */
VOID *recptr,                      /*   pointer to record to update        */
USHORT length)                     /*   length of record                   */
{
     ASSERT(dfa != NULL);
     if (recptr == NULL) {
          recptr=dfa->data;
     }
#ifdef GCWINNT
     length=min(length,sizeof(tmpData));
     memmove(tmpData,recptr,goodblk(recptr,length));
     recptr=tmpData;
#endif
     if (btvu(3,recptr,dfa->key,dfa->lastkn,length) != 0) {
          (*dfaErrPtr)("UPDATE");
     }
}

GBOOL                              /*   return TRUE=success                */
dfaUpdateDup(                      /* datafile update record (w/o catastro)*/
VOID *recptr)                      /*   pointer to record to update        */
{
     INT length;

     if (dfa == NULL) {
          ASSERT(dfa != NULL);
          return(FALSE);
     }
     if (recptr == NULL) {
          recptr=dfa->data;
     }
     length=dfa->reclen;
#ifdef GCWINNT
     length=min(length,sizeof(tmpData));
     memmove(tmpData,recptr,goodblk(recptr,length));
     recptr=tmpData;
#endif
     switch (btvu(3,recptr,dfa->key,dfa->lastkn,length)) {
     case 0:
          return(TRUE);
     case 5:
          break;
     default:
          (*dfaErrPtr)("UPDATE");
     }
     return(FALSE);
}

VOID
dfaInsert(                         /* datafile insert record               */
VOID *recptr)                      /*   pointer to record to insert        */
{
     ASSERT(dfa != NULL);
     dfaInsertV(recptr,dfa->reclen);
}

VOID
dfaInsertV(                        /* datafile insert variable length rec. */
VOID *recptr,                      /*   pointer to record to insert        */
USHORT length)                     /*   length of record                   */
{
     ASSERT(dfa != NULL);
     if (recptr == NULL) {
          recptr=dfa->data;
     }
#ifdef GCWINNT
     length=min(length,sizeof(tmpData));
     memmove(tmpData,recptr,goodblk(recptr,length));
     recptr=tmpData;
#endif
     if (btvu(2,recptr,dfa->key,0,length) != 0) {
          (*dfaErrPtr)("INSERT");
     }
}

GBOOL                              /*   return TRUE=success                */
dfaInsertDup(                      /* datafile insert record (w/o catastro)*/
VOID *recptr)                      /*   pointer to record to insert        */
{
     INT length;

     ASSERT(dfa != NULL);
     if (recptr == NULL) {
          recptr=dfa->data;
     }
     length=dfa->reclen;
#ifdef GCWINNT
     length=min(length,sizeof(tmpData));
     memmove(tmpData,recptr,goodblk(recptr,length));
     recptr=tmpData;
#endif
     switch (btvu(2,recptr,dfa->key,0,length)) {
     case 0:
          return(TRUE);
     case 5:
          break;
     default:
          (*dfaErrPtr)("INSERT");
     }
     return(FALSE);
}

VOID
dfaDelete(VOID)                    /* datafile delete record               */
{
     ASSERT(dfa != NULL);
     if (btvu(4,NULL,NULL,dfa->lastkn,dfa->reclen) != 0) {
          (*dfaErrPtr)("DELETE");
     }
}

VOID
dfaClose(                          /* close datafile                       */
struct dfablk *dfap)               /*   pointer to dfafile to close        */
{
     CHAR *filnam;

     ASSERT(dfap != NULL);
     if (goodptr(dfa=dfap) && dfa->filnam != NULL) {
          filnam=dfa->filnam;
          dfa->filnam=NULL;
          if (btvu(1,NULL,NULL,0,0) != 0) {
               catastro("BTRIEVE CLOSE ERROR %d ON FILE \"%s\"",status,filnam);
          }
          free(dfa->key);
          free(dfa->data);
          free(filnam);
          free(dfa);
     }
}

VOID
dfaCreateSpec(                     /* create file from spec                */
const CHAR *fileName,              /*   path and name of file to create    */
GBOOL overwrite,                   /*   overwrite existing file?           */
size_t recordLength,               /*   fixed-size record length           */
size_t pageSize,                   /*   page size (must be multiple of 512)*/
INT flags,                         /*   file creation flags                */
INT nPreAllocate,                  /*   number of pages to pre-allocate    */
INT nKeys,                         /*   number of keys (not segments)      */
struct dfaKeySpec *keys,           /*   array of key info                  */
const CHAR *altFile)               /*   alternating colating sequence file */
{
     INT iKey,iSeg;
     GBOOL useACS;
     size_t bufsiz;
     CHAR *buf,*bufptr;
     struct dfaStatFileSpec fs;
     struct dfaStatKeySpec ks;
     FILE *fp;

     useACS=FALSE;
     bufsiz=sizeof(struct dfaStatFileSpec);
     for (iKey=0 ; iKey < nKeys ; ++iKey) {
          for (iSeg=0 ; iSeg < keys[iKey].nSegments ; ++iSeg) {
               bufsiz+=sizeof(struct dfaStatKeySpec);
               if (keys[iKey].segs[iSeg].flags&DFASF_ALTCOLLATE) {
                    ASSERT(altFile != NULL);
                    useACS=TRUE;
               }
          }
     }
     if (useACS) {
          bufsiz+=ACSSIZ;
     }
     bufptr=buf=alcmem(bufsiz);

     memset(&fs,0,sizeof(struct dfaStatFileSpec));
     fs.recordLength=(USHORT)recordLength;
     fs.pageSize=(USHORT)pageSize;
     fs.nKeys=(USHORT)nKeys;
     fs.nPreAllocate=(USHORT)nPreAllocate;
     fs.flags=flags&DFACF_USERVALID;
     if (nPreAllocate != 0) {
          fs.flags|=DFACF_PREALLOC;
     }
     memcpy(bufptr,&fs,sizeof(struct dfaStatFileSpec));
     bufptr+=sizeof(struct dfaStatFileSpec);

     for (iKey=0 ; iKey < nKeys ; ++iKey) {
          for (iSeg=0 ; iSeg < keys[iKey].nSegments ; ++iSeg) {
               memset(&ks,0,sizeof(struct dfaStatKeySpec));
               ks.position=(USHORT)(keys[iKey].segs[iSeg].position+1);
               ks.length=(USHORT)keys[iKey].segs[iSeg].length;
               ks.extendedType=(UCHAR)keys[iKey].segs[iSeg].type;
               ks.flags=DFASF_EXTENDED;
               if (iSeg < keys[iKey].nSegments-1) {
                    ks.flags|=DFASF_SEGMENT;
               }
               ks.flags|=(USHORT)(keys[iKey].flags&DFASF_KEYVALID);
               ks.flags|=(USHORT)(keys[iKey].segs[iSeg].flags&DFASF_USERVALID);
               if (ks.flags&DFAKF_NULL) {
                    ks.nullChar=keys[iKey].segs[iSeg].nullChar;
               }
               memcpy(bufptr,&ks,sizeof(struct dfaStatKeySpec));
               bufptr+=sizeof(struct dfaStatKeySpec);
          }
     }
     if (useACS) {
          if ((fp=fopen(altFile,FOPRB)) == NULL) {
               free(buf);
               catastro("Unable to open alternate collating sequence file");
          }
          if (fread(bufptr,1,ACSSIZ,fp) != ACSSIZ) {
               free(buf);
               catastro("Unable to read alternate collating sequence file");
          }
          fclose(fp);
     }

     dfaCreate(fileName,buf,overwrite ? 0 : -1,bufsiz);
     free(buf);
}

VOID
dfaCreate(                         /* create datafile                      */
const VOID *filnam,                /*   filename to create                 */
VOID *databuf,                     /*   pointer to databuffer              */
SHORT keyno,                       /*   number of keys                     */
USHORT lendbuf)                    /*   length of data buffer              */
{
     DFAFILE *crtdfa;

     crtdfa=alczer(sizeof(DFAFILE));
     crtdfa->key=(CHAR *)filnam;   /* btvu will not modify */
     crtdfa->data=databuf;
     crtdfa->reclen=lendbuf;
     dfaSetBlk(crtdfa);
     if (btvu(14,crtdfa->data,crtdfa->key,keyno,crtdfa->reclen) != 0) {
          crtdfa->filnam=(CHAR *)filnam;
          (*dfaErrPtr)("CREATE");
     }
     free(crtdfa);
}

ULONG                              /*    returns number of records         */
dfaCountRec(VOID)                  /* count records in datafile            */
{
     CHAR kbf[64];
     struct statbf statbf;

     if (btvu(15,&statbf,kbf,0,sizeof(struct statbf)) != 0) {
          (*dfaErrPtr)("STAT");
     }
#ifdef GCWINNT
     cvtDataIP(&statbf,sizeof(struct statbf),sizeof(struct statbf),
               statbfFDA,CVTDFA,CVTSERVER,CHAN_NUL);
#endif // GCWINNT
     return(statbf.fs.numofr);
}

USHORT                             /*   returns record length              */
dfaRecLen(VOID)                    /* get record length of current record  */
{
     CHAR kbf[64];
     struct statbf statbf;

     if (btvu(15,&statbf,kbf,0,sizeof(struct statbf)) != 0) {
          (*dfaErrPtr)("STAT");
     }
#ifdef GCWINNT
     cvtDataIP(&statbf,sizeof(struct statbf),sizeof(struct statbf),
               statbfFDA,CVTDFA,CVTSERVER,CHAN_NUL);
#endif // GCWINNT
     return(statbf.fs.reclen);
}

VOID
dfaStat(                           /* return stats on current datafile     */
USHORT len)                        /*   length of buffer                   */
{
     ASSERT(dfa != NULL);
     if (btvu(15,dfa->data,dfa->key,0,len) != 0) {
          (*dfaErrPtr)("STAT");
     }
}

VOID
dfaStatus(                         /* Low level status routine             */
VOID *Buffer,                      /*  Buffer to store status in           */
USHORT BufferLength,               /*  Length of the buffer                */
INT Key)                           /*  Key number to call by               */
{
     CHAR kbf[64];

     ASSERT(dfa != NULL);
     ASSERT(Buffer != NULL);

     if (btvu(15,Buffer,kbf,Key,BufferLength) != 0) {
          (*dfaErrPtr)("STAT");
     }
}

VOID
dfaUnlock(                         /* unlock record                        */
LONG abspos,                       /*   position to unlock                 */
SHORT keynum)                      /*   key number or -1                   */
{
     if (keynum == -1) {
          btvu(27,&abspos,NULL,keynum,sizeof(LONG));
     }
     else {
          btvu(27,NULL,NULL,keynum,0);
     }
     if (status != 0) {
          (*dfaErrPtr)("UNLOCK");
     }
}

GBOOL                              /*   return TRUE=record/file was locked */
dfaWasLocked(VOID)                 /* check record lock status             */
{
     return(status == 84 || status == 85);
}

GBOOL                              /*   returns TRUE=success               */
dfaVirgin(                         /* copy virgin file into database file  */
const CHAR *src,                   /*   virgin database file name w/o ext  */
const CHAR *dst)                   /*   destination file name or NULL      */
{
     CHAR srcfil[GCMAXPTH];
     CHAR dstfil[GCMAXPTH];

#if defined(GCWIN3X)
     stlcat(stlcpy(srcfil,src,GCMAXPTH),".vir",GCMAXPTH);
#else
     sprintf(srcfil,GCVIRGIN"%s.vir",src);
#endif // GCWIN3X
     stlcat(stlcpy(dstfil,(dst == NULL) ? src : dst,GCMAXPTH),".dat",GCMAXPTH);
     return(dfaCopyFile(srcfil,dstfil));
}

VOID
dfaStop(VOID)                      /* stop btrieve operation               */
{
     btvu(25,NULL,NULL,0,0);
}

static VOID                        /* don't crash on overflow, truncate    */
dfaPosError(                       /* possible btrieve error               */
CHAR *recptr,                      /*   current record buffer              */
const CHAR *stg)                   /*   error string to display            */
{
     if (status != 22) {
          (*dfaErrPtr)(stg);
     }
     if (recptr == NULL) {
          dfa->data[dfa->reclen-1]='\0';
     }
     else {
          recptr[dfa->reclen-1]='\0';
     }
     status=0;
}

static VOID
dfaError(                          /* print out any btrieve error msg      */
const CHAR *who)                   /*   btrieve call responsible for error */
{
     char buff[40];

     sprintf(buff,"%s ERROR %d",who,status);
     catastro("BTRIEVE %s ON FILE \"%s\"",buff,dfa->filnam);
}

static USHORT                      /*   return Btrieve status              */
btvu(                              /* underlying Btrieve command interface */
USHORT funcno,                     /*   btrieve function number to execute */
VOID *datbuf,                      /*   ptr to data buffer                 */
VOID *key,                         /*   ptr to key buffer                  */
SHORT keyno,                       /*   key number to work with            */
USHORT rlen)                       /*   record length                      */
{
#if defined(GCDOSL) && !defined(GCWIN3X)
     union REGS regs;
     struct SREGS sregs;
     struct btvdat btvdat;
     static GBOOL btrvup=FALSE,alrcat=FALSE;

     if (!btrvup) {
          regs.x.ax=0x357B;
          int86x(0x21,&regs,&regs,&sregs);
          if (regs.x.bx != 0x33) {
               if (alrcat) {
                    return(0);
               }
               alrcat=TRUE;
               catastro("BTRIEVE must be running before this program can run!");
          }
          btrvup=TRUE;
     }
     btvdat.datbuf=datbuf;
     btvdat.dbflen=rlen;
     btvdat.posp38=((CHAR *)dfa->posblk)+38;
     btvdat.posblk=(CHAR *)dfa->posblk;
     btvdat.funcno=funcno;
     btvdat.key=key;
     btvdat.keylen=(CHAR)255;
     btvdat.keyno=(CHAR)keyno;
     *(btvdat.statpt=&status)=0;
     btvdat.magic=24950;
     regs.x.dx=(short)&btvdat;
     segread(&sregs);
     sregs.ds=sregs.ss;
     int86x(0x7B,&regs,&regs,&sregs);
     lastlen=btvdat.dbflen;
#elif defined(GCWIN3X)
     extern SHORT PASCAL BTRCALL (USHORT op,
                                  VOID *posBlock,
                                  VOID *dataBuffer,
                                  USHORT *dataLength,
                                  VOID *keyBuffer,
                                  UCHAR keyLength,
                                  CHAR keyNum);

     status=BTRCALL((USHORT)funcno,
                    dfa->posblk,
                    datbuf,
                    &rlen,
                    key,
                    (UCHAR)255,
                    (CHAR)keyno);
     lastlen=rlen;
#else
     extern SHORT __stdcall __import BTRCALL(USHORT operation,
                                             VOID *posBlock,
                                             VOID *dataBuffer,
                                             ULONG *dataLength,
                                             VOID *keyBuffer,
                                             CHAR keyLength,
                                             CHAR ckeynum);

     status=BTRCALL((USHORT)funcno,
                    (VOID *)dfa->posblk,
                    datbuf,
                    (ULONG*)&rlen,
                    key,
                    (CHAR)255,
                    (CHAR)keyno);
     lastlen=rlen;
#endif // GCDOSL
     return(status);
}

static GBOOL                       /*   returns TRUE=success               */
dfaCopyFile(                       /* copy file src to dst                 */
const CHAR *src,                   /*   source filename                    */
const CHAR *dst)                   /*   destination filename               */
{
     INT frsiz;
     FILE *fvir,*fnew;
     static CHAR inbuf[4096];

     if ((fvir=fopen(src,FOPRB)) == NULL) {
          return(FALSE);
     }
     if ((fnew=fopen(dst,FOPWB)) == NULL) {
          fclose(fvir);
          return(FALSE);
     }
     while (!feof(fvir)) {
          if ((frsiz=(INT)fread(inbuf,1,4096,fvir)) > 0) {
               fwrite(inbuf,1,frsiz,fnew);
          }
          if (ferror(fnew) || ferror(fvir)) {
               fclose(fvir);
               fclose(fnew);
               unlink(dst);
               return(FALSE);
          }
     }
     fclose(fvir);
     fclose(fnew);
     return(TRUE);
}

static USHORT                      /*   return largest key length          */
clckln(                            /* calculate all key lens in curr file  */
struct statbf *statInfo)           /*   buffer for file statistics         */
{
     CHAR kbf[64];
     USHORT i,keyno,klen,maxofa;

     if (btvu(15,statInfo,kbf,0,sizeof(struct statbf)) != 0) {
          (*dfaErrPtr)("STAT");
     }
#ifdef GCWINNT
     cvtDataIP(statInfo,sizeof(struct statbf),sizeof(struct statbf),
               statbfFDA,CVTDFA,CVTSERVER,CHAN_NUL);
#endif // GCWINNT
     maxofa=0;
     for (i=0,keyno=0 ; keyno < statInfo->fs.numofx ; i++,keyno++) {
          klen=statInfo->ks[i].keylen;
          while (statInfo->ks[i].flags&ANOSEG) {
               klen+=statInfo->ks[++i].keylen;
          }
          dfa->keylns[keyno]=klen;
          if (klen > maxofa) {
               maxofa=klen;
          }
     }
     return(maxofa+1);
}


#ifdef GCWINNT

#define MAXKEYSEG  16              /* max number of segments per key       */

static USHORT                      /*   returns largest unpacked key size  */
buildFDA(                          /* build FDA struct for each key        */
struct statbf *statInfo,           /*   statistics for current file        */
USHORT keyLength[],                /*   packed key sizes                   */
struct flddef *fdaList[],          /*   FDA list for current file          */
USHORT unpackKeySiz[])             /*   calculated unpacked key size       */
{
     struct flddef flddef[MAXKEYSEG];
     USHORT numofkeys,key,seg;
     USHORT fldidx;
     USHORT curKeySiz,maxKeySiz;

     numofkeys=statInfo->fs.numofx;
     maxKeySiz=0;
     if (numofkeys > SEGMAX) {
          catastro("buildFDA: too many keys!");
     }
     for (key=seg=0 ; key < numofkeys ; key++) {
                          /* we are not going to use first entry of flddef */
          fldidx=0;
          flddef[0].type=CVTFLD_END;
          do {
               if (fldidx > MAXKEYSEG-2) {
                    catastro("buildFDA: too many key segments!");
               }
               curKeySiz=getFDAfld(statInfo->ks[seg].extype,
                                   statInfo->ks[seg].keylen,flddef,&fldidx);
          } while (statInfo->ks[seg++].flags&ANOSEG);
          flddef[++fldidx].type=CVTFLD_END;
          if ((INT)curKeySiz > keyLength[key]) {
               fdaList[key]=alcmem(sizeof(struct flddef)*fldidx);
               memmove(fdaList[key],&flddef[1],sizeof(struct flddef)*fldidx);
          }
          else {
               fdaList[key]=NULL;
          }
          unpackKeySiz[key]=curKeySiz;
          if (curKeySiz > maxKeySiz) {
               maxKeySiz=curKeySiz;
          }
     }
     return(maxKeySiz+1);
}

static USHORT                      /*   rets current offset within key buf */
getFDAfld(                         /* creates FDA entry for single key fld */
USHORT keytype,                    /*   type of key to create entry for    */
USHORT keylen,                     /*   length of the key                  */
struct flddef *flddef,             /*   FDA to be filled in                */
USHORT *fldidx)                    /*   index to FDA array                 */
{
     static USHORT offset;

     if (flddef[*fldidx].type == CVTFLD_END) {
          offset=0;
     }
     switch (keytype) {
     case EXTYPE_INT:
     case EXTYPE_UINT:
          if (keylen == 2) {
               if (flddef[*fldidx].type == CVTFLD_SHORT) {
                    flddef[*fldidx].nelem++;
               }
               else {
                    if (offset%keylen != 0) {
                         offset+=(USHORT)(keylen-offset%keylen);/* add padding*/
                    }
                    ++(*fldidx);
                    flddef[*fldidx].type=CVTFLD_SHORT;
                    flddef[*fldidx].nelem=1;
                    flddef[*fldidx].offset=offset;
                    flddef[*fldidx].substruct=NULL;
               }
               break;
          }
          else if (keylen == 4) {
               if (flddef[*fldidx].type == CVTFLD_LONG) {
                    flddef[*fldidx].nelem++;
               }
               else {
                    if (offset%keylen != 0) {
                         offset+=(USHORT)(keylen-offset%keylen);/* add padding*/
                    }
                    ++(*fldidx);
                    flddef[*fldidx].type=CVTFLD_LONG;
                    flddef[*fldidx].nelem=1;
                    flddef[*fldidx].offset=offset;
                    flddef[*fldidx].substruct=NULL;
               }
               break;
          }
     default:
          if (flddef[*fldidx].type == CVTFLD_CHAR) {
               flddef[*fldidx].nelem+=keylen;
          }
          else {
               ++(*fldidx);
               flddef[*fldidx].type=CVTFLD_CHAR;
               flddef[*fldidx].nelem=keylen;
               flddef[*fldidx].offset=offset;
               flddef[*fldidx].substruct=NULL;
          }
          break;
     }
     offset+=keylen;
     return(offset);
}

#endif // GCWINNT
