/*************************************************************************\
* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/

#include <stdio.h>
#include <stddef.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <math.h>
#include <ctype.h>

#include "cantProceed.h"
#include "cvtFast.h"
#include "epicsAssert.h"
#include "dbDefs.h"
#include "dbmf.h"
#include "ellLib.h"
#include "epicsPrint.h"
#include "epicsStdio.h"
#include "epicsStdlib.h"
#include "epicsString.h"
#include "errlog.h"
#include "gpHash.h"
#include "osiFileName.h"
#include "postfix.h"

#define DBFLDTYPES_GBLSOURCE
#define SPECIAL_GBLSOURCE

#include "dbChannel.h"
#include "dbFldTypes.h"
#include "dbStaticLib.h"
#include "dbStaticPvt.h"
#include "devSup.h"
#include "drvSup.h"
#include "link.h"
#include "special.h"

#include "dbCommon.h"
#include "dbJLink.h"

int dbStaticDebug = 0;
static char *pNullString = "";
#define messagesize     276
#define RPCL_LEN INFIX_TO_POSTFIX_SIZE(80)

/* Must be big enough to hold a 64-bit integer in base 10, but in
 * the future when fields hold large JSON objects this fixed size
 * allocation will probably have to become variable sized.
 */
STATIC_ASSERT(messagesize >= 21);

static const char *ppstring[5]={" NPP"," PP"," CA"," CP"," CPP"};
static const char *msstring[4]={" NMS"," MS"," MSI"," MSS"};

const maplinkType pamaplinkType[LINK_NTYPES] = {
    {"CONSTANT",CONSTANT},
    {"PV_LINK",PV_LINK},
    {"VME_IO",VME_IO},
    {"CAMAC_IO",CAMAC_IO},
    {"AB_IO",AB_IO},
    {"GPIB_IO",GPIB_IO},
    {"BITBUS_IO",BITBUS_IO},
    {"MACRO_LINK",MACRO_LINK},
    {"JSON_LINK",JSON_LINK},
    {"PN_LINK",PN_LINK},
    {"DB_LINK",DB_LINK},
    {"CA_LINK",CA_LINK},
    {"INST_IO",INST_IO},
    {"BBGPIB_IO",BBGPIB_IO},
    {"RF_IO",RF_IO},
    {"VXI_IO",VXI_IO}
};

/*forward references for private routines*/
static long dbAddOnePath (DBBASE *pdbbase, const char *path, unsigned length);

/* internal routines*/
static FILE *openOutstream(const char *filename)
{
    FILE *stream;
    errno = 0;
    stream = fopen(filename,"w");
    if(!stream) {
        fprintf(stderr,ERL_ERROR " opening %s %s\n",filename,strerror(errno));
        return 0;
    }
    return stream;
}

static void finishOutstream(FILE *stream)
{
    if(stream==stdout) {
        fflush(stdout);
    } else {
        if(fclose(stream)) fprintf(stderr,"fclose error %s\n",strerror(errno));
    }
}

void dbFreeLinkContents(struct link *plink)
{
    char *parm = NULL;

    switch(plink->type) {
        case CONSTANT: free((void *)plink->value.constantStr); break;
        case MACRO_LINK: free((void *)plink->value.macro_link.macroStr); break;
        case PV_LINK: free((void *)plink->value.pv_link.pvname); break;
        case JSON_LINK:
            dbJLinkFree(plink->value.json.jlink);
            parm = plink->value.json.string;
            break;
        case VME_IO: parm = plink->value.vmeio.parm; break;
        case CAMAC_IO: parm = plink->value.camacio.parm; break;
        case AB_IO: parm = plink->value.abio.parm; break;
        case GPIB_IO: parm = plink->value.gpibio.parm; break;
        case BITBUS_IO: parm = plink->value.bitbusio.parm;break;
        case INST_IO: parm = plink->value.instio.string; break;
        case BBGPIB_IO: parm = plink->value.bbgpibio.parm;break;
        case RF_IO: break;
        case VXI_IO: parm = plink->value.vxiio.parm; break;
        default:
         epicsPrintf("dbFreeLink called but link type %d unknown\n", plink->type);
    }
    if(parm && (parm != pNullString)) free((void *)parm);
    if(plink->text) free(plink->text);
    plink->lset = NULL;
    plink->text = NULL;
    memset(&plink->value, 0, sizeof(union value));
}

void dbFreePath(DBBASE *pdbbase)
{
    ELLLIST     *ppathList;
    dbPathNode  *pdbPathNode;

    if(!pdbbase) return;
    ppathList = (ELLLIST *)pdbbase->pathPvt;
    if(!ppathList) return;
    while((pdbPathNode = (dbPathNode *)ellFirst(ppathList))) {
        ellDelete(ppathList,&pdbPathNode->node);
        free((void *)pdbPathNode->directory);
        free((void *)pdbPathNode);
    }
    free((void *)ppathList);
    pdbbase->pathPvt = 0;
    return;
}


static void zeroDbentry(DBENTRY *pdbentry)
{
    /*NOTE that pdbbase and message MUST NOT be set to NULL*/
    pdbentry->precordType=NULL;
    pdbentry->pflddes=NULL;
    pdbentry->precnode=NULL;
    pdbentry->pfield=NULL;
    pdbentry->indfield=0;
}

static char *getpMessage(DBENTRY *pdbentry)
{
    char *msg = pdbentry->message;

    if (!msg) {
        msg = dbCalloc(1, messagesize);
        pdbentry->message = msg;
    }
    else
        *msg = '\0';
    return msg;
}

static
void dbMsgCpy(DBENTRY *pdbentry, const char *msg)
{
    getpMessage(pdbentry);
    strncpy(pdbentry->message, msg, messagesize-1);
    pdbentry->message[messagesize-1] = '\0';
}

static
void dbMsgNCpy(DBENTRY *pdbentry, const char *msg, size_t len)
{
    getpMessage(pdbentry);
    if (len >= messagesize)
        len = messagesize-1;            /* FIXME: Quietly truncates */

    strncpy(pdbentry->message, msg, len);
    pdbentry->message[len] = '\0';
}

void dbMsgPrint(DBENTRY *pdbentry, const char *fmt, ...)
{
    va_list args;
    getpMessage(pdbentry);
    va_start(args, fmt);
    epicsVsnprintf(pdbentry->message, messagesize, fmt, args);
    va_end(args);
}

static void ulongToHexString(epicsUInt32 source, char *pdest)
{
    static const char hex_digit_to_ascii[16] = "0123456789abcdef";
    epicsUInt32 val,temp;
    char digit[10];
    int i,j;

    if (source==0) {
        strcpy(pdest,"0x0");
        return;
    }
    *pdest++ = '0'; *pdest++ = 'x';
    val = source;
    for (i=0; val!=0; i++) {
        temp = val/16;
        digit[i] = hex_digit_to_ascii[val - temp*16];
        val = temp;
    }
    for (j=i-1; j>=0; j--) {
        *pdest++ = digit[j];
    }
    *pdest = 0;
    return;
}

static void realToString(double value, char *preturn, int isdouble)
{
    static const double delta[2] = {1e-6, 1e-15};
    static const int precision[2] = {6, 14};
    double  absvalue;
    int     logval,prec;
    size_t  end;
    char    tstr[30];
    char    *ptstr = &tstr[0];
    int     round;
    int     ise = FALSE;
    char    *loce = NULL;

    if (value == 0) {
        strcpy(preturn, "0");
        return;
    }

    absvalue = value < 0 ? -value : value;
    if (absvalue < (double)INT_MAX) {
        epicsInt32 intval = (epicsInt32) value;
        double diff = value - intval;

        if (diff < 0) diff = -diff;
        if (diff < absvalue * delta[isdouble]) {
            cvtLongToString(intval, preturn);
            return;
        }
    }

    /*Now starts the hard cases*/
    if (value < 0) {
        *preturn++ = '-';
        value = -value;
    }

    logval = (int)log10(value);
    if (logval > 6 || logval < -2) {
        int nout;

        ise = TRUE;
        prec = precision[isdouble];
        nout = sprintf(ptstr, "%.*e", prec, value);
        loce = strchr(ptstr, 'e');

        if (!loce) {
            ptstr[nout] = 0;
            strcpy(preturn, ptstr);
            return;
        }

        *loce++ = 0;
    } else {
        prec = precision[isdouble] - logval;
        if ( prec < 0) prec = 0;
        sprintf(ptstr, "%.*f", prec, value);
    }

    if (prec > 0) {
        end = strlen(ptstr) - 1;
        round = FALSE;
        while (end > 0) {
            if (tstr[end] == '.') {end--; break;}
            if (tstr[end] == '0') {end--; continue;}
            if (!round && end < precision[isdouble]) break;
            if (!round && tstr[end] < '8') break;
            if (tstr[end-1] == '.') {
                if (round) end = end-2;
                break;
            }
            if (tstr[end-1] != '9') break;
            round = TRUE;
            end--;
        }
        tstr[end+1] = 0;
        while (round) {
            if (tstr[end] < '9') {tstr[end]++; break;}
            if (end == 0) { *preturn++ = '1'; tstr[end] = '0'; break;}
            tstr[end--] = '0';
        }
    }
    strcpy(preturn, &tstr[0]);
    if (ise) {
        if (!(strchr(preturn, '.'))) strcat(preturn, ".0");
        strcat(preturn, "e");
        strcat(preturn, loce);
    }
}

static void floatToString(float value, char *preturn)
{
    realToString((double)value, preturn, 0);
}

static void doubleToString(double value, char *preturn)
{
    realToString(value, preturn, 1);
}

/*Public only for dbStaticNoRun*/
dbDeviceMenu *dbGetDeviceMenu(DBENTRY *pdbentry)
{
    dbRecordType    *precordType = pdbentry->precordType;
    dbFldDes        *pflddes = pdbentry->pflddes;
    dbDeviceMenu    *pdbDeviceMenu;
    devSup          *pdevSup;
    int             ind;
    int             nChoice;

    if(!precordType) return(NULL);
    if(!pflddes) return(NULL);
    if(pflddes->field_type!=DBF_DEVICE) return(NULL);
    if(pflddes->ftPvt){
        pdbDeviceMenu = (dbDeviceMenu *)pflddes->ftPvt;
        if(pdbDeviceMenu->nChoice == ellCount(&precordType->devList))
            return(pdbDeviceMenu);
        free((void *)pdbDeviceMenu->papChoice);
        free((void *)pdbDeviceMenu);
        pflddes->ftPvt = NULL;
    }
    nChoice = ellCount(&precordType->devList);
    if(nChoice <= 0) return(NULL);
    pdbDeviceMenu = dbCalloc(1,sizeof(dbDeviceMenu));
    pdbDeviceMenu->nChoice = nChoice;
    pdbDeviceMenu->papChoice = dbCalloc(pdbDeviceMenu->nChoice,sizeof(char *));
    pdevSup = (devSup *)ellFirst(&precordType->devList);
    ind = 0;
    while(pdevSup) {
        pdbDeviceMenu->papChoice[ind] = pdevSup->choice;
        ind++;
        pdevSup = (devSup *)ellNext(&pdevSup->node);
    }
    pflddes->ftPvt = pdbDeviceMenu;
    return(pdbDeviceMenu);
}

/* Beginning of Public Routines */

#define INC_SIZE    256
void dbCatString(char **string,int *stringLength,char *src,char *separator)
{
    if((*string==NULL)
    || ((strlen(*string)+strlen(src)+2) > (size_t)*stringLength)) {
        char    *newString;
        size_t  size;

        size = strlen(src);
        if(*string) size += strlen(*string);
        /*Make size multiple of INC_SIZE*/
        size = ((size + 2 + INC_SIZE)/INC_SIZE) * INC_SIZE;
        newString = dbCalloc(size,sizeof(char));
        if(*string) {
            strcpy(newString,*string);
            free((void *)(*string));
        }
        *string = newString;
    }
    if(*stringLength>0) {
        strcat(*string,separator);
    *stringLength += (int) strlen(separator);
    }
    strcat(*string,src);
    *stringLength += (int) strlen(src);
}

dbBase * dbAllocBase(void)
{
    dbBase  *pdbbase;

    pdbbase = dbCalloc(1,sizeof(dbBase));
    ellInit(&pdbbase->menuList);
    ellInit(&pdbbase->recordTypeList);
    ellInit(&pdbbase->drvList);
    ellInit(&pdbbase->registrarList);
    ellInit(&pdbbase->functionList);
    ellInit(&pdbbase->variableList);
    ellInit(&pdbbase->bptList);
    ellInit(&pdbbase->filterList);
    ellInit(&pdbbase->guiGroupList);
    gphInitPvt(&pdbbase->pgpHash,256);
    dbPvdInitPvt(pdbbase);
    return (pdbbase);
}
void dbFreeBase(dbBase *pdbbase)
{
    dbMenu              *pdbMenu;
    dbMenu              *pdbMenuNext;
    dbRecordType        *pdbRecordType;
    dbRecordType        *pdbRecordTypeNext;
    dbFldDes            *pdbFldDes;
    dbRecordAttribute   *pAttribute;
    dbRecordAttribute   *pAttributeNext;
    devSup              *pdevSup;
    devSup              *pdevSupNext;
    dbText              *ptext;
    dbText              *ptextNext;
    dbVariableDef       *pvar;
    dbVariableDef       *pvarNext;
    drvSup              *pdrvSup;
    drvSup              *pdrvSupNext;
    linkSup             *plinkSup;
    brkTable            *pbrkTable;
    brkTable            *pbrkTableNext;
    chFilterPlugin      *pfilt;
    chFilterPlugin      *pfiltNext;
    dbGuiGroup          *pguiGroup;
    dbGuiGroup          *pguiGroupNext;
    int                 i;
    DBENTRY             dbentry;
    long status;

    if(!pdbbase)
        return;

    dbInitEntry(pdbbase,&dbentry);
    status = dbFirstRecordType(&dbentry);
    while(!status) {
        /* dbDeleteRecord() will remove alias or real record node.
         * For real record nodes, also removes the nodes of all aliases.
         * This complicates safe traversal, so we re-start iteration
         * from the first record after each call.
         */
        while((status = dbFirstRecord(&dbentry))==0) {
            dbDeleteRecord(&dbentry);
        }
        assert(status==S_dbLib_recNotFound);
        status = dbNextRecordType(&dbentry);
    }
    dbFinishEntry(&dbentry);
    pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList);
    while(pdbRecordType) {
        for(i=0; i<pdbRecordType->no_fields; i++) {
            pdbFldDes = pdbRecordType->papFldDes[i];
            free((void *)pdbFldDes->prompt);
            free((void *)pdbFldDes->name);
            free((void *)pdbFldDes->extra);
            free((void *)pdbFldDes->initial);
            if(pdbFldDes->field_type==DBF_DEVICE && pdbFldDes->ftPvt) {
                dbDeviceMenu *pdbDeviceMenu;

                pdbDeviceMenu = (dbDeviceMenu *)pdbFldDes->ftPvt;
                free((void *)pdbDeviceMenu->papChoice);
                free((void *)pdbDeviceMenu);
                pdbFldDes->ftPvt=0;
            }
            free((void *)pdbFldDes);
        }
        pdevSup = (devSup *)ellFirst(&pdbRecordType->devList);
        while(pdevSup) {
            pdevSupNext = (devSup *)ellNext(&pdevSup->node);
            ellDelete(&pdbRecordType->devList,&pdevSup->node);
            free((void *)pdevSup->name);
            free((void *)pdevSup->choice);
            free((void *)pdevSup);
            pdevSup = pdevSupNext;
        }
        ptext = (dbText *)ellFirst(&pdbRecordType->cdefList);
        while(ptext) {
            ptextNext = (dbText *)ellNext(&ptext->node);
            ellDelete(&pdbRecordType->cdefList,&ptext->node);
            free((void *)ptext->text);
            free((void *)ptext);
            ptext = ptextNext;
        }
        pAttribute =
                (dbRecordAttribute *)ellFirst(&pdbRecordType->attributeList);
        while(pAttribute) {
            pAttributeNext = (dbRecordAttribute *)ellNext(&pAttribute->node);
            ellDelete(&pdbRecordType->attributeList,&pAttribute->node);
            free((void *)pAttribute->name);
            free((void *)pAttribute->pdbFldDes);
            free(pAttribute);
            pAttribute = pAttributeNext;
        }
        pdbRecordTypeNext = (dbRecordType *)ellNext(&pdbRecordType->node);
        gphDelete(pdbbase->pgpHash,pdbRecordType->name,&pdbbase->recordTypeList);
        ellDelete(&pdbbase->recordTypeList,&pdbRecordType->node);
        free((void *)pdbRecordType->name);
        free((void *)pdbRecordType->link_ind);
        free((void *)pdbRecordType->papsortFldName);
        free((void *)pdbRecordType->sortFldInd);
        free((void *)pdbRecordType->papFldDes);
        free((void *)pdbRecordType);
        pdbRecordType = pdbRecordTypeNext;
    }
    pdbMenu = (dbMenu *)ellFirst(&pdbbase->menuList);
    while(pdbMenu) {
        pdbMenuNext = (dbMenu *)ellNext(&pdbMenu->node);
        gphDelete(pdbbase->pgpHash,pdbMenu->name,&pdbbase->menuList);
        ellDelete(&pdbbase->menuList,&pdbMenu->node);
        for(i=0; i< pdbMenu->nChoice; i++) {
            free((void *)pdbMenu->papChoiceName[i]);
            free((void *)pdbMenu->papChoiceValue[i]);
        }
        free((void *)pdbMenu->papChoiceName);
        free((void *)pdbMenu->papChoiceValue);
        free((void *)pdbMenu ->name);
        free((void *)pdbMenu);
        pdbMenu = pdbMenuNext;
    }
    pdrvSup = (drvSup *)ellFirst(&pdbbase->drvList);
    while(pdrvSup) {
        pdrvSupNext = (drvSup *)ellNext(&pdrvSup->node);
        ellDelete(&pdbbase->drvList,&pdrvSup->node);
        free((void *)pdrvSup->name);
        free((void *)pdrvSup);
        pdrvSup = pdrvSupNext;
    }
    while ((plinkSup = (linkSup *) ellGet(&pdbbase->linkList))) {
        free(plinkSup->jlif_name);
        free(plinkSup->name);
        free(plinkSup);
    }
    ptext = (dbText *)ellFirst(&pdbbase->registrarList);
    while(ptext) {
        ptextNext = (dbText *)ellNext(&ptext->node);
        ellDelete(&pdbbase->registrarList,&ptext->node);
        free((void *)ptext->text);
        free((void *)ptext);
        ptext = ptextNext;
    }
    ptext = (dbText *)ellFirst(&pdbbase->functionList);
    while(ptext) {
        ptextNext = (dbText *)ellNext(&ptext->node);
        ellDelete(&pdbbase->functionList,&ptext->node);
        free((void *)ptext->text);
        free((void *)ptext);
        ptext = ptextNext;
    }
    pvar = (dbVariableDef *)ellFirst(&pdbbase->variableList);
    while(pvar) {
        pvarNext = (dbVariableDef *)ellNext(&pvar->node);
        ellDelete(&pdbbase->variableList,&pvar->node);
        free((void *)pvar->name);
        free((void *)pvar->type);
        free((void *)pvar);
        pvar = pvarNext;
    }
    pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList);
    while(pbrkTable) {
        pbrkTableNext = (brkTable *)ellNext(&pbrkTable->node);
        gphDelete(pdbbase->pgpHash,pbrkTable->name,&pdbbase->bptList);
        ellDelete(&pdbbase->bptList,&pbrkTable->node);
        free(pbrkTable->name);
        free((void *)pbrkTable->paBrkInt);
        free((void *)pbrkTable);
        pbrkTable = pbrkTableNext;
    }
    pfilt = (chFilterPlugin *)ellFirst(&pdbbase->filterList);
    while(pfilt) {
        pfiltNext = (chFilterPlugin *)ellNext(&pfilt->node);
        free((char*)pfilt->name);
        if(pfilt->fif->priv_free)
            (*pfilt->fif->priv_free)(pfilt->puser);
        free(pfilt);
        pfilt = pfiltNext;
    }
    pguiGroup = (dbGuiGroup *)ellFirst(&pdbbase->guiGroupList);
    while (pguiGroup) {
        pguiGroupNext = (dbGuiGroup *)ellNext(&pguiGroup->node);
        gphDelete(pdbbase->pgpHash, pguiGroup->name, &pdbbase->guiGroupList);
        ellDelete(&pdbbase->guiGroupList, &pguiGroup->node);
        free(pguiGroup->name);
        free((void *)pguiGroup);
        pguiGroup = pguiGroupNext;
    }
    gphFreeMem(pdbbase->pgpHash);
    dbPvdFreeMem(pdbbase);
    dbFreePath(pdbbase);
    free((void *)pdbbase);
    pdbbase = NULL;
    return;
}

DBENTRY * dbAllocEntry(dbBase *pdbbase)
{
    DBENTRY *pdbentry;

    pdbentry = dbmfMalloc(sizeof(DBENTRY));
    memset(pdbentry,'\0',sizeof(DBENTRY));
    pdbentry->pdbbase = pdbbase;
    return(pdbentry);
}

void dbFreeEntry(DBENTRY *pdbentry)
{
    if (!pdbentry)
        return;
    if (pdbentry->message)
        free((void *)pdbentry->message);
    dbmfFree(pdbentry);
}

void dbInitEntry(dbBase *pdbbase,DBENTRY *pdbentry)
{
    memset((char *)pdbentry,'\0',sizeof(DBENTRY));
    pdbentry->pdbbase = pdbbase;
}

void dbFinishEntry(DBENTRY *pdbentry)
{
    if(pdbentry->message) {
        free((void *)pdbentry->message);
        pdbentry->message = NULL;
    }
}

DBENTRY * dbCopyEntry(const DBENTRY *pdbentry)
{
    DBENTRY *pnew;

    pnew = dbAllocEntry(pdbentry->pdbbase);
    *pnew = *pdbentry;
    pnew->message = NULL;
    return(pnew);
}

void dbCopyEntryContents(const DBENTRY *pfrom,DBENTRY *pto)
{
    *pto = *pfrom;
    pto->message = NULL;
}


long dbPath(DBBASE *pdbbase,const char *path)
{
    if(!pdbbase) return(-1);
    dbFreePath(pdbbase);
    if(!path || strlen(path)==0) return(dbAddPath(pdbbase,"."));
    return(dbAddPath(pdbbase,path));
}

long dbAddPath(DBBASE *pdbbase,const char *path)
{
    ELLLIST     *ppathList;
    const char  *pcolon;
    const char  *plast;
    unsigned    expectingPath;
    unsigned    sawMissingPath;

    if(!pdbbase) return(-1);
    ppathList = (ELLLIST *)pdbbase->pathPvt;
    if(!ppathList) {
        ppathList = dbCalloc(1,sizeof(ELLLIST));
        ellInit(ppathList);
        pdbbase->pathPvt = (void *)ppathList;
    }
    if (!path) return(0); /* Empty path strings are ignored */
    /* care is taken to properly deal with white space
     * 1) preceding and trailing white space is removed from paths
     * 2) white space in between path separator counts as an empty name
     *      (see below)
     */
    expectingPath = FALSE;
    sawMissingPath = FALSE;
    while (*path) {
        size_t len;

        /* preceding white space is removed */
        if (isspace((int)*path)) {
            path++;
            continue;
        }
        pcolon = strstr (path, OSI_PATH_LIST_SEPARATOR);
        if (pcolon==path) {
            sawMissingPath = TRUE;
            path += strlen (OSI_PATH_LIST_SEPARATOR);
            continue;
        }
        if (pcolon) {
            plast = pcolon - 1;
            expectingPath = TRUE;
        } else {
            plast = strlen (path) + path - 1;
            expectingPath = FALSE;
        }
        /* trailing white space is removed */
        while (isspace((int)*plast)) {
            plast--;
        }

        /*
         * len is always nonzero because we found something that
         * 1) isn't white space
         * 2) isn't a path separator
         */
        len = (plast - path) + 1;
        if (dbAddOnePath (pdbbase, path, (unsigned) len)) return (-1);
        path += len;
        if (pcolon) {
            path += strlen(OSI_PATH_LIST_SEPARATOR);
        }
    }

    /*
     * an empty name at beginning, middle, or end of a path string that isn't
     * empty means current directory
     */
    if (expectingPath||sawMissingPath) {
        return dbAddOnePath (pdbbase, ".", 1);
    }
    return(0);
}

static long dbAddOnePath (DBBASE *pdbbase, const char *path, unsigned length)
{
    ELLLIST     *ppathList;
    dbPathNode  *pdbPathNode;

    if(!pdbbase) return(-1);
    ppathList = (ELLLIST *)pdbbase->pathPvt;

    pdbPathNode = (dbPathNode *)dbCalloc(1, sizeof(dbPathNode));
    pdbPathNode->directory = (char *)dbCalloc(length+1, sizeof(char));
    strncpy(pdbPathNode->directory, path, length);
    pdbPathNode->directory[length] = '\0';
    ellAdd(ppathList, &pdbPathNode->node);
    return 0;
}

char *dbGetPromptGroupNameFromKey(DBBASE *pdbbase, const short key)
{
    dbGuiGroup *pdbGuiGroup;

    if (!pdbbase) return NULL;
    for (pdbGuiGroup = (dbGuiGroup *)ellFirst(&pdbbase->guiGroupList);
        pdbGuiGroup; pdbGuiGroup = (dbGuiGroup *)ellNext(&pdbGuiGroup->node)) {
        if (pdbGuiGroup->key == key) return pdbGuiGroup->name;
    }
    return NULL;
}

short dbGetPromptGroupKeyFromName(DBBASE *pdbbase, const char *name)
{
    GPHENTRY   *pgphentry;

    if (!pdbbase) return 0;
    pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->guiGroupList);
    if (!pgphentry) {
        return 0;
    } else {
        return ((dbGuiGroup*)pgphentry->userPvt)->key;
    }
}


long dbWriteRecord(DBBASE *ppdbbase,const char *filename,
    const char *precordTypename,int level)
{
    FILE *stream;
    long status;

    stream = openOutstream(filename);
    if(!stream) return -1;
    status = dbWriteRecordFP(ppdbbase,stream,precordTypename,level);
    finishOutstream(stream);
    return status;
}

long dbWriteRecordFP(
    DBBASE *pdbbase,FILE *fp,const char *precordTypename,int level)
{
    DBENTRY dbentry;
    DBENTRY *pdbentry=&dbentry;
    long    status;
    int     dctonly;

    dctonly = ((level>1) ? FALSE : TRUE);
    dbInitEntry(pdbbase,pdbentry);
    if (precordTypename) {
        if (*precordTypename == 0 || *precordTypename == '*')
            precordTypename = 0;
    }

    if(!precordTypename) {
        status = dbFirstRecordType(pdbentry);
        if(status) {
            /* No record descriptions, so no record instances */
            dbFinishEntry(pdbentry);
            return(0);
        }
    } else {
        status = dbFindRecordType(pdbentry,precordTypename);
        if(status) {
            fprintf(stderr,"dbWriteRecordFP: No record description for %s\n",
                precordTypename);
            dbFinishEntry(pdbentry);
            return(status);
        }
    }
    while(!status) {
        status = dbFirstRecord(pdbentry);
        while(!status) {
            if (dbIsAlias(pdbentry)) {
                status = dbNextRecord(pdbentry);
                continue;
            }
            if(dbIsVisibleRecord(pdbentry))
                fprintf(fp,"grecord(%s,\"%s\") {\n",
                    dbGetRecordTypeName(pdbentry),dbGetRecordName(pdbentry));
            else
                fprintf(fp,"record(%s,\"%s\") {\n",
                    dbGetRecordTypeName(pdbentry),dbGetRecordName(pdbentry));
            status = dbFirstField(pdbentry,dctonly);
            while(!status) {
                if (!dbIsDefaultValue(pdbentry) || level>0) {
                    char *pvalstring = dbGetString(pdbentry);

                    if (!pvalstring) {
                        fprintf(fp,"\tfield(%s,\"\")\n",
                            dbGetFieldName(pdbentry));
                    } else {
                        fprintf(fp,"\tfield(%s,\"",
                            dbGetFieldName(pdbentry));
                        epicsStrPrintEscaped(fp,pvalstring,strlen(pvalstring));
                        fprintf(fp,"\")\n");
                    }
                } else if(level>0) { /*generate 0 length string*/
                    fprintf(fp,"\tfield(%s,\"\")\n",dbGetFieldName(pdbentry));
                }
                status=dbNextField(pdbentry,dctonly);
            }
            status = dbFirstInfo(pdbentry);
            while (!status) {
                const char *pinfostr = dbGetInfoString(pdbentry);

                fprintf(fp, "\tinfo(\"%s\",\"",
                    dbGetInfoName(pdbentry));
                epicsStrPrintEscaped(fp, pinfostr, strlen(pinfostr));
                fprintf(fp, "\")\n");
                status = dbNextInfo(pdbentry);
            }
            fprintf(fp,"}\n");
            status = dbNextRecord(pdbentry);
        }
        status = dbFirstRecord(pdbentry);
        while (!status) {
            if (!dbIsAlias(pdbentry)) {
                status = dbNextRecord(pdbentry);
                continue;
            }
            fprintf(fp, "alias(\"%s\",\"%s\")\n",
                dbRecordName(pdbentry), dbGetRecordName(pdbentry));
            status = dbNextRecord(pdbentry);
        }
        if(precordTypename) break;
        status = dbNextRecordType(pdbentry);
    }
    dbFinishEntry(pdbentry);
    return(0);
}

long dbWriteMenu(
    DBBASE *ppdbbase,const char *filename,const char *menuName)
{
    FILE *stream;
    long status;

    stream = openOutstream(filename);
    status = dbWriteMenuFP(ppdbbase,stream,menuName);
    finishOutstream(stream);
    return status;
}

long dbWriteMenuFP(DBBASE *pdbbase,FILE *fp,const char *menuName)
{
    dbMenu  *pdbMenu;
    int     gotMatch;
    int     i;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return(-1);
    }
    if (menuName) {
        if (*menuName == 0 || *menuName == '*')
            menuName = 0;
    }
    pdbMenu = (dbMenu *)ellFirst(&pdbbase->menuList);
    while(pdbMenu) {
        if(menuName) {
            gotMatch = (strcmp(menuName,pdbMenu->name)==0) ? TRUE : FALSE;
        }else {
            gotMatch=TRUE;
        }
        if(gotMatch) {
            fprintf(fp,"menu(%s) {\n",pdbMenu->name);
            for(i=0; i<pdbMenu->nChoice; i++) {
                fprintf(fp,"\tchoice(%s,\"%s\")\n",pdbMenu->papChoiceName[i],
                    pdbMenu->papChoiceValue[i]);
            }
            fprintf(fp,"}\n");
            if(menuName) break;
        }
        pdbMenu = (dbMenu *)ellNext(&pdbMenu->node);
    }
    return(0);
}

long dbWriteRecordType(
    DBBASE *pdbbase,const char *filename,const char *recordTypeName)
{
    FILE *stream;
    long status;

    stream = openOutstream(filename);
    status = dbWriteRecordTypeFP(pdbbase,stream,recordTypeName);
    finishOutstream(stream);
    return status;
}

long dbWriteRecordTypeFP(
    DBBASE *pdbbase,FILE *fp,const char *recordTypeName)
{
    dbRecordType    *pdbRecordType;
    dbFldDes        *pdbFldDes;
    int             gotMatch;
    int             i;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return(-1);
    }
    if (recordTypeName) {
        if (*recordTypeName == 0 || *recordTypeName == '*')
            recordTypeName = 0;
    }

    for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList);
    pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) {
        if(recordTypeName) {
            gotMatch = (strcmp(recordTypeName,pdbRecordType->name)==0)
                 ? TRUE : FALSE;
        }else {
            gotMatch=TRUE;
        }
        if(!gotMatch) continue;
        fprintf(fp,"recordtype(%s) {\n",pdbRecordType->name);
        for(i=0; i<pdbRecordType->no_fields; i++) {
            int j;

            pdbFldDes = pdbRecordType->papFldDes[i];
            fprintf(fp,"\tfield(%s,%s) {\n",pdbFldDes->name,
                dbGetFieldTypeString(pdbFldDes->field_type));
            if(pdbFldDes->prompt)
                fprintf(fp,"\t\tprompt(\"%s\")\n",pdbFldDes->prompt);
            if(pdbFldDes->initial)
                fprintf(fp,"\t\tinitial(\"%s\")\n",pdbFldDes->initial);
        if (pdbFldDes->promptgroup) {
            fprintf(fp,"\t\tpromptgroup(\"%s\")\n",
                    dbGetPromptGroupNameFromKey(pdbbase, pdbFldDes->promptgroup));
        }
        if(pdbFldDes->special) {
                if(pdbFldDes->special >= SPC_NTYPES) {
                    fprintf(fp,"\t\tspecial(%d)\n",pdbFldDes->special);
                } else for(j=0; j<SPC_NTYPES; j++) {
                    if(pamapspcType[j].value == pdbFldDes->special) {
                        fprintf(fp,"\t\tspecial(%s)\n",
                                pamapspcType[j].strvalue);
                        break;
                    }
                }
            }
            if(pdbFldDes->extra)
                fprintf(fp,"\t\textra(\"%s\")\n",pdbFldDes->extra);
            if(pdbFldDes->field_type==DBF_MENU) {
                if(pdbFldDes->ftPvt)
                    fprintf(fp,"\t\tmenu(%s)\n",
                        ((dbMenu *)pdbFldDes->ftPvt)->name);
                else
                   fprintf(stderr,"\t\t  menu: NOT FOUND\n");
            }
            if(pdbFldDes->field_type==DBF_STRING) {
                fprintf(fp,"\t\tsize(%d)\n",
                    pdbFldDes->size);
            }
            if(pdbFldDes->process_passive) fprintf(fp,"\t\tpp(TRUE)\n");
            if(pdbFldDes->prop) fprintf(fp,"\t\tprop(YES)\n");
            if(pdbFldDes->base) fprintf(fp,"\t\tbase(HEX)\n");
            if(pdbFldDes->interest)
                fprintf(fp,"\t\tinterest(%d)\n",pdbFldDes->interest);
            if(!pdbFldDes->as_level) fprintf(fp,"\t\tasl(ASL0)\n");
            fprintf(fp,"\t}\n");
        }
        fprintf(fp,"}\n");
        if(recordTypeName) break;
    }
    return(0);
}

long dbWriteDevice(DBBASE *pdbbase,const char *filename)
{
    FILE *stream;
    long status;

    stream = openOutstream(filename);
    status = dbWriteDeviceFP(pdbbase,stream);
    finishOutstream(stream);
    return status;
}

long dbWriteDeviceFP(DBBASE *pdbbase,FILE *fp)
{
    dbRecordType    *pdbRecordType;
    devSup          *pdevSup;

    if(!pdbbase) {
        fprintf(stderr,"dbWriteDeviceFP: pdbbase not specified\n");
        return(-1);
    }
    for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList);
    pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) {
        for(pdevSup = (devSup *)ellFirst(&pdbRecordType->devList);
        pdevSup; pdevSup = (devSup *)ellNext(&pdevSup->node)) {
            int j;

            for(j=0; j< LINK_NTYPES; j++) {
                if(pamaplinkType[j].value==pdevSup->link_type) break;
            }
            if(j>=LINK_NTYPES) {
                fprintf(fp,"link_type not valid\n");
                continue;
            }
            fprintf(fp,"device(%s,%s,%s,\"%s\")\n",
                pdbRecordType->name,
                pamaplinkType[j].strvalue,
                pdevSup->name,pdevSup->choice);
        }
    }
    return(0);
}

long dbWriteDriver(DBBASE *pdbbase,const char *filename)
{
    FILE *stream;
    long status;

    stream = openOutstream(filename);
    status = dbWriteDriverFP(pdbbase,stream);
    finishOutstream(stream);
    return status;
}

long dbWriteDriverFP(DBBASE *pdbbase,FILE *fp)
{
    drvSup  *pdrvSup;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return(-1);
    }
    for(pdrvSup = (drvSup *)ellFirst(&pdbbase->drvList);
    pdrvSup; pdrvSup = (drvSup *)ellNext(&pdrvSup->node)) {
        fprintf(fp,"driver(%s)\n",pdrvSup->name);
    }
    return(0);
}

long dbWriteLinkFP(DBBASE *pdbbase, FILE *fp)
{
    linkSup *plinkSup;

    if (!pdbbase) {
        fprintf(stderr, "pdbbase not specified\n");
        return -1;
    }
    for (plinkSup = (linkSup *) ellFirst(&pdbbase->linkList);
        plinkSup; plinkSup = (linkSup *) ellNext(&plinkSup->node)) {
        fprintf(fp, "link(%s,%s)\n", plinkSup->name, plinkSup->jlif_name);
    }
    return 0;
}

long dbWriteRegistrarFP(DBBASE *pdbbase,FILE *fp)
{
    dbText  *ptext;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return(-1);
    }
    for(ptext = (dbText *)ellFirst(&pdbbase->registrarList);
    ptext; ptext = (dbText *)ellNext(&ptext->node)) {
        fprintf(fp,"registrar(%s)\n",ptext->text);
    }
    return(0);
}

long dbWriteFunctionFP(DBBASE *pdbbase,FILE *fp)
{
    dbText     *ptext;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return(-1);
    }
    for(ptext = (dbText *)ellFirst(&pdbbase->functionList);
    ptext; ptext = (dbText *)ellNext(&ptext->node)) {
        fprintf(fp,"function(%s)\n",ptext->text);
    }
    return(0);
}

long dbWriteVariableFP(DBBASE *pdbbase,FILE *fp)
{
    dbVariableDef   *pvar;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return(-1);
    }
    for(pvar = (dbVariableDef *)ellFirst(&pdbbase->variableList);
        pvar; pvar = (dbVariableDef *)ellNext(&pvar->node)) {
        fprintf(fp,"variable(%s,%s)\n",pvar->name,pvar->type);
    }
    return(0);
}

long dbWriteBreaktable(DBBASE *pdbbase,const char *filename)
{
    FILE *stream;
    long status;

    stream = openOutstream(filename);
    status = dbWriteBreaktableFP(pdbbase,stream);
    finishOutstream(stream);
    return status;
}

long dbWriteBreaktableFP(DBBASE *pdbbase,FILE *fp)
{
    brkTable    *pbrkTable;
    brkInt      *pbrkInt;
    int         i;

    if (!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return(-1);
    }
    for (pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList);
         pbrkTable;
         pbrkTable = (brkTable *)ellNext(&pbrkTable->node)) {
        fprintf(fp,"breaktable(%s) {\n",pbrkTable->name);
        pbrkInt = pbrkTable->paBrkInt;
        for(i=0; i < pbrkTable->number; i++) {
            fprintf(fp,"\t%e, %e\n",pbrkInt->raw,pbrkInt->eng);
            pbrkInt++;
        }
        fprintf(fp,"}\n");
    }
    return(0);
}

long dbFindRecordType(DBENTRY *pdbentry,const char *recordType)
{
    dbBase      *pdbbase = pdbentry->pdbbase;
    GPHENTRY    *phash;

    zeroDbentry(pdbentry);
    phash = gphFind(pdbbase->pgpHash,recordType,&pdbbase->recordTypeList);
    if(!phash) return(S_dbLib_recordTypeNotFound);
    pdbentry->precordType = phash->userPvt;
    return(0);
}

long dbFirstRecordType(DBENTRY *pdbentry)
{
    dbRecordType *precordType;

    zeroDbentry(pdbentry);
    precordType = (dbRecordType *)ellFirst(&pdbentry->pdbbase->recordTypeList);
    if(!precordType) return(S_dbLib_recordTypeNotFound);
    pdbentry->precordType = precordType;
    return(0);
}

long dbNextRecordType(DBENTRY *pdbentry)
{
    dbRecordType *precordType = pdbentry->precordType;

    zeroDbentry(pdbentry);
    precordType = (dbRecordType *)ellNext(&precordType->node);
    if(!precordType) return(S_dbLib_recordTypeNotFound);
    pdbentry->precordType = precordType;
    return(0);
}

char * dbGetRecordTypeName(DBENTRY *pdbentry)
{
    return(pdbentry->precordType->name);
}

int dbGetNRecordTypes(DBENTRY *pdbentry)
{
    return(ellCount(&pdbentry->pdbbase->recordTypeList));
}

long dbPutRecordAttribute(
    DBENTRY *pdbentry, const char *name, const char*value)
{
    dbRecordType        *precordType = pdbentry->precordType;
    int                 createNew = TRUE;
    int                 compare;
    dbRecordAttribute   *pattribute;

    if(!precordType) return(S_dbLib_recordTypeNotFound);
    pattribute = (dbRecordAttribute *)ellFirst(&precordType->attributeList);
    /*put new attribute name in sort order*/
    while(pattribute) {
        compare = strcmp(pattribute->name,name);
        if(compare==0) {
            createNew = FALSE;
        }
        if(compare>=0) break;
        pattribute = (dbRecordAttribute *)ellNext(&pattribute->node);
    }
    if(createNew) {
        dbRecordAttribute *pnew;
        dbFldDes          *pdbFldDes;

        pnew = dbCalloc(1,sizeof(dbRecordAttribute));
        if(pattribute) {
            ellInsert(&precordType->attributeList,pattribute->node.previous,
                &pnew->node);
        } else {
            ellAdd(&precordType->attributeList,&pnew->node);
        }
        pattribute = pnew;
        pattribute->name = dbCalloc(strlen(name)+1,sizeof(char));
        strcpy(pattribute->name,name);
        pdbFldDes = dbCalloc(1,sizeof(dbFldDes));
        pdbFldDes->name = pattribute->name;
        pdbFldDes->pdbRecordType = precordType;
        pdbFldDes->special = SPC_ATTRIBUTE;
        pdbFldDes->field_type = DBF_STRING;
        pdbFldDes->as_level = ASL1;
        pdbFldDes->size = MAX_STRING_SIZE;
        pattribute->pdbFldDes = pdbFldDes;
    }
    strncpy(pattribute->value,value,MAX_STRING_SIZE);
    pattribute->value[MAX_STRING_SIZE-1] = 0;
    return(0);
}

long dbGetAttributePart(DBENTRY *pdbentry, const char **ppname)
{
    dbRecordType *precordType = pdbentry->precordType;
    const char *pname = *ppname;
    dbRecordAttribute *pattribute;

    if (!precordType)
        return S_dbLib_recordTypeNotFound;

    pattribute = (dbRecordAttribute *)ellFirst(&precordType->attributeList);
    while (pattribute) {
        size_t nameLen = strlen(pattribute->name);
        int compare = strncmp(pattribute->name, pname, nameLen);

        if (compare == 0) {
            int ch = pname[nameLen];

            if (ch != '_' && !isalnum(ch)) {
                /* Any other character can't be in the attribute name */
                pdbentry->pflddes = pattribute->pdbFldDes;
                pdbentry->pfield = pattribute->value;
                *ppname = &pname[nameLen];
                return 0;
            }
            if (strlen(pname) > nameLen) {
                compare = -1;
            }
        }
        if (compare >= 0) break;
        pattribute = (dbRecordAttribute *)ellNext(&pattribute->node);
    }
    return S_dbLib_fieldNotFound;
}

long dbGetRecordAttribute(DBENTRY *pdbentry, const char *pname)
{
    return dbGetAttributePart(pdbentry, &pname);
}

long dbFirstField(DBENTRY *pdbentry,int dctonly)
{

    pdbentry->indfield = -1;
    return(dbNextField(pdbentry,dctonly));
}

long dbNextField(DBENTRY *pdbentry,int dctonly)
{
    dbRecordType    *precordType = pdbentry->precordType;
    dbRecordNode    *precnode = pdbentry->precnode;
    dbFldDes        *pflddes;
    short           indfield = pdbentry->indfield;

    if(!precordType) return(S_dbLib_recordTypeNotFound);
    indfield++;
    while(TRUE) {
        if(indfield>=precordType->no_fields) {
            pdbentry->indfield = 0;
            pdbentry->pflddes = NULL;
            pdbentry->pfield = NULL;
            return(S_dbLib_fieldNotFound);
        }
        pflddes = precordType->papFldDes[indfield];
        if(!dctonly || pflddes->promptgroup) {
            /*Skip field if dctonly and no device support*/
            if(!dctonly || (pflddes->field_type!=DBF_DEVICE)
            || (ellCount(&precordType->devList)>0)) {
                pdbentry->indfield = indfield;
                pdbentry->pflddes = pflddes;
                pdbentry->indfield = indfield;
                if(precnode) {
                    dbGetFieldAddress(pdbentry);
                }else {
                    pdbentry->pfield = NULL;
                }
                return(0);
            }
        }
        indfield++;
    }
}

int dbGetNFields(DBENTRY *pdbentry,int dctonly)
{
    dbRecordType    *precordType = pdbentry->precordType;
    dbFldDes        *pflddes;
    int             indfield,n;

    if(!precordType) return(S_dbLib_recordTypeNotFound);
    n = 0;
    for(indfield=0; indfield<precordType->no_fields; indfield++) {
        pflddes = precordType->papFldDes[indfield];
        if(dctonly && (pflddes->field_type==DBF_DEVICE)
        && (ellCount(&precordType->devList)==0) ) continue;
        if(!dctonly || pflddes->promptgroup) n++;
    }
    return(n);
}

char * dbGetFieldName(DBENTRY *pdbentry)
{
    dbFldDes    *pflddes = pdbentry->pflddes;

    if(!pflddes) return(NULL);
    return(pflddes->name);
}

int dbGetFieldDbfType(DBENTRY *pdbentry)
{
    dbFldDes  	*pflddes = pdbentry->pflddes;

    if(!pflddes) return(-1);
    return(pflddes->field_type);
}

char * dbGetDefault(DBENTRY *pdbentry)
{
    dbFldDes    *pflddes = pdbentry->pflddes;

    if(!pflddes) return(NULL);
    return(pflddes->initial);
}

char * dbGetPrompt(DBENTRY *pdbentry)
{
    dbFldDes    *pflddes = pdbentry->pflddes;

    if(!pflddes) return(NULL);
    return(&pflddes->prompt[0]);
}

int dbGetPromptGroup(DBENTRY *pdbentry)
{
    dbFldDes    *pflddes = pdbentry->pflddes;

    if(!pflddes) return(0);
    return(pflddes->promptgroup);
}

long dbCreateRecord(DBENTRY *pdbentry,const char *precordName)
{
    dbRecordType    *precordType = pdbentry->precordType;
    dbFldDes        *pdbFldDes;
    PVDENTRY        *ppvd;
    ELLLIST         *preclist = NULL;
    dbRecordNode    *pNewRecNode = NULL;
    long            status = 0;

    if(!precordType) return(S_dbLib_recordTypeNotFound);
    /*Get size of NAME field*/
    pdbFldDes = precordType->papFldDes[0];
    if(!pdbFldDes || (strcmp(pdbFldDes->name,"NAME")!=0))
        return(S_dbLib_nameLength);
    if((int)strlen(precordName)>=pdbFldDes->size) return(S_dbLib_nameLength);
    /* clear callers entry */
    zeroDbentry(pdbentry);
    if(!dbFindRecord(pdbentry,precordName)) return (S_dbLib_recExists);
    zeroDbentry(pdbentry);
    pdbentry->precordType = precordType;
    preclist = &precordType->recList;
    /* create a recNode */
    pNewRecNode = dbCalloc(1,sizeof(dbRecordNode));
    /* create a new record of this record type */
    pdbentry->precnode = pNewRecNode;
    if((status = dbAllocRecord(pdbentry,precordName))) return(status);
    pNewRecNode->recordname = dbRecordName(pdbentry);
    ellInit(&pNewRecNode->infoList);
    ellAdd(preclist, &pNewRecNode->node);
    pdbentry->precnode = pNewRecNode;
    ppvd = dbPvdAdd(pdbentry->pdbbase,precordType,pNewRecNode);
    if(!ppvd) {errMessage(-1,"Logic Err: Could not add to PVD");return(-1);}
    pNewRecNode->order = pdbentry->pdbbase->no_records++;
    return(0);
}

long dbDeleteAliases(DBENTRY *pdbentry)
{
    dbBase          *pdbbase = pdbentry->pdbbase;
    dbRecordType    *precordType = pdbentry->precordType;
    dbRecordNode    *precnode = pdbentry->precnode;
    ELLLIST         *preclist = &precordType->recList;
    dbRecordNode    *pAliasNode, *pAliasNodeNext;
    DBENTRY         dbentry;
    void *precord;

    if (!precnode) return S_dbLib_recNotFound;
    if (precnode->flags & DBRN_FLAGS_ISALIAS) return S_dbLib_recExists;
    precord = precnode->precord;

    dbInitEntry(pdbbase, &dbentry);
    pAliasNode = (dbRecordNode *)ellFirst(preclist);
    while (pAliasNode) {
        pAliasNodeNext = (dbRecordNode *)ellNext(&pAliasNode->node);
        if (pAliasNode->flags & DBRN_FLAGS_ISALIAS &&
            pAliasNode->precord == precord &&
            !dbFindRecord(&dbentry, pAliasNode->recordname)) {
            dbDeleteRecord(&dbentry);
        }
        pAliasNode = pAliasNodeNext;
    }
    precnode->flags &= ~DBRN_FLAGS_HASALIAS;
    return 0;
}

long dbDeleteRecord(DBENTRY *pdbentry)
{
    dbBase          *pdbbase = pdbentry->pdbbase;
    dbRecordType    *precordType = pdbentry->precordType;
    dbRecordNode    *precnode = pdbentry->precnode;
    ELLLIST         *preclist;
    long            status;

    if (!precnode) return S_dbLib_recNotFound;
    if (precnode->flags & DBRN_FLAGS_HASALIAS)
        dbDeleteAliases(pdbentry);

    preclist = &precordType->recList;
    ellDelete(preclist, &precnode->node);
    dbPvdDelete(pdbbase, precnode);
    while (!dbFirstInfo(pdbentry)) {
        dbDeleteInfo(pdbentry);
    }
    if (precnode->flags & DBRN_FLAGS_ISALIAS) {
        free(precnode->recordname);
        precordType->no_aliases--;
    } else {
        status = dbFreeRecord(pdbentry);
        if (status) return status;
    }
    free(precnode);
    pdbentry->precnode = NULL;
    return 0;
}

long dbFreeRecords(DBBASE *pdbbase)
{
    DBENTRY         dbentry;
    dbRecordType    *pdbRecordType;
    dbRecordNode    *pdbRecordNode;
    dbRecordNode    *pdbRecordNodeNext;

    dbInitEntry(pdbbase,&dbentry);
    pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList);
    while(pdbRecordType) {
        pdbRecordNode = (dbRecordNode *)ellFirst(&pdbRecordType->recList);
        while(pdbRecordNode) {
            pdbRecordNodeNext = (dbRecordNode *)ellNext(&pdbRecordNode->node);
            if(!dbFindRecord(&dbentry,pdbRecordNode->recordname))
                dbDeleteRecord(&dbentry);
            pdbRecordNode = pdbRecordNodeNext;
        }
        pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node);
    }
    dbFinishEntry(&dbentry);
    return(0);
}

long dbFindRecordPart(DBENTRY *pdbentry, const char **ppname)
{
    dbBase      *pdbbase = pdbentry->pdbbase;
    const char  *pname = *ppname;
    const char  *pfn;
    size_t      lenName;
    PVDENTRY    *ppvdNode;

    zeroDbentry(pdbentry);
    pfn = strchr(pname, '.');
    if (pfn) {
        lenName = (size_t) (pfn - pname);
    } else {
        lenName = strlen(pname);
    }

    ppvdNode = dbPvdFind(pdbbase, pname, lenName);
    if (!ppvdNode)
        return S_dbLib_recNotFound;

    pdbentry->precnode = ppvdNode->precnode;
    pdbentry->precordType = ppvdNode->precordType;
    *ppname = pname + lenName;
    return 0;
}

long dbFindRecord(DBENTRY *pdbentry, const char *pname)
{
    long status = dbFindRecordPart(pdbentry, &pname);

    if (status) return status;
    if (*pname == '.')
        return dbFindField(pdbentry, ++pname);
    return 0;
}

long dbFirstRecord(DBENTRY *pdbentry)
{
    dbRecordType    *precordType = pdbentry->precordType;
    dbRecordNode    *precnode;

    zeroDbentry(pdbentry);
    if(!precordType) return(S_dbLib_recordTypeNotFound);
    pdbentry->precordType = precordType;
    precnode = (dbRecordNode *)ellFirst(&precordType->recList);
    if(!precnode) return(S_dbLib_recNotFound);
    pdbentry->precnode = precnode;
    return(0);
}

long dbNextRecord(DBENTRY *pdbentry)
{
    dbRecordNode    *precnode=pdbentry->precnode;
    long            status=0;

    if(!precnode) return(S_dbLib_recNotFound);
    precnode = (dbRecordNode *)ellNext(&precnode->node);
    if(!precnode) status = S_dbLib_recNotFound;
    pdbentry->precnode = precnode;
    pdbentry->pfield = NULL;
    return(status);
}

int dbGetNRecords(DBENTRY *pdbentry)
{
    dbRecordType    *precordType = pdbentry->precordType;

    if(!precordType) return(0);
    return(ellCount(&precordType->recList));
}

int dbGetNAliases(DBENTRY *pdbentry)
{
    dbRecordType    *precordType = pdbentry->precordType;

    if(!precordType) return(0);
    return(precordType->no_aliases);
}

char * dbGetRecordName(DBENTRY *pdbentry)
{
    dbRecordType *pdbRecordType = pdbentry->precordType;
    dbRecordNode *precnode = pdbentry->precnode;

    if(!pdbRecordType) return NULL;
    if(!precnode) return NULL;
    return precnode->recordname;
}

long dbVisibleRecord(DBENTRY *pdbentry)
{
    dbRecordNode    *precnode = pdbentry->precnode;

    if(!precnode) return(S_dbLib_recNotFound);
    precnode->flags |= DBRN_FLAGS_VISIBLE;
    return 0;
}

long dbInvisibleRecord(DBENTRY *pdbentry)
{
    dbRecordNode    *precnode = pdbentry->precnode;

    if(!precnode) return(S_dbLib_recNotFound);
    precnode->flags &= ~DBRN_FLAGS_VISIBLE;
    return 0;
}

int dbIsVisibleRecord(DBENTRY *pdbentry)
{
    dbRecordNode    *precnode = pdbentry->precnode;

    if(!precnode) return 0;
    return precnode->flags & DBRN_FLAGS_VISIBLE ? 1 : 0;
}

long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
{
    dbRecordType *precordType = pdbentry->precordType;
    dbRecordNode *precnode = pdbentry->precnode;
    dbRecordNode *pnewnode;
    DBENTRY tempEntry;
    PVDENTRY *ppvd;
    long status;

    if (!precordType)
        return S_dbLib_recordTypeNotFound;

    /* alias of alias still references actual record */
    while (precnode && (precnode->flags & DBRN_FLAGS_ISALIAS))
        precnode = precnode->aliasedRecnode;

    if (!precnode)
        return S_dbLib_recNotFound;

    dbInitEntry(pdbentry->pdbbase, &tempEntry);
    status = dbFindRecord(&tempEntry, alias);
    dbFinishEntry(&tempEntry);
    if (!status)
        return S_dbLib_recExists;

    pnewnode = dbCalloc(1, sizeof(dbRecordNode));
    pnewnode->recordname = epicsStrDup(alias);
    pnewnode->precord = precnode->precord;
    pnewnode->aliasedRecnode = precnode;
    pnewnode->flags = DBRN_FLAGS_ISALIAS;
    precnode->flags |= DBRN_FLAGS_HASALIAS;
    ellInit(&pnewnode->infoList);

    ppvd = dbPvdAdd(pdbentry->pdbbase, precordType, pnewnode);
    if (!ppvd) {
        errMessage(-1, "dbCreateAlias: Add to PVD failed");
        free(pnewnode);
        return -1;
    }

    ellAdd(&precordType->recList, &pnewnode->node);
    pnewnode->order = pdbentry->pdbbase->no_records++;
    precordType->no_aliases++;

    return 0;
}

int dbFollowAlias(DBENTRY *pdbentry)
{
    if(!pdbentry->precnode)
        return S_dbLib_recNotFound;
    if(pdbentry->precnode->aliasedRecnode)
        pdbentry->precnode = pdbentry->precnode->aliasedRecnode;
    return 0;
}

int dbIsAlias(DBENTRY *pdbentry)
{
    dbRecordNode    *precnode = pdbentry->precnode;

    if(!precnode) return 0;
    return precnode->flags & DBRN_FLAGS_ISALIAS ? 1 : 0;
}

long dbCopyRecord(DBENTRY *pdbentry,const char *newRecordName,int overWriteOK)
{
    dbRecordType    *precordType = pdbentry->precordType;
    dbFldDes        *pdbFldDes;
    dbRecordNode    *precnode = pdbentry->precnode;
    long            status;
    DBENTRY         dbentry;
    char            *pvalue;

    if(!precordType) return(S_dbLib_recordTypeNotFound);
    /*Get size of NAME field*/
    pdbFldDes = precordType->papFldDes[0];
    if(!pdbFldDes || (strcmp(pdbFldDes->name,"NAME")!=0))
        return(S_dbLib_nameLength);
    if((int)strlen(newRecordName)>=pdbFldDes->size) return(S_dbLib_nameLength);
    if (!precnode || dbIsAlias(pdbentry)) return S_dbLib_recNotFound;
    dbInitEntry(pdbentry->pdbbase,&dbentry);
    status = dbFindRecord(&dbentry,newRecordName);
    if(!status) {
        if(!overWriteOK) {
            dbFinishEntry(&dbentry);
            return(S_dbLib_recExists);
        }
        status = dbDeleteRecord(&dbentry);
        if(status) return(status);
    }
    dbFinishEntry(&dbentry);
    if((status = dbFindRecordType(&dbentry,precordType->name))) return(status);
    if((status = dbCreateRecord(&dbentry,newRecordName))) return(status);
    if((status = dbFirstField(pdbentry,TRUE))) return(status);
    if((status = dbFirstField(&dbentry,TRUE))) return(status);
    while(!status) {
        if(!dbIsDefaultValue(pdbentry)) {
            pvalue = dbGetString(pdbentry);
            if((status = dbPutString(&dbentry,pvalue))) return(status);
        }
        status = dbNextField(pdbentry,TRUE);
        if(!status) status = dbNextField(&dbentry,TRUE);
        if(!status && (pdbentry->pflddes!=dbentry.pflddes)) {
            epicsPrintf("dbCopyRecord: Logic Error\n");
            return(-1);
        }
    }
    /*Copy the info strings too*/
    status = dbFirstInfo(pdbentry);
    while (!status) {
        status = dbPutInfo(&dbentry, dbGetInfoName(pdbentry), dbGetInfoString(pdbentry));
        if (status) return (status);
        status = dbNextInfo(pdbentry);
    }
    /*Leave pdbentry pointing to newRecordName*/
    return(dbFindRecord(pdbentry,newRecordName));
}

long dbFindFieldPart(DBENTRY *pdbentry,const char **ppname)
{
    dbRecordType *precordType = pdbentry->precordType;
    dbRecordNode *precnode = pdbentry->precnode;
    const char   *pname = *ppname;
    short        top, bottom, test;
    char         **papsortFldName;
    short        *sortFldInd;
    int          ch;
    size_t       nameLen;

    if (!precordType) return S_dbLib_recordTypeNotFound;
    if (!precnode) return S_dbLib_recNotFound;
    papsortFldName = precordType->papsortFldName;
    sortFldInd = precordType->sortFldInd;

    /* Measure field name length; name is a valid C identifier */
    nameLen = 0;
    if ((ch = *pname) &&
        (ch == '_' || isalpha(ch))) {
        while ((ch = pname[++nameLen]))
            if (!(ch == '_' || isalnum(ch))) break;
    }

    /* Handle absent field name */
    if (nameLen == 0) {
        dbFldDes *pflddes = precordType->pvalFldDes;

        if (!pflddes)
            return S_dbLib_recordTypeNotFound;
        pdbentry->pflddes = pflddes;
        pdbentry->indfield = precordType->indvalFlddes;
        *ppname = &pname[nameLen];
        return dbGetFieldAddress(pdbentry);
    }

    /* binary search through ordered field names */
    top = precordType->no_fields - 1;
    bottom = 0;
    test = (top + bottom) / 2;
    while (1) {
        int compare = strncmp(papsortFldName[test], pname, nameLen);
        if (compare == 0)
            compare = (int) (strlen(papsortFldName[test]) - nameLen);
        if (compare == 0) {
            dbFldDes *pflddes = precordType->papFldDes[sortFldInd[test]];

            if (!pflddes)
                return S_dbLib_recordTypeNotFound;
            pdbentry->pflddes = pflddes;
            pdbentry->indfield = sortFldInd[test];
            *ppname = &pname[nameLen];
            return dbGetFieldAddress(pdbentry);
        } else if (compare > 0) {
            top = test - 1;
            if (top < bottom) break;
            test = (top + bottom) / 2;
        } else {
            bottom = test + 1;
            if (top < bottom) break;
            test = (top + bottom) / 2;
        }
    }
    return S_dbLib_fieldNotFound;
}

long dbFindField(DBENTRY *pdbentry,const char *pname)
{
    long status = dbFindFieldPart(pdbentry, &pname);
    int ch;

    if (status == S_dbLib_fieldNotFound)
        return dbGetRecordAttribute(pdbentry, pname);
    if (status) return status;

    ch = *pname;
    if (ch == 0 || isspace(ch)) return 0;
    return S_dbLib_recNotFound;
}

int dbFoundField(DBENTRY *pdbentry)
{ return((pdbentry->pfield) ? TRUE : FALSE); }

char * dbGetString(DBENTRY *pdbentry)
{
    dbFldDes    *pflddes = pdbentry->pflddes;
    void        *pfield = pdbentry->pfield;
    DBLINK      *plink;

    if (!pflddes) {
        dbMsgCpy(pdbentry, "fldDes not found");
        return pdbentry->message;
    }
    switch (pflddes->field_type) {
    case DBF_STRING:
    case DBF_INLINK:
    case DBF_OUTLINK:
    case DBF_FWDLINK:
        if (!pfield) {
            dbMsgCpy(pdbentry, "Field not allocated (NULL)");
            return pdbentry->message;
        }
        break;
    default:
        break;
    }

    switch (pflddes->field_type) {
    case DBF_STRING:
        /* Protect against a missing nil-terminator */
        dbMsgNCpy(pdbentry, (char *)pfield, pflddes->size);
        break;
    case DBF_CHAR:
    case DBF_UCHAR:
    case DBF_SHORT:
    case DBF_USHORT:
    case DBF_ENUM:
    case DBF_LONG:
    case DBF_ULONG:
    case DBF_INT64:
    case DBF_UINT64:
    case DBF_FLOAT:
    case DBF_DOUBLE:
    case DBF_MENU:
    case DBF_DEVICE:
        return(dbGetStringNum(pdbentry));
    case DBF_INLINK:
    case DBF_OUTLINK:
        plink = (DBLINK *)pfield;
        switch(plink->type) {
        case CONSTANT:
            if (plink->value.constantStr) {
                dbMsgCpy(pdbentry, plink->value.constantStr);
            } else if (plink->text) {
                dbMsgCpy(pdbentry, plink->text);
            } else {
                dbMsgCpy(pdbentry, "");
            }
            break;
        case MACRO_LINK:
            if (plink->value.macro_link.macroStr) {
                dbMsgCpy(pdbentry, plink->value.macro_link.macroStr);
            } else {
                dbMsgCpy(pdbentry, "");
            }
            break;
        case JSON_LINK:
            dbMsgCpy(pdbentry, plink->value.json.string);
            break;
        case PN_LINK:
            dbMsgPrint(pdbentry, "%s%s",
                   plink->value.pv_link.pvname ? plink->value.pv_link.pvname : "",
                   msstring[plink->value.pv_link.pvlMask&pvlOptMsMode]);
            break;
        case PV_LINK:
        case CA_LINK:
        case DB_LINK: {
            int     ppind;
            short   pvlMask;

            pvlMask = plink->value.pv_link.pvlMask;
            if (pvlMask&pvlOptPP) ppind=1;
            else if(pvlMask&pvlOptCA) ppind=2;
            else if(pvlMask&pvlOptCP) ppind=3;
            else if(pvlMask&pvlOptCPP) ppind=4;
            else ppind=0;
            dbMsgPrint(pdbentry, "%s%s%s%s",
                   plink->value.pv_link.pvname ? plink->value.pv_link.pvname : "",
                   (plink->flags & DBLINK_FLAG_TSELisTIME) ? ".TIME" : "",
                   ppstring[ppind],
                   msstring[plink->value.pv_link.pvlMask&pvlOptMsMode]);
            break;
        }
        case VME_IO:
            dbMsgPrint(pdbentry, "#C%d S%d @%s",
                plink->value.vmeio.card,plink->value.vmeio.signal,
                plink->value.vmeio.parm);
            break;
        case CAMAC_IO:
            dbMsgPrint(pdbentry, "#B%d C%d N%d A%d F%d @%s",
                plink->value.camacio.b,plink->value.camacio.c,
                plink->value.camacio.n,plink->value.camacio.a,
                plink->value.camacio.f,plink->value.camacio.parm);
            break;
        case RF_IO:
            dbMsgPrint(pdbentry, "#R%d M%d D%d E%d",
                plink->value.rfio.cryo,
                plink->value.rfio.micro,
                plink->value.rfio.dataset,
                plink->value.rfio.element);
            break;
        case AB_IO:
            dbMsgPrint(pdbentry, "#L%d A%d C%d S%d @%s",
                plink->value.abio.link,plink->value.abio.adapter,
                plink->value.abio.card,plink->value.abio.signal,
                plink->value.abio.parm);
            break;
        case GPIB_IO:
            dbMsgPrint(pdbentry, "#L%d A%d @%s",
                plink->value.gpibio.link,plink->value.gpibio.addr,
                plink->value.gpibio.parm);
            break;
        case BITBUS_IO:
            dbMsgPrint(pdbentry, "#L%u N%u P%u S%u @%s",
                plink->value.bitbusio.link,plink->value.bitbusio.node,
                plink->value.bitbusio.port,plink->value.bitbusio.signal,
                plink->value.bitbusio.parm);
            break;
        case BBGPIB_IO:
            dbMsgPrint(pdbentry, "#L%u B%u G%u @%s",
                plink->value.bbgpibio.link,plink->value.bbgpibio.bbaddr,
                plink->value.bbgpibio.gpibaddr,plink->value.bbgpibio.parm);
            break;
        case INST_IO:
            dbMsgPrint(pdbentry, "@%s", plink->value.instio.string);
            break;
        case VXI_IO :
            if (plink->value.vxiio.flag == VXIDYNAMIC)
                dbMsgPrint(pdbentry, "#V%d C%d S%d @%s",
                    plink->value.vxiio.frame,plink->value.vxiio.slot,
                    plink->value.vxiio.signal,plink->value.vxiio.parm);
            else
                dbMsgPrint(pdbentry, "#V%d S%d @%s",
                    plink->value.vxiio.la,plink->value.vxiio.signal,
                    plink->value.vxiio.parm);
            break;
        default :
            return(NULL);
        }
        break;
    case DBF_FWDLINK: {
            DBLINK *plink=(DBLINK *)pfield;

            switch(plink->type) {
            case CONSTANT:
                if (plink->value.constantStr) {
                    dbMsgCpy(pdbentry, plink->value.constantStr);
                } else if (plink->text) {
                    dbMsgCpy(pdbentry, plink->text);
                } else {
                    dbMsgCpy(pdbentry, "");
                }
                break;
            case MACRO_LINK:
                if (plink->value.macro_link.macroStr) {
                    dbMsgCpy(pdbentry, plink->value.macro_link.macroStr);
                } else {
                    dbMsgCpy(pdbentry, "");
                }
                break;
            case JSON_LINK:
                dbMsgCpy(pdbentry, plink->value.json.string);
                break;
            case PV_LINK:
            case CA_LINK:
            case DB_LINK: {
                int     ppind;
                short   pvlMask;

                pvlMask = plink->value.pv_link.pvlMask;
                if (pvlMask&pvlOptCA) ppind=2;
                else ppind=0;
                dbMsgPrint(pdbentry, "%s%s",
                    plink->value.pv_link.pvname ? plink->value.pv_link.pvname : "",
                    ppind ? ppstring[ppind] : "");
                break;
            }
            default :
                return(NULL);
            }
        }
        break;
    default:
        return(NULL);
    }
    return pdbentry->message;
}

char *dbGetStringNum(DBENTRY *pdbentry)
{
    dbFldDes    *pflddes = pdbentry->pflddes;
    void        *pfield = pdbentry->pfield;
    char        *message = getpMessage(pdbentry);
    unsigned char cvttype;

    if (!pfield) {
        dbMsgCpy(pdbentry, "Field not found");
        return message;
    }

    /* the following assumes that messagesize is large enough
     * to hold the base 10 encoded value of a 32-bit integer.
     */
    cvttype = pflddes->base;
    switch (pflddes->field_type) {
    case DBF_CHAR:
        if (cvttype == CT_DECIMAL)
            cvtCharToString(*(char *) pfield, message);
        else
            ulongToHexString(*(char *) pfield, message);
        break;
    case DBF_UCHAR:
        if (cvttype==CT_DECIMAL)
            cvtUcharToString(*(epicsUInt8 *) pfield, message);
        else
            ulongToHexString(*(epicsUInt8 *) pfield, message);
        break;
    case DBF_SHORT:
        if (cvttype==CT_DECIMAL)
            cvtShortToString(*(epicsInt16 *) pfield, message);
        else
            ulongToHexString(*(epicsInt16 *) pfield, message);
        break;
    case DBF_USHORT:
    case DBF_ENUM:
        if (cvttype==CT_DECIMAL)
            cvtUshortToString(*(epicsUInt16 *) pfield, message);
        else
            ulongToHexString(*(epicsUInt16 *) pfield, message);
        break;
    case DBF_LONG:
        if (cvttype==CT_DECIMAL)
            cvtLongToString(*(epicsInt32 *) pfield, message);
        else
            ulongToHexString(*(epicsInt32 *) pfield, message);
        break;
    case DBF_ULONG:
        if (cvttype==CT_DECIMAL)
            cvtUlongToString(*(epicsUInt32 *) pfield, message);
        else
            ulongToHexString(*(epicsUInt32 *) pfield, message);
        break;
    case DBF_INT64:
        if (cvttype==CT_DECIMAL)
            cvtInt64ToString(*(epicsInt64 *) pfield, message);
        else
            cvtInt64ToHexString(*(epicsInt64 *) pfield, message);
        break;
    case DBF_UINT64:
        if (cvttype==CT_DECIMAL)
            cvtUInt64ToString(*(epicsUInt32 *) pfield, message);
        else
            cvtUInt64ToHexString(*(epicsUInt32 *) pfield, message);
        break;
    case DBF_FLOAT:
        floatToString(*(epicsFloat32 *) pfield, message);
        break;
    case DBF_DOUBLE:
        doubleToString(*(epicsFloat64 *) pfield, message);
        break;
    case DBF_MENU:
        {
            dbMenu *pdbMenu = (dbMenu *)pflddes->ftPvt;
            epicsEnum16 choice_ind;

            if (!pdbMenu)
                return NULL;

            choice_ind = *((epicsEnum16 *) pdbentry->pfield);
            if (choice_ind >= pdbMenu->nChoice) {
                dbMsgPrint(pdbentry, "%u", choice_ind);
            }
            else {
                dbMsgCpy(pdbentry, pdbMenu->papChoiceValue[choice_ind]);
            }
        }
        break;
    case DBF_DEVICE:
        {
            dbDeviceMenu *pdbDeviceMenu = dbGetDeviceMenu(pdbentry);
            epicsEnum16 choice_ind;

            if (!pdbDeviceMenu) {
                dbMsgCpy(pdbentry, "");
                break;
            }

            choice_ind = *((epicsEnum16 *) pdbentry->pfield);
            if (choice_ind>=pdbDeviceMenu->nChoice)
                return NULL;

            dbMsgCpy(pdbentry, pdbDeviceMenu->papChoice[choice_ind]);
        }
        break;
    default:
        return NULL;
    }
    return message;
}

long dbInitRecordLinks(dbRecordType *rtyp, struct dbCommon *prec)
{
    short i;

    for (i=0; i<rtyp->no_links; i++) {
        dbLinkInfo link_info;
        dbFldDes *pflddes = rtyp->papFldDes[rtyp->link_ind[i]];
        DBLINK *plink = (DBLINK *)(((char *)prec) + pflddes->offset);
        devSup *devsup = NULL;

        plink->precord = prec;

        /* link fields are zero'd on allocation.
         * so are effectively CONSTANT, but with constantStr==NULL.
         * Here we initialize them to have the correct link type,
         * with zero values and empty (but non-NULL) strings.
         */

        if(pflddes->isDevLink) {
            devsup = (devSup *)ellNth(&rtyp->devList, prec->dtyp+1);
        }
        if(devsup)
            plink->type = devsup->link_type;
        else
            plink->type = CONSTANT;

        switch (plink->type) {
        /* constantStr is allowed to remain NULL if plink->text==NULL
         * constantStr==NULL has special meaning in recGblInitConstantLink()
         */
        case CONSTANT: plink->value.constantStr = NULL; break;
        case PV_LINK:  plink->value.pv_link.pvname = callocMustSucceed(1, 1, "init PV_LINK"); break;
        case JSON_LINK: plink->value.json.string = pNullString; break;
        case VME_IO: plink->value.vmeio.parm = pNullString; break;
        case CAMAC_IO: plink->value.camacio.parm = pNullString; break;
        case AB_IO: plink->value.abio.parm = pNullString; break;
        case GPIB_IO: plink->value.gpibio.parm = pNullString; break;
        case BITBUS_IO: plink->value.bitbusio.parm = pNullString; break;
        case INST_IO: plink->value.instio.string = pNullString; break;
        case BBGPIB_IO: plink->value.bbgpibio.parm = pNullString; break;
        case VXI_IO: plink->value.vxiio.parm = pNullString; break;
        }

        if(!plink->text)
            continue;

        if(dbParseLink(plink->text, pflddes->field_type, &link_info)!=0) {
            /* This was already parsed once when ->text was set.
             * Any syntax error messages were printed at that time.
             */

        } else if(dbCanSetLink(plink, &link_info, devsup)!=0) {
            errlogPrintf(ERL_ERROR ": %s.%s: can't initialize link type %d with \"%s\" (type %d)\n",
                         prec->name, pflddes->name, plink->type, plink->text, link_info.ltype);

        } else if(dbSetLink(plink, &link_info, devsup)) {
            errlogPrintf(ERL_ERROR ": %s.%s: failed to initialize link type %d with \"%s\" (type %d)\n",
                         prec->name, pflddes->name, plink->type, plink->text, link_info.ltype);
        }
        free(plink->text);
        plink->text = NULL;
    }
    return 0;
}

void dbFreeLinkInfo(dbLinkInfo *pinfo)
{
    if (pinfo->ltype == JSON_LINK) {
        dbJLinkFree(pinfo->jlink);
        pinfo->jlink = NULL;
    }
    free(pinfo->target);
    pinfo->target = NULL;
}

long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo)
{
    char *pstr;
    size_t len;
    double value;

    memset(pinfo, 0, sizeof(*pinfo));

    /* Strip leading white space */
    while (*str && isspace((int)*str)) str++;

    len = strlen(str);
    /* Strip trailing white space */
    while (len > 0 && isspace((int)str[len-1])) len--;

    pstr = malloc(len + 1);
    if (!pstr)
        return S_dbLib_outMem;
    pinfo->target = pstr;

    /* Check for Instrument I/O links */
    if (*str == '@') {
        pinfo->ltype = INST_IO;

        /* Store everything after the '@' */
        memcpy(pstr, str+1, --len);
        pstr[len] = '\0';
        return 0;
    }

    /* Store the stripped string */
    memcpy(pstr, str, len);
    pstr[len] = '\0';

    /* Check for braces => JSON */
    if (*str == '{' && str[len-1] == '}') {
        if (dbJLinkParse(str, len, ftype, &pinfo->jlink))
            goto fail;

        pinfo->ltype = JSON_LINK;
        return 0;
    }

    /* Check for other HW link types */
    if (*pstr == '#') {
        int ret;
        char junk = 0;
        char *parm = strchr(pstr, '@'); /* find start of parm string */

        if (parm) {
            *parm++ = '\0'; /* isolate the parm string for later */
            len -= (parm - pstr);
        }

        /* generalized extraction of ID character and integer pairs (eg. "#C15 S14") */
        ret = sscanf(pinfo->target, "# %c%i %c%i %c%i %c%i %c%i %c",
                     &pinfo->hwid[0], &pinfo->hwnums[0],
                     &pinfo->hwid[1], &pinfo->hwnums[1],
                     &pinfo->hwid[2], &pinfo->hwnums[2],
                     &pinfo->hwid[3], &pinfo->hwnums[3],
                     &pinfo->hwid[4], &pinfo->hwnums[4],
                     &junk);

        /* ret<0 when pattern not matched
         * ret==11 when extra non-space before '@'.
         * ret is odd when a number is missing
         */
        if (ret<0 || ret>10 || ret%2==1) goto fail;

        if      (strcmp(pinfo->hwid, "CS")==0)    pinfo->ltype = VME_IO;
        else if (strcmp(pinfo->hwid, "BCN")==0)   pinfo->ltype = CAMAC_IO;
        else if (strcmp(pinfo->hwid, "BCNA")==0)  pinfo->ltype = CAMAC_IO;
        else if (strcmp(pinfo->hwid, "BCNF")==0)  pinfo->ltype = CAMAC_IO;
        else if (strcmp(pinfo->hwid, "BCNAF")==0) pinfo->ltype = CAMAC_IO;
        else if (strcmp(pinfo->hwid, "RMDE")==0)  pinfo->ltype = RF_IO;
        else if (strcmp(pinfo->hwid, "LACS")==0)  pinfo->ltype = AB_IO;
        else if (strcmp(pinfo->hwid, "LA")==0)    pinfo->ltype = GPIB_IO;
        else if (strcmp(pinfo->hwid, "LNPS")==0)  pinfo->ltype = BITBUS_IO;
        else if (strcmp(pinfo->hwid, "LBG")==0)   pinfo->ltype = BBGPIB_IO;
        else if (strcmp(pinfo->hwid, "VCS")==0)   pinfo->ltype = VXI_IO;
        else if (strcmp(pinfo->hwid, "VS")==0)    pinfo->ltype = VXI_IO;
        else goto fail;

        if (pinfo->ltype != RF_IO) {
            if (!parm) {
                pinfo->target[0] = '\0';
            } else {
                /* move parm string to beginning of buffer */
                memmove(pinfo->target, parm, len + 1);
            }
        } else if (!parm && pinfo->ltype == RF_IO) {
            /* RF_IO, the string isn't needed at all */
            free(pinfo->target);
            pinfo->target = NULL;
        }
        else goto fail;

        return 0;
    }

    /* Link is a constant if empty or it holds just a number */
    if (len == 0 || epicsParseDouble(pstr, &value, NULL) == 0) {
        pinfo->ltype = CONSTANT;
        return 0;
    }

    /* Link may be an array constant */
    if (pstr[0] == '[' && pstr[len-1] == ']') {
        pinfo->ltype = CONSTANT;
        return 0;
    }

    pinfo->ltype = PV_LINK;
    pstr = strchr(pstr, ' '); /* find start of link modifiers (can't be separated by tabs) */
    if (pstr) {
        *pstr++ = '\0'; /* isolate modifiers. pinfo->target is PV name only for re-use in struct pv_link */

        /* Space separation of modifiers isn't required, and other chars are ignored.
         * Order of comparisons resolves ambiguity by checking for
         * longer matches first.
         * eg. "QQCPPXMSITT" is pvlOptCPP|pvlOptMSI
         */

        if (strstr(pstr, "NPP")) pinfo->modifiers = 0;
        else if (strstr(pstr, "CPP")) pinfo->modifiers = pvlOptCPP;
        else if (strstr(pstr, "PP")) pinfo->modifiers = pvlOptPP;
        else if (strstr(pstr, "CA")) pinfo->modifiers = pvlOptCA;
        else if (strstr(pstr, "CP")) pinfo->modifiers = pvlOptCP;

        if (strstr(pstr, "NMS")) pinfo->modifiers |= pvlOptNMS;
        else if (strstr(pstr, "MSI")) pinfo->modifiers |= pvlOptMSI;
        else if (strstr(pstr, "MSS")) pinfo->modifiers |= pvlOptMSS;
        else if (strstr(pstr, "MS")) pinfo->modifiers |= pvlOptMS;

        /* filter modifiers based on link type */
        switch(ftype) {
        case DBF_INLINK: /* accept all */ break;
        case DBF_OUTLINK: pinfo->modifiers &= ~pvlOptCPP; break;
        case DBF_FWDLINK: pinfo->modifiers &= pvlOptCA; break;
        }
    }

    return 0;
fail:
    dbFreeLinkInfo(pinfo);
    return S_dbLib_badField;
}

long dbCanSetLink(DBLINK *plink, dbLinkInfo *pinfo, devSup *devsup)
{
    /* Release pinfo resources on failure */
    int expected_type = devsup ? devsup->link_type : CONSTANT;

    if (pinfo->ltype == expected_type)
        return 0;

    switch (pinfo->ltype) {
    case CONSTANT:
    case JSON_LINK:
    case PV_LINK:
        if (expected_type == CONSTANT ||
            expected_type == JSON_LINK ||
            expected_type == PV_LINK)
            return 0;
    default:
        dbFreeLinkInfo(pinfo);
        return 1;
    }
}

static
void dbSetLinkConst(DBLINK *plink, dbLinkInfo *pinfo)
{
    plink->type = CONSTANT;
    plink->value.constantStr = pinfo->target;

    pinfo->target = NULL;
}

static
void dbSetLinkPV(DBLINK *plink, dbLinkInfo *pinfo)
{
    plink->type = PV_LINK;
    plink->value.pv_link.pvname = pinfo->target;
    plink->value.pv_link.pvlMask = pinfo->modifiers;

    pinfo->target = NULL;
}

static
void dbSetLinkJSON(DBLINK *plink, dbLinkInfo *pinfo)
{
    plink->type = JSON_LINK;
    plink->value.json.string = pinfo->target;
    plink->value.json.jlink = pinfo->jlink;

    pinfo->target = NULL;
    pinfo->jlink = NULL;
}

static
void dbSetLinkHW(DBLINK *plink, dbLinkInfo *pinfo)
{
    switch(pinfo->ltype) {
    case JSON_LINK:
        plink->value.json.string = pinfo->target;
        break;
    case INST_IO:
        plink->value.instio.string = pinfo->target;
        break;
    case VME_IO:
        plink->value.vmeio.card = pinfo->hwnums[0];
        plink->value.vmeio.signal = pinfo->hwnums[1];
        plink->value.vmeio.parm = pinfo->target;
        break;
    case CAMAC_IO:
        plink->value.camacio.b = pinfo->hwnums[0];
        plink->value.camacio.c = pinfo->hwnums[1];
        plink->value.camacio.n = pinfo->hwnums[2];
        plink->value.camacio.a = pinfo->hwnums[3];
        plink->value.camacio.f = pinfo->hwnums[4];
        plink->value.camacio.parm = pinfo->target;
        break;
    case RF_IO:
        plink->value.rfio.cryo = pinfo->hwnums[0];
        plink->value.rfio.micro = pinfo->hwnums[1];
        plink->value.rfio.dataset = pinfo->hwnums[2];
        plink->value.rfio.element = pinfo->hwnums[3];
        break;
    case AB_IO:
        plink->value.abio.link = pinfo->hwnums[0];
        plink->value.abio.adapter = pinfo->hwnums[1];
        plink->value.abio.card = pinfo->hwnums[2];
        plink->value.abio.signal = pinfo->hwnums[3];
        plink->value.abio.parm = pinfo->target;
        break;
    case GPIB_IO:
        plink->value.gpibio.link = pinfo->hwnums[0];
        plink->value.gpibio.addr = pinfo->hwnums[1];
        plink->value.gpibio.parm = pinfo->target;
        break;
    case BITBUS_IO:
        plink->value.bitbusio.link = pinfo->hwnums[0];
        plink->value.bitbusio.node = pinfo->hwnums[1];
        plink->value.bitbusio.port = pinfo->hwnums[2];
        plink->value.bitbusio.signal = pinfo->hwnums[3];
        plink->value.bitbusio.parm = pinfo->target;
        break;
    case BBGPIB_IO:
        plink->value.bbgpibio.link = pinfo->hwnums[0];
        plink->value.bbgpibio.bbaddr = pinfo->hwnums[1];
        plink->value.bbgpibio.gpibaddr = pinfo->hwnums[2];
        plink->value.bbgpibio.parm = pinfo->target;
        break;
    case VXI_IO:
        if(strcmp(pinfo->hwid, "VCS")==0) {
            plink->value.vxiio.flag=VXIDYNAMIC;
            plink->value.vxiio.frame = pinfo->hwnums[0];
            plink->value.vxiio.slot = pinfo->hwnums[1];
            plink->value.vxiio.signal = pinfo->hwnums[2];
        } else if(strcmp(pinfo->hwid, "VS")==0) {
            plink->value.vxiio.flag=VXISTATIC;
            plink->value.vxiio.la = pinfo->hwnums[0];
            plink->value.vxiio.signal = pinfo->hwnums[1];
        } else {
            cantProceed("dbSetLinkHW: logic error, unknown VXI_IO variant");
        }
        plink->value.vxiio.parm = pinfo->target;
        break;

    default:
        cantProceed("dbSetLinkHW: logic error, unhandled link type");
        return;
    }

    plink->type = pinfo->ltype;

    pinfo->target = NULL; /* now owned by link field */
}

long dbSetLink(DBLINK *plink, dbLinkInfo *pinfo, devSup *devsup)
{
    int expected_type = devsup ? devsup->link_type : CONSTANT;

    if (expected_type == CONSTANT ||
        expected_type == JSON_LINK ||
        expected_type == PV_LINK) {
        switch (pinfo->ltype) {
        case CONSTANT:
            dbFreeLinkContents(plink);
            dbSetLinkConst(plink, pinfo);
            break;
        case PV_LINK:
            dbFreeLinkContents(plink);
            dbSetLinkPV(plink, pinfo);
            break;
        case JSON_LINK:
            dbFreeLinkContents(plink);
            dbSetLinkJSON(plink, pinfo);
            break;
        default:
            errlogMessage("Warning: dbSetLink: forgot to test with dbCanSetLink() or logic error");
            goto fail; /* can't assign HW link */
        }
    }
    else if (expected_type == pinfo->ltype) {
        dbFreeLinkContents(plink);
        dbSetLinkHW(plink, pinfo);
    }
    else
        goto fail;

    return 0;
fail:
    dbFreeLinkInfo(pinfo);
    return S_dbLib_badField;
}

long dbPutString(DBENTRY *pdbentry,const char *pstring)
{
    dbFldDes    *pflddes = pdbentry->pflddes;
    void        *pfield = pdbentry->pfield;
    long        status=0;
    int         macroIsOk;
    int         stringHasMacro=FALSE;

    if(!pflddes) return(S_dbLib_flddesNotFound);
    macroIsOk = dbIsMacroOk(pdbentry);
    stringHasMacro = strstr(pstring,"$(") || strstr(pstring,"${");
    if(!macroIsOk && stringHasMacro) {
        epicsPrintf("%s.%s Has unexpanded macro\n",
                dbGetRecordName(pdbentry),dbGetFieldName(pdbentry));
        return(S_dbLib_badField);
    }
    switch (pflddes->field_type) {
    case DBF_STRING:
        if(!pfield) return(S_dbLib_fieldNotFound);
        if(strlen(pstring) >= (size_t)pflddes->size) return S_dbLib_strLen;
        strncpy((char *)pfield, pstring, pflddes->size-1);
        ((char *)pfield)[pflddes->size-1] = 0;

        if((pflddes->special == SPC_CALC) && !stringHasMacro) {
            char  rpcl[RPCL_LEN];
            short err;

            if (postfix(pstring,rpcl,&err)) {
                status = S_dbLib_badField;
                errlogPrintf("%s in CALC expression '%s'\n",
                              calcErrorStr(err), pstring);
            }
        }
        break;

    case DBF_CHAR:
    case DBF_SHORT:
    case DBF_LONG:
    case DBF_INT64:
    case DBF_UCHAR:
    case DBF_USHORT:
    case DBF_ULONG:
    case DBF_UINT64:
    case DBF_ENUM:
    case DBF_FLOAT:
    case DBF_DOUBLE:
    case DBF_MENU:
    case DBF_DEVICE:
        status = dbPutStringNum(pdbentry,pstring);
        break;

    case DBF_INLINK:
    case DBF_OUTLINK:
    case DBF_FWDLINK: {
            dbLinkInfo link_info;
            DBLINK *plink = (DBLINK *)pfield;

            status = dbParseLink(pstring, pflddes->field_type, &link_info);
            if (status) break;

            if (plink->type==CONSTANT && plink->value.constantStr==NULL) {
                /* links not yet initialized by dbInitRecordLinks() */
                free(plink->text);
                plink->text = epicsStrDup(pstring);
                dbFreeLinkInfo(&link_info);
            } else {
                /* assignment after init (eg. autosave restore) */
                struct dbCommon *prec = pdbentry->precnode->precord;
                devSup *devsup = (devSup *)ellNth(&pdbentry->precordType->devList, prec->dtyp+1);

                status = dbCanSetLink(plink, &link_info, devsup);
                if (status == 0)
                    status = dbSetLink(plink, &link_info, devsup);
            }
        }
        break;

    default:
        return S_dbLib_badField;
    }

    if (!status && strcmp(pflddes->name, "VAL") == 0) {
        DBENTRY dbentry;

        dbCopyEntryContents(pdbentry, &dbentry);
        if (!dbFindField(&dbentry, "UDF")) {
            dbPutString(&dbentry, "0");
        }
        dbFinishEntry(&dbentry);
    }
    return(status);
}

void dbPutStringSuggest(DBENTRY *pdbentry, const char *pstring)
{
    dbFldDes    *pflddes = pdbentry->pflddes;

    switch (pflddes->field_type) {
    case DBF_MENU:
    case DBF_DEVICE: {
        int i, nchoices = 0;
        char** choices = NULL;
        const char *best = NULL;
        double maxdist = 0.0; /* don't offer suggestions which have no similarity */

        if(pflddes->field_type==DBF_MENU) {
            dbMenu  *pdbMenu = (dbMenu *)pflddes->ftPvt;
            if(!pdbMenu)
                return;

            choices = pdbMenu->papChoiceValue;
            nchoices = pdbMenu->nChoice;

        } else {
            dbDeviceMenu *pdbDeviceMenu = dbGetDeviceMenu(pdbentry);
            if(!pdbDeviceMenu)
                return;

            choices = pdbDeviceMenu->papChoice;
            nchoices = pdbDeviceMenu->nChoice;
        }

        for(i=0; i<nchoices; i++) {
            double dist = epicsStrSimilarity(pstring, choices[i]);
            if(dist>maxdist) {
                best = choices[i];
                maxdist = dist;
            }
        }
        if(best) {
            epicsPrintf("    Did you mean \"%s\"?\n", best);
        }
    }
        break;
    default:
        break;
    }
}

char * dbVerify(DBENTRY *pdbentry, const char *pstring)
{
    dbFldDes *pflddes = pdbentry->pflddes;
    char *message = getpMessage(pdbentry);
    long status;
    union {
        epicsInt8    i8;
        epicsUInt8   u8;
        epicsInt16   i16;
        epicsUInt16  u16;
        epicsInt32   i32;
        epicsUInt32  u32;
        epicsInt64   i64;
        epicsUInt64  u64;
        epicsFloat32 f32;
        epicsFloat64 f64;
    } val;

    if (!pflddes) {
        strcpy(message, "fldDes not found");
        return message;
    }

    if (strstr(pstring,"$(") || strstr(pstring,"${"))
        return NULL;

    switch (pflddes->field_type) {
    case DBF_STRING:
    {
        size_t length = strlen(pstring);

        if (length >= pflddes->size) {
            sprintf(message, "String too long, max %d characters",
                pflddes->size - 1);
            return message;
        }

        if (pflddes->special == SPC_CALC) {
            char  rpcl[RPCL_LEN];
            short err;

            status = postfix(pstring, rpcl, &err);
            if (status)  {
                sprintf(message,"%s in CALC expression '%s'",
                        calcErrorStr(err), pstring);
                return message;
            }
        }

        return NULL;
    }

    case DBF_CHAR:
        status = epicsParseInt8(pstring, &val.i8, 0, NULL);
        break;

    case DBF_UCHAR:
        status = epicsParseUInt8(pstring, &val.u8, 0, NULL);
        break;

    case DBF_SHORT:
        status = epicsParseInt16(pstring, &val.i16, 0, NULL);
        break;

    case DBF_ENUM:
    case DBF_USHORT:
        status = epicsParseUInt16(pstring, &val.u16, 0, NULL);
        break;

    case DBF_LONG:
        status = epicsParseInt32(pstring, &val.i32, 0, NULL);
        break;

    case DBF_ULONG:
        status = epicsParseUInt32(pstring, &val.u32, 0, NULL);
        break;

    case DBF_INT64:
        status = epicsParseInt64(pstring, &val.i64, 0, NULL);
        break;

    case DBF_UINT64:
        status = epicsParseUInt64(pstring, &val.u64, 0, NULL);
        break;

    case DBF_FLOAT:
        status = epicsParseFloat32(pstring, &val.f32, NULL);
        break;

    case DBF_DOUBLE:
        status = epicsParseFloat64(pstring, &val.f64, NULL);
        break;

    case DBF_MENU:
        {
            dbMenu *pdbMenu = (dbMenu *)pflddes->ftPvt;
            int i;

            if (!pdbMenu)
                return NULL;

            for (i = 0; i < pdbMenu->nChoice; i++) {
                const char *pchoice = pdbMenu->papChoiceValue[i];

                if (!pchoice)
                    continue;

                if (strcmp(pchoice, pstring) == 0) {
                    return NULL;
                }
            }
        }
        strcpy(message, "Not a valid menu choice");
        return message;

    case DBF_DEVICE:
        {
            dbDeviceMenu *pdbDeviceMenu = dbGetDeviceMenu(pdbentry);
            int i;

            if (!pdbDeviceMenu || pdbDeviceMenu->nChoice == 0)
                return NULL;

            for (i = 0; i < pdbDeviceMenu->nChoice; i++) {
                const char *pchoice = pdbDeviceMenu->papChoice[i];

                if (!pchoice)
                    continue;

                if (strcmp(pchoice, pstring) == 0) {
                    return NULL;
                }
            }
        }
        strcpy(message, "Not a valid device type");
        return message;

    case DBF_INLINK:
    case DBF_OUTLINK:
    case DBF_FWDLINK:
        return NULL;

    default:
        strcpy(message, "Not a valid field type");
        return message;
    }

    switch (status) {
    case 0:
        message = NULL;
        break;

    case S_stdlib_noConversion:
        strcpy(message, "Not a valid integer");
        break;

    case S_stdlib_badBase:
        strcpy(message, "Internal error (badBase)");
        break;

    case S_stdlib_overflow:
        strcpy(message, "Number too large for field type");
        break;

    case S_stdlib_underflow:
        strcpy(message, "Number too small for field type");
        break;

    case S_stdlib_extraneous:
        strcpy(message, "Extraneous characters after number");
        break;

    default:
        strcpy(message, "Unknown numeric conversion error");
    }
    return message;
}

long dbFirstInfo(DBENTRY *pdbentry)
{
    dbRecordNode *precnode = pdbentry->precnode;

    pdbentry->pinfonode = NULL;
    if (!precnode) return (S_dbLib_recNotFound);

    pdbentry->pinfonode = (dbInfoNode *)ellFirst(&precnode->infoList);
    return (pdbentry->pinfonode ? 0 : S_dbLib_infoNotFound);
}

long dbNextInfo(DBENTRY *pdbentry)
{
    dbRecordNode *precnode = pdbentry->precnode;
    dbInfoNode *pinfo;

    if (!precnode) return (S_dbLib_recNotFound);
    pinfo = pdbentry->pinfonode;
    if (!pinfo) return (S_dbLib_infoNotFound);

    pinfo = (dbInfoNode *)ellNext(&pinfo->node);
    pdbentry->pinfonode = pinfo;
    return (pinfo ? 0 : S_dbLib_infoNotFound);
}

long dbNextMatchingInfo(DBENTRY *pdbentry, const char *pattern)
{
    long status;

    if (!pdbentry->precordType)
    {
        status = dbFirstRecordType(pdbentry);
        goto first;
    }
    while(1) {
        status = dbNextInfo(pdbentry);
        while (status) {
            status = dbNextRecord(pdbentry);
            while (status) {
                status = dbNextRecordType(pdbentry);
first:
                if (status) return status;
                status = dbFirstRecord(pdbentry);
            }
            status = dbFirstInfo(pdbentry);
        }
        if (!pattern || !*pattern) return 0;
        if (epicsStrGlobMatch(dbGetInfoName(pdbentry), pattern)) return 0;
    }
}

long dbFindInfo(DBENTRY *pdbentry,const char *name)
{
    dbRecordNode *precnode = pdbentry->precnode;
    dbInfoNode *pinfo;

    pdbentry->pinfonode = NULL;
    if (!precnode) return(S_dbLib_recNotFound);

    pinfo = (dbInfoNode *)ellFirst(&precnode->infoList);
    while (pinfo) {
        if (!strcmp(pinfo->name, name)) {
            pdbentry->pinfonode = pinfo;
            return (0);
        }
        pinfo = (dbInfoNode *)ellNext(&pinfo->node);
    }
    return (S_dbLib_infoNotFound);
}

long dbDeleteInfo(DBENTRY *pdbentry)
{
    dbRecordNode    *precnode = pdbentry->precnode;
    dbInfoNode      *pinfo = pdbentry->pinfonode;

    if (!precnode) return (S_dbLib_recNotFound);
    if (!pinfo) return (S_dbLib_infoNotFound);
    ellDelete(&precnode->infoList,&pinfo->node);
    free(pinfo->name);
    free(pinfo->string);
    free(pinfo);
    pdbentry->pinfonode = NULL;
    return (0);
}

const char * dbGetInfoName(DBENTRY *pdbentry)
{
    dbInfoNode *pinfo = pdbentry->pinfonode;
    if (!pinfo) return (NULL);
    return (pinfo->name);
}

const char * dbGetInfoString(DBENTRY *pdbentry)
{
    dbInfoNode *pinfo = pdbentry->pinfonode;
    if (!pinfo) return (NULL);
    return (pinfo->string);
}

long dbPutInfoString(DBENTRY *pdbentry,const char *string)
{
    dbInfoNode *pinfo = pdbentry->pinfonode;
    char *newstring;
    if (!pinfo) return (S_dbLib_infoNotFound);
    newstring = realloc(pinfo->string,1+strlen(string));
    if (!newstring) return (S_dbLib_outMem);
    strcpy(newstring, string);
    pinfo->string = newstring;
    return (0);
}

long dbPutInfoPointer(DBENTRY *pdbentry, void *pointer)
{
    dbInfoNode *pinfo = pdbentry->pinfonode;
    if (!pinfo) return (S_dbLib_infoNotFound);
    pinfo->pointer = pointer;
    return (0);
}

void * dbGetInfoPointer(DBENTRY *pdbentry)
{
    dbInfoNode *pinfo = pdbentry->pinfonode;
    if (!pinfo) return (NULL);
    return (pinfo->pointer);
}

const char * dbGetInfo(DBENTRY *pdbentry,const char *name)
{
    if (dbFindInfo(pdbentry, name)) return NULL;
    return dbGetInfoString(pdbentry);
}

long dbPutInfo(DBENTRY *pdbentry,const char *name,const char *string)
{
    dbInfoNode *pinfo;
    dbRecordNode *precnode = pdbentry->precnode;
    if (!precnode) return (S_dbLib_recNotFound);

    dbFindInfo(pdbentry, name);
    pinfo = pdbentry->pinfonode;
    if (pinfo) return (dbPutInfoString(pdbentry, string));

    /*Create new info node*/
    pinfo = calloc(1,sizeof(dbInfoNode));
    if (!pinfo) return (S_dbLib_outMem);
    pinfo->name = calloc(1,1+strlen(name));
    if (!pinfo->name) {
        free(pinfo);
        return (S_dbLib_outMem);
    }
    strcpy(pinfo->name, name);
    pinfo->string = calloc(1,1+strlen(string));
    if (!pinfo->string) {
        free(pinfo->name);
        free(pinfo);
        return (S_dbLib_outMem);
    }
    strcpy(pinfo->string, string);
    ellAdd(&precnode->infoList,&pinfo->node);
    pdbentry->pinfonode = pinfo;
    return (0);
}

brkTable * dbFindBrkTable(dbBase *pdbbase,const char *name)
{
    GPHENTRY *pgph;

    pgph = gphFind(pdbbase->pgpHash,name,(void *)&pdbbase->bptList);
    if(!pgph) return(NULL);
    return((brkTable *)pgph->userPvt);
}

const char * dbGetFieldTypeString(int dbfType)
{
    int i;

    for (i=0; i < DBF_NTYPES; i++) {
        if (pamapdbfType[i].value == dbfType) {
            return pamapdbfType[i].strvalue;
        }
    }
    return "BAD_DBF_TYPE";
}

int dbFindFieldType(const char *type)
{
    int i;

    for (i = 0; i < DBF_NTYPES; i++) {
        if (strcmp(type, pamapdbfType[i].strvalue) == 0) {
            return pamapdbfType[i].value;
        }
    }
    return -1;
}

dbMenu * dbFindMenu(dbBase *pdbbase,const char *name)
{
    GPHENTRY *pgph;

    pgph = gphFind(pdbbase->pgpHash,name,(void *)&pdbbase->menuList);
    if(!pgph) return(NULL);
    return((dbMenu *)pgph->userPvt);
}

char   ** dbGetMenuChoices(DBENTRY *pdbentry)
{
    dbFldDes    *pflddes = pdbentry->pflddes;

    if(!pflddes) return(NULL);
    switch (pflddes->field_type) {
    case DBF_MENU: {
            dbMenu  *pdbMenu = (dbMenu *)pflddes->ftPvt;

            if(!pdbMenu) return(NULL);
            return(pdbMenu->papChoiceValue);
        }
    case DBF_DEVICE: {
            dbDeviceMenu *pdbDeviceMenu;

            pdbDeviceMenu = dbGetDeviceMenu(pdbentry);
            if(!pdbDeviceMenu) return(NULL);
            return(pdbDeviceMenu->papChoice);
        }
    default:
        return(NULL);
    }
}

int dbGetNMenuChoices(DBENTRY *pdbentry)
{
    dbFldDes    *pflddes = pdbentry->pflddes;

    if(!pflddes) return(-1);
    switch (pflddes->field_type) {
    case DBF_MENU: {
            dbMenu  *pdbMenu = (dbMenu *)pflddes->ftPvt;

            if(!pdbMenu) return(0);
            return(pdbMenu->nChoice);
        }
    case DBF_DEVICE: {
            dbDeviceMenu *pdbDeviceMenu;

            pdbDeviceMenu = dbGetDeviceMenu(pdbentry);
            if(!pdbDeviceMenu) return(0);
            return(pdbDeviceMenu->nChoice);
        }
    default:
        break;
    }
    return (-1);
}

char * dbGetMenuStringFromIndex(DBENTRY *pdbentry, int index)
{
    dbFldDes    *pflddes = pdbentry->pflddes;

    if(!pflddes) return(NULL);
    switch (pflddes->field_type) {
    case DBF_MENU: {
            dbMenu  *pdbMenu = (dbMenu *)pflddes->ftPvt;

            if(!pdbMenu) return(NULL);
            if(index<0 || index>=pdbMenu->nChoice) return(NULL);
            return(pdbMenu->papChoiceValue[index]);
        }
    case DBF_DEVICE: {
            dbDeviceMenu *pdbDeviceMenu;

            pdbDeviceMenu = dbGetDeviceMenu(pdbentry);
            if(!pdbDeviceMenu) return(NULL);
            if(index<0 || index>=pdbDeviceMenu->nChoice) return(NULL);
            return(pdbDeviceMenu->papChoice[index]);
        }
    default:
        break;
    }
    return (NULL);
}

int dbGetMenuIndexFromString(DBENTRY *pdbentry, const char *choice)
{
    dbFldDes    *pflddes = pdbentry->pflddes;
    int         ind;
    int         nChoice = 0;
    char        **papChoice = NULL;

    if(!pflddes) return(-1);
    switch (pflddes->field_type) {
    case DBF_MENU: {
            dbMenu  *pdbMenu = (dbMenu *)pflddes->ftPvt;

            if(!pdbMenu) return(-1);
            papChoice = pdbMenu->papChoiceValue;
            nChoice = pdbMenu->nChoice;
            break;
        }
    case DBF_DEVICE: {
            dbDeviceMenu *pdbDeviceMenu;

            pdbDeviceMenu = dbGetDeviceMenu(pdbentry);
            if(!pdbDeviceMenu) return(-1);
            papChoice = pdbDeviceMenu->papChoice;
            nChoice = pdbDeviceMenu->nChoice;
            break;
        }
    default:
        return(-1);
    }
    if(nChoice<=0 || !papChoice) return(-1);
    for(ind=0; ind<nChoice; ind++) {
        if(strcmp(choice,papChoice[ind])==0) return(ind);
    }
    return (-1);
}

drvSup * dbFindDriver(dbBase *pdbbase, const char *name) {
    GPHENTRY *pgph = gphFind(pdbbase->pgpHash,name,&pdbbase->drvList);
    if (!pgph) return NULL;
    return (drvSup *) pgph->userPvt;
}

char * dbGetRelatedField(DBENTRY *psave)
{
    DBENTRY     dbEntry;
    DBENTRY     *pdbentry= &dbEntry;
    dbFldDes    *pflddes;
    char        *rtnval = NULL;
    long        status;

    pflddes = psave->pflddes;
    if(pflddes->field_type !=DBF_DEVICE) return(NULL);
    dbCopyEntryContents(psave,pdbentry);
    pflddes = pdbentry->pflddes;
    status = dbFindField(pdbentry,"INP");
    if(status) status = dbFindField(pdbentry,"OUT");
    if(!status) rtnval = pdbentry->pflddes->name;
    dbFinishEntry(pdbentry);
    return(rtnval);
}

linkSup* dbFindLinkSup(dbBase *pdbbase, const char *name) {
    GPHENTRY *pgph = gphFind(pdbbase->pgpHash,name,&pdbbase->linkList);
    if (!pgph) return NULL;
    return (linkSup *) pgph->userPvt;
}

int  dbGetNLinks(DBENTRY *pdbentry)
{
    dbRecordType    *precordType = pdbentry->precordType;

    if(!precordType) return(S_dbLib_recordTypeNotFound);
    return((int)precordType->no_links);
}

long  dbGetLinkField(DBENTRY *pdbentry, int index)
{
    dbRecordType *precordType = pdbentry->precordType;
    dbFldDes *pflddes;

    if (!precordType)
        return S_dbLib_recordTypeNotFound;

    if (index < 0 || index >= precordType->no_links)
        return S_dbLib_badLink;

    pdbentry->indfield = precordType->link_ind[index];
    pdbentry->pflddes = pflddes = precordType->papFldDes[pdbentry->indfield];
    dbGetFieldAddress(pdbentry);
    return 0;
}

void  dbDumpPath(DBBASE *pdbbase)
{
    ELLLIST     *ppathList;
    dbPathNode  *pdbPathNode;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    ppathList = (ELLLIST *)pdbbase->pathPvt;
    if(!ppathList || !(pdbPathNode = (dbPathNode *)ellFirst(ppathList))) {
        printf("no path defined\n");
        return;
    }
    while(pdbPathNode) {
        printf("%s",pdbPathNode->directory);
        pdbPathNode = (dbPathNode *)ellNext(&pdbPathNode->node);
        if(pdbPathNode) printf("%s", OSI_PATH_LIST_SEPARATOR);
    }
    printf("\n");
    return;
}

void  dbDumpRecord(
    dbBase *pdbbase,const char *precordTypename,int level)
{
    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    dbWriteRecordFP(pdbbase,stdout,precordTypename,level);
}

void  dbDumpMenu(dbBase *pdbbase,const char *menuName)
{
    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    dbWriteMenuFP(pdbbase,stdout,menuName);
}

void dbDumpRecordType(DBBASE *pdbbase,const char *recordTypeName)
{
    dbRecordType    *pdbRecordType;
    dbFldDes        *pdbFldDes;
    int             gotMatch;
    int             i;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList);
    pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) {
        if(recordTypeName) {
            gotMatch = (strcmp(recordTypeName,pdbRecordType->name)==0)
                ? TRUE : FALSE;
        }else {
            gotMatch=TRUE;
        }
        if(!gotMatch) continue;
        printf("name(%s) no_fields(%hd) no_prompt(%hd) no_links(%hd)\n",
            pdbRecordType->name,pdbRecordType->no_fields,
            pdbRecordType->no_prompt,pdbRecordType->no_links);
        printf("index offset size name\tsortind sortname\n");
        for(i=0; i<pdbRecordType->no_fields; i++) {
            pdbFldDes = pdbRecordType->papFldDes[i];
            printf("%5d %6u %4u %s\t%7d %s\n",
                i,pdbFldDes->offset,pdbFldDes->size, pdbFldDes->name,
                pdbRecordType->sortFldInd[i],pdbRecordType->papsortFldName[i]);
        }
        printf("link_ind ");
        for(i=0; i<pdbRecordType->no_links; i++)
            printf(" %hd",pdbRecordType->link_ind[i]);
        printf("\n");
        printf("indvalFlddes %d name %s\n",pdbRecordType->indvalFlddes,
            pdbRecordType->pvalFldDes->name);
        printf("rset * %p rec_size %d\n",
            (void *)pdbRecordType->prset,pdbRecordType->rec_size);
        if(recordTypeName) break;
    }
}

void  dbDumpField(
    DBBASE *pdbbase,const char *recordTypeName,const char *fname)
{
    dbRecordType        *pdbRecordType;
    dbFldDes            *pdbFldDes;
    int                 gotMatch;
    int                 i;
    dbRecordAttribute   *pAttribute;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList);
    pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) {
        if(recordTypeName) {
            gotMatch = (strcmp(recordTypeName,pdbRecordType->name)==0)
                ? TRUE : FALSE;
        }else {
            gotMatch=TRUE;
        }
        if(!gotMatch) continue;
        printf("recordtype(%s) \n",pdbRecordType->name);
        for(i=0; i<pdbRecordType->no_fields; i++) {
            int j;

            pdbFldDes = pdbRecordType->papFldDes[i];
            if(fname && strcmp(fname,pdbFldDes->name)!=0) continue;
            printf("    %s\n", pdbFldDes->name);
            printf("\t         prompt: %s\n",
                (pdbFldDes->prompt ? pdbFldDes->prompt : ""));
            printf("\t          extra: %s\n",
                (pdbFldDes->extra ? pdbFldDes->extra: ""));
            printf("\t      indRecordType: %hd\n",pdbFldDes->indRecordType);
            printf("\t        special: %hd ",pdbFldDes->special);
            if(pdbFldDes->special) {
                for(j=0; j<SPC_NTYPES; j++) {
                    if(pamapspcType[j].value == pdbFldDes->special) {
                        printf("%s",pamapspcType[j].strvalue);
                        break;
                    }
                }
            }
            printf("\n");
            printf("\t     field_type: %s\n",
                dbGetFieldTypeString(pdbFldDes->field_type));
            printf("\tprocess_passive: %u\n",pdbFldDes->process_passive);
            printf("\t       property: %u\n",pdbFldDes->prop);
            printf("\t           base: %d\n",pdbFldDes->base);
            if(!pdbFldDes->promptgroup) {
                printf("\t    promptgroup: %d\n",pdbFldDes->promptgroup);
            } else {
            printf("\t    promptgroup: %s\n",
                    dbGetPromptGroupNameFromKey(pdbbase, pdbFldDes->promptgroup));
        }
            printf("\t       interest: %hd\n", pdbFldDes->interest);
            printf("\t       as_level: %d\n",pdbFldDes->as_level);
            printf("\t        initial: %s\n",
                (pdbFldDes->initial ? pdbFldDes->initial : ""));
            if(pdbFldDes->field_type==DBF_MENU) {
                if(pdbFldDes->ftPvt)
                    printf("\t\t  menu: %s\n",
                        ((dbMenu *)pdbFldDes->ftPvt)->name);
                else
                    printf("\t\t  menu: NOT FOUND\n");
            }
            if(pdbFldDes->field_type==DBF_DEVICE) {
                printf("\t          ftPvt: %p\n",pdbFldDes->ftPvt);
            }
            printf("\t           size: %hd\n",pdbFldDes->size);
            printf("\t         offset: %hd\n",pdbFldDes->offset);
        }
        pAttribute =
            (dbRecordAttribute *)ellFirst(&pdbRecordType->attributeList);
        while(pAttribute) {
            printf("Attribute: name %s value %s\n",
                pAttribute->name,pAttribute->value);
            pAttribute = (dbRecordAttribute *)ellNext(&pAttribute->node);
        }
        if(recordTypeName) break;
    }
}

void  dbDumpDevice(DBBASE *pdbbase,const char *recordTypeName)
{
    dbRecordType    *pdbRecordType;
    devSup          *pdevSup;
    int             gotMatch;

    if (recordTypeName) {
        if (*recordTypeName == 0 || *recordTypeName == '*')
            recordTypeName = 0;
    }
    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList);
    pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) {
        if(recordTypeName) {
            gotMatch = (strcmp(recordTypeName,pdbRecordType->name)==0)
                ? TRUE : FALSE;
        }else {
            gotMatch=TRUE;
        }
        if(!gotMatch) continue;
        printf("recordtype(%s)\n",pdbRecordType->name);
        for(pdevSup = (devSup *)ellFirst(&pdbRecordType->devList);
        pdevSup; pdevSup = (devSup *)ellNext(&pdevSup->node)) {
            printf("    device name:   %s\n",pdevSup->name);
            printf("\tchoice:    %s\n",pdevSup->choice);
            printf("\tlink_type: %d\n",pdevSup->link_type);
            printf("\tpdset:     %p\n",(void *)pdevSup->pdset);
            if (pdevSup->pdset) {
                static const char *names[] = {
                    " - report()",
                    " - init()",
                    " - init_record()",
                    " - get_ioint_info()"
                };
                int i, n = pdevSup->pdset->number;
                DEVSUPFUN *pfunc = &pdevSup->pdset->report;

                printf("\t    number: %d\n", n);
                for (i = 0; i < n; ++i, ++pfunc) {
                    const char *name = (i < NELEMENTS(names)) ? names[i] : "";

                    printf("\t    func %d: %p%s\n", i, (void *)*pfunc, name);
                }
            }
            printf("\tpdsxt:     %p\n",(void *)pdevSup->pdsxt);
            if (pdevSup->pdsxt) {
                printf("\t    add_record: %p\n",
                    (void *)pdevSup->pdsxt->add_record);
                printf("\t    del_record: %p\n",
                    (void *)pdevSup->pdsxt->del_record);
            }
        }
        if(recordTypeName) break;
    }
}

void  dbDumpDriver(DBBASE *pdbbase)
{
    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    dbWriteDriverFP(pdbbase,stdout);
}

void  dbDumpLink(DBBASE *pdbbase)
{
    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    dbWriteLinkFP(pdbbase,stdout);
}

void  dbDumpRegistrar(DBBASE *pdbbase)
{
    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    dbWriteRegistrarFP(pdbbase,stdout);
}

void  dbDumpFunction(DBBASE *pdbbase)
{
    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    dbWriteFunctionFP(pdbbase,stdout);
}

void  dbDumpVariable(DBBASE *pdbbase)
{
    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    dbWriteVariableFP(pdbbase,stdout);
}

void  dbDumpBreaktable(DBBASE *pdbbase,const char *name)
{
    brkTable *pbrkTable;
    brkInt   *pbrkInt;
    int      ind;

    if(!pdbbase) {
        fprintf(stderr,"pdbbase not specified\n");
        return;
    }
    for(pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList);
    pbrkTable; pbrkTable = (brkTable *)ellNext(&pbrkTable->node)) {
        if (name && strcmp(name,pbrkTable->name)!=0) continue;
        printf("breaktable(%s) {\n",pbrkTable->name);
        pbrkInt = pbrkTable->paBrkInt;
        for(ind=0; ind < pbrkTable->number; ind++) {
            printf("\traw=%f slope=%e eng=%f\n",
                   pbrkInt->raw, pbrkInt->slope, pbrkInt->eng);
            pbrkInt++;
        }
        printf("}\n");
    }
    return;
}

static char *bus[LINK_NTYPES] = {
    "",     /* CONSTANT */
    NULL,   /* PV_LINK */
    "VME",
    "CAMAC",
    "AB",
    "GPIB",
    "BITBUS",
    NULL,   /* MACRO_LINK */
    NULL,   /* JSON_LINK */
    NULL,   /* PN_LINK */
    NULL,   /* DB_LINK */
    NULL,   /* CA_LINK */
    "INST",
    "BBGPIB",
    "VXI"
};
void  dbReportDeviceConfig(dbBase *pdbbase, FILE *report)
{
    DBENTRY dbentry, *pdbentry = &dbentry;
    long status;
    FILE *stream = report ? report : stdout;

    if (!pdbbase) {
        fprintf(stderr, "dbReportDeviceConfig: pdbbase not specified\n");
        return;
    }

    dbInitEntry(pdbbase,pdbentry);
    status = dbFirstRecordType(pdbentry);
    while (!status) {
        const int nlinks = dbGetNLinks(pdbentry);

        status = dbFirstRecord(pdbentry);
        while (!status) {
            int ilink;

            for (ilink=0; ilink<nlinks; ilink++) {
                char linkValue[messagesize];
                char dtypValue[50];
                char cvtValue[40];
                struct link *plink;
                int linkType;

                status = dbGetLinkField(pdbentry, ilink);
                if (status)
                    continue;

                plink = pdbentry->pfield;
                linkType = plink->type;
                if (plink->text) {  /* Not yet parsed */
                    dbLinkInfo linfo;

                    if (dbParseLink(plink->text, pdbentry->pflddes->field_type, &linfo))
                        continue;

                    linkType = linfo.ltype;
                    if (linkType && bus[linkType])
                        strncpy(linkValue, plink->text, messagesize-1);

                    dbFreeLinkInfo(&linfo);
                }
                else {
                    strncpy(linkValue, dbGetString(pdbentry), messagesize-1);
                }

                if (!linkType || !bus[linkType])
                    continue;
                linkValue[messagesize-1] = '\0';

                status = dbFindField(pdbentry, "DTYP");
                if (status)
                    break;  /* Next record type */

                strcpy(dtypValue, dbGetString(pdbentry));
                status = dbFindField(pdbentry, "LINR");
                if (status) {
                    cvtValue[0] = 0;
                }
                else {
                    if (strcmp(dbGetString(pdbentry), "LINEAR") != 0) {
                        cvtValue[0] = 0;
                    }
                    else {
                        strcpy(cvtValue,"cvt(");
                        status = dbFindField(pdbentry, "EGUL");
                        if (!status)
                            strcat(cvtValue, dbGetString(pdbentry));
                        status = dbFindField(pdbentry, "EGUF");
                        if (!status) {
                            strcat(cvtValue, ",");
                            strcat(cvtValue, dbGetString(pdbentry));
                        }
                        strcat(cvtValue, ")");
                    }
                }
                fprintf(stream,"%-8s %-20s %-20s %-20s %-s\n",
                        bus[linkType], linkValue, dtypValue,
                        dbGetRecordName(pdbentry), cvtValue);
                break;
            }
            status = dbNextRecord(pdbentry);
        }
        status = dbNextRecordType(pdbentry);
    }
    dbFinishEntry(pdbentry);
    finishOutstream(stream);
    return;
}
