Files
epics-base/modules/database/src/ioc/db/dbAccess.c
2026-01-06 14:57:16 +01:00

1446 lines
44 KiB
C

/*************************************************************************\
* 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)==sizeof(pfl->amsg));
strncpy(pbuffer, pcommon->amsg, sizeof(pcommon->amsg)-1);
} else {
strncpy(pbuffer, pfl->amsg,sizeof(pfl->amsg)-1);
}
pbuffer[sizeof(pcommon->amsg)-1] = '\0';
pbuffer += sizeof(pcommon->amsg);
}
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-1);
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;
}