/*************************************************************************\
* Copyright (c) 2010 Brookhaven National Laboratory.
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
*     fuer Materialien und Energie GmbH.
* Copyright (c) 2002 The University of Chicago, 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.
\*************************************************************************/
/* dbAccess.c */
/*
 *      Original Author: Bob Dalesio
 *      Current Author:  Marty Kraimer
 *                       Andrew Johnson <anj@aps.anl.gov>
 *                       Ralph Lange <Ralph.Lange@bessy.de>
 */

#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "alarm.h"
#include "cantProceed.h"
#include "cvtFast.h"
#include "dbDefs.h"
#include "ellLib.h"
#include "epicsMath.h"
#include "epicsThread.h"
#include "epicsTime.h"
#include "errlog.h"
#include "errMdef.h"

#include "caeventmask.h"
#include "callback.h"
#include "dbAccessDefs.h"
#include "dbAddr.h"
#include "dbBase.h"
#include "dbBkpt.h"
#include "dbCommonPvt.h"
#include "dbConvertFast.h"
#include "dbConvert.h"
#include "dbEvent.h"
#include "db_field_log.h"
#include "dbFldTypes.h"
#include "dbFldTypes.h"
#include "dbLink.h"
#include "dbLockPvt.h"
#include "dbNotify.h"
#include "dbScan.h"
#include "dbServer.h"
#include "dbStaticLib.h"
#include "dbStaticPvt.h"
#include "devSup.h"
#include "epicsEvent.h"
#include "link.h"
#include "recGbl.h"
#include "recSup.h"
#include "special.h"
#include "epicsExport.h"

struct dbBase *pdbbase = 0;
volatile int interruptAccept=FALSE;

int dbAccessDebugPUTF = 0;
epicsExportAddress(int, dbAccessDebugPUTF);

/* Hook Routines */

DB_LOAD_RECORDS_HOOK_ROUTINE dbLoadRecordsHook = NULL;

static const short mapDBFToDBR[DBF_NTYPES] = {
    /* DBF_STRING   => */    DBR_STRING,
    /* DBF_CHAR     => */    DBR_CHAR,
    /* DBF_UCHAR    => */    DBR_UCHAR,
    /* DBF_SHORT    => */    DBR_SHORT,
    /* DBF_USHORT   => */    DBR_USHORT,
    /* DBF_LONG     => */    DBR_LONG,
    /* DBF_ULONG    => */    DBR_ULONG,
    /* DBF_INT64    => */    DBR_INT64,
    /* DBF_UINT64   => */    DBR_UINT64,
    /* DBF_FLOAT    => */    DBR_FLOAT,
    /* DBF_DOUBLE   => */    DBR_DOUBLE,
    /* DBF_ENUM,    => */    DBR_ENUM,
    /* DBF_MENU,    => */    DBR_ENUM,
    /* DBF_DEVICE   => */    DBR_ENUM,
    /* DBF_INLINK   => */    DBR_STRING,
    /* DBF_OUTLINK  => */    DBR_STRING,
    /* DBF_FWDLINK  => */    DBR_STRING,
    /* DBF_NOACCESS => */    DBR_NOACCESS
};

/*
 *  The number of consecutive attempts that can be made to process an
 *    active record before a SCAN_ALARM is raised.  Active records
 *    (records with the pact flag set) cannot be processed until
 *    that flag becomes unset.
 */
#define MAX_LOCK 10

/* The following is to handle SPC_AS */
static SPC_ASCALLBACK spcAsCallback = 0;

void dbSpcAsRegisterCallback(SPC_ASCALLBACK func)
{
    spcAsCallback = func;
}

long dbPutSpecial(DBADDR *paddr,int pass)
{
    long int    (*pspecial)(struct dbAddr *, int)=NULL;
    rset        *prset;
    dbCommon    *precord = paddr->precord;
    long        status=0;
    long        special=paddr->special;

    prset = dbGetRset(paddr);
    if(special<100) { /*global processing*/
        if((special==SPC_NOMOD) && (pass==0)) {
            status = S_db_noMod;
            recGblDbaddrError(status,paddr,"dbPut");
            return(status);
        }else if(special==SPC_SCAN){
            if(pass==0)
                scanDelete(precord);
            else
                scanAdd(precord);
        }else if((special==SPC_AS) && (pass==1)) {
            if(spcAsCallback) (*spcAsCallback)(precord);
        }
    }else {
        if( prset && (pspecial = (prset->special))) {
            status=(*pspecial)(paddr,pass);
            if(status) return(status);
        } else if(pass==0){
            recGblRecSupError(S_db_noSupport,paddr,"dbPut", "special");
            return(S_db_noSupport);
        }
    }
    return(0);
}

static void get_enum_strs(DBADDR *paddr, char **ppbuffer,
                          rset *prset, long *options)
{
    struct dbr_enumStrs *penum=(struct dbr_enumStrs*)(*ppbuffer);
    /* advance output buffer on success or failure for next option */
    *ppbuffer = dbr_enumStrs_size + (char*)penum;

    memset(penum, 0, dbr_enumStrs_size);

    /* from this point
     * on success, return early
     * on failure, jump or fall through to clear *options bit.
     */

    if(paddr->field_type == DBF_ENUM) {
        if( prset && prset->get_enum_strs ) {
            (*prset->get_enum_strs)(paddr,penum);
            return;
        }

    } else if(paddr->field_type == DBF_MENU || paddr->field_type == DBF_DEVICE) {
        char **ppchoices;
        epicsUInt32 i, nchoices = 0;

        if(paddr->field_type == DBF_MENU) {
            dbMenu *pmenu = paddr->pfldDes->ftPvt;
            nchoices = pmenu->nChoice;
            ppchoices= pmenu->papChoiceValue;

        } else if(paddr->field_type == DBF_DEVICE) {
            dbDeviceMenu *pdevs = paddr->pfldDes->ftPvt;
            if(!pdevs)
                goto nostrs;

            nchoices = pdevs->nChoice;
            ppchoices = pdevs->papChoice;
        }

        if(nchoices > NELEMENTS(penum->strs))
            nchoices = NELEMENTS(penum->strs); /* available > capacity, truncated list */

        penum->no_str = nchoices;

        for(i=0; i<nchoices; i++) {
            if(ppchoices[i]) {
                strncpy(penum->strs[i], ppchoices[i],
                        sizeof(penum->strs[i]));
                /* strs[i][] allowed to omit trailing nil */
            } else {
                penum->strs[i][0] = '\0';
            }
        }
        return;

    } else {
        /* other DBF_* fall through to error */
    }

nostrs:
    /* indicate option data not available.  distinct from no_str==0 */
    *options = (*options)^DBR_ENUM_STRS;
}

static void get_graphics(DBADDR *paddr, char **ppbuffer,
    rset *prset,long    *options)
{
        struct          dbr_grDouble grd;
        int             got_data=FALSE;

        grd.upper_disp_limit = grd.lower_disp_limit = 0.0;
        if( prset && prset->get_graphic_double ) {
                (*prset->get_graphic_double)(paddr,&grd);
                got_data=TRUE;
        }
        if( (*options) & (DBR_GR_LONG) ) {
                char    *pbuffer=*ppbuffer;

                if(got_data) {
                    struct dbr_grLong *pgr=(struct dbr_grLong*)pbuffer;
                    pgr->upper_disp_limit = (epicsInt32)grd.upper_disp_limit;
                    pgr->lower_disp_limit = (epicsInt32)grd.lower_disp_limit;
                } else {
                    memset(pbuffer,'\0',dbr_grLong_size);
                    *options = (*options) ^ DBR_GR_LONG; /*Turn off option*/
                }
                *ppbuffer = ((char *)*ppbuffer) + dbr_grLong_size;
        }
        if( (*options) & (DBR_GR_DOUBLE) ) {
                char    *pbuffer=*ppbuffer;

                if(got_data) {
                    struct dbr_grDouble *pgr=(struct dbr_grDouble*)pbuffer;
                    pgr->upper_disp_limit = grd.upper_disp_limit;
                    pgr->lower_disp_limit = grd.lower_disp_limit;
                } else {
                    memset(pbuffer,'\0',dbr_grDouble_size);
                    *options = (*options) ^ DBR_GR_DOUBLE; /*Turn off option*/
                }
                *ppbuffer = ((char *)*ppbuffer) + dbr_grDouble_size;
        }
        return;
}

static void get_control(DBADDR *paddr, char **ppbuffer,
    rset *prset,long    *options)
{
        struct dbr_ctrlDouble   ctrld;
        int                     got_data=FALSE;

        ctrld.upper_ctrl_limit = ctrld.lower_ctrl_limit = 0.0;
        if( prset && prset->get_control_double ) {
                (*prset->get_control_double)(paddr,&ctrld);
                got_data=TRUE;
        }
        if( (*options) & (DBR_CTRL_LONG) ) {
                char    *pbuffer=*ppbuffer;

                if(got_data) {
                    struct dbr_ctrlLong *pctrl=(struct dbr_ctrlLong*)pbuffer;
                    pctrl->upper_ctrl_limit = (epicsInt32)ctrld.upper_ctrl_limit;
                    pctrl->lower_ctrl_limit = (epicsInt32)ctrld.lower_ctrl_limit;
                } else {
                    memset(pbuffer,'\0',dbr_ctrlLong_size);
                    *options = (*options) ^ DBR_CTRL_LONG; /*Turn off option*/
                }
                *ppbuffer = ((char *)*ppbuffer) + dbr_ctrlLong_size;
        }
        if( (*options) & (DBR_CTRL_DOUBLE) ) {
                char    *pbuffer=*ppbuffer;

                if(got_data) {
                   struct dbr_ctrlDouble *pctrl=(struct dbr_ctrlDouble*)pbuffer;
                   pctrl->upper_ctrl_limit = ctrld.upper_ctrl_limit;
                   pctrl->lower_ctrl_limit = ctrld.lower_ctrl_limit;
                } else {
                    memset(pbuffer,'\0',dbr_ctrlDouble_size);
                    *options = (*options) ^ DBR_CTRL_DOUBLE; /*Turn off option*/
                }
                *ppbuffer = ((char *)*ppbuffer) + dbr_ctrlDouble_size;
        }
        return;
}

static void get_alarm(DBADDR *paddr, char **ppbuffer,
    rset *prset, long *options)
{
    char *pbuffer = *ppbuffer;
    struct dbr_alDouble ald = {epicsNAN, epicsNAN, epicsNAN, epicsNAN};
    long no_data = TRUE;

    if (prset && prset->get_alarm_double)
        no_data = prset->get_alarm_double(paddr, &ald);

    if (*options & DBR_AL_LONG) {
        struct dbr_alLong *pal = (struct dbr_alLong*) pbuffer;

        pal->upper_alarm_limit   = finite(ald.upper_alarm_limit) ?
            (epicsInt32) ald.upper_alarm_limit : 0;
        pal->upper_warning_limit = finite(ald.upper_warning_limit) ?
            (epicsInt32) ald.upper_warning_limit : 0;
        pal->lower_warning_limit = finite(ald.lower_warning_limit) ?
            (epicsInt32) ald.lower_warning_limit : 0;
        pal->lower_alarm_limit   = finite(ald.lower_alarm_limit) ?
            (epicsInt32) ald.lower_alarm_limit : 0;

        if (no_data)
            *options ^= DBR_AL_LONG; /*Turn off option*/

        *ppbuffer += dbr_alLong_size;
    }
    if (*options & DBR_AL_DOUBLE) {
        struct dbr_alDouble *pal = (struct dbr_alDouble*) pbuffer;

        pal->upper_alarm_limit   = ald.upper_alarm_limit;
        pal->upper_warning_limit = ald.upper_warning_limit;
        pal->lower_warning_limit = ald.lower_warning_limit;
        pal->lower_alarm_limit   = ald.lower_alarm_limit;

        if (no_data)
            *options ^= DBR_AL_DOUBLE; /*Turn off option*/

        *ppbuffer += dbr_alDouble_size;
    }
}

/*
 * This code relies on *poriginal being aligned and all increments done by the
 * blocks only changing the buffer pointer in a way that does not break alignment.
 */
static void getOptions(DBADDR *paddr, char **poriginal, long *options,
        void *pflin)
{
        db_field_log    *pfl= (db_field_log *)pflin;
        rset            *prset;
        short           field_type;
        dbCommon        *pcommon;
        char            *pbuffer = *poriginal;

        if (!pfl)
            field_type = paddr->field_type;
        else
            field_type = pfl->field_type;
        prset=dbGetRset(paddr);
        /* Process options */
        pcommon = paddr->precord;
        if( (*options) & DBR_STATUS ) {
            unsigned short *pushort = (unsigned short *)pbuffer;

            if (!pfl) {
                *pushort++ = pcommon->stat;
                *pushort++ = pcommon->sevr;
            } else {
                *pushort++ = pfl->stat;
                *pushort++ = pfl->sevr;
            }
            *pushort++ = pcommon->acks;
            *pushort++ = pcommon->ackt;
            pbuffer = (char *)pushort;
        }
        if( (*options) & DBR_AMSG ) {
            if (!pfl) {
                STATIC_ASSERT(sizeof(pcommon->amsg)==DB_AMSG_SIZE);
                strncpy(pbuffer, pcommon->amsg, DB_AMSG_SIZE);
            } else {
                STATIC_ASSERT(sizeof(pfl->amsg)==DB_AMSG_SIZE);
                strncpy(pbuffer, pfl->amsg, DB_AMSG_SIZE);
            }
            pbuffer[DB_AMSG_SIZE-1] = '\0';
            pbuffer += DB_AMSG_SIZE;
        }
        if( (*options) & DBR_UNITS ) {
            memset(pbuffer,'\0',dbr_units_size);
            if( prset && prset->get_units ){
                (*prset->get_units)(paddr, pbuffer);
                pbuffer[DB_UNITS_SIZE-1] = '\0';
            } else {
                *options ^= DBR_UNITS; /*Turn off DBR_UNITS*/
            }
            pbuffer += dbr_units_size;
        }
        if( (*options) & DBR_PRECISION ) {
            memset(pbuffer, '\0', dbr_precision_size);
            if((field_type==DBF_FLOAT || field_type==DBF_DOUBLE)
            &&  prset && prset->get_precision ){
                (*prset->get_precision)(paddr,(long *)pbuffer);
            } else {
                *options ^= DBR_PRECISION; /*Turn off DBR_PRECISION*/
            }
            pbuffer += dbr_precision_size;
        }
        if( (*options) & DBR_TIME ) {
            epicsUInt32 *ptime = (epicsUInt32 *)pbuffer;

            if (!pfl) {
                *ptime++ = pcommon->time.secPastEpoch;
                *ptime++ = pcommon->time.nsec;
            } else {
                *ptime++ = pfl->time.secPastEpoch;
                *ptime++ = pfl->time.nsec;
            }
            pbuffer = (char *)ptime;
        }
        if( (*options) & DBR_UTAG ) {
            epicsUInt64 *ptag = (epicsUInt64*)pbuffer;
            if (!pfl) {
                *ptag++ = pcommon->utag;
            } else {
                *ptag++ = pfl->utag;
            }
            pbuffer = (char *)ptag;
        }
        if( (*options) & DBR_ENUM_STRS )
            get_enum_strs(paddr, &pbuffer, prset, options);
        if( (*options) & (DBR_GR_LONG|DBR_GR_DOUBLE ))
            get_graphics(paddr, &pbuffer, prset, options);
        if((*options) & (DBR_CTRL_LONG | DBR_CTRL_DOUBLE ))
            get_control(paddr, &pbuffer, prset, options);
        if((*options) & (DBR_AL_LONG | DBR_AL_DOUBLE ))
            get_alarm(paddr, &pbuffer, prset, options);
        *poriginal = pbuffer;
}

rset * dbGetRset(const struct dbAddr *paddr)
{
        struct dbFldDes *pfldDes = paddr->pfldDes;

        if(!pfldDes) return(0);
        return(pfldDes->pdbRecordType->prset);
}

long dbPutAttribute(
    const char *recordTypename, const char *name, const char *value)
{
    DBENTRY dbEntry;
    DBENTRY *pdbEntry = &dbEntry;
    long status = 0;

    if (!pdbbase)
        return S_db_notFound;
    if (!name) {
        status = S_db_badField;
        goto done;
    }
    if (!value)
        value = "";
    dbInitEntry(pdbbase, pdbEntry);
    status = dbFindRecordType(pdbEntry, recordTypename);
    if (!status)
        status = dbPutRecordAttribute(pdbEntry, name, value);
    dbFinishEntry(pdbEntry);
done:
    if (status)
        errMessage(status, "dbPutAttribute failure");
    return status;
}

int dbIsValueField(const struct dbFldDes *pdbFldDes)
{
    if (pdbFldDes->pdbRecordType->indvalFlddes == pdbFldDes->indRecordType)
        return TRUE;
    else
        return FALSE;
}

int dbGetFieldIndex(const struct dbAddr *paddr)
{
    return paddr->pfldDes->indRecordType;
}

/*
 *   Process the record.
 *     1.  Check for breakpoints.
 *     2.  Check the process active flag (PACT).
 *     3.  Check the disable link.
 *     4.  Check the RSET (record support entry table) exists.
 *     5.  Run the process routine specific to the record type.
 *     6.  Check to see if record contents should be automatically printed.
 */
long dbProcess(dbCommon *precord)
{
    rset *prset = precord->rset;
    dbRecordType *pdbRecordType = precord->rdes;
    unsigned char tpro = precord->tpro;
    char context[40] = "";
    long status = 0;
    int *ptrace;
    int set_trace = FALSE;
    dbFldDes *pdbFldDes;
    int callNotifyCompletion = FALSE;

    ptrace = dbLockSetAddrTrace(precord);
    /*
     *  Note that it is likely that if any changes are made
     *   to dbProcess() corresponding changes will have to
     *   be made in the breakpoint handler.
     */

    /* see if there are any stopped records or breakpoints */
    if (lset_stack_count != 0) {
        /*
         *  Check to see if the record should be processed
         *   and activate breakpoint accordingly.  If this
         *   function call returns non-zero, skip record
         *   support and fall out of dbProcess().  This is
         *   done so that a dbContTask() can be spawned to
         *   take over record processing for the lock set
         *   containing a breakpoint.
         */
        if (dbBkpt(precord))
            goto all_done;
    }

    /* check for trace processing*/
    if (tpro) {
        if (!*ptrace) {
            *ptrace = 1;
            set_trace = TRUE;
        }
    }

    if (*ptrace) {
        /* Identify this thread's client from server layer */
        if (dbServerClient(context, sizeof(context))) {
            /* No client, use thread name */
            strncpy(context, epicsThreadGetNameSelf(), sizeof(context));
            context[sizeof(context) - 1] = 0;
        }
    }

    /* If already active don't process */
    if (precord->pact) {
        unsigned short monitor_mask;

        if (*ptrace)
            printf("%s: dbProcess of Active '%s' with RPRO=%d\n",
                context, precord->name, precord->rpro);

        /* raise scan alarm after MAX_LOCK times */
        if ((precord->stat == SCAN_ALARM) ||
            (precord->lcnt++ < MAX_LOCK) ||
            (precord->sevr >= INVALID_ALARM)) goto all_done;

        recGblSetSevrMsg(precord, SCAN_ALARM, INVALID_ALARM, "Async in progress");
        monitor_mask = recGblResetAlarms(precord);
        monitor_mask |= DBE_VALUE|DBE_LOG;
        pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->indvalFlddes];
        db_post_events(precord,
                ((char *)precord) + pdbFldDes->offset,
                monitor_mask);
        goto all_done;
    }
    else
        precord->lcnt = 0;

    /*
     *  Check the record disable link.  A record will not be
     *    processed if the value retrieved through this link
     *    is equal to constant set in the record's disv field.
     */
    status = dbGetLink(&precord->sdis, DBR_SHORT, &precord->disa, 0, 0);

    /* if disabled check disable alarm severity and return success */
    if (precord->disa == precord->disv) {
        if (*ptrace)
            printf("%s: dbProcess of Disabled '%s'\n",
                context, precord->name);

        /*take care of caching and notifyCompletion*/
        precord->rpro = FALSE;
        precord->putf = FALSE;
        callNotifyCompletion = TRUE;

        /* raise disable alarm */
        if (precord->stat == DISABLE_ALARM)
            goto all_done;

        precord->sevr = precord->diss;
        precord->stat = DISABLE_ALARM;
        precord->nsev = 0;
        precord->nsta = 0;
        db_post_events(precord, &precord->stat, DBE_VALUE);
        db_post_events(precord, &precord->sevr, DBE_VALUE);
        pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->indvalFlddes];
        db_post_events(precord,
                ((char *)precord) + pdbFldDes->offset,
                DBE_VALUE|DBE_ALARM);
        goto all_done;
    }

    /* locate record processing routine */
    /* FIXME: put this in iocInit() !!! */
    if (!prset || !prset->process) {
        callNotifyCompletion = TRUE;
        precord->pact = 1;/*set pact so error is issued only once*/
        recGblRecordError(S_db_noRSET, precord, "dbProcess");
        status = S_db_noRSET;
        if (*ptrace)
            printf("%s: No RSET for %s\n", context, precord->name);
        goto all_done;
    }

    if (*ptrace)
        printf("%s: dbProcess of '%s'\n", context, precord->name);

    /* process record */
    status = prset->process(precord);

    /* Print record's fields if PRINT_MASK set in breakpoint field */
    if (lset_stack_count != 0) {
        dbPrint(precord);
    }

all_done:
    if (set_trace)
        *ptrace = 0;
    if (callNotifyCompletion && precord->ppn)
        dbNotifyCompletion(precord);

    return status;
}

long dbEntryToAddr(const DBENTRY *pdbentry, DBADDR *paddr)
{
    dbFldDes *pflddes = pdbentry->pflddes;
    short dbfType = pflddes->field_type;

    paddr->precord = pdbentry->precnode->precord;
    paddr->pfield = pdbentry->pfield;
    paddr->pfldDes = pflddes;
    paddr->no_elements = 1;
    paddr->field_type  = dbfType;
    paddr->field_size  = pflddes->size;
    paddr->special     = pflddes->special;
    paddr->dbr_field_type = mapDBFToDBR[dbfType];

    if (paddr->special == SPC_DBADDR) {
        const rset *prset = dbGetRset(paddr);

        /* Let record type modify paddr */
        if (prset && prset->cvt_dbaddr) {
            return prset->cvt_dbaddr(paddr);
        }
    }
    return 0;
}

/*
 *  Fill out a database structure (*paddr) for
 *    a record given by the name "pname."
 *
 *    Returns error codes from StaticLib module, not
 *        from dbAccess.
 */
long dbNameToAddr(const char *pname, DBADDR *paddr)
{
    DBENTRY dbEntry;
    long status = 0;

    if (!pname || !*pname || !pdbbase)
        return S_db_notFound;

    dbInitEntry(pdbbase, &dbEntry);
    status = dbFindRecordPart(&dbEntry, &pname);
    if (status) goto finish;

    if (*pname == '.') ++pname;
    status = dbFindFieldPart(&dbEntry, &pname);
    if (status == S_dbLib_fieldNotFound)
        status = dbGetAttributePart(&dbEntry, &pname);
    if (status) goto finish;

    status = dbEntryToAddr(&dbEntry, paddr);
    if (status) goto finish;

    /* Handle field modifiers */
    if (*pname++ == '$') {
        short dbfType = paddr->field_type;

        /* Some field types can be accessed as char arrays */
        if (dbfType == DBF_STRING) {
            paddr->no_elements = paddr->field_size;
            paddr->field_type = DBF_CHAR;
            paddr->field_size = 1;
            paddr->dbr_field_type = DBR_CHAR;
        }
        else if (dbfType >= DBF_INLINK && dbfType <= DBF_FWDLINK) {
            /* Clients see a char array, but keep original dbfType */
            paddr->no_elements = PVLINK_STRINGSZ;
            paddr->field_size = 1;
            paddr->dbr_field_type = DBR_CHAR;
        }
        else {
            status = S_dbLib_fieldNotFound;
        }
    }

finish:
    dbFinishEntry(&dbEntry);
    return status;
}

void dbInitEntryFromAddr(struct dbAddr *paddr, DBENTRY *pdbentry)
{
    struct dbCommon *prec = paddr->precord;
    dbCommonPvt *ppvt = dbRec2Pvt(prec);

    memset(pdbentry, '\0', sizeof(DBENTRY));

    pdbentry->pdbbase = pdbbase;
    pdbentry->precordType = prec->rdes;
    pdbentry->precnode = ppvt->recnode;
    pdbentry->pflddes = paddr->pfldDes;
    pdbentry->pfield = paddr->pfield;
    pdbentry->indfield = paddr->pfldDes->indRecordType;
}

void dbInitEntryFromRecord(struct dbCommon *prec, DBENTRY *pdbentry)
{
    dbCommonPvt *ppvt = dbRec2Pvt(prec);

    memset(pdbentry, '\0', sizeof(DBENTRY));

    pdbentry->pdbbase = pdbbase;
    pdbentry->precordType = prec->rdes;
    pdbentry->precnode = ppvt->recnode;
}

struct link* dbGetDevLink(struct dbCommon* prec)
{
    DBLINK *plink = 0;
    DBENTRY entry;
    dbInitEntryFromRecord(prec, &entry);
    if(dbFindField(&entry, "INP")==0 || dbFindField(&entry, "OUT")==0) {
        plink = (DBLINK*)entry.pfield;
    }
    dbFinishEntry(&entry);
    return plink;
}

long dbValueSize(short dbr_type)
{
    /* sizes for value associated with each DBR request type */
    static long size[] = {
        MAX_STRING_SIZE,             /* STRING       */
        sizeof(epicsInt8),           /* CHAR         */
        sizeof(epicsUInt8),          /* UCHAR        */
        sizeof(epicsInt16),          /* SHORT        */
        sizeof(epicsUInt16),         /* USHORT       */
        sizeof(epicsInt32),          /* LONG         */
        sizeof(epicsUInt32),         /* ULONG        */
        sizeof(epicsInt64),          /* INT64        */
        sizeof(epicsUInt64),         /* UINT64       */
        sizeof(epicsFloat32),        /* FLOAT        */
        sizeof(epicsFloat64),        /* DOUBLE       */
        sizeof(epicsEnum16)};        /* ENUM         */

    if(dbr_type>=NELEMENTS(size))
        return 0;
    return(size[dbr_type]);
}


long dbBufferSize(short dbr_type, long options, long no_elements)
{
    long nbytes=0;

    nbytes += dbValueSize(dbr_type) * no_elements;
    if (options & DBR_STATUS)      nbytes += dbr_status_size;
    if (options & DBR_UNITS)       nbytes += dbr_units_size;
    if (options & DBR_PRECISION)   nbytes += dbr_precision_size;
    if (options & DBR_TIME)        nbytes += dbr_time_size;
    if (options & DBR_ENUM_STRS)   nbytes += dbr_enumStrs_size;
    if (options & DBR_GR_LONG)     nbytes += dbr_grLong_size;
    if (options & DBR_GR_DOUBLE)   nbytes += dbr_grDouble_size;
    if (options & DBR_CTRL_LONG)   nbytes += dbr_ctrlLong_size;
    if (options & DBR_CTRL_DOUBLE) nbytes += dbr_ctrlDouble_size;
    if (options & DBR_AL_LONG)     nbytes += dbr_alLong_size;
    if (options & DBR_AL_DOUBLE)   nbytes += dbr_alDouble_size;
    return(nbytes);
}
int dbLoadDatabase(const char *file, const char *path, const char *subs)
{
    if (!file) {
        printf("Usage: dbLoadDatabase \"file\", \"path\", \"subs\"\n");
        return -1;
    }
    return dbReadDatabase(&pdbbase, file, path, subs);
}

int dbLoadRecords(const char* file, const char* subs)
{
    int status;

    if (!file) {
        printf("Usage: dbLoadRecords \"file\", \"subs\"\n");
        return -1;
    }
    status = dbReadDatabase(&pdbbase, file, 0, subs);
    if(status==0) {
        if(dbLoadRecordsHook)
            dbLoadRecordsHook(file, subs);
    } else {
        fprintf(stderr, ERL_ERROR ": Failed to load '%s'\n", file);
        if(status==-2)
            fprintf(stderr, "    Records cannot be loaded after iocInit!\n");
    }
    return status;
}


static long getLinkValue(DBADDR *paddr, short dbrType,
    char *pbuf, long *nRequest)
{
    /* size of pbuf storage in bytes, including space for trailing nil */
    int maxlen;
    DBENTRY dbEntry;
    long nReq = nRequest ? *nRequest : 1;

    /* below will always succeed as we have a
     * valid DBADDR, so no point to check again.
     * Request for zero elements always succeeds
     */
    if(!nReq)
        return 0;

    switch (dbrType) {
    case DBR_STRING:
        maxlen = MAX_STRING_SIZE;
        nReq = 1;
        break;

    case DBR_DOUBLE:    /* Needed for dbCa links */
        if (nRequest) *nRequest = 1;
        *(double *)pbuf = epicsNAN;
        return 0;

    case DBR_CHAR:
    case DBR_UCHAR:
        maxlen = nReq;
        break;
    default:
        return S_db_badDbrtype;
    }

    dbInitEntryFromAddr(paddr, &dbEntry);
    {
        const char *rtnString = dbGetString(&dbEntry);

        strncpy(pbuf, rtnString, maxlen);
        pbuf[maxlen-1] = 0;
        if(dbrType!=DBR_STRING)
            nReq = strlen(pbuf)+1;
        if(nRequest) *nRequest = nReq;
    }
    dbFinishEntry(&dbEntry);
    return 0;
}

static long getAttrValue(DBADDR *paddr, short dbrType,
        char *pbuf, long *nRequest)
{
    int maxlen;
    long nReq = nRequest ? *nRequest : 1;

    if (!paddr->pfield) return S_db_badField;

    switch (dbrType) {
    case DBR_STRING:
        maxlen = MAX_STRING_SIZE;
        nReq = 1;
        break;

    case DBR_CHAR:
    case DBR_UCHAR:
        maxlen = nReq;
        break;

        /* else fall through ... */
    default:
        return S_db_badDbrtype;
    }

    strncpy(pbuf, paddr->pfield, maxlen-1);
    pbuf[maxlen-1] = 0;
    if(dbrType!=DBR_STRING)
        nReq = strlen(pbuf)+1;
    if(nRequest) *nRequest = nReq;
    return 0;
}

long dbGetField(DBADDR *paddr,short dbrType,
    void *pbuffer, long *options, long *nRequest, void *pflin)
{
    dbCommon *precord = paddr->precord;
    long status = 0;

    dbScanLock(precord);
    status = dbGet(paddr, dbrType, pbuffer, options, nRequest, pflin);
    dbScanUnlock(precord);
    return status;
}

long dbGet(DBADDR *paddr, short dbrType,
    void *pbuffer, long *options, long *nRequest, void *pflin)
{
    char *pbuf = pbuffer;
    void *pfieldsave = paddr->pfield;
    db_field_log *pfl = (db_field_log *)pflin;
    short field_type;
    long capacity, no_elements, offset;
    rset *prset;
    long status = 0;

    if (options && *options)
        getOptions(paddr, &pbuf, options, pflin);
    if (nRequest && *nRequest == 0)
        return 0;

    if (!pfl) {
        field_type = paddr->field_type;
        no_elements = capacity = paddr->no_elements;
    } else {
        field_type = pfl->field_type;
        no_elements = capacity = pfl->no_elements;
    }

    /* Update field info from record (if necessary);
     * may modify paddr->pfield.
     */
    if (!dbfl_has_copy(pfl) &&
        paddr->pfldDes->special == SPC_DBADDR &&
        (prset = dbGetRset(paddr)) &&
        prset->get_array_info) {
        status = prset->get_array_info(paddr, &no_elements, &offset);
    } else {
        offset = 0;
    }

    if (field_type >= DBF_INLINK && field_type <= DBF_FWDLINK) {
        status = getLinkValue(paddr, dbrType, pbuf, nRequest);
        goto done;
    }

    if (paddr->special == SPC_ATTRIBUTE) {
        status = getAttrValue(paddr, dbrType, pbuf, nRequest);
        goto done;
    }

    /* Check for valid request */
    if (INVALID_DB_REQ(dbrType) || field_type > DBF_DEVICE) {
        char message[80];

        sprintf(message, "dbGet: Request type is %d\n", dbrType);
        recGblDbaddrError(S_db_badDbrtype, paddr, message);
        status = S_db_badDbrtype;
        goto done;
    }

    if (offset == 0 && (!nRequest || no_elements == 1)) {
        if (nRequest)
            *nRequest = 1;
        else if (no_elements < 1) {
            status = S_db_onlyOne;
            goto done;
        }

        if (!dbfl_has_copy(pfl)) {
            status = dbFastGetConvertRoutine[field_type][dbrType]
                (paddr->pfield, pbuf, paddr);
        } else {
            DBADDR localAddr = *paddr; /* Structure copy */

            if (no_elements < 1) {
                status = S_db_badField;
                goto done;
            }

            localAddr.field_type = pfl->field_type;
            localAddr.field_size = pfl->field_size;
            /* not used by dbFastConvert: */
            localAddr.no_elements = pfl->no_elements;
            localAddr.pfield = dbfl_pfield(pfl);
            status = dbFastGetConvertRoutine[field_type][dbrType]
                (localAddr.pfield, pbuf, &localAddr);
        }
    } else {
        long n;
        GETCONVERTFUNC convert;

        if (nRequest) {
            if (no_elements < *nRequest)
                *nRequest = no_elements;
            if (capacity < *nRequest)
                *nRequest = capacity;
            n = *nRequest;
        } else {
            n = 1;
        }
        convert = dbGetConvertRoutine[field_type][dbrType];
        if (!convert) {
            char message[80];

            sprintf(message, "dbGet: Missing conversion for [%d][%d]\n",
                    field_type, dbrType);
            recGblDbaddrError(S_db_badDbrtype, paddr, message);
            status = S_db_badDbrtype;
            goto done;
        }
        /* convert data into the caller's buffer */
        if (n <= 0) {
            ;                           /*do nothing */
        } else if (!dbfl_has_copy(pfl)) {
            status = convert(paddr, pbuf, n, capacity, offset);
        } else {
            DBADDR localAddr = *paddr; /* Structure copy */

            if (pfl->no_elements < 1) {
                status = S_db_badField;
                goto done;
            }

            localAddr.field_type = pfl->field_type;
            localAddr.field_size = pfl->field_size;
            /* not used by dbConvert, it uses the passed capacity instead: */
            localAddr.no_elements = pfl->no_elements;
            localAddr.pfield = dbfl_pfield(pfl);
            status = convert(&localAddr, pbuf, n, capacity, offset);
        }

        if(!status && dbrType==DBF_CHAR && nRequest &&
                paddr->pfldDes && paddr->pfldDes->field_type==DBF_STRING)
        {
            /* long string ensure nil and truncate to actual length */
            long nReq = *nRequest;
            pbuf[nReq-1] = '\0';
            *nRequest = strlen(pbuf)+1;
        }
    }
done:
    paddr->pfield = pfieldsave;
    return status;
}

devSup* dbDTYPtoDevSup(dbRecordType *prdes, int dtyp) {
    return (devSup *)ellNth(&prdes->devList, dtyp+1);
}

devSup* dbDSETtoDevSup(dbRecordType *prdes, dset *pdset) {
    devSup *pdevSup = (devSup *)ellFirst(&prdes->devList);
    while (pdevSup) {
        if (pdset == pdevSup->pdset) return pdevSup;
        pdevSup = (devSup *)ellNext(&pdevSup->node);
    }
    return NULL;
}

static long dbPutFieldLink(DBADDR *paddr,
    short dbrType, const void *pbuffer, long nRequest)
{
    dbLinkInfo  link_info;
    dbChannel   *chan = NULL;
    dbCommon    *precord = paddr->precord;
    dbCommon    *lockrecs[2];
    dbLocker    locker;
    dbFldDes    *pfldDes = paddr->pfldDes;
    long        special = paddr->special;
    struct link *plink = (struct link *)paddr->pfield;
    const char  *pstring = pbuffer;
    struct dsxt *old_dsxt = NULL;
    dset *new_dset = NULL;
    struct dsxt *new_dsxt = NULL;
    devSup      *new_devsup = NULL;
    long        status;
    int         isDevLink;
    short       scan;

    STATIC_ASSERT(DBLOCKER_NALLOC>=2);

    switch (dbrType) {
    case DBR_CHAR:
    case DBR_UCHAR:
        if (pstring[nRequest - 1] != '\0')
            return S_db_badDbrtype;
        break;

    case DBR_STRING:
        break;

    default:
        return S_db_badDbrtype;
    }

    status = dbParseLink(pstring, pfldDes->field_type, &link_info, precord->name, pfldDes->name);
    if (status)
        return status;

    if (link_info.ltype == PV_LINK &&
        (link_info.modifiers & (pvlOptCA | pvlOptCP | pvlOptCPP)) == 0) {
        chan = dbChannelCreate(link_info.target);
        if (chan && (status = dbChannelOpen(chan)) != 0) {
            errlogPrintf(ERL_ERROR ": dbPutFieldLink %s.%s=%s: dbChannelOpen() failed w/ 0x%lx\n",
                precord->name, pfldDes->name, link_info.target, status);
            goto cleanup;
        }
    }

    isDevLink = ellCount(&precord->rdes->devList) > 0 &&
                pfldDes->isDevLink;

    memset(&locker, 0, sizeof(locker));
    lockrecs[0] = precord;
    lockrecs[1] = chan ? dbChannelRecord(chan) : NULL;
    dbLockerPrepare(&locker, lockrecs, 2);

    dbScanLockMany(&locker);

    scan = precord->scan;

    if (isDevLink) {
        new_devsup = dbDTYPtoDevSup(precord->rdes, precord->dtyp);
        if (new_devsup) {
            new_dset = new_devsup->pdset;
            new_dsxt = new_devsup->pdsxt;
        }
    }

    if (dbCanSetLink(plink, &link_info, new_devsup)) {
        /* link type mismatch prevents assignment */
        status = S_dbLib_badField;
        goto unlock;
    }

    if (isDevLink) {
        if (precord->dset) {
            devSup *old_devsup = dbDSETtoDevSup(precord->rdes, precord->dset);

            if (old_devsup)
                old_dsxt = old_devsup->pdsxt;
        }

        if (new_dsxt == NULL ||
            new_dsxt->add_record == NULL ||
            (precord->dset && old_dsxt == NULL) ||
            (old_dsxt && old_dsxt->del_record == NULL)) {
            status = S_db_noSupport;
            goto unlock;
        }

        if (scan == menuScanI_O_Intr) {
            scanDelete(precord);
            precord->scan = menuScanPassive;
        }

        if (old_dsxt) {
            status = old_dsxt->del_record(precord);
            if (status)
                goto restoreScan;
        }
    }

    if (dbLinkIsDefined(plink)) {
        dbRemoveLink(&locker, plink);   /* Clear out old link */
    }
    else if (!isDevLink) {
        status = S_db_badHWaddr;
        goto restoreScan;
    }

    if (special) status = dbPutSpecial(paddr, 0);

    if (!status) status = dbSetLink(plink, &link_info, new_devsup);

    if (status) {
        if (isDevLink) {
            precord->dset = NULL;
            precord->pact = TRUE;
        }
        goto postScanEvent;
    }

    /* We need to initialize any links with a link support layer, i.e.
     * any CONSTANT, JSON_LINK, or PV_LINK types. However for a PV_LINK
     * when isDevLink is set (i.e. this is the record's INP or OUT link)
     * we must wait until after calling dsxt->add_record(). This allows
     * the Async Soft Channel input supports to change it to a PN_LINK.
     * For other cases we initialize the link before the second call to
     * dbPutSpecial() because some record types such as calcout need to
     * be able to call link support methods from prset->special().
     */

    switch (plink->type) { /* New type */
    case PV_LINK:
        if (isDevLink)
            break;
        /* else fall through */
    case CONSTANT:
    case JSON_LINK:
        dbAddLink(&locker, plink, pfldDes->field_type, chan);
        chan = NULL; /* we used it, don't clean it up */
    }

    if (special) status = dbPutSpecial(paddr, 1);

    if (!status && isDevLink) {
        precord->dpvt = NULL;
        precord->dset = new_dset;
        precord->pact = FALSE;

        status = new_dsxt->add_record(precord);
    }

    if (status) {
        if (isDevLink) {
            precord->dset = NULL;
            precord->pact = TRUE;
        }
        goto postScanEvent;
    }

    switch (plink->type) { /* New link type */
    case CONSTANT:
    case CA_LINK:
    case DB_LINK:
    case PN_LINK:
    case JSON_LINK:
        break;

    case PV_LINK:
        if (isDevLink) {
            dbAddLink(&locker, plink, pfldDes->field_type, chan);
            chan = NULL; /* we used it, don't clean it up */
        }
        break;

    case MACRO_LINK:
        break;  /* should never get here */

    default: /* Hardware address */
        if (!isDevLink) {
            status = S_db_badHWaddr;
            goto postScanEvent;
        }
    }
    db_post_events(precord, plink, DBE_VALUE | DBE_LOG);

restoreScan:
    if (isDevLink &&
        scan == menuScanI_O_Intr) { /* undo scanDelete() */
        precord->scan = scan;
        scanAdd(precord);
    }
postScanEvent:
    if (scan != precord->scan)
        db_post_events(precord, &precord->scan, DBE_VALUE | DBE_LOG);
unlock:
    dbScanUnlockMany(&locker);
    dbLockerFinalize(&locker);
cleanup:
    if (chan)
        dbChannelDelete(chan);
    free(link_info.target);
    return status;
}

long dbPutField(DBADDR *paddr, short dbrType,
    const void *pbuffer, long nRequest)
{
    long        status = 0;
    long        special  = paddr->special;
    dbFldDes    *pfldDes = paddr->pfldDes;
    dbCommon    *precord = paddr->precord;
    short       dbfType  = paddr->field_type;

    if (special == SPC_ATTRIBUTE)
        return S_db_noMod;

    /*check for putField disabled*/
    if (precord->disp && paddr->pfield != &precord->disp)
        return S_db_putDisabled;

    if (dbfType >= DBF_INLINK && dbfType <= DBF_FWDLINK)
        return dbPutFieldLink(paddr, dbrType, pbuffer, nRequest);

    dbScanLock(precord);
    status = dbPut(paddr, dbrType, pbuffer, nRequest);
    if (status == 0) {
        if (paddr->pfield == &precord->proc ||
            (pfldDes->process_passive &&
             precord->scan == 0 &&
             dbrType < DBR_PUT_ACKT)) {
            if (precord->pact) {
                if (dbAccessDebugPUTF && precord->tpro)
                    printf("%s: dbPutField to Active '%s', setting RPRO=1\n",
                        epicsThreadGetNameSelf(), precord->name);
                precord->rpro = TRUE;
            } else {
                /* indicate that dbPutField called dbProcess */
                precord->putf = TRUE;
                status = dbProcess(precord);
            }
        }
    }
    dbScanUnlock(precord);
    return status;
}

static long putAckt(DBADDR *paddr, const void *pbuffer, long nRequest,
    long no_elements, long offset)
{
    dbCommon *precord = paddr->precord;
    const unsigned short *ptrans = pbuffer;

    if (*ptrans == precord->ackt) return 0;
    precord->ackt = *ptrans;
    db_post_events(precord, &precord->ackt, DBE_VALUE | DBE_ALARM);
    if (!precord->ackt &&
        precord->acks > precord->sevr) {
        precord->acks = precord->sevr;
        db_post_events(precord, &precord->acks, DBE_VALUE | DBE_ALARM);
    }
    db_post_events(precord, NULL, DBE_ALARM);
    return 0;
}

static long putAcks(DBADDR *paddr, const void *pbuffer, long nRequest,
    long no_elements, long offset)
{
    dbCommon *precord = paddr->precord;
    const unsigned short *psev = pbuffer;

    if (*psev >= precord->acks) {
        precord->acks = 0;
        db_post_events(precord, &precord->acks, DBE_VALUE | DBE_ALARM);
        db_post_events(precord, NULL, DBE_ALARM);
    }
    return 0;
}

long dbPut(DBADDR *paddr, short dbrType,
    const void *pbuffer, long nRequest)
{
    dbCommon *precord = paddr->precord;
    short field_type  = paddr->field_type;
    long no_elements  = paddr->no_elements;
    long special      = paddr->special;
    void *pfieldsave  = paddr->pfield;
    rset *prset = dbGetRset(paddr);
    long status = 0;
    dbFldDes *pfldDes;
    int isValueField;
    int propertyUpdate = paddr->pfldDes->prop && precord->mlis.count;

    if (special == SPC_ATTRIBUTE)
        return S_db_noMod;

    if (dbrType == DBR_PUT_ACKT && field_type <= DBF_DEVICE) {
        return putAckt(paddr, pbuffer, 1, 1, 0);
    } else if (dbrType == DBR_PUT_ACKS && field_type <= DBF_DEVICE) {
        return putAcks(paddr, pbuffer, 1, 1, 0);
    } else if (INVALID_DB_REQ(dbrType) || field_type > DBF_DEVICE) {
        char message[80];

        sprintf(message, "dbPut: Request type is %d", dbrType);
        recGblDbaddrError(S_db_badDbrtype, paddr, message);
        return S_db_badDbrtype;
    }

    if (special) {
        status = dbPutSpecial(paddr, 0);
        if (status) return status;
    }

    if (nRequest>1 || paddr->pfldDes->special == SPC_DBADDR) {
        long offset = 0;
        if (paddr->pfldDes->special == SPC_DBADDR &&
            prset && prset->get_array_info) {
            long dummy;

            status = prset->get_array_info(paddr, &dummy, &offset);
            /* paddr->pfield may be modified */
            if (status) goto done;
        }
        if (no_elements < nRequest)
            nRequest = no_elements;
        status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer,
            nRequest, no_elements, offset);
        /* update array info */
        if (!status && paddr->pfldDes->special == SPC_DBADDR &&
                prset && prset->put_array_info) {
            status = prset->put_array_info(paddr, nRequest);
        }
    } else {
        if (nRequest < 1) {
            recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM);
        } else {
            if (propertyUpdate && paddr->field_size <= MAX_STRING_SIZE) {
                char propBuffer[MAX_STRING_SIZE];
                status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
                    &propBuffer, paddr);
                if (!status) {
                    if (memcmp(paddr->pfield, &propBuffer, paddr->field_size) != 0) {
                        memcpy(paddr->pfield, &propBuffer, paddr->field_size);
                    } else {
                        /* suppress DBE_PROPERTY event if property did not change */
                        propertyUpdate = 0;
                    }
                }
            } else {
                status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
                    paddr->pfield, paddr);
            }
            nRequest = 1;
        }
    }

    /* Post property updates before second dbPutSpecial */
    /* which may post DBE_VALUE and/or DBE_LOG events */
    if (propertyUpdate && !status)
        db_post_events(precord, NULL, DBE_PROPERTY);

    /* Always do special processing if needed */
    if (special) {
        long status2 = dbPutSpecial(paddr, 1);
        if (status2)
            status = status2;
    }
    if (status) goto done;

    /* Propagate monitor events for this field, */
    /* unless the field is VAL and PP is true. */
    pfldDes = paddr->pfldDes;
    isValueField = dbIsValueField(pfldDes);
    if (isValueField) precord->udf = FALSE;
    if (precord->mlis.count &&
        !(isValueField && pfldDes->process_passive))
        db_post_events(precord, pfieldsave, DBE_VALUE | DBE_LOG);
done:
    paddr->pfield = pfieldsave;
    return status;
}
