Merge remote-tracking branch 'lp-zimoch/dbChannelForDBLinks' into 7.0

* lp-zimoch/dbChannelForDBLinks: (43 commits)
  add tests for empty array filter results
  Fix linkFilterTest, move Release Notes to the right place
  do not handle empty arrays (undefined behavior)
  Revert "new error code for empty arrays"
  test code beautification
  make db_init_event_freelists private
  remove unnecessary check
  remove needless pointer access
  new error code for empty arrays
  clean up code structure
  Release notes updated
  set number of planned link filter tests
  removed unnecessary recGblSetSevr call
  re-order link filter tests to alternate between success and failure
  unused variable removed
  Revert "fix crash in PINI: use local db_field_log"
  initialize free lists when starting dbChannel
  db link filter tests added
  bugfix: dbGet should not crash because of empty array requests
  fix crash in PINI: use local db_field_log
  ...

# Conflicts:
#	documentation/RELEASE_NOTES.md
This commit is contained in:
Michael Davidsaver
2020-11-18 10:53:50 -08:00
23 changed files with 484 additions and 130 deletions

View File

@@ -17,6 +17,95 @@ should also be read to understand what has changed since earlier releases.
<!-- Insert new items immediately below here ... -->
### Filters in database input links
Input database links can now use channel filters, it is not necessary to
make them CA links for the filters to work.
### ai Soft Channel support
The Soft Channel device support for ai records now returns failure when
fetching the INP link fails.
### Support for zero-length arrays
Several modifications have been made to properly support zero-length
array values inside the IOC and over Channel Access. Some of these changes
may affect external code that interfaces with the IOC, either directly or
over the CA client API so we recommend thorough testing of any external
code that handles array fields when upgrading to this release.
Since these changes affect the Channel Access client-side API they will
require rebuilding any CA Gateways against this version or Base to
properly handle zero-length arrays. The `caget`, `caput` and `camonitor`
client programs are known to work with empty arrays as long as they were
built with this or a later version of EPICS.
#### Change to the db_access.h `dbr_size_n(TYPE, COUNT)` macro
When called with COUNT=0 this macro no longer returns the number of bytes
required for a scalar (1 element) but for an empty array (0 elements).
Make sure code that uses this doesn't call it with COUNT=0 when it really
means COUNT=1.
Note that the db_access.h header file is included by cadef.h so the change
can impact Channel Access client programs that use this macro.
#### Channel Access support for zero-length arrays
The `ca_array_put()` and `ca_array_put_callback()` routines now accept an
element count of zero, and will write a zero-length array to the PV if
possible. No error will be raised if the target is a scalar field though,
and the field's value will not be changed.
The `ca_array_get_callback()` and `ca_create_subscription()` routines
still accept a count of zero to mean fetch as many elements as the PV
currently holds.
Client programs should be prepared for the `count` fields of any
`struct event_handler_args` or `struct exception_handler_args` passed to
their callback routines to be zero.
#### Array records
The soft device support for the array records aai, waveform, and subArray
as well as the aSub record type now correctly report reading 0 elements
when getting an empty array from an input link.
#### Array support for dbpf
The dbpf command now accepts array values, including empty arrays, when
provided as a JSON string. This must be enclosed in quotes so the iocsh
argument parser sees the JSON as a single argument:
```
epics> dbpf wf10:i32 '[1, 2, 3, 4, 5]'
DBF_LONG[5]: 1 = 0x1 2 = 0x2 3 = 0x3 4 = 0x4 5 = 0x5
```
#### Reading empty arrays as scalar values
Record links that get a scalar value from an array that is currently
empty will cause the record that has the link field to be set to an
`INVALID/LINK` alarm status.
The record code must call `dbGetLink()` with `pnRequest=NULL` for it to
be recognized as a request for a scalar value though.
This changes the semantics of passing `pnRequest=NULL` to `dbGetLink()`,
which now behaves differently than passing it a pointer to a long integer
containing the value 1, which was previously equivalent.
The latter can successfully fetch a zero-element array without triggering
a LINK alarm.
#### Writing empty arrays to scalar fields
Record links that put a zero-element array into a scalar field will now set
the target record to `INVALID/LINK` alarm without changing the field's value.
Previously the field was set to 0 in this case (with no alarm).
The target field must be marked as `special(SPC_DBADDR)` to be recognized
as an array field, and its record support must define a `put_array_info()`
routine.
### Timestamp before processing output links
The record processing code for records with output links has been modified
to update the timestamp via recGblGetTimeStamp() before processing the

View File

@@ -517,7 +517,7 @@ struct dbr_ctrl_double{
};
#define dbr_size_n(TYPE,COUNT)\
((unsigned)((COUNT)<=0?dbr_size[TYPE]:dbr_size[TYPE]+((COUNT)-1)*dbr_value_size[TYPE]))
((unsigned)((COUNT)<0?dbr_size[TYPE]:dbr_size[TYPE]+((COUNT)-1)*dbr_value_size[TYPE]))
/* size for each type - array indexed by the DBR_ type code */
LIBCA_API extern const unsigned short dbr_size[];

View File

@@ -329,7 +329,7 @@ void nciu::write (
if ( ! this->accessRightState.writePermit() ) {
throw cacChannel::noWriteAccess();
}
if ( countIn > this->count || countIn == 0 ) {
if ( countIn > this->count) {
throw cacChannel::outOfBounds();
}
if ( type == DBR_STRING ) {
@@ -350,7 +350,7 @@ cacChannel::ioStatus nciu::write (
if ( ! this->accessRightState.writePermit() ) {
throw cacChannel::noWriteAccess();
}
if ( countIn > this->count || countIn == 0 ) {
if ( countIn > this->count) {
throw cacChannel::outOfBounds();
}
if ( type == DBR_STRING ) {

View File

@@ -946,13 +946,18 @@ long dbGet(DBADDR *paddr, short dbrType,
if (offset == 0 && (!nRequest || no_elements == 1)) {
if (nRequest)
*nRequest = 1;
else if (no_elements < 1) {
status = S_db_onlyOne;
goto done;
}
if (!pfl || pfl->type == dbfl_type_rec) {
status = dbFastGetConvertRoutine[field_type][dbrType]
(paddr->pfield, pbuf, paddr);
} else {
DBADDR localAddr = *paddr; /* Structure copy */
if (pfl->no_elements < 1) {
if (no_elements < 1) {
status = S_db_badField;
goto done;
}
@@ -996,6 +1001,11 @@ long dbGet(DBADDR *paddr, short dbrType,
} 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;
localAddr.no_elements = pfl->no_elements;
@@ -1037,7 +1047,7 @@ static long dbPutFieldLink(DBADDR *paddr,
short dbrType, const void *pbuffer, long nRequest)
{
dbLinkInfo link_info;
DBADDR *pdbaddr = NULL;
dbChannel *chan = NULL;
dbCommon *precord = paddr->precord;
dbCommon *lockrecs[2];
dbLocker locker;
@@ -1075,16 +1085,11 @@ static long dbPutFieldLink(DBADDR *paddr,
if (link_info.ltype == PV_LINK &&
(link_info.modifiers & (pvlOptCA | pvlOptCP | pvlOptCPP)) == 0) {
DBADDR tempaddr;
if (dbNameToAddr(link_info.target, &tempaddr)==0) {
/* This will become a DB link. */
pdbaddr = malloc(sizeof(*pdbaddr));
if (!pdbaddr) {
status = S_db_noMemory;
goto cleanup;
}
*pdbaddr = tempaddr; /* struct copy */
chan = dbChannelCreate(link_info.target);
if (chan && dbChannelOpen(chan) != 0) {
errlogPrintf("ERROR: dbPutFieldLink %s.%s=%s: dbChannelOpen() failed\n",
precord->name, pfldDes->name, link_info.target);
goto cleanup;
}
}
@@ -1093,7 +1098,7 @@ static long dbPutFieldLink(DBADDR *paddr,
memset(&locker, 0, sizeof(locker));
lockrecs[0] = precord;
lockrecs[1] = pdbaddr ? pdbaddr->precord : NULL;
lockrecs[1] = chan ? dbChannelRecord(chan) : NULL;
dbLockerPrepare(&locker, lockrecs, 2);
dbScanLockMany(&locker);
@@ -1181,7 +1186,8 @@ static long dbPutFieldLink(DBADDR *paddr,
case PV_LINK:
case CONSTANT:
case JSON_LINK:
dbAddLink(&locker, plink, pfldDes->field_type, pdbaddr);
dbAddLink(&locker, plink, pfldDes->field_type, chan);
chan = NULL; /* don't clean it up */
break;
case DB_LINK:
@@ -1211,6 +1217,8 @@ unlock:
dbScanUnlockMany(&locker);
dbLockerFinalize(&locker);
cleanup:
if (chan)
dbChannelDelete(chan);
free(link_info.target);
return status;
}
@@ -1330,25 +1338,21 @@ long dbPut(DBADDR *paddr, short dbrType,
status = prset->get_array_info(paddr, &dummy, &offset);
/* paddr->pfield may be modified */
if (status) goto done;
} else
offset = 0;
if (no_elements <= 1) {
status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
paddr->pfield, paddr);
nRequest = 1;
} else {
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);
/* update array info */
if (!status && prset->put_array_info)
status = prset->put_array_info(paddr, nRequest);
} else {
if (nRequest < 1) {
recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM);
} else {
status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
paddr->pfield, paddr);
nRequest = 1;
}
}
/* Always do special processing if needed */

View File

@@ -411,9 +411,15 @@ long dbCaGetLink(struct link *plink, short dbrType, void *pdest,
goto done;
}
newType = dbDBRoldToDBFnew[pca->dbrType];
if (!nelements || *nelements == 1) {
if (!nelements) {
long (*fConvert)(const void *from, void *to, struct dbAddr *paddr);
if (pca->usedelements < 1) {
pca->sevr = INVALID_ALARM;
pca->stat = LINK_ALARM;
status = -1;
goto done;
}
fConvert = dbFastGetConvertRoutine[newType][dbrType];
assert(pca->pgetNative);
status = fConvert(pca->pgetNative, pdest, 0);

View File

@@ -18,6 +18,8 @@
#include <stddef.h>
#include <string.h>
#define EPICS_PRIVATE_API
#include "cantProceed.h"
#include "epicsAssert.h"
#include "epicsString.h"
@@ -68,6 +70,7 @@ void dbChannelInit (void)
freeListInitPvt(&dbChannelFreeList, sizeof(dbChannel), 128);
freeListInitPvt(&chFilterFreeList, sizeof(chFilter), 64);
freeListInitPvt(&dbchStringFreeList, sizeof(epicsOldString), 128);
db_init_event_freelists();
}
static void chf_value(parseContext *parser, parse_result *presult)

View File

@@ -61,6 +61,7 @@
#include "dbConvertFast.h"
#include "dbConvert.h"
#include "db_field_log.h"
#include "db_access_routines.h"
#include "dbFldTypes.h"
#include "dbLink.h"
#include "dbLockPvt.h"
@@ -74,7 +75,7 @@
#include "recSup.h"
#include "special.h"
#include "dbDbLink.h"
#include "dbChannel.h"
/***************************** Database Links *****************************/
@@ -83,45 +84,51 @@ static lset dbDb_lset;
static long processTarget(dbCommon *psrc, dbCommon *pdst);
#define linkChannel(plink) ((dbChannel *) (plink)->value.pv_link.pvt)
long dbDbInitLink(struct link *plink, short dbfType)
{
DBADDR dbaddr;
long status;
DBADDR *pdbAddr;
dbChannel *chan;
dbCommon *precord;
status = dbNameToAddr(plink->value.pv_link.pvname, &dbaddr);
chan = dbChannelCreate(plink->value.pv_link.pvname);
if (!chan)
return S_db_notFound;
status = dbChannelOpen(chan);
if (status)
return status;
precord = dbChannelRecord(chan);
plink->lset = &dbDb_lset;
plink->type = DB_LINK;
pdbAddr = dbCalloc(1, sizeof(struct dbAddr));
*pdbAddr = dbaddr; /* structure copy */
plink->value.pv_link.pvt = pdbAddr;
ellAdd(&dbaddr.precord->bklnk, &plink->value.pv_link.backlinknode);
plink->value.pv_link.pvt = chan;
ellAdd(&precord->bklnk, &plink->value.pv_link.backlinknode);
/* merging into the same lockset is deferred to the caller.
* cf. initPVLinks()
*/
dbLockSetMerge(NULL, plink->precord, dbaddr.precord);
assert(plink->precord->lset->plockSet == dbaddr.precord->lset->plockSet);
dbLockSetMerge(NULL, plink->precord, precord);
assert(plink->precord->lset->plockSet == precord->lset->plockSet);
return 0;
}
void dbDbAddLink(struct dbLocker *locker, struct link *plink, short dbfType,
DBADDR *ptarget)
dbChannel *chan)
{
plink->lset = &dbDb_lset;
plink->type = DB_LINK;
plink->value.pv_link.pvt = ptarget;
ellAdd(&ptarget->precord->bklnk, &plink->value.pv_link.backlinknode);
plink->value.pv_link.pvt = chan;
ellAdd(&dbChannelRecord(chan)->bklnk, &plink->value.pv_link.backlinknode);
/* target record is already locked in dbPutFieldLink() */
dbLockSetMerge(locker, plink->precord, ptarget->precord);
dbLockSetMerge(locker, plink->precord, dbChannelRecord(chan));
}
static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink)
{
DBADDR *pdbAddr = (DBADDR *) plink->value.pv_link.pvt;
dbChannel *chan = linkChannel(plink);
dbCommon *precord = dbChannelRecord(chan);
plink->type = PV_LINK;
@@ -131,10 +138,10 @@ static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink)
plink->value.pv_link.getCvt = 0;
plink->value.pv_link.pvlMask = 0;
plink->value.pv_link.lastGetdbrType = 0;
ellDelete(&pdbAddr->precord->bklnk, &plink->value.pv_link.backlinknode);
dbLockSetSplit(locker, plink->precord, pdbAddr->precord);
ellDelete(&precord->bklnk, &plink->value.pv_link.backlinknode);
dbLockSetSplit(locker, plink->precord, precord);
}
free(pdbAddr);
dbChannelDelete(chan);
}
static int dbDbIsConnected(const struct link *plink)
@@ -144,16 +151,14 @@ static int dbDbIsConnected(const struct link *plink)
static int dbDbGetDBFtype(const struct link *plink)
{
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
return paddr->field_type;
dbChannel *chan = linkChannel(plink);
return dbChannelFinalFieldType(chan);
}
static long dbDbGetElements(const struct link *plink, long *nelements)
{
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
*nelements = paddr->no_elements;
dbChannel *chan = linkChannel(plink);
*nelements = dbChannelFinalElements(chan);
return 0;
}
@@ -161,47 +166,75 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
long *pnRequest)
{
struct pv_link *ppv_link = &plink->value.pv_link;
DBADDR *paddr = ppv_link->pvt;
dbChannel *chan = linkChannel(plink);
DBADDR *paddr = &chan->addr;
dbCommon *precord = plink->precord;
db_field_log *pfl = NULL;
long status;
/* scan passive records if link is process passive */
if (ppv_link->pvlMask & pvlOptPP) {
status = dbScanPassive(precord, paddr->precord);
status = dbScanPassive(precord, dbChannelRecord(chan));
if (status)
return status;
}
if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType) {
status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr);
} else {
unsigned short dbfType = paddr->field_type;
if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType)
{
/* shortcut: scalar with known conversion, no filter */
status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);
}
else if (dbChannelFinalElements(chan) == 1 && (!pnRequest || *pnRequest == 1)
&& dbChannelSpecial(chan) != SPC_DBADDR
&& dbChannelSpecial(chan) != SPC_ATTRIBUTE
&& ellCount(&chan->filters) == 0)
{
/* simple scalar: set up shortcut */
unsigned short dbfType = dbChannelFinalFieldType(chan);
if (dbrType < 0 || dbrType > DBR_ENUM || dbfType > DBF_DEVICE)
return S_db_badDbrtype;
if (paddr->no_elements == 1 && (!pnRequest || *pnRequest == 1)
&& paddr->special != SPC_DBADDR
&& paddr->special != SPC_ATTRIBUTE) {
ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr);
} else {
ppv_link->getCvt = NULL;
status = dbGet(paddr, dbrType, pbuffer, NULL, pnRequest, NULL);
}
ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
ppv_link->lastGetdbrType = dbrType;
status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);
}
else
{
/* filter, array, or special */
ppv_link->getCvt = NULL;
if (ellCount(&chan->filters)) {
/* If filters are involved in a read, create field log and run filters */
pfl = db_create_read_log(chan);
if (!pfl)
return S_db_noMemory;
pfl = dbChannelRunPreChain(chan, pfl);
pfl = dbChannelRunPostChain(chan, pfl);
}
status = dbChannelGet(chan, dbrType, pbuffer, NULL, pnRequest, pfl);
if (pfl)
db_delete_field_log(pfl);
if (status)
return status;
}
if (!status && precord != paddr->precord)
if (!status && precord != dbChannelRecord(chan))
recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode,
plink->precord, paddr->precord->stat, paddr->precord->sevr);
plink->precord,
dbChannelRecord(chan)->stat, dbChannelRecord(chan)->sevr);
return status;
}
static long dbDbGetControlLimits(const struct link *plink, double *low,
double *high)
{
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
dbChannel *chan = linkChannel(plink);
DBADDR *paddr = &chan->addr;
struct buffer {
DBRctrlDouble
double value;
@@ -222,7 +255,8 @@ static long dbDbGetControlLimits(const struct link *plink, double *low,
static long dbDbGetGraphicLimits(const struct link *plink, double *low,
double *high)
{
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
dbChannel *chan = linkChannel(plink);
DBADDR *paddr = &chan->addr;
struct buffer {
DBRgrDouble
double value;
@@ -243,7 +277,8 @@ static long dbDbGetGraphicLimits(const struct link *plink, double *low,
static long dbDbGetAlarmLimits(const struct link *plink, double *lolo,
double *low, double *high, double *hihi)
{
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
dbChannel *chan = linkChannel(plink);
DBADDR *paddr = &chan->addr;
struct buffer {
DBRalDouble
double value;
@@ -265,7 +300,8 @@ static long dbDbGetAlarmLimits(const struct link *plink, double *lolo,
static long dbDbGetPrecision(const struct link *plink, short *precision)
{
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
dbChannel *chan = linkChannel(plink);
DBADDR *paddr = &chan->addr;
struct buffer {
DBRprecision
double value;
@@ -284,7 +320,8 @@ static long dbDbGetPrecision(const struct link *plink, short *precision)
static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize)
{
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
dbChannel *chan = linkChannel(plink);
DBADDR *paddr = &chan->addr;
struct buffer {
DBRunits
double value;
@@ -304,20 +341,20 @@ static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize)
static long dbDbGetAlarm(const struct link *plink, epicsEnum16 *status,
epicsEnum16 *severity)
{
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
dbChannel *chan = linkChannel(plink);
dbCommon *precord = dbChannelRecord(chan);
if (status)
*status = paddr->precord->stat;
*status = precord->stat;
if (severity)
*severity = paddr->precord->sevr;
*severity = precord->sevr;
return 0;
}
static long dbDbGetTimeStamp(const struct link *plink, epicsTimeStamp *pstamp)
{
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
*pstamp = paddr->precord->time;
dbChannel *chan = linkChannel(plink);
dbCommon *precord = dbChannelRecord(chan);
*pstamp = precord->time;
return 0;
}
@@ -325,9 +362,10 @@ static long dbDbPutValue(struct link *plink, short dbrType,
const void *pbuffer, long nRequest)
{
struct pv_link *ppv_link = &plink->value.pv_link;
dbChannel *chan = linkChannel(plink);
struct dbCommon *psrce = plink->precord;
DBADDR *paddr = (DBADDR *) ppv_link->pvt;
dbCommon *pdest = paddr->precord;
DBADDR *paddr = &chan->addr;
dbCommon *pdest = dbChannelRecord(chan);
long status = dbPut(paddr, dbrType, pbuffer, nRequest);
recGblInheritSevr(ppv_link->pvlMask & pvlOptMsMode, pdest, psrce->nsta,
@@ -335,7 +373,7 @@ static long dbDbPutValue(struct link *plink, short dbrType,
if (status)
return status;
if (paddr->pfield == (void *) &pdest->proc ||
if (dbChannelField(chan) == (void *) &pdest->proc ||
(ppv_link->pvlMask & pvlOptPP && pdest->scan == 0)) {
status = processTarget(psrce, pdest);
}
@@ -346,9 +384,8 @@ static long dbDbPutValue(struct link *plink, short dbrType,
static void dbDbScanFwdLink(struct link *plink)
{
dbCommon *precord = plink->precord;
dbAddr *paddr = (dbAddr *) plink->value.pv_link.pvt;
dbScanPassive(precord, paddr->precord);
dbChannel *chan = linkChannel(plink);
dbScanPassive(precord, dbChannelRecord(chan));
}
static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv)

View File

@@ -27,7 +27,7 @@ struct dbLocker;
epicsShareFunc long dbDbInitLink(struct link *plink, short dbfType);
epicsShareFunc void dbDbAddLink(struct dbLocker *locker, struct link *plink,
short dbfType, DBADDR *ptarget);
short dbfType, dbChannel *ptarget);
#ifdef __cplusplus
}

View File

@@ -252,18 +252,15 @@ int dbel ( const char *pname, unsigned level )
}
/*
* DB_INIT_EVENTS()
* DB_INIT_EVENT_FREELISTS()
*
*
* Initialize the event facility for this task. Must be called at least once
* by each task which uses the db event facility
* Initialize the free lists used by the event facility.
* Safe to be called multiple times.
*
* returns: ptr to event user block or NULL if memory can't be allocated
*/
dbEventCtx db_init_events (void)
void db_init_event_freelists (void)
{
struct event_user * evUser;
if (!dbevEventUserFreeList) {
freeListInitPvt(&dbevEventUserFreeList,
sizeof(struct event_user),8);
@@ -280,6 +277,22 @@ dbEventCtx db_init_events (void)
freeListInitPvt(&dbevFieldLogFreeList,
sizeof(struct db_field_log),2048);
}
}
/*
* DB_INIT_EVENTS()
*
*
* Initialize the event facility for this task. Must be called at least once
* by each task which uses the db event facility
*
* returns: ptr to event user block or NULL if memory can't be allocated
*/
dbEventCtx db_init_events (void)
{
struct event_user * evUser;
db_init_event_freelists();
evUser = (struct event_user *)
freeListCalloc(dbevEventUserFreeList);

View File

@@ -66,6 +66,7 @@ epicsShareFunc void db_event_change_priority ( dbEventCtx ctx, unsigned epicsPri
#ifdef EPICS_PRIVATE_API
epicsShareFunc void db_cleanup_events(void);
epicsShareFunc void db_init_event_freelists (void);
#endif
typedef void EVENTFUNC (void *user_arg, struct dbChannel *chan,

View File

@@ -144,7 +144,7 @@ void dbInitLink(struct link *plink, short dbfType)
}
void dbAddLink(struct dbLocker *locker, struct link *plink, short dbfType,
DBADDR *ptarget)
dbChannel *ptarget)
{
struct dbCommon *precord = plink->precord;

View File

@@ -21,6 +21,7 @@
#include "epicsTypes.h"
#include "epicsTime.h"
#include "dbAddr.h"
#include "dbChannel.h"
#ifdef __cplusplus
extern "C" {
@@ -369,7 +370,7 @@ epicsShareFunc const char * dbLinkFieldName(const struct link *plink);
epicsShareFunc void dbInitLink(struct link *plink, short dbfType);
epicsShareFunc void dbAddLink(struct dbLocker *locker, struct link *plink,
short dbfType, DBADDR *ptarget);
short dbfType, dbChannel *ptarget);
epicsShareFunc void dbLinkOpen(struct link *plink);
epicsShareFunc void dbRemoveLink(struct dbLocker *locker, struct link *plink);

View File

@@ -743,14 +743,14 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond)
for(i=0; i<rtype->no_links; i++) {
dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]];
DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset);
DBADDR *ptarget;
dbChannel *chan;
lockRecord *lr;
if(plink->type!=DB_LINK)
continue;
ptarget = plink->value.pv_link.pvt;
lr = ptarget->precord->lset;
chan = plink->value.pv_link.pvt;
lr = dbChannelRecord(chan)->lset;
assert(lr);
if(lr->precord==pfirst) {

View File

@@ -41,6 +41,7 @@
#include "recGbl.h"
#include "recSup.h"
#include "special.h"
#include "dbConvertJSON.h"
#define MAXLINE 80
#define MAXMESS 128
@@ -364,8 +365,9 @@ long dbpf(const char *pname,const char *pvalue)
{
DBADDR addr;
long status;
short dbrType;
size_t n = 1;
short dbrType = DBR_STRING;
long n = 1;
char *array = NULL;
if (!pname || !*pname || !pvalue) {
printf("Usage: dbpf \"pv name\", \"value\"\n");
@@ -380,16 +382,25 @@ long dbpf(const char *pname,const char *pvalue)
return -1;
}
if (addr.no_elements > 1 &&
(addr.dbr_field_type == DBR_CHAR || addr.dbr_field_type == DBR_UCHAR)) {
if (addr.no_elements > 1) {
dbrType = addr.dbr_field_type;
n = strlen(pvalue) + 1;
if (addr.dbr_field_type == DBR_CHAR || addr.dbr_field_type == DBR_UCHAR) {
n = (long)strlen(pvalue) + 1;
} else {
n = addr.no_elements;
array = calloc(n, dbValueSize(dbrType));
if (!array) {
printf("Out of memory\n");
return -1;
}
status = dbPutConvertJSON(pvalue, dbrType, array, &n);
if (status)
return status;
pvalue = array;
}
}
else {
dbrType = DBR_STRING;
}
status = dbPutField(&addr, dbrType, pvalue, (long) n);
status = dbPutField(&addr, dbrType, pvalue, n);
free(array);
dbgf(pname);
return status;
}
@@ -943,13 +954,13 @@ static void printBuffer(
}
/* Now print values */
if (no_elements == 0)
return;
if (no_elements == 1)
sprintf(pmsg, "DBF_%s: ", dbr[dbr_type]);
else
else {
sprintf(pmsg, "DBF_%s[%ld]: ", dbr[dbr_type], no_elements);
if (no_elements == 0)
strcat(pmsg, "(empty)");
}
dbpr_msgOut(pMsgBuff, tab_size);
if (status != 0) {

View File

@@ -61,9 +61,10 @@ static long init_record(dbCommon *pcommon)
}
status = dbLoadLinkArray(plink, prec->ftvl, prec->bptr, &nRequest);
if (!status && nRequest > 0) {
if (!status) {
prec->nord = nRequest;
prec->udf = FALSE;
return status;
}
}
return 0;
@@ -75,7 +76,7 @@ static long readLocked(struct link *pinp, void *dummy)
long nRequest = prec->nelm;
long status = dbGetLink(pinp, prec->ftvl, prec->bptr, 0, &nRequest);
if (!status && nRequest > 0) {
if (!status) {
prec->nord = nRequest;
prec->udf = FALSE;
@@ -90,8 +91,12 @@ static long read_aai(aaiRecord *prec)
{
epicsUInt32 nord = prec->nord;
struct link *pinp = prec->simm == menuYesNoYES ? &prec->siol : &prec->inp;
long status = dbLinkDoLocked(pinp, readLocked, NULL);
long status;
if (dbLinkIsConstant(pinp))
return 0;
status = dbLinkDoLocked(pinp, readLocked, NULL);
if (status == S_db_noLSET)
status = readLocked(pinp, NULL);

View File

@@ -86,9 +86,10 @@ static long read_ai(aiRecord *prec)
prec->udf = FALSE;
prec->dpvt = &devAiSoft; /* Any non-zero value */
return 2;
}
else
prec->dpvt = NULL;
return 2;
return status;
}

View File

@@ -66,7 +66,7 @@ static long init_record(dbCommon *pcommon)
status = dbLoadLinkArray(&prec->inp, prec->ftvl, prec->bptr, &nRequest);
if (!status && nRequest > 0)
if (!status)
subset(prec, nRequest);
return status;
@@ -116,7 +116,7 @@ static long read_sa(subArrayRecord *prec)
status = readLocked(&prec->inp, &rt);
}
if (!status && rt.nRequest > 0) {
if (!status) {
subset(prec, rt.nRequest);
if (nord != prec->nord)

View File

@@ -42,7 +42,7 @@ static long init_record(dbCommon *pcommon)
long nelm = prec->nelm;
long status = dbLoadLinkArray(&prec->inp, prec->ftvl, prec->bptr, &nelm);
if (!status && nelm > 0) {
if (!status) {
prec->nord = nelm;
prec->udf = FALSE;
}
@@ -78,11 +78,14 @@ static long read_wf(waveformRecord *prec)
rt.ptime = (dbLinkIsConstant(&prec->tsel) &&
prec->tse == epicsTimeEventDeviceTime) ? &prec->time : NULL;
if (dbLinkIsConstant(&prec->inp))
return 0;
status = dbLinkDoLocked(&prec->inp, readLocked, &rt);
if (status == S_db_noLSET)
status = readLocked(&prec->inp, &rt);
if (!status && rt.nRequest > 0) {
if (!status) {
prec->nord = rt.nRequest;
prec->udf = FALSE;
if (nord != prec->nord)

View File

@@ -278,10 +278,9 @@ static long fetch_values(aSubRecord *prec)
long nRequest = (&prec->noa)[i];
status = dbGetLink(&(&prec->inpa)[i], (&prec->fta)[i], (&prec->a)[i], 0,
&nRequest);
if (nRequest > 0)
(&prec->nea)[i] = nRequest;
if (status)
return status;
(&prec->nea)[i] = nRequest;
}
return 0;
}

View File

@@ -159,6 +159,13 @@ asyncproctest_SRCS += asyncproctest_registerRecordDeviceDriver.cpp
TESTFILES += $(COMMON_DIR)/asyncproctest.dbd ../asyncproctest.db
TESTS += asyncproctest
TESTPROD_HOST += linkFilterTest
linkFilterTest_SRCS += linkFilterTest.c
linkFilterTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += linkFilterTest.c
TESTFILES += ../linkFilterTest.db
TESTS += linkFilterTest
# dbHeader* is only a compile test
# no need to actually run
TESTPROD += dbHeaderTest

View File

@@ -0,0 +1,158 @@
/*************************************************************************\
* Copyright (c) 2020 Dirk Zimoch
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <string.h>
#include "dbAccess.h"
#include "devSup.h"
#include "alarm.h"
#include "dbUnitTest.h"
#include "errlog.h"
#include "epicsThread.h"
#include "longinRecord.h"
#include "testMain.h"
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
static void startTestIoc(const char *dbfile)
{
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase(dbfile, NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
}
static void expectProcSuccess(const char *rec)
{
char fieldname[20];
testDiag("expecting success from %s", rec);
sprintf(fieldname, "%s.PROC", rec);
testdbPutFieldOk(fieldname, DBF_LONG, 1);
sprintf(fieldname, "%s.SEVR", rec);
testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
sprintf(fieldname, "%s.STAT", rec);
testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
}
static void expectProcFailure(const char *rec)
{
char fieldname[20];
testDiag("expecting failure S_db_badField %#x from %s", S_db_badField, rec);
sprintf(fieldname, "%s.PROC", rec);
testdbPutFieldFail(S_db_onlyOne, fieldname, DBF_LONG, 1);
sprintf(fieldname, "%s.SEVR", rec);
testdbGetFieldEqual(fieldname, DBF_LONG, INVALID_ALARM);
sprintf(fieldname, "%s.STAT", rec);
testdbGetFieldEqual(fieldname, DBF_LONG, LINK_ALARM);
}
static void changeRange(long start, long stop, long step)
{
char linkstring[60];
if (step)
sprintf(linkstring, "src.[%ld:%ld:%ld]", start, step, stop);
else if (stop)
sprintf(linkstring, "src.[%ld:%ld]", start, stop);
else
sprintf(linkstring, "src.[%ld]", start);
testDiag("modifying link: %s", linkstring);
testdbPutFieldOk("ai.INP", DBF_STRING, linkstring);
testdbPutFieldOk("wf.INP", DBF_STRING, linkstring);
}
static const double buf[] = {1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 2, 4, 6};
static void expectRange(long start, long end)
{
long n = end-start+1;
testdbGetFieldEqual("ai.VAL", DBF_DOUBLE, buf[start]);
testdbGetFieldEqual("wf.NORD", DBF_LONG, n);
testdbGetArrFieldEqual("wf.VAL", DBF_DOUBLE, n+2, n, buf+start);
}
static void expectEmptyArray(void)
{
testDiag("expecting empty array");
testdbGetFieldEqual("wf.NORD", DBF_LONG, 0);
}
MAIN(linkFilterTest)
{
testPlan(102);
startTestIoc("linkFilterTest.db");
testDiag("PINI");
expectRange(2,4);
testDiag("modify range");
changeRange(3,6,0);
expectProcSuccess("ai");
expectProcSuccess("wf");
expectRange(3,6);
testDiag("backward range");
changeRange(5,3,0);
expectProcFailure("ai");
expectProcSuccess("wf");
expectEmptyArray();
testDiag("step 2");
changeRange(1,6,2);
expectProcSuccess("ai");
expectProcSuccess("wf");
expectRange(10,12);
testDiag("range start beyond src.NORD");
changeRange(8,9,0);
expectProcFailure("ai");
expectProcSuccess("wf");
expectEmptyArray();
testDiag("range end beyond src.NORD");
changeRange(3,9,0);
expectProcSuccess("ai");
expectProcSuccess("wf");
expectRange(3,7); /* clipped range */
testDiag("range start beyond src.NELM");
changeRange(11,12,0);
expectProcFailure("ai");
expectProcSuccess("wf");
testDiag("range end beyond src.NELM");
changeRange(4,12,0);
expectProcSuccess("ai");
expectProcSuccess("wf");
expectRange(4,7); /* clipped range */
testDiag("single value beyond src.NORD");
changeRange(8,0,0);
expectProcFailure("ai");
expectProcSuccess("wf");
expectEmptyArray();
testDiag("single value");
changeRange(5,0,0);
expectProcSuccess("ai");
expectProcSuccess("wf");
expectRange(5,5);
testDiag("single beyond rec.NELM");
changeRange(12,0,0);
expectProcFailure("ai");
expectProcSuccess("wf");
expectEmptyArray();
testIocShutdownOk();
testdbCleanup();
return testDone();
}

View File

@@ -0,0 +1,16 @@
record(waveform, "src") {
field(NELM, "10")
field(FTVL, "SHORT")
field(INP, [1, 2, 3, 4, 5, 6, 7, 8])
}
record(ai, "ai") {
field(INP, "src.[2]") # expect 3
field(PINI, "YES")
}
record(waveform, "wf") {
field(NELM, "5")
field(FTVL, "DOUBLE")
field(INP, "src.[2:4]") # expect 3,4,5
field(PINI, "YES")
}

View File

@@ -133,7 +133,7 @@ void testCADisconn(void)
startRegressTestIoc("badCaLink.db");
testdbPutFieldOk("ai:disconn.PROC", DBF_LONG, 1);
testdbPutFieldFail(-1, "ai:disconn.PROC", DBF_LONG, 1);
testdbGetFieldEqual("ai:disconn.SEVR", DBF_LONG, INVALID_ALARM);
testdbGetFieldEqual("ai:disconn.STAT", DBF_LONG, LINK_ALARM);
}