/***************************************************************************
 *                                                                         *
 *   GMELOC.C                                                              *
 *                                                                         *
 *   Copyright (c) 1994-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   This is the Galacticomm Messaging Engine local message base manager.  *
 *                                                                         *
 *                                            - J. Alvrus   6/5/94         *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "gcspsrv.h"
#include "galme.h"
#include "gme.h"
#include "gmeutl.h"
#include "gmeloc.h"
#include "threadu.h"

#define FILREV "$Revision: 34 $"

#define EMLADIR  "email"           /* E-mail attachment directory          */
#define GMECIF   "galcupi.sav"     /* file name for GME cleanup info file  */
#define FORDEFN  "galfdef2.dat"    /* forum definition file name           */
#define VIRFDFNM "galfmsg2"        /* forum data file template name        */
#define QSCFNM   "galqsc2.dat"     /* user quickscan file name             */
#define QIKFNM   "galquik2.dat"    /* !QUICK list file name                */
#define GRPFNM   "galfgrp2.dat"    /* forum group info file name           */

#define MINMSGREC (fldoff(msgdsk,info)+sizeof(struct fmidky))
                                   /* minimum message record size          */

/* GME State Codes */
#define AVLFID   SRCHING+1         /* gme1crf() state(s)                   */
#define CRFDF    SRCHING+2
#define CRFAD    SRCHING+3
#define INSFD    SRCHING+4
#define ATTACH   SRCHING+5         /* gme1wnm(), gme1wap() state(s)        */
#define HDLHOOK  SRCHING+6
#define AVLGRID  SRCHING+7         /* gme1crg() state(s)                   */

#define MAXFID  65535U             /* max allowable forum ID               */
#define MXTHRC  64                 /* maximum number of threads to cache   */

struct ffblk gmefb;               /* for finding files (one-shot only)    */

VOID *tmpbuf=NULL,                 /* temporary buffer, one-cycle use only */
     *usrqs=NULL;                  /* head of aclblok'd quickscan array    */

struct qscfg *utlqsc;              /* GME utility quickscan buffer         */
CHAR *utlapi;                      /* GME utility app-defined info buffer  */

CHAR *emldp=NULL,                  /* E-mail data file path and file name  */
      killstr[]={"\1"};            /* for finding deleted forums           */
#define KILLCHR '\1'               /* for marking forums as deleted        */
#define NOADDRC '\xFF'             /* placeholder for zero-length address  */

SHORT  emllif;                     /* lifetime of an E-Mail message (days) */

#ifdef STARTER
#else
INT maxfdf;                        /* max forum data files allowed by Sysop*/
#endif // STARTER

INT    cfmsgs,                     /* frequency (cups) for old msg cleanup */
       bgmdly,                     /* delay factor for background cleanup  */
       cffors;                     /* frequency (cups) for del'd Forum "   */

GBOOL bgmcup,                      /* do old message cleanup in background?*/
      bgcipg=FALSE;                /* is background cleanup in progress?   */

struct gmestor {                   /* stored cleanup info (in GMECIF)      */
     LONG highmsg;                 /*   current high message number        */
     USHORT ncsmsg;                /*   # of mcus since cleaning old msgs  */
     USHORT ncsfor;                /*   # of mcus since "  deleted Forums  */
     USHORT bgmfid;                /*   current Forum-ID in bground msg cup*/
     LONG bgmmid;                  /*   current msgno in bground msg cup   */
} cupinf;                          /* current info (stored at shutdown)    */

LONG _highmsg=0L;                  /* highest message ID on system         */

INT maxfgrp;                       /* maximum number of forums per group   */
INT grpnmem;                       /* # groups to keep in memory at once   */
VOID *grparr;                      /* group info array header              */
ULONG *grplru;                     /* group info least-recently-used values*/
struct forgrp *utlgrp;             /* temporary group info buffer          */

struct fmidky fmidky;              /* composite keys for one-cycle use     */
struct ftidky ftidky;
struct etidky etidky;
struct umidky umidky;
struct pnamky pnamky;

struct fordef *curfdp=NULL;        /* pointer to current forum definition  */

INT nforums=0;                     /* number of forums                     */
struct fordef **defarr=NULL;       /* array of pointers to forum defs      */
struct fidxrf {                    /* Forum-ID cross reference structure   */
     USHORT fid;                   /*   forum ID                           */
     INT idx;                      /*   index in defarr[]                  */
} *fidxrf=NULL;                    /* array of Forum-ID cross references   */

INT nfdf=0;                        /* number of forum data files           */
DFAFILE **fdfarr=NULL;             /* forum data file array                */

DFAFILE *emlbb=NULL,               /* E-mail message data file             */
        *fordefbb=NULL,            /* forum definition data file           */
        *curfbb=NULL,              /* current forum data file              */
        *qscbb=NULL,               /* user quickscan data file             */
        *qikbb=NULL,               /* !QUICK list file                     */
        *grpbb=NULL;               /* group info data file                 */

#define emdptr ((struct msgdsk *)(emlbb->data))
                                   /* E-mail dfafile message data          */
#define fddptr ((struct fordsk *)(fordefbb->data))
                                   /* forum def dfafile data               */
#define cfdptr ((struct msgdsk *)(curfbb->data))
                                   /* cur forum dfafile message data       */
#define grpdptr ((struct forgrp *)(grpbb->data))
                                   /* group info btvfile data              */

LONG opneml(VOID);
VOID clseml(VOID);
LONG opnfor(VOID);
VOID clsfor(GBOOL updfors);
VOID loadfor(VOID);
struct fordef *addafor(const struct fordsk *newdef,const CHAR *echoes);
VOID fixseq(VOID);
GBOOL newer(USHORT first,USHORT second);
VOID dsk2def(const struct fordsk *dsk,struct fordef *def);
CHAR *setfad(const CHAR *path);
VOID setcurf(USHORT fid);
VOID setcfdf(VOID);
VOID insxrf(USHORT fid,INT idx);
VOID resortf(INT oldidx);
INT nearnmi(INT *cond,const CHAR *name);
INT xrfidx(USHORT fid);
VOID wtouid(const CHAR *userid);
struct fordef *addfarr(const struct fordsk *newdef,const CHAR *echoes);
VOID addseq(struct fordef *newdef);
VOID def2dsk(const struct fordef *def,struct fordsk *dsk);
VOID msg2dsk(const struct message *msg,struct msgdsk *dsk);
VOID dsk2msg(const struct msgdsk *dsk,struct message *msg);
VOID gettxt(const struct msgdsk *dskbuf,CHAR *text,CHAR *appinf,UINT reclen);
VOID foradd(const struct message *msg);
VOID lofadd(INT flags);
VOID fordel(const struct msgdsk *msg);
VOID insnmsg(struct msgdsk *wrtbuf,const struct message *msg,const CHAR *text,const CHAR *appinf);
VOID insmptr(struct msgdsk *wrtbuf,const struct message *msg,const struct fmidky *mptr);
VOID updamsg(struct msgdsk *wrtbuf,const struct message *msg,const CHAR *text,const CHAR *appinf);
VOID updmptr(struct msgdsk *wrtbuf,const struct message *msg,const struct fmidky *mptr);
UINT fillbuf(struct msgdsk *datbuf,const struct message *msg,const CHAR *text,const CHAR *appinf);
CHAR *textptr(const struct msgdsk *dsk);
INT nvmsiz(const struct msgdsk *dsk);
USHORT hifid(VOID);
USHORT nxtafid(USHORT fid);
VOID recount(VOID);
GBOOL fndbgmf(VOID);
VOID qikdel(VOID);
USHORT getexpd(SHORT daysago);
INT nxtqki(INT pos,const struct qikdat *qikbuf);
INT qiklen(const struct qikdat *qikbuf);
INT lastqk(const struct qikdat *qikbuf);
VOID updpri(VOID *pWork,struct message *msg);
VOID whackit(const struct msgdsk *msg);
VOID lowhack(const struct msgdsk *msg);
GBOOL touchk(VOID *pWork,const struct msgdsk *msg);
GBOOL fruchk(VOID *pWork,const struct msgdsk *msg);
GBOOL ethchk(VOID *pWork,const struct msgdsk *msg);
GBOOL forchk(VOID *pWork,const struct msgdsk *msg);
GBOOL fthchk(VOID *pWork,const struct msgdsk *msg);
GBOOL acqmsg(struct msgdsk *msgbuf,VOID *keybuf,INT keynum,INT direct,
            VOID *pWork,
            GBOOL (*keychk)(VOID *pWork,const struct msgdsk *msg));
GBOOL getbdy(const struct msgdsk *dsk,struct message *msg,CHAR *text);

VOID
iniloc(VOID)                       /* initialize GME local message base    */
{
     LONG emlhi,forhi;
     FILE *fp;

     inithu(MXTHRC);
     tmpbuf=alcmem(TMPBSZ);
     qscbb=dfaOpen(QSCFNM,MAXQSR,NULL);
     usrqs=alcblok(nterms,qssiz);
     utlqsc=(struct qscfg *)alcmem(max(MAXQSR,fldoff(fordsk,info)+MAXFDV));
     qikbb=dfaOpen(QIKFNM,MAXQKR,NULL);
     utlapi=alcmem(TXTLEN);
     cfmsgs=numopt(CFMSGS,1,365);
     cffors=numopt(CFFORS,1,365);
     bgmcup=ynopt(BGMCUP);
     bgmdly=numopt(BGMDLY,0,32767);
     maxfgrp=numopt(MAXFGRP,1,MAXGRPF);
     grpnmem=numopt(GRPNMEM,1,32767);
     grpbb=dfaOpen(GRPFNM,grprlen(maxfgrp)+1,NULL); /* allow for truncation */
     grparr=alcblok(grpnmem,grprlen(maxfgrp));
     grplru=(ULONG *)alczer(sizeof(ULONG)*grpnmem);
     utlgrp=(struct forgrp *)alcmem(grprlen(maxfgrp));
     setmem(&cupinf,sizeof(struct gmestor),0);
     fp=fopen(GMECIF,FOPRB);
     if (fp != NULL) {
          fread(&cupinf,sizeof(struct gmestor),1,fp);
          _highmsg=cupinf.highmsg;
          fclose(fp);
     }
     emlhi=opneml();
     forhi=opnfor();
     if (emlhi > forhi) {
          forhi=emlhi;
     }
     if (forhi == 0L) {
          forhi=1L;
     }
     if (forhi > _highmsg) {
          _highmsg=forhi;
     }
}

VOID
clsloc(                            /* close GME local message base         */
GBOOL updfors)                     /*   update all the Forums on disk?     */
{
     FILE *fp;

     fp=fopen(GMECIF,FOPWB);
     if (fp != NULL) {
          cupinf.highmsg=_highmsg;
          fwrite(&cupinf,sizeof(struct gmestor),1,fp);
          fclose(fp);
     }
     else if (!gmeoffl()) {
          shocst("GME CLEANUP INFO ERROR",
                 "unable to write cleanup info file: \"%s\"",GMECIF);
     }
     clseml();
     clsfor(updfors);
     if (qscbb != NULL) {
          dfaClose(qscbb);
          qscbb=NULL;
     }
     if (qikbb != NULL) {
          dfaClose(qikbb);
          qikbb=NULL;
     }
     clsthu();
}

LONG                               /*   returns highest message number     */
opneml(VOID)                       /* initialize E-mail data file          */
{
     emllif=numopt(EMLLIF,-1,32767);
     emldp=stgopt(EMLDPTH2);
     emlbb=dfaOpen(emldp,_reclen,NULL);
     if (!fmdir(EMLADIR)) {
          catastro("Unable to find or create E-mail attachment directory:\n"
                   "\"%s\"",fnmcse(EMLADIR));
     }
#ifdef DEBUG
     if (fnd1st(&gmefb,spr("%s"SLS"*.tmp",EMLADIR),0)) {
          do {
               unlink(spr("%s"SLS"%s",EMLADIR,gmefb.ff_name));
          } while (fndnxt(&gmefb));
     }
#endif
     if (dfaQueryHI(EMIDKY)) {
          return(*((LONG *)emlbb->key));
     }
     else {
          return(0L);
     }
}

VOID
clseml(VOID)                       /* close E-mail data file               */
{
     if (emlbb != NULL) {
          dfaClose(emlbb);
          emlbb=NULL;
     }
}

LONG                               /*   returns highest message number     */
opnfor(VOID)                       /* initialize forum data files & arrays */
{
     INT i;
     LONG high=0L;
     LONG tmpmsgid;
     struct fmidky *tmpfmidky;

#ifdef STARTER
     defarr=(struct fordef **)alczer(MAXSFORS*sizeof(struct fordef *));
     fidxrf=(struct fidxrf *)alczer(MAXSFORS*sizeof(struct fidxrf));
#else
     maxfdf=gnumdb(GMEMDF);
#endif // STARTER
     fdfarr=(DFAFILE **)alczer(maxfdf*sizeof(DFAFILE *));
     fordefbb=dfaOpen(FORDEFN,fldoff(fordsk,info)+MAXFDV,NULL);
     loadfor();
     for (i=0 ; i < nforums ; ++i) {
          curfdp=defarr[i];
          setcfdf();
          fmidky.forum=curfdp->forum;
          fmidky.msgid=LASTM;
          if (dfaQueryLE(&fmidky,FFMKY)) {
               tmpfmidky=(struct fmidky*)curfbb->key;
               if (tmpfmidky->forum == fmidky.forum) {
                    tmpmsgid=tmpfmidky->msgid;
                    high=max(high,tmpmsgid);
               }
          }
     }
     return(high);
}

VOID
clsfor(                            /* close forum data files               */
GBOOL updfors)                     /*   update all the Forums on disk?     */
{
     INT i,k;

     dfaSetBlk(fordefbb);
     if (updfors) {
          for (i=0 ; i < nforums ; ++i) {
               if (dfaAcqEQ(NULL,&defarr[i]->forum,DIDKY)) {
                    movmem(fddptr,utlqsc,fldoff(fordsk,info));
                    def2dsk(defarr[i],(struct fordsk *)utlqsc);
                    if (memcmp(fddptr,utlqsc,fldoff(fordsk,info)) != 0) {
                         movmem(utlqsc,fddptr,fldoff(fordsk,info));
                         dfaUpdateV(NULL,dfaLastLen());
                    }
               }
          }
     }
     if (fordefbb != NULL) {
          dfaClose(fordefbb);
          fordefbb=NULL;
     }
     for (k=0 ; k < nfdf ; ++k) {
          if (fdfarr[k] != NULL) {
               dfaClose(fdfarr[k]);
               fdfarr[k]=NULL;
          }
     }
}

VOID
loadfor(VOID)                      /* load forum definitions               */
{
     if (dfaAcqGT(NULL," ",DNMKY)) {
          do {
               if (!fexist(fddptr->datfil)) {
                    if (!creatfdf(fddptr->datfil)) {
                         catastro("Unable to create forum data file: %s",
                                  fnmcse(fddptr->datfil));
                    }
               }
               curfdp=addafor(fddptr,fddptr->info);
               dfaSetBlk(fordefbb);
          } while (dfaQueryNX());
     }
     else if (!gmeoffl()) {
          catastro("No Forums defined!");
     }
     fixseq();
}

struct fordef *                    /*   returns pointer to new in-mem def  */
addafor(                           /* add a forum to in-memory arrays      */
const struct fordsk *newdef,       /*   pointer to forum on-disk structure */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{
     struct fordef *memdef;
#ifdef DEBUG
     INT tmpctr;
     adr_t *tmpecho;
#endif

     ASSERT(newdef != NULL);
     ASSERT(newdef->forum != EMLID);
#ifdef STARTER
     if (nforums == MAXSFORS) {
          catastro("Worlgroup Starter permits only %d forums.",MAXSFORS);
     }
#else
     if (!alcpar((VOID ***)&defarr,nforums,BIGBLK)
      || !alcarr((VOID **)&fidxrf,sizeof(struct fidxrf),nforums,BIGBLK)) {
          catastro("Not enough memory to load Forums");
     }
#endif // STARTER
     memdef=(struct fordef *)alcmem(sizeof(struct fordef));
     defarr[nforums]=memdef;
     insxrf(newdef->forum,nforums);
     ++nforums;
     if (nfdf == maxfdf && !fdfisopn(newdef->datfil)) {
          catastro("Too many forum data files required. "
             "Increase \"Dynamic Btrieve files:\" in %s",
             fnmcse("galme.mdf"));
     }
     memdef->dfnum=opnfdf(newdef->datfil);
     dsk2def(newdef,memdef);
     if (!fmdir(memdef->attpath)) {
          catastro("Unable to find or create attachment directory "
             "\"%s\" for the %s forum",
                   memdef->attpath,memdef->name);
     }
#ifdef DEBUG
     if (fnd1st(&gmefb,spr("%s"SLS"*.tmp",memdef->attpath),0)) {
       do {
            unlink(spr("%s"SLS"%s",memdef->attpath,gmefb.ff_name));
       } while (fndnxt(&gmefb));
     }
#endif
     if (memdef->necho > 0) {
          ASSERT(memdef->necho <= MAXECHO);
          memdef->echoes=alcmem(MAXADR*memdef->necho);
          movmem(echoes,memdef->echoes,MAXADR*memdef->necho);
#ifdef DEBUG
       tmpecho=(adr_t *)memdef->echoes;
       for (tmpctr=0 ; tmpctr < memdef->necho ; ++tmpctr) {
            ASSERT(strlen(tmpecho[tmpctr]) < MAXADR);
       }
#endif
     }
     else {
          memdef->echoes=NULL;
     }
     return(memdef);
}

VOID
fixseq(VOID)                       /* resolve any sequence ID conflicts    */
{
     UINT seq,i,j,newest;
     GBOOL fix;

     for (seq=0 ; seq < nforums ; ++seq) {
          do {
               for (i=0 ; i < nforums ; ++i) {
                    if (defarr[i]->seqid == seq) {
                         break;
                    }
               }
               if (i == nforums) {
                    for (j=0 ; j < nforums ; ++j) {
                         if (defarr[j]->seqid > seq) {
                              --defarr[j]->seqid;
                         }
                    }
               }
          } while (i == nforums);
          newest=i;
          fix=FALSE;
          for (j=0 ; j < nforums ; ++j) {
               if (j != i && defarr[j]->seqid == seq) {
                    fix=TRUE;
                    if (newer(defarr[j]->forum,defarr[newest]->forum)) {
                         newest=j;
                    }
               }
          }
          if (fix) {
               for (j=0 ; j < nforums ; ++j) {
                    if (j != newest && defarr[j]->seqid >= seq) {
                         ++defarr[j]->seqid;
                    }
               }
          }
     }
}

GBOOL
newer(                             /* is first forum newer than second?    */
USHORT first,                      /*   first forum                        */
USHORT second)                     /*   second forum                       */
{
     USHORT date1,time1,date2,time2;

     dfaSetBlk(fordefbb);
     dfaGetEQ(NULL,&first,DIDKY);
     date1=fddptr->crdate;
     time1=fddptr->crtime;
     dfaGetEQ(NULL,&second,DIDKY);
     date2=fddptr->crdate;
     time2=fddptr->crtime;
     dfaRstBlk();
     if (date1 > date2) {
          return(TRUE);
     }
     if (date1 < date2) {
          return(FALSE);
     }
     return(time1 > time2);
}

LONG
himsgid(VOID)                      /* get current highest message ID       */
{
     return(_highmsg);
}

INT
numforums(VOID)                    /* get number of forums                 */
{
     return(nforums);
}

INT                                /*   returns index of data file in array*/
opnfdf(                            /* open forum data file & add to array  */
const CHAR *name)                  /*   path and file name of data file    */
{
     INT i;

     ASSERT(name != NULL);
     ASSERT(strlen(name) < GCMAXPTH);
     for (i=0 ; i < nfdf ; ++i) {
          ASSERT(fdfarr != NULL);
          ASSERT(fdfarr[i] != NULL);
          if (sameas(fdfarr[i]->filnam,name)) {
               return(i);
          }
     }
     ASSERT(nfdf < maxfdf);
     fdfarr[nfdf]=dfaOpen(name,_reclen,NULL);
     return(nfdf++);
}

INT                                /*   returns standard GME status codes  */
recfdf(                            /* recommend a forum data file          */
VOID *pWork,                       /*   GME work space (provided by caller)*/
CHAR *recname)                     /*   buffer for recommended name        */
{
     INT i;
     LONG nmsgs;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(recname != NULL);
     switch (work->state1) {
     case START:
          work->counter=0;
          work->fpos=-1;
          work->fdfnum=0;
          work->state1=SRCHING;
     case SRCHING:
          for (i=0 ; work->counter < nfdf && i < 2*MAXBTV ;
               ++i,++work->counter) {
               dfaSetBlk(fdfarr[work->counter]);
               nmsgs=dfaCountRec();
               if (work->fpos == -1 || nmsgs < work->fpos) {
                    work->fpos=nmsgs;
                    work->fdfnum=work->counter;
               }
          }
          break;
     default:
          state_error(1,"recfdf()",work->state1);
     }
     if (work->counter == nfdf) {
          stlcpy(recname,fdfarr[work->fdfnum]->filnam,GCMAXPTH);
          work->state1=START;
          return(GMEOK);
     }
     return(GMEAGAIN);
}

GBOOL
fdfisopn(                          /* is forum data file already open?     */
const CHAR *name)                  /*   path+name of forum data file       */
{
     INT i;

     ASSERT(name != NULL);
     ASSERT(strlen(name) < GCMAXPTH);
     for (i=0 ; i < nfdf ; ++i) {
          ASSERT(fdfarr != NULL);
          ASSERT(fdfarr[i] != NULL);
          if (sameas(normspec((CHAR *)tmpbuf,fdfarr[i]->filnam),
                     normspec((CHAR *)tmpbuf+GCMAXPTH,name))) {
               return(TRUE);
          }
     }
     return(FALSE);
}

GBOOL                              /*   return TRUE if file created        */
creatfdf(                          /* create a new forum data file         */
const CHAR *name)                  /*   name of new forum data file        */
{
     CHAR *pname;
     static CHAR fname[GCMAXPTH];

     ASSERT(name != NULL);
     stlcpy(fname,name,sizeof(fname));
     pname=strrchr(fname,'.');
     /* .DAT ext is enforced in gme1crf() for displaying etc.              */
     if (pname == NULL || !sameas(pname,".DAT")) {
          return(FALSE);
     }
     *pname='\0';
     return(dfaVirgin(VIRFDFNM,fname));
}

USHORT                             /*   returns packed date                */
getexpd(                           /* get message expiration date          */
SHORT daysago)                     /*   to this many days ago              */
{
     SHORT coftod;

     if (daysago > (coftod=(SHORT)cofdat(today()))) {
          daysago=coftod;
     }
     return(datofc(coftod-daysago));
}

VOID
dsk2def(                           /* copies forum definition from         */
const struct fordsk *dsk,          /*   on-disk format to                  */
struct fordef *def)                /*   in-memory format                   */
{
     ASSERT(dsk != NULL && def != NULL);
     def->forum=dsk->forum;
     ASSERT(strlen(dsk->name) < FORNSZ);
     stlcpy(def->name,dsk->name,FORNSZ);
     ASSERT(strlen(dsk->topic) < TPCSIZ);
     stlcpy(def->topic,dsk->topic,TPCSIZ);
     ASSERT(strlen(dsk->forop) < UIDSIZ);
     stlcpy(def->forop,dsk->forop,UIDSIZ);
     ASSERT(strlen(dsk->attpath) < GCMAXPTH);
     def->attpath=NULL;
     def->attpath=setfad(dsk->attpath);
     def->nthrs=dsk->nthrs;
     def->nmsgs=dsk->nmsgs;
     def->nfiles=dsk->nfiles;
     def->nw4app=dsk->nw4app;
     ASSERT(strlen(dsk->forlok) < KEYSIZ);
     stlcpy(def->forlok,dsk->forlok,KEYSIZ);
     def->dfnpv=(CHAR)dsk->dfnpv;
     def->dfprv=(CHAR)dsk->dfprv;
     def->mxnpv=(CHAR)dsk->mxnpv;
     def->msglif=dsk->msglif;
     def->chgmsg=dsk->chgmsg;
     def->chgrdm=dsk->chgrdm;
     def->chgatt=dsk->chgatt;
     def->chgadl=dsk->chgadl;
     def->chgupk=dsk->chgupk;
     def->chgdpk=dsk->chgdpk;
     def->ccr=dsk->ccr;
     def->pfnlvl=(CHAR)dsk->pfnlvl;
     def->crdate=dsk->crdate;
     def->crtime=dsk->crtime;
     def->seqid=dsk->seqid;
     def->necho=dsk->necho;
}

CHAR *                             /*   pointer to att path string         */
setfad(                            /* set forum attachment dir pointer     */
const CHAR *path)                  /*   path buffer to set to              */
{
     INT i;
     CHAR old[GCMAXPTH],*curr;

     ASSERT(path != NULL);
     curr=(CHAR *)tmpbuf;
     normspec(curr,path);
     for (i=0 ; i < nforums ; ++i) {
          if (defarr[i]->attpath != NULL
           && sameas(curr,normspec(old,defarr[i]->attpath))) {
               return(defarr[i]->attpath);
          }
     }
     return(strdup(path));
}

VOID
setcurf(                           /* set current forum context            */
USHORT fid)                        /*   given forum ID                     */
{
     curfdp=getdef(fid);
     setcfdf();
}

VOID
setcfdf(VOID)                      /* set current forum data file pointer  */
{
     ASSERT(fdfarr != NULL);
     ASSERT(curfdp->dfnum >= 0 && curfdp->dfnum < nfdf);
     curfbb=fdfarr[curfdp->dfnum];
     ASSERT(curfbb != NULL);
     dfaSetBlk(curfbb);
}

VOID
insxrf(                            /* insert forum ID cross-ref in array   */
USHORT fid,                        /*   forum ID                           */
INT idx)                           /*   index in defarr                    */
{                                  /*   (assumes nforums not yet inc'd)    */
     INT i,j;

     ASSERT(fid != EMLID);
     ASSERT(fidxrf != NULL);
     for (i=0 ; i < nforums ; ++i) {
          if (fidxrf[i].fid > fid) {
               break;
          }
     }
     movmem(&fidxrf[i],&fidxrf[i+1],sizeof(struct fidxrf)*(nforums-i));
     fidxrf[i].fid=fid;
     fidxrf[i].idx=idx;
     for (j=0 ; j <= nforums ; ++j) {
          if (j != i && fidxrf[j].idx >= idx) {
               ++fidxrf[j].idx;
          }
     }
}

VOID
resortf(                           /* resort forum arrays after name change*/
INT oldidx)                        /*   current index of forum that changed*/
{
     INT i,newidx;
     GBOOL gtprv,ltnxt;
     struct fordef *tmpdef;

     gtprv=(oldidx == 0
         || stricmp(defarr[oldidx]->name,defarr[oldidx-1]->name) > 0);
     ltnxt=(oldidx == nforums-1
         || stricmp(defarr[oldidx]->name,defarr[oldidx+1]->name) < 0);
     if (gtprv && ltnxt) {
          return;
     }
     newidx=oldidx;
     if (gtprv) {                  /* i.e. greater-than next (move up)     */
          do {
               ++newidx;
          } while (newidx < nforums-1
              && stricmp(defarr[oldidx]->name,defarr[newidx+1]->name) > 0);
     }
     else {                        /* i.e. less-than previous (move down)  */
          do {
               --newidx;
          } while (newidx > 0
              && stricmp(defarr[oldidx]->name,defarr[newidx-1]->name) < 0);
     }
     tmpdef=defarr[oldidx];
     movmem(&defarr[oldidx+1],&defarr[oldidx],
            sizeof(struct fordef *)*(nforums-oldidx-1));
     movmem(&defarr[newidx],&defarr[newidx+1],
            sizeof(struct fordef *)*(nforums-newidx-1));
     defarr[newidx]=tmpdef;
     for (i=0 ; i < nforums ; ++i) {
          ASSERT(xrfidx(defarr[i]->forum) != NOIDX);
          fidxrf[xrfidx(defarr[i]->forum)].idx=i;
     }
}

INT                                /*   (returns NOIDX if not found)       */
fnmidx(                            /* get index of named forum in def array*/
const CHAR *name)                  /*   forum name                         */
{
     INT i,cond;

     ASSERT(name != NULL);
     ASSERT(defarr != NULL);
     i=nearnmi(&cond,name);
     if (cond == 0) {
          return(i);
     }
     return(NOIDX);
}

INT                                /*   (returns NOIDX if not found)       */
prvfnmi(                           /* get index of prev forum in def array */
const CHAR *name)                  /*   before this forum name             */
{
     INT i,cond;

     ASSERT(name != NULL);
     ASSERT(defarr != NULL);
     i=nearnmi(&cond,name);
     if (cond < 0) {
          return(i);
     }
     if (cond >= 0 && i > 0) {
          return(i-1);
     }
     return(NOIDX);
}

INT                                /*   (returns NOIDX if not found)       */
nxtfnmi(                           /* get index of next forum in def array */
const CHAR *name)                  /*   after this forum name              */
{
     INT i,cond;

     ASSERT(name != NULL);
     ASSERT(defarr != NULL);
     i=nearnmi(&cond,name);
     if (cond > 0) {
          return(i);
     }
     if (cond <= 0 && i < nforums-1) {
          return(i+1);
     }
     return(NOIDX);
}

INT
nearnmi(                           /* get index of "nearest" forum         */
INT *cond,                         /*   buffer for result of last stricmp()*/
const CHAR *name)                  /*   name to search for                 */
{
     INT lo,mid,hi,comp;

     ASSERT(name != NULL);
     lo=0;
     hi=nforums-1;
     while (lo <= hi) {
          mid=lo+(hi-lo)/2;
          if ((comp=stricmp(defarr[mid]->name,name)) < 0) {
               lo=mid+1;
          }
          else if (comp > 0) {
               hi=mid-1;
          }
          else {
               break;
          }
     }
     *cond=comp;
     return(mid);
}

INT                                /*   (returns NOIDX if not found)       */
fididx(                            /* get index of forum ID in def array   */
USHORT fid)                        /*   forum ID                           */
{
     INT i;

     ASSERT(fid != EMLID);
     ASSERT(defarr != NULL);
     i=xrfidx(fid);
     if (i != NOIDX) {
          return(fidxrf[i].idx);
     }
     return(NOIDX);
}

INT                                /*   (returns NOIDX if not found)       */
xrfidx(                            /* get index of forum ID in xref array  */
USHORT fid)                        /*   forum ID                           */
{
     INT i;
     UINT start;

     ASSERT(fid != EMLID);
     ASSERT(defarr != NULL);
     start=(UINT)(fid-1);
     if (start >= nforums) {
          start=nforums-1;
     }
     for (i=start ; i >= 0 ; --i) {
          if (fidxrf[i].fid == fid) {
               return(i);
          }
     }
     return(NOIDX);
}

USHORT                             /*   returns EMLID if doesn't exist     */
getfid(                            /* get forum ID                         */
const CHAR *name)                  /*   given forum name                   */
{
     INT i;

     ASSERT(defarr != NULL);
     i=fnmidx(name);
     if (i == NOIDX) {
          return(EMLID);
     }
     return(defarr[i]->forum);
}

USHORT
hifid(VOID)                        /* get highest forum ID in use          */
{
     USHORT hi;
     INT i;

     ASSERT(defarr != NULL);
     for (i=0,hi=0 ; i < nforums ; ++i) {
          ASSERT(defarr[i] != NULL);
          if (defarr[i]->forum > hi) {
               hi=defarr[i]->forum;
          }
     }
     return(hi);
}

USHORT
nxtafid(                           /* get next available forum ID          */
USHORT fid)                        /*   starting at forum ID               */
{
     ASSERT(fid != 0);
     do {
          ++fid;
     } while (fid != 0 && fidxst(fid));
     return(fid);
}

struct fordef *                    /*   returns NULL if doesn't exist      */
getdef(                            /* get pointer to forum definition      */
USHORT fid)                        /*   given forum ID                     */
{
     INT i;

     ASSERT(fid != EMLID);
     if ((i=fididx(fid)) != NOIDX) {
          return(defarr[i]);
     }
     return(NULL);
}

struct fordef *                    /*   returns NULL if doesn't exist      */
idxdef(                            /* get pointer to forum definition      */
INT idx)                           /*   given def array index              */
{
     if (idx < nforums) {
          return(defarr[idx]);
     }
     return(NULL);
}

struct fordef *                    /*   returns NULL if doesn't exist      */
fiddef(                            /* get pointer to forum definition      */
INT idx)                           /*   given xrf array index              */
{
     if (idx >= 0 && idx < nforums) {
          return(defarr[fidxrf[idx].idx]);
     }
     return(NULL);
}

struct fordef *                    /*   returns NULL if doesn't exist      */
seqdef(                            /* get pointer to forum definition      */
USHORT seqid)                      /*   given sequence ID                  */
{
     INT i;

     for (i=0 ; i < nforums ; ++i) {
          if (defarr[i]->seqid == seqid) {
               return(defarr[i]);
          }
     }
     return(NULL);
}

VOID
gmeclean(                          /* do full GME cleanup                  */
GBOOL crashed)                     /*   have we crashed some time today?   */
{
     bgcipg=FALSE;
     if (++cupinf.ncsfor >= (USHORT)cffors) {
          cleandlf();
          cupinf.ncsfor=0;
     }
     if (crashed || ++cupinf.ncsmsg >= (USHORT)cfmsgs) {
          if (crashed) {
               recount();
          }
          else {
               bgmdly=0;
               while (pumpcup()) {
               }
          }
          cupinf.bgmfid=EMLID;
          cupinf.bgmmid=FIRSTM;
          cupinf.ncsmsg=0;
          qikdel();
     }
}

VOID
recount(VOID)                      /* do complete recount message cleanup  */
{
     SHORT forlif;
     USHORT expdate;
     INT i,thrcnt;
     UINT reclen;
     GBOOL moinfo,notify;

     if (!gmeoffl()) {
          shocst("E-MAIL/FORUMS: RE-COUNTING",
                 "Re-counting all messages due to a crash today.");
     }
     notify=!gmeoffl() && nexthook(GMEHOOK_NOT_DELMSG,NOIDX) != NOIDX;
     sv.emlopn=0;
     if (emllif >= 0) {
          expdate=getexpd(emllif);
     }
     dfaSetBlk(emlbb);
     if (dfaAcqLO(NULL,EMIDKY)) {
          do {
               if (emllif >= 0 && emdptr->crdate < expdate) {
                    if (notify) {
                         dsk2msg(emdptr,utlmsg);
                         gettxt(emdptr,utltxt,utlapi,dfaLastLen());
                         notdlm(DELMSG_CLNUP,utlwork,utlmsg,utltxt);
                    }
                    lowhack(emdptr);
               }
               else {
                    ++sv.emlopn;
               }
          } while (dfaAcqGT(NULL,&emdptr->msgid,EMIDKY));
     }
     sv.sigopn=0;
     for (i=0 ; i < nforums ; ++i) {
          setcurf(ftidky.forum=defarr[i]->forum);
          curfdp->nthrs=0;
          curfdp->nmsgs=0;
          curfdp->nfiles=0;
          curfdp->nw4app=0;
          if ((forlif=curfdp->msglif) >= 0) {
               expdate=getexpd(forlif);
          }
          ftidky.thrid=0L;
          ftidky.msgid=0L;
          moinfo=dfaAcqGE(NULL,&ftidky,FFTKY);
          reclen=dfaLastLen();
          while (moinfo && cfdptr->forum == ftidky.forum) {
               ftidky.thrid=cfdptr->thrid;
               thrcnt=0;
               do {
                    ftidky.msgid=cfdptr->msgid;
                    if (forlif >= 0 && cfdptr->crdate < expdate
                     && !(cfdptr->flags&EXEMPT)) {
                         if (notify) {
                              dsk2msg(cfdptr,utlmsg);
                              gettxt(cfdptr,utltxt,utlapi,reclen);
                              notdlm(DELMSG_CLNUP,utlwork,utlmsg,utltxt);
                         }
                         lowhack(cfdptr);
                    }
                    else {
                         lofadd((INT)cfdptr->flags);
                         ++sv.sigopn;
                         ++thrcnt;
                    }
                    moinfo=dfaAcqGT(NULL,&ftidky,FFTKY);
                    reclen=dfaLastLen();
               } while (moinfo && cfdptr->forum == ftidky.forum
                     && cfdptr->thrid == ftidky.thrid);
               if (thrcnt != 0) {
                    ++curfdp->nthrs;
               }
               setthrc(ftidky.forum,ftidky.thrid,thrcnt);
          }
     }
}

GBOOL
needbgc(VOID)                      /* do we need to do background cleanup? */
{
     return(bgmcup && cupinf.ncsmsg+1 >= cfmsgs && fndbgmf());
}

GBOOL                              /*   keep pumping?                      */
pumpcup(VOID)                      /* pump the normal cleanup process      */
{
     struct msgdsk *tmpdsk;
     VOID *key;
     INT keynum;
     SHORT msglif;
     UINT tmplen;
     static INT dlyctr=0;

     if (dlyctr++ < bgmdly) {
          return(TRUE);
     }
     dlyctr=0;
     if (!fndbgmf()) {
          return(FALSE);
     }
     if (cupinf.bgmfid == EMLID) {
          dfaSetBlk(emlbb);
          key=&cupinf.bgmmid;
          keynum=EMIDKY;
          tmpdsk=emdptr;
          msglif=emllif;
     }
     else {
          setcurf(cupinf.bgmfid);
          fmidky.forum=cupinf.bgmfid;
          fmidky.msgid=cupinf.bgmmid;
          key=&fmidky;
          keynum=FFMKY;
          tmpdsk=cfdptr;
          msglif=curfdp->msglif;
     }
     if (msglif >= 0 && dfaAcqGT(NULL,key,keynum)
      && tmpdsk->forum == cupinf.bgmfid && tmpdsk->crdate < getexpd(msglif)) {
          if (!(tmpdsk->flags&EXEMPT)) {
               tmplen=dfaLastLen();
               if (gmeoffl() || !bgcipg
                || !gencfl(utlwork,tmpdsk->forum,tmpdsk->msgid)) {
                    dsk2msg(tmpdsk,utlmsg);
                    gettxt(tmpdsk,utltxt,utlapi,tmplen);
                    notdlm(DELMSG_CLNUP,utlwork,utlmsg,utltxt);
                    whackit(tmpdsk);
               }
          }
          cupinf.bgmmid=tmpdsk->msgid;
     }
     else {
          cupinf.bgmfid++;
          cupinf.bgmmid=FIRSTM;
     }
     return(TRUE);
}

GBOOL                              /*   found a forid >= current one?      */
fndbgmf(VOID)                      /* find (and set) forid for bground cup */
{
     INT i;
     USHORT start;

     if (cupinf.bgmfid == EMLID || fidxst(cupinf.bgmfid)) {
          return(TRUE);
     }
     if (cupinf.bgmfid > fidxrf[nforums-1].fid) {
          return(FALSE);
     }
     start=cupinf.bgmfid-1;
     if (start >= nforums) {
          start=nforums-1;
     }
     for (i=start ; i > 0 ; --i) {
          if (fidxrf[i-1].fid < cupinf.bgmfid) {
               break;
          }
     }
     ASSERT(cupinf.bgmfid < fidxrf[i].fid);
     cupinf.bgmfid=fidxrf[i].fid;
     cupinf.bgmmid=FIRSTM;
     return(TRUE);
}

VOID
qikdel(VOID)                       /* clean up old-style dist list att's   */
{
     INT tdy,age;
     struct ffblk fb;

     tdy=cofdat(today());
     if (fnd1st(&fb,spr("%s"SLS"*.atq",attpth(EMLID)),0)) {
          do {
               age=tdy-cofdat(fb.ff_fdate);
               if (emllif >= 0 && age > emllif) {
                    unlink(spr("%s"SLS"%s",attpth(EMLID),fb.ff_name));
               }
          } while (fndnxt(&fb));
     }
}

VOID
cleandlf(VOID)                     /* removes all traces of deleted forums */
{                                  /*   (executed at clean-up)             */
     INT i,nfors;
     GBOOL update;
     USHORT tmpfid;
     USHORT *forlst;
     struct fmidky *qsfm,killky;

     dfaSetBlk(fordefbb);
     if (!dfaQueryGE(killstr,DNMKY) || fordefbb->key[0] != KILLCHR) {
          return;
     }
     dfaSetBlk(qscbb);
     if (dfaStepLO(NULL)) {
          do {
               update=FALSE;
               qsfm=(struct fmidky *)qsdptr->accmsg;
               for (i=0 ; i < qsdptr->nforums ; ++i) {
                    while (i < qsdptr->nforums && !fidxst(qsfm[i].forum)) {
                         update=TRUE;
                         idelqs(qsdptr,i);
                    }
               }
               if (update) {
                    dfaUpdateV(NULL,qsrlen(qsdptr->nforums));
               }
          } while (dfaStepNX(NULL));
     }
     dfaSetBlk(grpbb);
     if (dfaAcqLO(utlgrp,GGIDKY)) {
          do {
               update=FALSE;
               nfors=utlgrp->nforums;
               if (nfors > maxfgrp) {
                    nfors=maxfgrp;
               }
               forlst=utlgrp->forarr;
               for (i=0 ; i < nfors ; ) {
                    if ((tmpfid=forlst[i]) == EMLID || xrfidx(tmpfid) == NOIDX) {
                         update=TRUE;
                         --nfors;
                         movmem(&forlst[i+1],&forlst[i],
                                (nfors-i)*sizeof(USHORT));
                    }
                    else {
                         ++i;
                    }
               }
               if (update) {
                    utlgrp->nforums=nfors;
                    dfaUpdateV(utlgrp,grprlen(nfors));
               }
          } while(dfaAcqGT(utlgrp,&utlgrp->grpid,GGIDKY));
     }
     dfaSetBlk(fordefbb);
     while (dfaAcqGE(NULL,killstr,DNMKY)) {
          if (fddptr->name[0] == KILLCHR) {
               if (fexist(fddptr->datfil)) {
                    dfaSetBlk(curfbb=fdfarr[opnfdf(fddptr->datfil)]);
                    killky.forum=fddptr->forum;
                    killky.msgid=FIRSTM;
                    while (dfaAcqGE(NULL,&killky,FFMKY)) {
                         if (cfdptr->forum == fddptr->forum) {
                              if ((cfdptr->flags&FILATT)
                               && !(cfdptr->flags&ISTCPY)) {
                                   normspec((CHAR *)tmpbuf,fddptr->attpath);
                                   unlink(spr("%s"SLS"%s",(CHAR *)tmpbuf,
                                          attnam(cfdptr->msgid)));
                              }
                              dfaDelete();
                         }
                         else {
                              break;
                         }
                    }
                    dfaSetBlk(fordefbb);
               }
               dfaDelete();
               cfthrs(fddptr->forum);
          }
          else {
               break;
          }
     }
}

VOID
cleandla(                          /* clean up after a deleted account     */
const CHAR *userid)                /*   account to clean up after          */
{
     CHAR tmpuid[UIDSIZ];

     stlcpy(tmpuid,userid,UIDSIZ);
     wtouid(tmpuid);
     tmpuid[0]=lwclch(tmpuid[0]);
     wtouid(tmpuid);
}

VOID
wtouid(                            /* whack messages to a specific user    */
const CHAR *userid)                /*   User-ID to whack messages to       */
{
     struct umidky umkey;

     dfaSetBlk(emlbb);
     stlcpy(umkey.usrid,userid,UIDSIZ);
     umkey.msgid=FIRSTM;
     while (dfaAcqGT(NULL,&umkey,ETOMKY) && sameas(emdptr->to,userid)) {
          umkey.msgid=emdptr->msgid;
          whackit(emdptr);
          dfaSetBlk(emlbb);
     }
}

VOID
def2dsk(                           /* copy fordef to fordsk header         */
const struct fordef *def,          /*   source definition                  */
struct fordsk *dsk)                /*   destination definition             */
{                                  /* (only copies fields that change)     */
     stzcpy(dsk->name,def->name,FORNSZ);
     stzcpy(dsk->topic,def->topic,TPCSIZ);
     stzcpy(dsk->forop,def->forop,UIDSIZ);
     stzcpy(dsk->datfil,fdfarr[def->dfnum]->filnam,GCMAXPTH);
     stzcpy(dsk->attpath,def->attpath,GCMAXPTH);
     stzcpy(dsk->forlok,def->forlok,KEYSIZ);
     dsk->nthrs=def->nthrs;
     dsk->nmsgs=def->nmsgs;
     dsk->nfiles=def->nfiles;
     dsk->nw4app=def->nw4app;
     dsk->dfnpv=(INT)def->dfnpv;
     dsk->dfprv=(INT)def->dfprv;
     dsk->mxnpv=(INT)def->mxnpv;
     dsk->msglif=def->msglif;
     dsk->chgmsg=def->chgmsg;
     dsk->chgrdm=def->chgrdm;
     dsk->chgatt=def->chgatt;
     dsk->chgadl=def->chgadl;
     dsk->chgupk=def->chgupk;
     dsk->chgdpk=def->chgdpk;
     dsk->ccr=def->ccr;
     dsk->pfnlvl=(INT)def->pfnlvl;
     dsk->necho=def->necho;
     dsk->seqid=def->seqid;
}

VOID
cpymdef(                           /* copy modifyable part of in-memory def*/
struct fordef *dest,               /*   destination definition             */
const struct fordef *src)          /*   source definition                  */
{
     stlcpy(dest->name,src->name,FORNSZ);
     stlcpy(dest->topic,src->topic,TPCSIZ);
     stlcpy(dest->forop,src->forop,UIDSIZ);
     stlcpy(dest->forlok,src->forlok,KEYSIZ);
     dest->dfnpv=src->dfnpv;
     dest->dfprv=src->dfprv;
     dest->mxnpv=src->mxnpv;
     dest->msglif=src->msglif;
     dest->chgmsg=src->chgmsg;
     dest->chgrdm=src->chgrdm;
     dest->chgatt=src->chgatt;
     dest->chgadl=src->chgadl;
     dest->chgupk=src->chgupk;
     dest->chgdpk=src->chgdpk;
     dest->ccr=src->ccr;
     dest->pfnlvl=src->pfnlvl;
     dest->necho=src->necho;
}

struct fordef *                    /*   returns pointer to new in-mem def  */
addfarr(                           /* add new forum to in-memory arrays    */
const struct fordsk *newdef,       /*   pointer to forum on-disk structure */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{
     INT i;
     struct fordef *memdef;
#ifdef DEBUG
     INT tmpctr;
     adr_t *tmpecho;
#endif

     ASSERT(newdef != NULL);
     ASSERT(newdef->forum != EMLID);
#ifdef STARTER
     if (nforums == MAXSFORS) {
          catastro("Worlgroup Starter permits only %d forums.",MAXSFORS);
     }
#else
     if (!alcpar((VOID ***)&defarr,nforums,BIGBLK)
      || !alcarr((VOID **)&fidxrf,sizeof(struct fidxrf),nforums,BIGBLK)) {
          return(NULL);
     }
#endif // STARTER
     memdef=(struct fordef *)malloc(sizeof(struct fordef));
     if (memdef == NULL) {
          return(NULL);
     }
     memdef->dfnum=opnfdf(newdef->datfil);
     dsk2def(newdef,memdef);
     if (memdef->necho > 0) {
          ASSERT(memdef->necho <= MAXECHO);
          memdef->echoes=(CHAR *)malloc(MAXADR*memdef->necho);
          if (memdef->echoes == NULL) {
               free(memdef);
               return(NULL);
          }
          movmem(echoes,memdef->echoes,MAXADR*memdef->necho);
#ifdef DEBUG
               tmpecho=(adr_t *)memdef->echoes;
               for (tmpctr=0 ; tmpctr < memdef->necho ; ++tmpctr) {
                    ASSERT(strlen(tmpecho[tmpctr]) < MAXADR);
               }
#endif
     }
     else {
          memdef->echoes=NULL;
     }
     for (i=0 ; i < nforums ; ++i) {
          if (stricmp(defarr[i]->name,memdef->name) > 0) {
               break;
          }
     }
     movmem(&defarr[i],&defarr[i+1],sizeof(struct fordef *)*(nforums-i));
     defarr[i]=memdef;
     insxrf(memdef->forum,i);
     ++nforums;
     return(memdef);
}

VOID
addseq(                            /* add new forum to sequence            */
struct fordef *newdef)             /*   new forum definition (in list)     */
{
     INT i,seq;

     ASSERT(newdef != NULL);
#ifdef DEBUG
          for (i=0 ; i < nforums && newdef != defarr[i] ; ++i) {
          }
          ASSERT(i < nforums);
#endif
     for (seq=0 ; seq < nforums-1 ; ++seq) {
          for (i=0 ; i < nforums ; ++i) {
               if (defarr[i]->seqid == seq && defarr[i] != newdef) {
                    break;
               }
          }
          ASSERT(i < nforums);
          ASSERT(defarr[i]->seqid == seq);
          if (stricmp(defarr[i]->name,newdef->name) >= 0) {
               break;
          }
     }
     ASSERT(seq < nforums);
     newdef->seqid=seq;
     for (i=0 ; i < nforums ; ++i) {
          if (defarr[i] != newdef && defarr[i]->seqid >= seq) {
               ++defarr[i]->seqid;
          }
     }
}

const CHAR *                       /*   returns pointer to path string     */
attpfn(                            /* attachment path+file name            */
USHORT fid,                        /*   for specified forum                */
LONG mid)                          /*   and message number                 */
{
     return(fnmcse(spr("%s"SLS"%s",attpth(fid),attnam(mid))));
}

const CHAR *                       /*   pointer to static name buffer      */
attnam(                            /* attachment name                      */
LONG mid)                          /*   given message number               */
{
     static CHAR name[GCMAXFNM];

     ASSERT(mid > 0);
     sprintf(name,"%010ldA",mid);
     name[12]='\0';
     name[11]=name[10];
     name[10]=name[9];
     name[9]=name[8];
     name[8]='.';
     return(name);
}

const CHAR *                       /*   returns pointer to path string     */
attpth(                            /* attachment path                      */
USHORT fid)                        /*   for specified forum                */
{
     static CHAR retfil[GCMAXPTH];

     ASSERT(fid == EMLID || fidxst(fid));
     normspec(retfil,fid == EMLID ? EMLADIR : getdef(fid)->attpath);
     return(retfil);
}

VOID
msg2dsk(                           /* copy in-memory msg struct to on-disk */
const struct message *msg,         /*   in-memory structure                */
struct msgdsk *dsk)                /*   on-disk structure (must be large   */
{                                  /*   enough to hold 256-byte addresses) */
     ASSERT(msg != NULL);
     ASSERT(dsk != NULL);
     memset(dsk,0,MINMSGREC);
     dsk->forum=msg->forum;
     dsk->msgid=msg->msgid;
     dsk->gmid=msg->gmid;
     dsk->thrid=msg->thrid;
     stzcpy(dsk->topic,msg->topic,TPCSIZ);
     stzcpy(dsk->history,msg->history,HSTSIZ);
     if (msg->flags&FILATT) {
          fnmcse(stzcpy(dsk->attname,msg->attname,sizeof(dsk->attname)));
     }
     else {
          setmem(dsk->attname,sizeof(dsk->attname),0);
     }
     dsk->crdate=msg->crdate;
     dsk->crtime=msg->crtime;
     dsk->rplto=msg->rplto;
     dsk->nrpl=msg->nrpl;
     dsk->flags=msg->flags;
     if (strlen(msg->from) >= UIDSIZ) {
          stzcpy(dsk->info,msg->from,MAXADR);
          setmem(dsk->from,UIDSIZ,0);
     }
     else if ((msg->from[0] == '\0') || (msg->from[0] == NOADDRC)) {
          dsk->from[0]=NOADDRC;
          setmem(&dsk->from[1],UIDSIZ-1,0);
     }
     else {
          stzcpy(dsk->from,msg->from,UIDSIZ);
     }
     if (strlen(msg->to) >= UIDSIZ) {
          stzcpy(dsk->info+(*dsk->from == '\0' ? MAXADR : 0),msg->to,MAXADR);
          setmem(dsk->to,UIDSIZ,0);
     }
     else if ((msg->to[0] == '\0') || (msg->to[0] == NOADDRC)) {
          dsk->to[0]=NOADDRC;
          setmem(&dsk->to[1],UIDSIZ-1,0);
     }
     else {
          stzcpy(dsk->to,msg->to,UIDSIZ);
     }
}

VOID
dsk2msg(                           /* copy on-disk msg struct to in-memory */
const struct msgdsk *dsk,          /*   on-disk structure                  */
struct message *msg)               /*   in-memory structure                */
{
     ASSERT(msg != NULL);
     ASSERT(dsk != NULL);
     msg->forum=dsk->forum;
     msg->msgid=dsk->msgid;
     msg->gmid=dsk->gmid;
     msg->thrid=dsk->thrid;
     stlcpy(msg->topic,dsk->topic,TPCSIZ);
     stlcpy(msg->history,dsk->history,HSTSIZ);
     stlcpy(msg->attname,dsk->attname,GCMAXFNM);
     msg->crdate=dsk->crdate;
     msg->crtime=dsk->crtime;
     msg->rplto=dsk->rplto;
     msg->nrpl=dsk->nrpl;
     msg->flags=dsk->flags;
     if (*dsk->from == '\0') {
          stlcpy(msg->from,dsk->info,MAXADR);
     }
     else if (dsk->from[0] == NOADDRC) {
          *msg->from='\0';
     }
     else {
          stlcpy(msg->from,dsk->from,UIDSIZ);
     }
     uclfrom(msg);
     if (*dsk->to == '\0') {
          stlcpy(msg->to,dsk->info+(*dsk->from == '\0' ? MAXADR : 0),MAXADR);
     }
     else if (dsk->to[0] == NOADDRC) {
          *msg->to='\0';
     }
     else {
          stlcpy(msg->to,dsk->to,UIDSIZ);
     }
     uclto(msg);
}

VOID
gettxt(                            /* get text from data file buffer       */
const struct msgdsk *dskbuf,       /*   data file buffer                   */
CHAR *text,                        /*   buffer to read text into           */
CHAR *appinf,                      /*   buf to read app-defined info into  */
UINT reclen)                       /*   record length                      */
{
     UINT nvsiz,txtsiz,apisiz;
     CHAR *cp;

     cp=textptr((struct msgdsk *)dskbuf);
     nvsiz=nvmsiz(dskbuf);
     txtsiz=reclen-nvsiz;
     ASSERT(txtsiz < reclen);
     stlcpy(text,cp,min(txtsiz,TXTLEN));
     txtsiz=strlen(cp)+1;
     apisiz=reclen-nvsiz-txtsiz;
     if (apisiz == 0) {
          *appinf='\0';
     }
     else {
          ASSERT(apisiz < reclen);
          stlcpy(appinf,cp+txtsiz,min(apisiz,TXTLEN));
     }
}

GBOOL                              /*   returns TRUE if started OK         */
iniqksnd(                          /* start up !QUICK distribution         */
VOID *pWork,                       /*   work area to initialize            */
const CHAR *userid)                /*   user ID to use                     */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     dfaSetBlk(qikbb);
     work->d.q.qikbuf=NULL;
     if (dfaAcqEQ(NULL,userid,0)) {
          if ((work->d.q.qikbuf=malloc(dfaLastLen())) != NULL) {
               movmem(qkdptr,work->d.q.qikbuf,dfaLastLen());
               work->d.q.qikctr=0;
          }
     }
     dfaRstBlk();
     return(work->d.q.qikbuf != NULL);
}

GBOOL
nxtqik(                            /* get next !QUICK entry                */
VOID *pWork,                       /*   work area to use                   */
CHAR *addr)                        /*   buffer for address                 */
{
     INT i;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(work->d.q.qikbuf != NULL);
     ASSERT(work->d.q.qikctr >= 0 && work->d.q.qikctr <= MAXQIK);
     if (work->d.q.qikctr == MAXQIK) {
          return(FALSE);
     }
     i=nxtqki(work->d.q.qikctr,work->d.q.qikbuf);
     if (i == NOIDX) {
          return(FALSE);
     }
     stlcpy(addr,&work->d.q.qikbuf->list[work->d.q.qikbuf->idx[i]],MAXADR);
     work->d.q.qikctr=i+1;
     return(TRUE);
}

VOID
clsqik(                            /* shut down !QUICK dist                */
VOID *pWork)                       /*   work area used                     */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     if (work->d.q.qikbuf != NULL) {
          free(work->d.q.qikbuf);
          work->d.q.qikbuf=NULL;
     }
}

struct qikdat *                    /*   copy of pointer to buffer          */
getqik(                            /* get a !QUICK list from disk          */
struct qikdat *qikbuf,             /*   pointer to !QUICK buffer           */
const CHAR *userid)                /*   user ID to use                     */
{
     ASSERT(qikbuf != NULL);
     dfaSetBlk(qikbb);
     if (dfaAcqEQ(NULL,userid,0)) {
          movmem(qkdptr,qikbuf,dfaLastLen());
     }
     else {
          iniqik(qikbuf,userid);
     }
     dfaRstBlk();
     return(qikbuf);
}

VOID
savqik(                            /* save a !QUICK list to disk           */
const struct qikdat *qikbuf)       /*   pointer to !QUICK buffer           */
{
#ifdef DEBUG
     INT i;

     ASSERT(qikbuf != NULL);
     for (i=0 ; i < MAXQIK ; ++i) {
          if (qikbuf->idx[i] != NOIDX) {
               ASSERT(strlen(&qikbuf->list[qikbuf->idx[i]]) < MAXADR);
               if (qikbuf->idx[i] > 0) {
                    ASSERT(qikbuf->list[qikbuf->idx[i]-1] == '\0');
               }
          }
     }
#endif
     dfaSetBlk(qikbb);
     if (dfaAcqEQ(NULL,qikbuf->userid,0)) {
          dfaUpdateV(qikbuf,qiklen(qikbuf));
     }
     else {
          dfaInsertV(qikbuf,qiklen(qikbuf));
     }
     dfaRstBlk();
}

struct qikdat *                    /*   copy of pointer to buffer          */
iniqik(                            /* initialize !QUICK record for cur user*/
struct qikdat *qikbuf,             /*   pointer to !QUICK buffer           */
const CHAR *userid)                /*   user ID to use                     */
{
     INT i;

     ASSERT(qikbuf != NULL);
     stzcpy(qikbuf->userid,userid,UIDSIZ);
     for (i=0 ; i < MAXQIK ; ++i) {
          qikbuf->idx[i]=NOIDX;
     }
     return(qikbuf);
}

VOID
insqik(                            /* insert entry into !QUICK buffer      */
INT pos,                           /*   position to insert at              */
const CHAR *entry,                 /*   new entry for position             */
struct qikdat *qikbuf)             /*   pointer to !QUICK buffer           */
{
     INT i;

     ASSERT(qikbuf != NULL);
     ASSERT(pos >= 0 && pos < MAXQIK);
     ASSERT(entry != NULL);
     ASSERT(strlen(entry) < MAXADR);
     if (qikbuf->idx[pos] != NOIDX) {
          delqik(pos,qikbuf);
     }
     if ((i=lastqk(qikbuf)) == NOIDX) {
          i=0;
     }
     else {
          i+=strlen(&qikbuf->list[i])+1;
     }
     stlcpy(&qikbuf->list[i],entry,MAXADR);
     qikbuf->idx[pos]=i;
}

VOID
delqik(                            /* delete an entry from !QUICK buffer   */
INT pos,                           /*   position to delete                 */
struct qikdat *qikbuf)             /*   pointer to !QUICK buffer           */
{
     INT i,curi,nxti;

     ASSERT(qikbuf != NULL);
     ASSERT(pos >= 0 && pos < MAXQIK);
     if ((curi=qikbuf->idx[pos]) == NOIDX) {
          return;
     }
     if (curi != lastqk(qikbuf)) {
          nxti=curi+strlen(&qikbuf->list[curi])+1;
          movmem(&qikbuf->list[nxti],&qikbuf->list[curi],
                 qiklen(qikbuf)-fldoff(qikdat,list)+nxti);
          for (i=0 ; i < MAXQIK ; ++i) {
               if (qikbuf->idx[i] > curi) {
                    qikbuf->idx[i]-=nxti-curi;
               }
          }
     }
     qikbuf->idx[pos]=NOIDX;
}

INT                                /*   returns index into qikdat.idx      */
nxtqki(                            /* get next non-empty !QUICK index      */
INT pos,                           /*   position to check first            */
const struct qikdat *qikbuf)       /*   pointer to !QUICK buffer           */
{
     INT i;

     ASSERT(qikbuf != NULL);
     ASSERT(pos >= 0 && pos < MAXQIK);
     for (i=pos ; i < MAXQIK ; ++i) {
          if (qikbuf->idx[i] != NOIDX) {
               return(i);
          }
     }
     return(NOIDX);
}

INT
qiklen(                            /* get !QUICK buffer space used         */
const struct qikdat *qikbuf)       /*   pointer to !QUICK buffer           */
{
     INT i;

     ASSERT(qikbuf != NULL);
     if ((i=lastqk(qikbuf)) == NOIDX) {
          return(fldoff(qikdat,list));
     }
     return(fldoff(qikdat,list)+i+strlen(&qikbuf->list[i])+1);
}

INT                                /*   returns index in qikdat.list       */
lastqk(                            /* get largest index in !QUICK list     */
const struct qikdat *qikbuf)       /*   pointer to !QUICK buffer           */
{
     INT i,max;

     ASSERT(qikbuf != NULL);
     max=NOIDX;
     for (i=0 ; i < MAXQIK ; ++i) {
          if (qikbuf->idx[i] > max) {
               max=qikbuf->idx[i];
          }
     }
     return(max);
}

VOID
foradd(                            /* add message to forum statistics      */
const struct message *msg)         /*   message to add                     */
{
     ASSERT(msg != NULL);
     ASSERT(msg->forum != EMLID);
     if (incthr(msg->forum,msg->thrid)) {
          ++curfdp->nthrs;
     }
     lofadd((INT)msg->flags);
}

VOID
lofadd(                            /* low-level add message to forum stats */
INT flags)                         /*   user flags of message to add       */
{
     ++curfdp->nmsgs;
     if (flags&FILATT) {
          if (flags&FILAPV) {
               ++curfdp->nfiles;
          }
          else {
               ++curfdp->nw4app;
          }
     }
}

VOID
fordel(                            /* remove deleted msg from forum stats  */
const struct msgdsk *msg)          /*   message that was deleted           */
{
     ASSERT(msg != NULL);
     ASSERT(msg->forum != EMLID);
     --curfdp->nmsgs;
     if (decthr(msg->forum,msg->thrid)) {
          --curfdp->nthrs;
     }
     if (msg->flags&FILATT) {
          if (msg->flags&FILAPV) {
               --curfdp->nfiles;
          }
          else {
               --curfdp->nw4app;
          }
     }
}

VOID
insnmsg(                           /* insert new message in current file   */
struct msgdsk *wrtbuf,             /*   buffer to use when writing         */
const struct message *msg,         /*   new message header                 */
const CHAR *text,                  /*   pointer to message body text       */
const CHAR *appinf)                /*   pointer to app-defined info        */
{
     dfaInsertV(wrtbuf,fillbuf(wrtbuf,msg,text,appinf));
}

VOID
insmptr(                           /* insert message pointer in cur file   */
struct msgdsk *wrtbuf,             /*   buffer to use when writing         */
const struct message *msg,         /*   message pointer header             */
const struct fmidky *mptr)         /*   pointer to body message            */
{
     ASSERT(msg != NULL);
     ASSERT(wrtbuf != NULL);
     ASSERT(mptr != NULL);
     msg2dsk((struct message *)msg,wrtbuf);
     movmem(mptr,textptr(wrtbuf),sizeof(struct fmidky));
     dfaInsertV(wrtbuf,nvmsiz(wrtbuf)+sizeof(struct fmidky));
}

VOID
updamsg(                           /* update message in current file       */
struct msgdsk *wrtbuf,             /*   buffer to use when writing         */
const struct message *msg,         /*   new message header                 */
const CHAR *text,                  /*   pointer to message body text       */
const CHAR *appinf)                /*   pointer to app-defined info        */
{
     dfaUpdateV(wrtbuf,fillbuf(wrtbuf,msg,text,appinf));
}

VOID
updmptr(                           /* update message pointer in cur file   */
struct msgdsk *wrtbuf,             /*   buffer to use when writing         */
const struct message *msg,         /*   message pointer header             */
const struct fmidky *mptr)         /*   pointer to body message            */
{
     ASSERT(msg != NULL);
     ASSERT(wrtbuf != NULL);
     ASSERT(mptr != NULL);
     msg2dsk((struct message *)msg,wrtbuf);
     movmem(mptr,textptr(wrtbuf),sizeof(struct fmidky));
     dfaUpdateV(wrtbuf,nvmsiz(wrtbuf)+sizeof(struct fmidky));
}

UINT                               /*   returns new record size            */
fillbuf(                           /* fill data file record buffer         */
struct msgdsk *datbuf,             /*   data file buffer                   */
const struct message *msg,         /*   message header                     */
const CHAR *text,                  /*   message body text                  */
const CHAR *appinf)                /*   app-defined info                   */
{
     UINT totsiz,txtsiz;
     CHAR *cp;

     ASSERT(msg != NULL);
     ASSERT(datbuf != NULL);
     ASSERT(text != NULL);
     msg2dsk((struct message *)msg,datbuf);
     totsiz=nvmsiz(datbuf);
     stlcpy(cp=textptr(datbuf),(CHAR *)text,TXTLEN);
     totsiz+=(txtsiz=strlen(cp)+1);
     if (appinf != NULL && *appinf != '\0') {
          cp+=txtsiz;
          stlcpy(cp,(CHAR *)appinf,TXTLEN);
          totsiz+=strlen(cp)+1;
     }
     if (totsiz < MINMSGREC) {
          totsiz=MINMSGREC;
     }
     ASSERT(totsiz <= _reclen);
     return(totsiz);
}

CHAR *
textptr(                           /* get pointer to text portion of msg   */
const struct msgdsk *dsk)          /*   given completed message structure  */
{
     return((CHAR *)dsk->info+(*dsk->from == '\0' ? MAXADR : 0)
                             +(*dsk->to == '\0' ? MAXADR : 0));
}

INT
nvmsiz(                            /* get size of "non-variable" msg part  */
const struct msgdsk *dsk)          /*   given completed message structure  */
{
     return(fldoff(msgdsk,info)+(*dsk->from == '\0' ? MAXADR : 0)
                               +(*dsk->to == '\0' ? MAXADR : 0));
}

INT
gmeMaxGrpFor(VOID)                 /* get max # of forums per group        */
{
     return(maxfgrp);
}

GBOOL
gmeGrpExist(                       /* does the specified group exist?      */
USHORT grpid)                      /*   group ID to check for              */
{
     GBOOL rc;

     if (grpid == 0 || grpinmem(grpid) != NOIDX) {
          rc=TRUE;
     }
     else {
          dfaSetBlk(grpbb);
          rc=dfaQueryEQ(&grpid,GGIDKY);
          dfaRstBlk();
     }
     return(rc);
}

struct forgrp *                    /*   returns NULL if group ID not found */
getgrp(                            /* load & return ptr to group info      */
USHORT grpid)                      /*   group ID to load                   */
{
     INT i;
     struct forgrp *grpptr;

     if ((i=grpinmem(grpid)) != NOIDX) {
          grplru[i]=cyccnt();
          grpptr=(struct forgrp *)ptrblok(grparr,i);
     }
     else {
          dfaSetBlk(grpbb);
          if (dfaQueryEQ(&grpid,GGIDKY)) {
               grpptr=grpbuf();
               dfaAbsRec(grpptr,GGIDKY);
               grpfix(grpptr);
          }
          else if (grpid == 0) {
               grpptr=grpbuf();
               setmem(grpptr,sizeof(struct forgrp),0);
          }
          else {
               grpptr=NULL;
          }
          dfaRstBlk();
     }
     return(grpptr);
}

struct forgrp *                    /*   returns NULL if not found          */
getgrpd(                           /* directional get group                */
INT rddir,                         /*   dir to read (RDNEXT,RDPREV,RDEXCT) */
USHORT pargrp,                     /*   with this parent                   */
const CHAR *name)                  /*   base group name                    */
{
     INT i;
     GBOOL got;
     USHORT rdsiz;
     struct forgrp *grpptr;

     ASSERT(gmeGrpExist(pargrp));
     ASSERT(name != NULL);
     grpptr=NULL;
     dfaSetBlk(grpbb);
     pnamky.pargrp=pargrp;
     stlcpy(pnamky.name,(CHAR *)name,FORNSZ);
     switch (rddir) {
     case RDNEXT:
          got=dfaAcqGT(NULL,&pnamky,GPNMKY);
          break;
     case RDPREV:
          got=dfaAcqLT(NULL,&pnamky,GPNMKY);
          break;
     case RDEXCT:
          got=dfaAcqEQ(NULL,&pnamky,GPNMKY);
          break;
     default:
          ASSERT(FALSE);
     }
     if (got && grpdptr->parid == pargrp) {
          rdsiz=dfaLastLen();
          if ((i=grpinmem(grpdptr->grpid)) != NOIDX) {
               grplru[i]=cyccnt();
               grpptr=(struct forgrp *)ptrblok(grparr,i);
          }
          else {
               grpptr=grpbuf();
               movmem(grpbb->data,grpptr,rdsiz);
               grpfix(grpptr);
          }
     }
     dfaRstBlk();
     return(grpptr);
}

VOID
grpfix(                            /* fix up list of forums in a group     */
struct forgrp *grpptr)             /*   group info record                  */
{
     INT i,j,nfors;
     USHORT tmpfid;
     USHORT *forlst;
     CHAR *basename,*testname;

     nfors=grpptr->nforums;
     if (nfors > maxfgrp) {
          nfors=maxfgrp;
     }
     forlst=grpptr->forarr;
     for (i=0 ; i < nfors ; ) {
          if ((tmpfid=forlst[i]) == EMLID || xrfidx(tmpfid) == NOIDX) {
               --nfors;
               movmem(&forlst[i+1],&forlst[i],(nfors-i)*sizeof(USHORT));
          }
          else {
               ++i;
          }
     }
     for (i=0 ; i < nfors-1 ; ++i) {
          basename=getdef(forlst[i])->name;
          for (j=i+1 ; j < nfors ; ++j) {
               testname=getdef(forlst[j])->name;
               if (stricmp(basename,testname) > 0) {
                    tmpfid=forlst[i];
                    forlst[i]=forlst[j];
                    forlst[j]=tmpfid;
                    basename=testname;
               }
          }
     }
     for (i=0 ; i < nfors-1 ; ) {
          if (forlst[i] == forlst[i+1]) {
               --nfors;
               movmem(&forlst[i+1],&forlst[i],(nfors-i)*sizeof(USHORT));
          }
          else {
               ++i;
          }
     }
     grpptr->nforums=nfors;
}

INT                                /*   index in cache array or NOIDX      */
grpinmem(                          /* find a group if already in memory    */
USHORT grpid)                      /*   group ID to find                   */
{
     INT i;

     for (i=0 ; i < grpnmem ; ++i) {
          if (grplru[i] != 0L
           && ((struct forgrp *)ptrblok(grparr,i))->grpid == grpid) {
               return(i);
          }
     }
     return(NOIDX);
}

struct forgrp *
grpbuf(VOID)                       /* get least-recently-used group buf    */
{
     INT i,lruidx;
     ULONG lruval;

     lruidx=0;
     lruval=grplru[0];
     for (i=1 ; i < grpnmem ; ++i) {
          if (grplru[i] <= lruval) {
               lruidx=i;
               lruval=grplru[i];
          }
     }
     grplru[lruidx]=cyccnt();
     return((struct forgrp *)ptrblok(grparr,lruidx));
}

VOID
smemgrp(                           /* re-sort forums for in-memory groups  */
USHORT forum)                      /*   forum ID that changed              */
{
     INT i;
     struct forgrp *grpptr;

     for (i=0 ; i < grpnmem ; ++i) {
          if (grplru[i] != 0L) {
               grpptr=(struct forgrp *)ptrblok(grparr,i);
               if (delgrpf(grpptr,forum)) {
                    addgrpf(grpptr,forum);
               }
          }
     }
}

VOID
rmemgrp(                           /* remove del forum from in-memory grps */
USHORT forum)                      /*   forum ID deleted                   */
{
     INT i;

     for (i=0 ; i < grpnmem ; ++i) {
          if (grplru[i] != 0L) {
               delgrpf((struct forgrp *)ptrblok(grparr,i),forum);
          }
     }
}

VOID
setfop(                            /* set the forum-op                     */
USHORT fid,                        /*   for this forum                     */
const CHAR *uid)                   /*   to this user                       */
{
     struct fordef *tmpdef;

     ASSERT(fidxst(fid));
     ASSERT(uid != NULL);
     tmpdef=getdef(fid);
     dfaSetBlk(fordefbb);
     if (dfaAcqEQ(NULL,&tmpdef->forum,DIDKY)) {
          stlcpy(tmpdef->forop,uid,UIDSIZ);
          def2dsk(tmpdef,fddptr);
          dfaUpdateV(NULL,dfaLastLen());
     }
     dfaRstBlk();
}

CHAR *                             /*   returns pointer to description     */
gme1fdsc(                          /* get forum description                */
USHORT fid)                        /*   given forum ID                     */
{
     dfaSetBlk(fordefbb);
     if (!dfaAcqEQ(NULL,&fid,DIDKY)) {
          dfaRstBlk();
          return(NULL);
     }
     dfaRstBlk();
     return(fddptr->info+MAXADR*fddptr->necho);
}

VOID
gme1afi(                           /* get all forum info                   */
USHORT forum,                      /*   forum ID to get                    */
struct fordsk *workdef,            /*   on-disk forum definition format    */
CHAR *desc,                        /*   buffer for description             */
CHAR *echoes)                      /*   buffer for echoes                  */
{                                  /*   (desc & echoes can be NULL)        */
     ASSERT(fidxst(forum));
     ASSERT(workdef != NULL);
     dfaSetBlk(fordefbb);
     if (!dfaAcqEQ(NULL,&forum,DIDKY)) {
          dfaRstBlk();
          return;
     }
     dfaRstBlk();
     movmem(fddptr,workdef,sizeof(struct fordsk));
     curfdp=getdef(forum);
     def2dsk(curfdp,workdef);
     if (desc != NULL) {
          stlcpy(desc,fddptr->info+MAXADR*fddptr->necho,MAXFDESC);
     }
     if (echoes != NULL && curfdp->echoes != NULL) {
          movmem(curfdp->echoes,echoes,MAXADR*curfdp->necho);
     }
}

INT                                /*   returns standard GME status codes  */
gme1crf(                           /* create a new forum                   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct fordsk *newdef,             /*   new forum definition structure     */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{                                  /*   (may be NULL if no echoes)         */
     INT i,nbtv;
     CHAR *pname;
     adr_t *tmpecho;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(newdef != NULL);
     ASSERT(desc != NULL);
     ASSERT(strlen(newdef->name) > 0 && strlen(newdef->name) < FORNSZ);
     ASSERT(strlen(newdef->forop) > 0 && strlen(newdef->forop) < UIDSIZ);
     ASSERT(strlen(newdef->datfil) > 0 && strlen(newdef->datfil) < GCMAXPTH);
     ASSERT(strlen(newdef->attpath) < GCMAXPTH);
#ifdef DEBUG
          if (newdef->necho > 0) {
               ASSERT(newdef->necho <= MAXECHO);
               ASSERT(echoes != NULL);
               tmpecho=(adr_t *)echoes;
               for (i=0 ; i < newdef->necho ; ++i) {
                    ASSERT(strlen(tmpecho[i]) < MAXADR);
               }
          }
#endif
     ASSERT(newdef->msglif >= -1);
     ASSERT(!(newdef->dfprv&1) && newdef->dfprv <= OPAXES);
     ASSERT(!(newdef->mxnpv&1) && newdef->mxnpv <= OPAXES);
     ASSERT(!(newdef->dfnpv&1) && newdef->dfnpv <= OPAXES);
     ASSERT(newdef->pfnlvl >= 0 && newdef->pfnlvl <= DFTPFN);
     ASSERT(strlen(desc)+newdef->necho*MAXADR < MAXFDV);
     nbtv=0;
     while (TRUE) {
          switch (work->state1) {
          case START:
               if (nforums == MAXPARSZ) {
                    return(GME2MFR);
               }
               normspec((CHAR *)tmpbuf,newdef->datfil);
               if (sameas((CHAR *)tmpbuf,normspec((CHAR *)tmpbuf+GCMAXPTH,
                                emldp))
                || sameas((CHAR *)tmpbuf,normspec((CHAR *)tmpbuf+GCMAXPTH,
                                   FORDEFN))) {
                    return(GMEERR);
               }
               pname=strrchr(newdef->datfil,'.');
               if (pname == NULL || !sameas(pname,".DAT")) {
                    return(GMEERR);
               }
               newdef->forum=1U;
               work->state1=AVLFID;
          case AVLFID:
               dfaSetBlk(fordefbb);
               while (newdef->forum > 0
                   && dfaQueryEQ((CHAR *)&newdef->forum,DIDKY)) {
                    newdef->forum=nxtafid(newdef->forum);
                    if (++nbtv == MAXBTV) {
                         return(GMEAGAIN);
                    }
               }
               if (newdef->forum == 0) {
                    work->state1=START;
                    return(GMENFID);
               }
               work->state1=CRFAD;
          case CRFAD:
               work->state1=CRFDF;
               if (!dexist(newdef->attpath)) {
                    if (!makedir(newdef->attpath)) {
                         work->state1=START;
                         return(GMEERR);
                    }
                    return(GMEAGAIN);
               }
          case CRFDF:
               work->state1=INSFD;
               if (!fdfisopn(newdef->datfil)) {
                    if (nfdf == maxfdf) {
                         work->state1=START;
                         return(GME2MFL);
                    }
                    if (!fexist(newdef->datfil)) {
                         if (!creatfdf(newdef->datfil)) {
                              work->state1=START;
                              return(GMEERR);
                         }
                         return(GMEAGAIN);
                    }
               }
          case INSFD:
               dfaSetBlk(fordefbb);
               if (dfaQueryEQ(newdef->name,DNMKY)) {
                    work->state1=START;
                    return(GMEDUP);
               }
               if (dfaQueryEQ((CHAR *)&newdef->forum,DIDKY)) {
                    work->state1=START;
                    return(GMEAGAIN);
               }
               newdef->nmsgs=0;
               newdef->nthrs=0;
               newdef->nfiles=0;
               newdef->nw4app=0;
               newdef->crdate=today();
               newdef->crtime=now();
               curfdp=addfarr(newdef,echoes);
               if (curfdp == NULL) {
                    work->state1=START;
                    return(GMEMEM);
               }
               addseq(curfdp);
               newdef->seqid=curfdp->seqid;
               movmem(newdef,fddptr,fldoff(fordsk,info));
               zpad(fddptr->name,FORNSZ);
               zpad(fddptr->topic,TPCSIZ);
               zpad(fddptr->forop,UIDSIZ);
               zpad(fddptr->datfil,GCMAXPTH);
               zpad(fddptr->attpath,GCMAXPTH);
               zpad(fddptr->forlok,KEYSIZ);
               if (newdef->necho > 0) {
                    movmem(echoes,fddptr->info,MAXADR*newdef->necho);
                    tmpecho=(adr_t *)fddptr->info;
                    for (i=0 ; i < newdef->necho ; ++i) {
                         zpad(tmpecho[i],MAXADR);
                    }
               }
               stlcpy(fddptr->info+MAXADR*newdef->necho,desc,
                      MAXFDV-MAXADR*newdef->necho);
               dfaSetBlk(fordefbb);
               dfaInsertV(NULL,fldoff(fordsk,info)+1
                          +MAXADR*newdef->necho+strlen(desc));
               if (newdef->forum > (USHORT)sv.hisign) {
                    sv.hisign=newdef->forum;
               }
               work->curhook=NOIDX;
               work->state1=HDLHOOK;
          case HDLHOOK:
               work->curhook=notnwf(work->curhook,newdef,desc,echoes);
               if (work->curhook != NOIDX) {
                    return(GMEAGAIN);
               }
               work->state1=START;
               return(GMEOK);
          default:
               state_error(1,"gme1crf()",work->state1);
          }
     }
}

INT                                /*   returns standard GME status codes  */
gme1modf(                          /* modify a forum definition            */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct fordef *newdef,             /*   modified forum description         */
const CHAR *desc,                  /*   descriptive text                   */
const CHAR *echoes)                /*   pointer to array of echo addresses */
{                                  /*(desc & echoes may be NULL if no chng)*/
     INT curidx,i,tmplen;
     GBOOL namechg;
     adr_t *tmpecho;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(newdef != NULL);
     ASSERT(strlen(newdef->name) > 0 && strlen(newdef->name) < FORNSZ);
     ASSERT(strlen(newdef->forop) > 0 && strlen(newdef->forop) < UIDSIZ);
#ifdef DEBUG
     if (newdef->necho > 0) {
          ASSERT(newdef->necho <= MAXECHO);
          if (echoes != NULL) {
               tmpecho=(adr_t *)echoes;
               for (i=0 ; i < newdef->necho ; ++i) {
                    ASSERT(strlen(tmpecho[i]) < MAXADR);
               }
          }
     }
     if (desc != NULL) {
          ASSERT(strlen(desc) < MAXFDESC);
          ASSERT(strlen(desc)+newdef->necho*MAXADR < MAXFDV);
     }
#endif
     ASSERT(newdef->msglif >= -1);
     ASSERT(!(newdef->dfprv&1) && newdef->dfprv <= OPAXES);
     ASSERT(!(newdef->mxnpv&1) && newdef->mxnpv <= OPAXES);
     ASSERT(!(newdef->dfnpv&1) && newdef->dfnpv <= OPAXES);
     ASSERT(newdef->pfnlvl <= DFTPFN);
     ASSERT(newdef->necho >= 0 && newdef->necho < MAXECHO);
     switch (work->state1) {
     case START:
          curidx=fididx(newdef->forum);
          if (curidx == NOIDX) {
               work->state1=START;
               return(GMENFND);
          }
          i=fnmidx(newdef->name);
          if (i != NOIDX && i != curidx) {
               work->state1=START;
               return(GMEDUP);
          }
          dfaSetBlk(fordefbb);
          if (!dfaAcqEQ(NULL,&newdef->forum,DIDKY)) {
               dfaRstBlk();
               work->state1=START;
               return(GMEERR);
          }
          dfaRstBlk();
          curfdp=defarr[curidx];
          if (curfdp->necho != newdef->necho) {
               if (newdef->necho != 0 && echoes == NULL) {
                    work->state1=START;
                    return(GMEERR);
               }
               if (newdef->necho == 0) {
                    free(curfdp->echoes);
                    curfdp->echoes=NULL;
               }
               else {
                    curfdp->echoes=alcrsz(curfdp->echoes,MAXADR*curfdp->necho,
                                          MAXADR*newdef->necho);
               }
               if (desc == NULL) {
                    tmplen=strlen(fddptr->info+MAXADR*fddptr->necho);
                    if (MAXADR*newdef->necho+tmplen+1 >= MAXFDV) {
                         work->state1=START;
                         return(GMEFDV);
                    }
                    movmem(fddptr->info+MAXADR*fddptr->necho,
                           fddptr->info+MAXADR*newdef->necho,tmplen+1);
               }
          }
          if (newdef->necho != 0 && echoes != NULL) {
               movmem(echoes,curfdp->echoes,MAXADR*newdef->necho);
               tmpecho=(adr_t *)curfdp->echoes;
               for (i=0 ; i < newdef->necho ; ++i) {
                    zpad(tmpecho[i],MAXADR);
               }
               movmem(curfdp->echoes,fddptr->info,MAXADR*newdef->necho);
          }
          if (desc != NULL) {
               strcpy(fddptr->info+MAXADR*newdef->necho,desc);
          }
          namechg=!sameas(newdef->name,curfdp->name);
          cpymdef(curfdp,newdef);
          def2dsk(curfdp,fddptr);
          dfaSetBlk(fordefbb);
          dfaUpdateV(NULL,fldoff(fordsk,info)+1+MAXADR*fddptr->necho
                     +strlen(fddptr->info+MAXADR*fddptr->necho));
          dfaRstBlk();
          if (namechg) {
               resortf(curidx);
               smemgrp(newdef->forum);
          }
          work->curhook=NOIDX;
          work->state1=HDLHOOK;
          return(GMEAGAIN);
     case HDLHOOK:
          work->curhook=notmdf(work->curhook,getdef(newdef->forum),desc,echoes);
          if (work->curhook != NOIDX) {
               return(GMEAGAIN);
          }
          work->state1=START;
          return(GMEOK);
     default:
          state_error(1,"gme1modf()",work->state1);
     }
     ASSERT(FALSE);
     work->state1=START;
     return(GMEERR);               /* should never happen                  */
}

INT                                /*   returns standard GME status codes  */
gme1dlf(                           /* delete a forum                       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT forum)                      /*   forum ID to delete                 */
{
     INT i,j;
     USHORT reclen;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(forum != EMLID);
     switch (work->state1) {
     case START:
          if ((i=fididx(forum)) == NOIDX) {
               return(GMEERR);
          }
          dfaSetBlk(fordefbb);
          if (!dfaAcqEQ(NULL,&forum,DIDKY)) {
               dfaRstBlk();
               return(GMEERR);
          }
          reclen=dfaLastLen();
          curfdp=defarr[i];
          notdlf(fddptr,curfdp->echoes,fddptr->info+MAXADR*fddptr->necho);
          j=xrfidx(curfdp->forum);
          --nforums;
          movmem(&defarr[i+1],&defarr[i],sizeof(struct fordef *)*(nforums-i));
          movmem(&fidxrf[j+1],&fidxrf[j],sizeof(struct fidxrf)*(nforums-j));
          for (j=0 ; j < nforums ; ++j) {
               if (fidxrf[j].idx > i) {
                    --fidxrf[j].idx;
               }
               if (defarr[j]->seqid > curfdp->seqid) {
                    --defarr[j]->seqid;
               }
          }
          free(curfdp);
          rmemgrp(forum);
          sprintf(fddptr->name,"%c%hu",KILLCHR,forum);
          dfaUpdateV(NULL,reclen);
          dfaRstBlk();
          work->curhook=NOIDX;
          work->state1=HDLHOOK;
          return(GMEAGAIN);
     case HDLHOOK:
          work->curhook=notdlfl(work->curhook,forum);
          if (work->curhook != NOIDX) {
               return(GMEAGAIN);
          }
          work->state1=START;
          return(GMEOK);
     default:
          state_error(1,"gme1dlf()",work->state1);
     }
     ASSERT(FALSE);
     work->state1=START;
     return(GMEERR);               /* should never happen                  */
}

INT                                /*   returns standard GME status codes  */
gme1crg(                           /* create a new group                   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct forgrp *crtbuf)             /*   group information structure        */
{
     INT nbtv;
     USHORT grpid,tmpgrid;
     struct forgrp *recptr;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(crtbuf != NULL);
     ASSERT(crtbuf->nforums >= 0 && crtbuf->nforums <= maxfgrp);
     ASSERT(valfornm(crtbuf->name));
     nbtv=0;
     switch (work->state1) {
     case START:
          if (!gmeGrpExist(crtbuf->parid)) {
               return(GMENFND);
          }
          dfaSetBlk(grpbb);
          pnamky.pargrp=crtbuf->parid;
          stlcpy(pnamky.name,crtbuf->name,FORNSZ);
          if (dfaQueryEQ(&pnamky,GPNMKY)) {
               dfaRstBlk();
               return(GMEDUP);
          }
          dfaRstBlk();
          crtbuf->grpid=1U;
          work->state1=AVLGRID;
     case AVLGRID:
          dfaSetBlk(grpbb);
          grpid=crtbuf->grpid;
          while (grpid != ROOTGRP && grpid != NOGRP) {
               if (!dfaQueryEQ(&grpid,GGIDKY)) {
                    break;
               }
               if (!dfaQueryGT(&grpid,GGIDKY)) {
                    ++grpid;
                    break;
               }
               tmpgrid=*((USHORT *)grpbb->key);
               if (tmpgrid != grpid+1) {
                    ++grpid;
                    break;
               }
               grpid=tmpgrid+1;
               if (++nbtv == MAXBTV) {
                    crtbuf->grpid=grpid;
                    dfaRstBlk();
                    return(GMEAGAIN);
               }
          }
          dfaRstBlk();
          if (grpid == 0 || grpid == NOGRP) {
               work->state1=START;
               return(GMENFID);
          }
          crtbuf->grpid=grpid;
          work->state1=WRITING;
          if (nbtv > 1) {
               return(GMEAGAIN);
          }
     case WRITING:
          if (!gmeGrpExist(crtbuf->parid)) {
               work->state1=START;
               return(GMENFND);
          }
          if (gmeGrpExist(crtbuf->grpid)) {
               work->state1=START;
               return(GMEAGAIN);
          }
          dfaSetBlk(grpbb);
          pnamky.pargrp=crtbuf->parid;
          stlcpy(pnamky.name,crtbuf->name,FORNSZ);
          if (dfaQueryEQ(&pnamky,GPNMKY)) {
               work->state1=START;
               dfaRstBlk();
               return(GMEDUP);
          }
          recptr=grpbuf();
          setmem(recptr,sizeof(struct forgrp),0);
          recptr->grpid=crtbuf->grpid;
          recptr->parid=crtbuf->parid;
          recptr->nforums=crtbuf->nforums;
          stzcpy(recptr->name,crtbuf->name,FORNSZ);
          stzcpy(recptr->topic,crtbuf->topic,TPCSIZ);
          stzcpy(recptr->key,crtbuf->key,KEYSIZ);
          if (crtbuf->nforums != 0) {
               movmem(crtbuf->forarr,recptr->forarr,
                      crtbuf->nforums*sizeof(USHORT));
               grpfix(recptr);
          }
          dfaInsertV(recptr,grprlen(recptr->nforums));
          work->curhook=NOIDX;
          work->state1=HDLHOOK;
          dfaRstBlk();
          return(GMEAGAIN);
     case HDLHOOK:
          if ((work->curhook=notnwg(work->curhook,crtbuf)) != NOIDX) {
               return(GMEAGAIN);
          }
          work->state1=START;
          return(GMEOK);
     default:
          state_error(1,"gme1crg()",work->state1);
     }
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gme1modg(                          /* modify a group                       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
GBOOL inclfor,                     /*   including a new forum list         */
struct forgrp *grpbuf)             /*   group information structure        */
{
     INT i;
     struct forgrp *grpptr;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(grpbuf != NULL);
     ASSERT(grpbuf->nforums >=0 && grpbuf->nforums <= maxfgrp);
     ASSERT(valfornm(grpbuf->name));
     switch (work->state1) {
     case START:
          if (grpbuf->grpid == ROOTGRP) {
               return(GMEERR);
          }
          if (!gmeGrpExist(grpbuf->grpid)
           || !gmeGrpExist(grpbuf->parid)) {
               return(GMENFND);
          }
          dfaSetBlk(grpbb);
          grpptr=getgrp(grpbuf->grpid);
          pnamky.pargrp=grpbuf->parid;
          stlcpy(pnamky.name,grpbuf->name,FORNSZ);
          if (dfaAcqEQ(NULL,&pnamky,GPNMKY) && grpdptr->grpid != grpbuf->grpid) {
               dfaRstBlk();
               return(GMEDUP);
          }
          grpptr->parid=grpbuf->parid;
          stzcpy(grpptr->name,grpbuf->name,FORNSZ);
          stzcpy(grpptr->topic,grpbuf->topic,TPCSIZ);
          stzcpy(grpptr->key,grpbuf->key,KEYSIZ);
          if (inclfor) {
               if ((grpptr->nforums=grpbuf->nforums) != 0) {
                    movmem(grpbuf->forarr,grpptr->forarr,
                           grpbuf->nforums*sizeof(USHORT));
                    grpfix(grpptr);
               }
          }
          if (!dfaAcqEQ(NULL,&grpptr->grpid,GGIDKY)) {
               dfaRstBlk();
               if ((i=grpinmem(grpptr->grpid)) != NOIDX) {
                    grplru[i]=0L;
               }
               return(GMEERR);
          }
          dfaUpdateV(grpptr,grprlen(grpptr->nforums));
          dfaRstBlk();
          work->curhook=NOIDX;
          work->state1=HDLHOOK;
          return(GMEAGAIN);
     case HDLHOOK:
          grpptr=getgrp(grpbuf->grpid);
          if (grpptr != NULL
           && (work->curhook=notmodg(work->curhook,grpptr)) != NOIDX) {
               return(GMEAGAIN);
          }
          work->state1=START;
          return(GMEOK);
     default:
          state_error(1,"gme1modg()",work->state1);
     }
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gme1mgf(                           /* modify a group's forums              */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid,                      /*   group ID to modify                 */
INT nfors,                         /*   number of forums in list           */
const USHORT *forlst)              /*   new list of forums                 */
{
     struct forgrp *grpptr;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(nfors >= 0 && nfors <= maxfgrp);
     ASSERT(nfors == 0 || forlst != NULL);
     switch (work->state1) {
     case START:
          if (!gmeGrpExist(grpid)) {
               return(GMENFND);
          }
          grpptr=getgrp(grpid);
          if ((grpptr->nforums=nfors) != 0) {
               movmem(forlst,grpptr->forarr,nfors*sizeof(USHORT));
               grpfix(grpptr);
          }
          dfaSetBlk(grpbb);
          if (grpid == ROOTGRP) {
               if (dfaAcqEQ(NULL,&grpid,GGIDKY)) {
                    if (grpptr->nforums == 0) {
                         dfaDelete();
                    }
                    else {
                         dfaUpdateV(grpptr,grprlen(grpptr->nforums));
                    }
               }
               else if (grpptr->nforums != 0) {
                    grpptr->parid=NOGRP;
                    dfaInsertV(grpptr,grprlen(grpptr->nforums));
                    grpptr->parid=ROOTGRP;
               }
          }
          else if (dfaAcqEQ(NULL,&grpid,GGIDKY)) {
               dfaUpdateV(grpptr,grprlen(grpptr->nforums));
          }
          else {
               dfaRstBlk();
               return(GMEERR);
          }
          dfaRstBlk();
          work->curhook=NOIDX;
          work->state1=HDLHOOK;
          return(GMEAGAIN);
     case HDLHOOK:
          grpptr=getgrp(grpid);
          if (grpptr != NULL
           && (work->curhook=notmodg(work->curhook,grpptr)) != NOIDX) {
               return(GMEAGAIN);
          }
          work->state1=START;
          return(GMEOK);
     default:
          state_error(1,"gme1mgf()",work->state1);
     }
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gme1adgf(                          /* add to a group's forums              */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid,                      /*   group ID to modify                 */
USHORT forum)                      /*   new forum ID to add                */
{
     struct forgrp *grpptr;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state1) {
     case START:
          if (!gmeGrpExist(grpid) || !fidxst(forum)) {
               return(GMENFND);
          }
          grpptr=getgrp(grpid);
          if (grpptr->nforums == maxfgrp) {
               return(GME2MFR);
          }
          if (!addgrpf(grpptr,forum)) {
               return(GMEOK);
          }
          dfaSetBlk(grpbb);
          if (dfaAcqEQ(NULL,&grpid,GGIDKY)) {
               dfaUpdateV(grpptr,grprlen(grpptr->nforums));
          }
          else if (grpid == ROOTGRP) {
               grpptr->parid=NOGRP;
               dfaInsertV(grpptr,grprlen(grpptr->nforums));
               grpptr->parid=ROOTGRP;
          }
          else {
               dfaRstBlk();
               return(GMEERR);
          }
          dfaRstBlk();
          work->curhook=NOIDX;
          work->state1=HDLHOOK;
          return(GMEAGAIN);
     case HDLHOOK:
          grpptr=getgrp(grpid);
          if (grpptr != NULL
           && (work->curhook=notmodg(work->curhook,grpptr)) != NOIDX) {
               return(GMEAGAIN);
          }
          work->state1=START;
          return(GMEOK);
     default:
          state_error(1,"gme1adgf()",work->state1);
     }
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gme1dlgf(                          /* delete a forum from a group          */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid,                      /*   group ID to modify                 */
USHORT forum)                      /*   forum ID to delete                 */
{
     struct forgrp *grpptr;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state1) {
     case START:
          if (!gmeGrpExist(grpid)) {
               return(GMENFND);
          }
          grpptr=getgrp(grpid);
          if (!delgrpf(grpptr,forum)) {
               return(GMEOK);
          }
          dfaSetBlk(grpbb);
          if (dfaAcqEQ(NULL,&grpid,GGIDKY)) {
               if (grpid == ROOTGRP && grpptr->nforums == 0) {
                    dfaDelete();
               }
               else {
                    dfaUpdateV(grpptr,grprlen(grpptr->nforums));
               }
          }
          else {
               dfaRstBlk();
               return(GMEERR);
          }
          dfaRstBlk();
          work->curhook=NOIDX;
          work->state1=HDLHOOK;
          return(GMEAGAIN);
     case HDLHOOK:
          grpptr=getgrp(grpid);
          if (grpptr != NULL
           && (work->curhook=notmodg(work->curhook,grpptr)) != NOIDX) {
               return(GMEAGAIN);
          }
          work->state1=START;
          return(GMEOK);
     default:
          state_error(1,"gme1dlgf()",work->state1);
     }
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gme1delg(                          /* delete a forum group                 */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT grpid)                      /*   group ID to delete                 */
{
     INT i;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     switch (work->state1) {
     case START:
          if (grpid == ROOTGRP) {
               return(GMEERR);
          }
          if (!gmeGrpExist(grpid)) {
               return(GMENFND);
          }
          dfaSetBlk(grpbb);
          pnamky.pargrp=grpid;
          *pnamky.name='\0';
          if (dfaAcqGE(NULL,&pnamky,GPNMKY) && grpdptr->parid == grpid) {
               dfaRstBlk();
               return(GMENDEL);
          }
          if ((i=grpinmem(grpid)) != NOIDX) {
               grplru[i]=0L;
          }
          if (!dfaAcqEQ(NULL,&grpid,GGIDKY)) {
               dfaRstBlk();
               return(GMEERR);
          }
          notdlg(grpdptr);
          dfaDelete();
          dfaRstBlk();
          work->curhook=NOIDX;
          work->state1=HDLHOOK;
          return(GMEAGAIN);
     case HDLHOOK:
          if ((work->curhook=notdlgl(work->curhook,grpid)) != NOIDX) {
               return(GMEAGAIN);
          }
          work->state1=START;
          return(GMEOK);
     default:
          state_error(1,"gme1dlgf()",work->state1);
     }
     ASSERT(FALSE);
     return(GMEERR);
}

INT                                /*   returns standard GME status codes  */
gme1wnm(                           /* send a new message                   */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure           */
const CHAR *text,                  /*   message body text                  */
const CHAR *filatt)                /*   path+file name of attachment       */
{
     FILE *fp;
     const CHAR *attfnm;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     ASSERT(strlen(text) < TXTLEN);
     while (TRUE) {
          switch (work->state1) {
          case START:
               if (msg->flags&FILATT) {
                    work->state1=ATTACH;
               }
               else {
                    work->state1=WRITING;
               }
               break;
          case ATTACH:
               ASSERT(filatt != NULL);
               work->state1=WRITING;
               if (msg->flags&FILIND) {
                    stlcpy(work->cpyatt,attpfn(msg->forum,msg->msgid),GCMAXPTH);
                    fp=fopen(work->cpyatt,FOPWA);
                    if (fp == NULL) {
                         work->state1=START;
                         return(GMENOAT);
                    }
                    fputs(normspec((CHAR *)tmpbuf,filatt),fp);
                    fclose(fp);
                    return(GMEAGAIN);
               }
               else {
                    attfnm=attpfn(msg->forum,msg->msgid);
                    unlink(attfnm);
                    if (rename(filatt,attfnm) != 0) {
                         work->state1=START;
                         return(GMENOAT);
                    }
                    break;
               }
          case WRITING:
               if (msg->forum == EMLID) {
                    dfaSetBlk(emlbb);
                    insnmsg(emdptr,msg,text,work->appinf);
                    dfaRstBlk();
                    ++sv.emlopn;
               }
               else {
                    setcurf(msg->forum);
                    insnmsg(cfdptr,msg,text,work->appinf);
                    dfaRstBlk();
                    foradd(msg);
                    ++sv.sigopn;
               }
               *work->cpyatt='\0';
               work->curhook=NOIDX;
               work->state1=HDLHOOK;
          case HDLHOOK:
               work->curhook=notnwml(work->curhook,msg,text,work->appinf);
               if (work->curhook != NOIDX) {
                    return(GMEAGAIN);
               }
               work->state1=START;
               return(GMEOK);
          default:
               state_error(1,"gme1wnm()",work->state1);
          }
     }
}

INT                                /*   returns standard GME status codes  */
gme1wap(                           /* write message as pointer to another  */
VOID *pWork,                       /*   GME work space (provided by caller)*/
USHORT orgfor,                     /*   forum ID of original message       */
LONG orgmid,                       /*   message ID of original message     */
GBOOL truecopy,                    /*   is pointer a complete copy?        */
struct message *msg,               /*   message header structure           */
const CHAR *filatt)                /*   path+file name of attachment       */
{
     GBOOL ok;
     struct fmidky orgptr;
     FILE *fp;
     const CHAR *attfnm;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(orgfor == EMLID || fidxst(orgfor));
     ASSERT(msg->forum == EMLID || fidxst(msg->forum));
     while (TRUE) {
          switch (work->state1) {
          case START:
               orgptr.forum=orgfor;
               orgptr.msgid=orgmid;
               if (orgfor == EMLID) {
                    dfaSetBlk(emlbb);
                    ok=dfaQueryEQ(&orgmid,EMIDKY);
               }
               else {
                    setcurf(orgfor);
                    ok=dfaQueryEQ(&orgptr,FFMKY);
               }
               dfaRstBlk();
               if (!ok) {
                    work->state1=START;
                    return(GMENFND);
               }
               msg->flags|=ISMPTR;
               if (truecopy) {
                    msg->flags|=ISTCPY;
               }
               if (msg->flags&FILATT && !truecopy) {
                    work->state1=ATTACH;
               }
               else {
                    work->state1=WRITING;
               }
               break;
          case ATTACH:
               ASSERT(filatt != NULL);
               work->state1=WRITING;
               if (msg->flags&FILIND) {
                    stlcpy(work->cpyatt,attpfn(msg->forum,msg->msgid),GCMAXPTH);
                    fp=fopen(work->cpyatt,FOPWA);
                    if (fp == NULL) {
                         work->state1=START;
                         return(GMENOAT);
                    }
                    fputs(normspec((CHAR *)tmpbuf,filatt),fp);
                    fclose(fp);
                    return(GMEAGAIN);
               }
               else {
                    attfnm=attpfn(msg->forum,msg->msgid);
                    unlink(attfnm);
                    if (rename(filatt,attfnm) != 0) {
                         work->state1=START;
                         return(GMENOAT);
                    }
                    break;
               }
          case WRITING:
               orgptr.forum=orgfor;
               orgptr.msgid=orgmid;
               if (msg->forum == EMLID) {
                    dfaSetBlk(emlbb);
                    insmptr(emdptr,msg,&orgptr);
                    dfaRstBlk();
                    ++sv.emlopn;
               }
               else {
                    setcurf(msg->forum);
                    insmptr(cfdptr,msg,&orgptr);
                    dfaRstBlk();
                    foradd(msg);
                    ++sv.sigopn;
               }
               *work->cpyatt='\0';
               work->curhook=NOIDX;
               work->state1=HDLHOOK;
          case HDLHOOK:
               work->curhook=notnwml(work->curhook,msg,NULL,NULL);
               if (work->curhook != NOIDX) {
                    return(GMEAGAIN);
               }
               work->state1=START;
               return(GMEOK);
          default:
               state_error(1,"gme1wap()",work->state1);
          }
     }
}

INT                                /*   returns standard GME status codes  */
gme1rti(                           /* read info on a thread (orig message) */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT direct,                        /*   direction (0=same, 1=next, -1=prev)*/
USHORT *nmsgs,                     /*   number of messages in thread       */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     GBOOL found;
     struct msgdsk *tmpdsk;
     UINT tmplen;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->rdctx.fid == EMLID || nmsgs != NULL);
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(work->rdctx.fid == EMLID || fidxst(work->rdctx.fid));
     switch (work->state1) {
     case START:
          if (work->rdctx.fid == EMLID) {
               dfaSetBlk(emlbb);
               tmpdsk=emdptr;
               etidky.thrid=work->rdctx.tid;
               switch (direct) {
               case 0:
                    etidky.msgid=FIRSTM;
                    found=dfaAcqGE(NULL,&etidky,ETIDKY)
                       && emdptr->thrid == etidky.thrid;
                    break;
               case 1:
                    etidky.msgid=LASTM;
                    found=dfaAcqGT(NULL,&etidky,ETIDKY);
                    break;
               case -1:
                    etidky.msgid=FIRSTM;
                    found=dfaAcqLT(NULL,&etidky,ETIDKY);
                    break;
               }
               if (!found) {
                    work->state1=START;
                    dfaRstBlk();
                    return(GMENFND);
               }
          }
          else {
               setcurf(work->rdctx.fid);
               tmpdsk=cfdptr;
               ftidky.forum=work->rdctx.fid;
               ftidky.thrid=work->rdctx.tid;
               switch (direct) {
               case 0:
                    ftidky.msgid=FIRSTM;
                    found=dfaAcqGE(NULL,&ftidky,FFTKY);
              found=found && cfdptr->thrid == ftidky.thrid;
                    break;
               case 1:
                    ftidky.msgid=LASTM;
                    found=dfaAcqGT(NULL,&ftidky,FFTKY);
                    break;
               case -1:
                    ftidky.msgid=FIRSTM;
                    found=dfaAcqLT(NULL,&ftidky,FFTKY);
                    break;
               }
               if (!found) {
                    work->state1=START;
                    dfaRstBlk();
                    return(GMENFND);
               }
          }
          tmplen=dfaLastLen();
          if (tmpdsk->flags&ISMPTR) {
               work->state1=START;
               if (getbdy(tmpdsk,msg,text)) {
                    if (msg->forum != EMLID) {
                         *nmsgs=ninthr(msg->forum,msg->thrid);
                    }
                    dfaRstBlk();
                    return(GMEOK);
               }
               else {
                    whackit(tmpdsk);
                    dfaRstBlk();
                    switch (direct) {
                    case 0:
                         return(GMENFND);
                    case -1:
                    case 1:
                         return(GMEAGAIN);
                    default:
                         ASSERT(FALSE);
                    }
               }
          }
          else {
               dsk2msg(tmpdsk,msg);
               gettxt(tmpdsk,text,utlapi,tmplen);
               if (msg->forum != EMLID) {
                    *nmsgs=ninthr(msg->forum,msg->thrid);
               }
               work->state1=START;
               dfaRstBlk();
               return(GMEOK);
          }
     default:
          state_error(1,"gme1rti()",work->state1);
     }
     ASSERT(FALSE);
     return(GMEERR);               /* should never happen                  */
}

INT                                /*   returns standard GME status codes  */
gme1rdm(                           /* read a message utility               */
VOID *pWork,                       /*   GME work space (provided by caller)*/
INT direct,                        /*   read direction code                */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     UINT bdyfor,tmplen;
     LONG bdymsg;
     LONG tmpfpos;
     struct msgdsk *tmpdsk;
     struct fmidky fmkey;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(work->rdctx.fid == EMLID || fidxst(work->rdctx.fid));
     switch (work->state1) {
     case START:
          if (work->rdctx.fid == EMLID) {
               dfaSetBlk(emlbb);
               tmpdsk=emdptr;
               switch (work->rdctx.seq) {
               case ESQTOU:
                    stlcpy(umidky.usrid,work->rdctx.uid,UIDSIZ);
                    umidky.msgid=work->rdctx.mid;
                    if (!acqmsg(emdptr,&umidky,ETOMKY,direct,pWork,touchk)) {
                         work->state1=START;
                         dfaRstBlk();
                         return(GMENFND);
                    }
                    break;
               case ESQFRU:
                    stlcpy(umidky.usrid,work->rdctx.uid,UIDSIZ);
                    umidky.msgid=work->rdctx.mid;
                    if (!acqmsg(emdptr,&umidky,EFRMKY,direct,pWork,fruchk)) {
                         work->state1=START;
                         dfaRstBlk();
                         return(GMENFND);
                    }
                    break;
               case ESQTHR:
                    etidky.thrid=work->rdctx.tid;
                    etidky.msgid=work->rdctx.mid;
                    if (!acqmsg(emdptr,&etidky,ETIDKY,direct,pWork,ethchk)) {
                         work->state1=START;
                         dfaRstBlk();
                         return(GMENFND);
                    }
                    break;
               default:
                    ASSERT(FALSE);
               }
          }
          else {
               setcurf(work->rdctx.fid);
               tmpdsk=cfdptr;
               switch (work->rdctx.seq) {
               case FSQFOR:
                    fmidky.forum=work->rdctx.fid;
                    fmidky.msgid=work->rdctx.mid;
                    if (!acqmsg(cfdptr,&fmidky,FFMKY,direct,pWork,forchk)) {
                         work->state1=START;
                         dfaRstBlk();
                         return(GMENFND);
                    }
                    break;
               case FSQTHR:
                    ftidky.forum=work->rdctx.fid;
                    ftidky.thrid=work->rdctx.tid;
                    ftidky.msgid=work->rdctx.mid;
                    if (!acqmsg(cfdptr,&ftidky,FFTKY,direct,pWork,fthchk)) {
                         work->state1=START;
                         dfaRstBlk();
                         return(GMENFND);
                    }
                    break;
               default:
                    ASSERT(FALSE);
               }
          }
          tmplen=dfaLastLen();
          tmpfpos=dfaAbs();
          if (tmpdsk->flags&ISMPTR) {
               work->state1=START;
               memcpy(&fmkey,tmpdsk->info,sizeof(fmkey));
               bdyfor=fmkey.forum;
               bdymsg=fmkey.msgid;
               gmelok(pWork,bdyfor,bdymsg);
               if (getbdy(tmpdsk,msg,text)) {
                    work->rdfpos=tmpfpos;
                    estctx(&work->rdctx,msg);
                    dfaRstBlk();
                    return(GMEOK);
               }
               else {
                    gmeulk(pWork,bdyfor,bdymsg);
                    whackit(tmpdsk);
                    dfaRstBlk();
                    switch (direct) {
                    case RDEXCT:
                         return(GMENFND);
                    case RDLEGT:
                    case RDLTGE:
                    case RDGELT:
                    case RDGTLE:
                    case RDNEXT:
                    case RDPREV:
                         return(GMEAGAIN);
                    default:
                         ASSERT(FALSE);
                    }
               }
          }
          else {
               dsk2msg(tmpdsk,msg);
               gettxt(tmpdsk,text,utlapi,tmplen);
               work->rdfpos=tmpfpos;
               estctx(&work->rdctx,msg);
               work->state1=START;
               dfaRstBlk();
               return(GMEOK);
          }
     default:
          state_error(1,"gme1rdm()",work->state1);
     }
     ASSERT(FALSE);
     return(GMEERR);               /* should never happen                  */
}

INT                                /*   returns standard GME status codes  */
gme1rdg(                           /* read a message given global ID       */
VOID *pWork,                       /*   GME work space (provided by caller)*/
const struct globid *gmid,         /*   global message ID of parent        */
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     UINT bdyfor,tmpfid,tmplen;
     LONG bdymsg,tmpfpos;
     struct msgdsk *tmpdsk;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(work->rdctx.fid == EMLID || fidxst(work->rdctx.fid));
     switch (work->state1) {
     case START:
          if (work->rdctx.fid == EMLID) {
               tmpfid=(USHORT)gmid->sysid;
               if (tmpfid != EMLID && !fidxst(tmpfid)) {
                    return(GMENFND);
               }
          }
          else {
               tmpfid=work->rdctx.fid;
          }
          if (tmpfid == EMLID) {
               dfaSetBlk(emlbb);
               tmpdsk=emdptr;
               if (!dfaAcqEQ(NULL,&gmid->msgid,EMIDKY)
                || !sameas(tmpdsk->from,work->rdctx.uid)) {
                    dfaRstBlk();
                    return(GMENFND);
               }
          }
          else {
               setcurf(tmpfid);
               tmpdsk=cfdptr;
               if (work->rdctx.fid == EMLID) {
                    fmidky.forum=tmpfid;
                    fmidky.msgid=gmid->msgid;
                    if (!dfaAcqEQ(NULL,&fmidky,FFMKY)) {
                         dfaRstBlk();
                         return(GMENFND);
                    }
               }
               else {
                    if (!dfaAcqEQ(NULL,gmid,FGIDKY)
                     || tmpdsk->forum != tmpfid) {
                         dfaRstBlk();
                         return(GMENFND);
                    }
               }
          }
          tmplen=dfaLastLen();
          tmpfpos=dfaAbs();
          if (tmpdsk->flags&ISMPTR) {
               work->state1=START;
               bdyfor=((struct fmidky *)tmpdsk->info)->forum;
               bdymsg=((struct fmidky *)tmpdsk->info)->msgid;
               gmelok(pWork,bdyfor,bdymsg);
               if (getbdy(tmpdsk,msg,text)) {
                    work->rdfpos=tmpfpos;
                    estctx(&work->rdctx,msg);
                    dfaRstBlk();
                    return(GMEOK);
               }
               else {
                    gmeulk(pWork,bdyfor,bdymsg);
                    whackit(tmpdsk);
                    dfaRstBlk();
                    return(GMENFND);
               }
          }
          else {
               dsk2msg(tmpdsk,msg);
               gettxt(tmpdsk,text,utlapi,tmplen);
               work->rdfpos=tmpfpos;
               estctx(&work->rdctx,msg);
               work->state1=START;
               dfaRstBlk();
               return(GMEOK);
          }
     default:
          state_error(1,"gme1rdg()",work->state1);
     }
     ASSERT(FALSE);
     return(GMEERR);               /* should never happen                  */
}

GBOOL
gidxst(                            /* does message with global ID exist    */
USHORT forum,                      /*   in this forum                      */
const struct globid *globid)       /*   with this global ID                */
{
     GBOOL found;

     if (forum == EMLID) {
          return(FALSE);
     }
     setcurf(forum);
     found=dfaQueryEQ(globid,FGIDKY);
     dfaRstBlk();
     return(found);
}

GBOOL                              /*   returns TRUE if could get message  */
grabmsg(                           /* grab message using file position     */
VOID *pWork,                       /*   GME work space (provided by caller)*/
struct message *msg,               /*   message header structure buffer    */
CHAR *text)                        /*   message body text buffer           */
{
     UINT bdyfor,tmplen;
     LONG bdymsg,tmpfpos;
     struct msgdsk *tmpdsk;
     struct fmidky fmk;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     if (work->rdctx.fid == EMLID) {
          dfaSetBlk(emlbb);
          tmpdsk=emdptr;
     }
     else {
          setcurf(work->rdctx.fid);
          tmpdsk=cfdptr;
     }
     if (work->rdfpos == 0L) {
          if (work->rdctx.fid == EMLID) {
               if (!dfaAcqEQ(NULL,&work->rdctx.mid,EMIDKY)) {
                    dfaRstBlk();
                    return(FALSE);
               }
          }
          else {
               fmidky.forum=work->rdctx.fid;
               fmidky.msgid=work->rdctx.mid;
               if (!dfaAcqEQ(NULL,&fmidky,FFMKY)) {
                    dfaRstBlk();
                    return(FALSE);
               }
          }
     }
     else {
          if (!dfaAcqAbs(NULL,work->rdfpos,0)) {
               work->rdfpos=0L;
               dfaRstBlk();
               return(FALSE);
          }
     }
     tmplen=dfaLastLen();
     tmpfpos=dfaAbs();
     if (tmpdsk->flags&ISMPTR) {
          movmem(tmpdsk->info,&fmk,sizeof(struct fmidky));
          bdyfor=fmk.forum;           /* using fmk for alignment       */
          bdymsg=fmk.msgid;
          gmelok(pWork,bdyfor,bdymsg);
          if (getbdy(tmpdsk,msg,text)) {
               work->rdfpos=tmpfpos;
          }
          else {
               gmeulk(pWork,bdyfor,bdymsg);
               whackit(tmpdsk);
               work->rdfpos=0L;
               dfaRstBlk();
               return(FALSE);
          }
     }
     else {
          dsk2msg(tmpdsk,msg);
          gettxt(tmpdsk,text,utlapi,tmplen);
          work->rdfpos=tmpfpos;
     }
     dfaRstBlk();
     return(TRUE);
}

GBOOL                              /*   returns TRUE if update successful  */
updmsg(                            /* update message on disk               */
VOID *pWork,                       /*   GME work space (provided by caller)*/
const struct message *msg,         /*   message header structure buffer    */
const CHAR *text,                  /*   message body text buffer           */
const CHAR *appinf)                /*   app-defined info buffer            */
{
     struct msgdsk *tmpdsk;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     ASSERT(work->rdfpos != 0L);
     if (work->rdctx.fid == EMLID) {
          dfaSetBlk(emlbb);
          tmpdsk=emdptr;
     }
     else {
          setcurf(work->rdctx.fid);
          tmpdsk=cfdptr;
     }
     if (dfaAcqAbs(NULL,work->rdfpos,0)) {
          if (tmpdsk->flags&ISMPTR) {
               if (tmpdsk->flags&ISTCPY) {
                    dfaRstBlk();
                    if (fmidky.forum == EMLID) {
                         dfaSetBlk(emlbb);
                         if (dfaAcqEQ(NULL,&fmidky.msgid,EMIDKY)) {
                              updamsg(emdptr,msg,text,appinf);
                         }
                    }
                    else {
                         setcurf(fmidky.forum);
                         if (dfaAcqEQ(NULL,&fmidky,FFMKY)) {
                              updamsg(cfdptr,msg,text,appinf);
                         }
                    }
               }
               else {
                    fmidky=*((struct fmidky *)tmpdsk->info);
                    updmptr(tmpdsk,msg,&fmidky);
               }
          }
          else {
               updamsg(tmpdsk,msg,text,appinf);
          }
          dfaRstBlk();
          return(TRUE);
     }
     work->rdfpos=0L;
     dfaRstBlk();
     return(FALSE);
}

GBOOL                              /*   returns TRUE if delete successful  */
gme1dlm(                           /* delete message (and attachment)      */
VOID *pWork)                       /*   GME work space (provided by caller)*/
{
     struct msgdsk *tmpdsk;
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&work->rdctx));
     ASSERT(work->rdfpos != 0L);
     if (work->rdctx.fid == EMLID) {
          dfaSetBlk(emlbb);
          tmpdsk=emdptr;
     }
     else {
          setcurf(work->rdctx.fid);
          tmpdsk=cfdptr;
     }
     if (dfaAcqAbs(NULL,work->rdfpos,0)) {
          whackit(tmpdsk);
          work->rdfpos=0L;
          dfaRstBlk();
          return(TRUE);
     }
     work->rdfpos=0L;
     dfaRstBlk();
     return(FALSE);
}

VOID
whackit(                           /* delete current msg & att & upd counts*/
const struct msgdsk *msg)          /*   current message header             */
{
     lowhack(msg);
     if (msg->forum == EMLID) {
          --sv.emlopn;
     }
     else {
          --sv.sigopn;
          fordel(msg);
     }
}

VOID
lowhack(                           /* low-level whack message              */
const struct msgdsk *msg)          /*   current message header             */
{
     dfaDelete();
     if ((msg->flags&FILATT) && !(msg->flags&ISTCPY)) {
          unlink(attpfn(msg->forum,msg->msgid));
     }
     notdlml(msg->forum,msg->msgid);
}

GBOOL
touchk(                            /* check if message is to user          */
VOID *pWork,                       /*   given work area                    */
const struct msgdsk *msg)          /*   and message from disk              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     return(sameas(work->rdctx.uid,msg->to));
}

GBOOL
fruchk(                            /* check if message is from user        */
VOID *pWork,                       /*   given work area                    */
const struct msgdsk *msg)          /*   and message from disk              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     return(sameas(work->rdctx.uid,msg->from));
}

GBOOL
ethchk(                            /* check if message is in E-mail thread */
VOID *pWork,                       /*   given work area                    */
const struct msgdsk *msg)          /*   and message from disk              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     return(work->rdctx.tid == msg->thrid
         && (sameas(work->rdctx.uid,msg->from)
          || sameas(work->rdctx.uid,msg->to)));
}

GBOOL
forchk(                            /* check if message is in forum         */
VOID *pWork,                       /*   given work area                    */
const struct msgdsk *msg)          /*   and message from disk              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     return(work->rdctx.fid == msg->forum);
}

GBOOL
fthchk(                            /* check if message is in forum thread  */
VOID *pWork,                       /*   given work area                    */
const struct msgdsk *msg)          /*   and message from disk              */
{
     struct gmework *work=(struct gmework *)BYTEALIGN(pWork);

     return(work->rdctx.fid == msg->forum && work->rdctx.tid == msg->thrid);
}

GBOOL                              /*   returns TRUE if record found       */
acqmsg(                            /* acquire message from current file    */
struct msgdsk *msgbuf,             /*   message buffer                     */
VOID *keybuf,                      /*   key to acquire using               */
INT keynum,                        /*   key number to acquire on           */
INT direct,                        /*   direction code                     */
VOID *pWork,                       /*   current work area                  */
GBOOL (*keychk)(VOID *pWork,const struct msgdsk *msg))
                                   /*   check function                     */
{
     ASSERT(msgbuf != NULL);
     ASSERT(pWork != NULL);
     ASSERT(gmerqopn(pWork));
     ASSERT(ctxok(&((struct gmework *)BYTEALIGN(pWork))->rdctx));
     switch (direct) {
     case RDEXCT:
          return(dfaAcqEQ(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf));
     case RDLEGT:
          return((dfaAcqLE(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf))
              || (dfaAcqGT(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf)));
     case RDLTGE:
          return((dfaAcqLT(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf))
              || (dfaAcqGE(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf)));
     case RDGELT:
          return((dfaAcqGE(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf))
              || (dfaAcqLT(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf)));
     case RDGTLE:
          return((dfaAcqGT(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf))
              || (dfaAcqLE(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf)));
     case RDNEXT:
          return(dfaAcqGT(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf));
     case RDPREV:
          return(dfaAcqLT(msgbuf,keybuf,keynum) && keychk(pWork,msgbuf));
     default:
          ASSERT(FALSE);
     }
     return(FALSE);
}

GBOOL                              /*   returns FALSE if body not found    */
getbdy(                            /* get body of pointer message          */
const struct msgdsk *dsk,          /*   pointer message in on-disk format  */
struct message *msg,               /*   message buffer to use              */
CHAR *text)                        /*   text buffer to use                 */
{
     INT i;
     GBOOL rc,found,truecopy;
     DFAFILE *tmpbb;

     ASSERT(dsk != NULL);
     ASSERT(msg != NULL);
     ASSERT(text != NULL);
     movmem(textptr(dsk),&fmidky,sizeof(struct fmidky));
     if (fmidky.forum == EMLID) {
          tmpbb=emlbb;
          dfaSetBlk(tmpbb);
          found=dfaQueryEQ(&fmidky.msgid,EMIDKY);
     }
     else if ((i=fididx(fmidky.forum)) != NOIDX) {
          tmpbb=fdfarr[defarr[i]->dfnum];
          dfaSetBlk(tmpbb);
          found=dfaQueryEQ(&fmidky,FFMKY);
     }
     else {
          return(FALSE);
     }
     if (found) {
          truecopy=((dsk->flags&ISTCPY) != 0L);
          if (!truecopy) {
               dsk2msg(dsk,msg);
          }
          dfaAbsRec(NULL,0);          /* don't care which key                 */
          dsk=(const struct msgdsk *)tmpbb->data;
          if (truecopy) {
               dsk2msg(dsk,msg);
          }
          gettxt(dsk,text,utlapi,dfaLastLen());
          rc=TRUE;
     }
     else {
          rc=FALSE;
     }
     dfaRstBlk();
     return(rc);
}

VOID
estctx(                            /* establish current read context       */
struct rdctx *rdctx,               /*   context structure to establish     */
const struct message *msg)         /*   message to use as context          */
{
     rdctx->mid=msg->msgid;
     rdctx->tid=msg->thrid;
     /* User-ID and forum ID don't change */
}
