diff --git a/src/ioc/db/dbAccess.c b/src/ioc/db/dbAccess.c index 8e3b81fce..1c01da331 100644 --- a/src/ioc/db/dbAccess.c +++ b/src/ioc/db/dbAccess.c @@ -739,28 +739,34 @@ static long getLinkValue(DBADDR *paddr, short dbrType, { dbCommon *precord = paddr->precord; dbFldDes *pfldDes = paddr->pfldDes; + /* size of pbuf storage in bytes, including space for trailing nil */ int maxlen; DBENTRY dbEntry; long status; + long nReq = nRequest ? *nRequest : 1; + + /* dbFindRecord() 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 - 1; - if (nRequest && *nRequest > 1) *nRequest = 1; + maxlen = MAX_STRING_SIZE; + nReq = 1; break; case DBR_DOUBLE: /* Needed for dbCa links */ - if (nRequest && *nRequest) *nRequest = 1; + if (nRequest) *nRequest = 1; *(double *)pbuf = epicsNAN; return 0; case DBR_CHAR: case DBR_UCHAR: - if (nRequest && *nRequest > 0) { - maxlen = *nRequest - 1; - break; - } - /* else fall through ... */ + maxlen = nReq; + break; default: return S_db_badDbrtype; } @@ -769,10 +775,13 @@ static long getLinkValue(DBADDR *paddr, short dbrType, status = dbFindRecord(&dbEntry, precord->name); if (!status) status = dbFindField(&dbEntry, pfldDes->name); if (!status) { - char *rtnString = dbGetString(&dbEntry); + const char *rtnString = dbGetString(&dbEntry); - strncpy(pbuf, rtnString, --maxlen); - pbuf[maxlen] = 0; + strncpy(pbuf, rtnString, maxlen-1); + pbuf[maxlen-1] = 0; + if(dbrType!=DBR_STRING) + nReq = strlen(pbuf)+1; + if(nRequest) *nRequest = nReq; } dbFinishEntry(&dbEntry); return status; @@ -782,28 +791,31 @@ 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 - 1; - if (nRequest && *nRequest > 1) *nRequest = 1; + maxlen = MAX_STRING_SIZE; + nReq = 1; break; case DBR_CHAR: case DBR_UCHAR: - if (nRequest && *nRequest > 0) { - maxlen = *nRequest - 1; - break; - } + maxlen = nReq; + break; + /* else fall through ... */ default: return S_db_badDbrtype; } - strncpy(pbuf, paddr->pfield, --maxlen); - pbuf[maxlen] = 0; + strncpy(pbuf, paddr->pfield, maxlen-1); + pbuf[maxlen-1] = 0; + if(dbrType!=DBR_STRING) + nReq = strlen(pbuf)+1; + if(nRequest) *nRequest = nReq; return 0; } @@ -931,6 +943,15 @@ long dbGet(DBADDR *paddr, short dbrType, localAddr.pfield = (char *) pfl->u.r.field; 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; diff --git a/src/ioc/db/dbUnitTest.c b/src/ioc/db/dbUnitTest.c index d0d5906c8..ad665a209 100644 --- a/src/ioc/db/dbUnitTest.c +++ b/src/ioc/db/dbUnitTest.c @@ -222,6 +222,71 @@ void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap) } } +void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsigned long cnt, const void *pbuf) +{ + DBADDR addr; + const long vSize = dbValueSize(dbfType); + const long nStore = vSize * nRequest; + long status; + void *gbuf, *gstore; + + if(dbNameToAddr(pv, &addr)) { + testFail("Missing PV \"%s\"", pv); + return; + } + + gbuf = gstore = malloc(nStore); + if(!gbuf && nStore!=0) { /* note that malloc(0) is allowed to return NULL on success */ + testFail("Allocation failed esize=%ld total=%ld", vSize, nStore); + return; + } + + status = dbGetField(&addr, dbfType, gbuf, NULL, &nRequest, NULL); + if (status) { + testFail("dbGetField(\"%s\", %d, ...) -> %#lx", pv, dbfType, status); + + } else { + unsigned match = nRequest==cnt; + long n, N = nRequest < cnt ? nRequest : cnt; + + if(!match) + testDiag("Length mis-match. expected=%lu actual=%lu", cnt, nRequest); + + for(n=0; n pbufcnt will detect truncation. + * nRequest < pbufcnt always fails. + * nRequest ==pbufcnt checks prefix (actual may be longer than expected) + */ +epicsShareFunc void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsigned long pbufcnt, const void *pbuf); + epicsShareFunc dbCommon* testdbRecordPtr(const char* pv); typedef struct testMonitor testMonitor; diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index 67ef688e0..ce6862fda 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -152,7 +152,14 @@ recGblCheckDeadbandTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp testHarness_SRCS += recGblCheckDeadbandTest.c TESTS += recGblCheckDeadbandTest -# The testHarness runs all the test programs in a known working order. +TESTPROD_HOST += testPutGetTest +testPutGetTest_SRCS += dbPutGetTest.c +testPutGetTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbPutGetTest.db +TESTFILES += ../dbPutGetTest.db +TESTS += testPutGetTest + +# This runs all the test programs in a known working order: testHarness_SRCS += epicsRunDbTests.c dbTestHarness_SRCS += $(testHarness_SRCS) diff --git a/src/ioc/db/test/dbPutGetTest.c b/src/ioc/db/test/dbPutGetTest.c new file mode 100644 index 000000000..61b31a785 --- /dev/null +++ b/src/ioc/db/test/dbPutGetTest.c @@ -0,0 +1,150 @@ + +#include + +#include +#include +#include +#include +#include +#include + +static +void testdbGetStringEqual(const char *pv, const char *expected) +{ + const char *actual; + int ok; + DBENTRY ent; + + dbInitEntry(pdbbase, &ent); + + if(dbFindRecord(&ent, pv)) { + testFail("Failed to find record '%s'", pv); + return; + } + + actual = dbGetString(&ent); + ok = (!actual && !expected) + || (actual && expected && strcmp(actual, expected)==0); + + testOk(ok, "dbGetString(\"%s\") -> \"%s\" == \"%s\"", pv, actual, expected); + + dbFinishEntry(&ent); +} + +static +void testGetString(void) +{ + testDiag("testGetString()"); + + testdbGetStringEqual("recempty.DTYP", "Soft Channel"); + testdbGetStringEqual("recempty.DESC", ""); + testdbGetStringEqual("recempty.PHAS", "0"); + testdbGetStringEqual("recempty.TSE" , "0"); + testdbGetStringEqual("recempty.DISA", "0"); + testdbGetStringEqual("recempty.DISV", "0"); + + testdbGetStringEqual("recoverwrite.DTYP", "Soft Channel"); + testdbGetStringEqual("recoverwrite.DESC", ""); + testdbGetStringEqual("recoverwrite.PHAS", "0"); + testdbGetStringEqual("recoverwrite.TSE" , "0"); + testdbGetStringEqual("recoverwrite.DISA", "0"); + testdbGetStringEqual("recoverwrite.DISV", "0"); +} + +static +void testStringMax(void) +{ + testDiag("testStringMax()"); + + testdbGetStringEqual("recmax.DISA", "-1"); +} + +static +void testLongLink(void) +{ + testDiag("testLonkLink()"); + + testdbGetFieldEqual("lnktest.INP", DBR_STRING, "lnktarget NPP NMS"); + testdbGetFieldEqual("lnktest.INP$", DBR_STRING, "lnktarget NPP NMS"); + testDiag("dbGet() w/ nRequest==1 gets only trailing nil"); + testdbGetFieldEqual("lnktest.INP$", DBR_CHAR, '\0'); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 19, 18, "lnktarget NPP NMS"); + + testDiag("get w/ truncation"); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 0, 0, NULL); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 1, 1, ""); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 2, 2, "l"); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 3, 3, "ln"); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 17, 17, "lnktarget NPP NM"); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 18, 18, "lnktarget NPP NMS"); + + testdbGetArrFieldEqual("lnktest.INP", DBR_STRING, 2, 1, "lnktarget NPP NMS"); +} + +static +void testLongAttr(void) +{ + testDiag("testLongAttr()"); + + testdbGetFieldEqual("lnktest.RTYP", DBR_STRING, "x"); + testdbGetFieldEqual("lnktest.RTYP$", DBR_STRING, "x"); + testDiag("dbGet() w/ nRequest==1 gets only trailing nil"); + testdbGetFieldEqual("lnktest.RTYP$", DBR_CHAR, '\0'); + + testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 4, 2, "x"); + + testDiag("get w/ truncation"); + testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 0, 0, NULL); + testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 1, 1, ""); + testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 2, 2, "x"); +} + +static +void testLongField(void) +{ + testDiag("testLongField()"); + + testdbGetFieldEqual("lnktest.NAME", DBR_STRING, "lnktest"); + testdbGetFieldEqual("lnktest.NAME$", DBR_STRING, "108"); + testDiag("dbGet() w/ nRequest==1 gets only trailing nil"); + testdbGetFieldEqual("lnktest.NAME$", DBR_CHAR, '\0'); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 10, 8, "lnktest"); + + testDiag("get w/ truncation"); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 0, 0, NULL); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 1, 1, ""); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 2, 2, "l"); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 3, 3, "ln"); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 7, 7, "lnktes"); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 8, 8, "lnktest"); +} + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(dbPutGet) +{ + testPlan(41); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbPutGetTest.db", NULL, NULL); + + testGetString(); + + testStringMax(); + + eltc(0); + testIocInitOk(); + eltc(1); + + testLongLink(); + testLongAttr(); + testLongField(); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} diff --git a/src/ioc/db/test/dbPutGetTest.db b/src/ioc/db/test/dbPutGetTest.db new file mode 100644 index 000000000..bacaa5638 --- /dev/null +++ b/src/ioc/db/test/dbPutGetTest.db @@ -0,0 +1,41 @@ + +record(x, "recempty") { +# empty string is the same "0" for numeric fields + field(DTYP, "") + field(DESC, "") + field(PHAS, "") + field(TSE , "") + field(DISA, "") + field(DISV, "") +} + +record(x, "recoverwrite") { +# first with non-default values +# field(DTYP, "Scan I/O") + field(DESC, "hello") + field(PHAS, "2") + field(TSE , "5") + field(DISA, "6") + field(DISV, "7") +} + +record(x, "recoverwrite") { +# now restore default values + field(DTYP, "") + field(DESC, "") + field(PHAS, "") + field(TSE , "") + field(TSEL, "") + field(DISA, "") + field(DISV, "") +} + +record(x, "recmax") { + field(DISA, "0xffffffff") +} + +record(x, "lnktarget") {} + +record(x, "lnktest") { + field(INP, "lnktarget NPP NMS") +}