diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index 133a0130b..0a54dc67f 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -17,11 +17,49 @@ should also be read to understand what has changed since earlier releases. +### Support for empty arrays + +Several problems with empty arrays have been fixed. + +#### Changed dbr_size_n(TYPE,COUNT) macro + +When called with COUNT=0 the macro no longer returns the number of bytes +required for a scalar (1 element) but for an empty array (0 elements). +Make sure you don't call it with COUNT=0 when you really mean COUNT=1. + +#### Array records + +The soft supports of array records aai, waveform, and subArray as well as +the aSub record type have been fixed to correctly report 0 elements read +when reading empty arrays from an input link. + +#### Array support for dbpf + +The dbpf function now accepts arrays, including empty arrays as a JSON string. + +#### Scalar records reading from empty arrays + +Records reading scalar fields from empty arrays are now set to INVALID/LINK +alarm status. +Links have to call dbGetLink with pnRequest=NULL to be recognized as requests +for scalars. +This changes the semantics of pnRequest=NULL. It is now different from +requesting up to 1 array element, which may return a valid empty array. + +### Writing empty arrays to scalar records + +Witing an empty array to a scalar field now sets the target record to +INVALID/LINK alarm but does not modify the value. Before, the value used +to be set to 0 (without any alarm). +A target field needs to have the SPC_DBADDR tag to be recognized as an array +field and the record support must define a put_array_info method. + ### Add registerAllRecordDeviceDrivers() Addition of registerAllRecordDeviceDrivers() as an iocsh function -and in iocshRegisterCommon.h. This function uses dynamic lookup with epicsFindSymbol() -to perform the same function as a generated \*_registerRecordDeviceDriver() function. +and in iocshRegisterCommon.h. This function uses dynamic lookup with +`epicsFindSymbol()` to perform the same function as a generated +`*_registerRecordDeviceDriver()` function. This allows dynamic loading/linking of support modules without code generation. This feature is not intended for use by IOCs constructed using the standard EPICS application diff --git a/modules/ca/src/client/db_access.h b/modules/ca/src/client/db_access.h index 9123b5b0a..c13977e51 100644 --- a/modules/ca/src/client/db_access.h +++ b/modules/ca/src/client/db_access.h @@ -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[]; diff --git a/modules/ca/src/client/nciu.cpp b/modules/ca/src/client/nciu.cpp index eed2bce11..61ccebafc 100644 --- a/modules/ca/src/client/nciu.cpp +++ b/modules/ca/src/client/nciu.cpp @@ -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 ) { diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c index f1e9fc8a2..2f20f41b8 100644 --- a/modules/database/src/ioc/db/dbAccess.c +++ b/modules/database/src/ioc/db/dbAccess.c @@ -946,6 +946,11 @@ 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); @@ -1330,25 +1335,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 */ diff --git a/modules/database/src/ioc/db/dbCa.c b/modules/database/src/ioc/db/dbCa.c index c076561c9..0397064d7 100644 --- a/modules/database/src/ioc/db/dbCa.c +++ b/modules/database/src/ioc/db/dbCa.c @@ -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); diff --git a/modules/database/src/ioc/db/dbTest.c b/modules/database/src/ioc/db/dbTest.c index 8baed5aea..d4ac1d792 100644 --- a/modules/database/src/ioc/db/dbTest.c +++ b/modules/database/src/ioc/db/dbTest.c @@ -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; } diff --git a/modules/database/src/std/dev/devAaiSoft.c b/modules/database/src/std/dev/devAaiSoft.c index e8fa05f44..55975bae0 100644 --- a/modules/database/src/std/dev/devAaiSoft.c +++ b/modules/database/src/std/dev/devAaiSoft.c @@ -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); diff --git a/modules/database/src/std/dev/devAiSoft.c b/modules/database/src/std/dev/devAiSoft.c index fe0ccec56..bd0ef9c76 100644 --- a/modules/database/src/std/dev/devAiSoft.c +++ b/modules/database/src/std/dev/devAiSoft.c @@ -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; } diff --git a/modules/database/src/std/dev/devSASoft.c b/modules/database/src/std/dev/devSASoft.c index 55e55e290..d8e6e5c62 100644 --- a/modules/database/src/std/dev/devSASoft.c +++ b/modules/database/src/std/dev/devSASoft.c @@ -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) diff --git a/modules/database/src/std/dev/devWfSoft.c b/modules/database/src/std/dev/devWfSoft.c index 3d455a29e..ccc984352 100644 --- a/modules/database/src/std/dev/devWfSoft.c +++ b/modules/database/src/std/dev/devWfSoft.c @@ -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) diff --git a/modules/database/src/std/rec/aSubRecord.c b/modules/database/src/std/rec/aSubRecord.c index 48c420281..0b52789b2 100644 --- a/modules/database/src/std/rec/aSubRecord.c +++ b/modules/database/src/std/rec/aSubRecord.c @@ -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; } diff --git a/modules/database/test/std/rec/regressTest.c b/modules/database/test/std/rec/regressTest.c index 7e9cb2f73..182b6bc8b 100644 --- a/modules/database/test/std/rec/regressTest.c +++ b/modules/database/test/std/rec/regressTest.c @@ -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); }