Merge branch 'aliases' into PSI-7.0

This commit is contained in:
2024-08-26 16:37:57 +02:00
24 changed files with 421 additions and 89 deletions

View File

@ -60,7 +60,10 @@ jobs:
# Remove respective -Wno-error=... flag once it is fixed.
extra: "CMD_CXXFLAGS=-std=c++20
CMD_CPPFLAGS='-fdiagnostics-color
-fstack-protector-strong
-Wformat
-Werror
-Werror=format-security
-Wno-error=deprecated-declarations
-Wno-error=stringop-truncation
-Wno-error=restrict
@ -68,8 +71,9 @@ jobs:
-Wno-error=nonnull
-Wno-error=dangling-pointer
-Wno-error=format-overflow
-Wno-error=format-security
-Wno-error=stringop-overread'"
-Wno-error=stringop-overread
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3'
CMD_LDFLAGS=-Wl,-z,relro"
- os: ubuntu-20.04
cmp: gcc

View File

@ -22,6 +22,28 @@ should also be read to understand what has changed since earlier releases:
## Changes made on the 7.0 branch since 7.0.8.1
### Allow to load the same alias multiple times
Aliases can now be defined multiple times as long as they still refer to the
same record, unless the shell variable dbRecordsOnceOnly is set.
This allows to load database files multiple times, even if they contain
alias definitions.
### Allow users to delete previously created records from the database
From this release, record instances and aliases that have already been loaded
by an IOC can be removed from the database again before the call to iocInit
by loading a second instance of the named records but using `"#"` in place of
the record type. Values for the fields are not required or advised, just use
an empty record body { }. This is useful when a template defines records that
are not wanted in some IOCs, without having to split or duplicate the original
template.
For example this will remove the record named "unwanted":
```
record("#", "unwanted") { }
```
-----

View File

@ -676,7 +676,7 @@ union db_access_val{
(type)%(LAST_TYPE+1) == DBR_DOUBLE)
#define dbf_type_to_text(type) \
( ((type) >= -1 && (type) < dbf_text_dim-2) ? \
( ((type+1) >= 0 && (type) < dbf_text_dim-2) ? \
dbf_text[type+1] : dbf_text_invalid )
#define dbf_text_to_type(text, type) \

View File

@ -145,63 +145,66 @@ long dbPutSpecial(DBADDR *paddr,int pass)
}
static void get_enum_strs(DBADDR *paddr, char **ppbuffer,
rset *prset,long *options)
rset *prset, long *options)
{
short field_type=paddr->field_type;
dbFldDes *pdbFldDes = paddr->pfldDes;
dbMenu *pdbMenu;
dbDeviceMenu *pdbDeviceMenu;
char **papChoice;
unsigned long no_str;
char *ptemp;
struct dbr_enumStrs *pdbr_enumStrs=(struct dbr_enumStrs*)(*ppbuffer);
unsigned int i;
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(pdbr_enumStrs,'\0',dbr_enumStrs_size);
switch(field_type) {
case DBF_ENUM:
if( prset && prset->get_enum_strs ) {
(*prset->get_enum_strs)(paddr,pdbr_enumStrs);
} else {
*options = (*options)^DBR_ENUM_STRS;/*Turn off option*/
}
break;
case DBF_MENU:
pdbMenu = (dbMenu *)pdbFldDes->ftPvt;
no_str = pdbMenu->nChoice;
papChoice= pdbMenu->papChoiceValue;
goto choice_common;
case DBF_DEVICE:
pdbDeviceMenu = (dbDeviceMenu *)pdbFldDes->ftPvt;
if(!pdbDeviceMenu) {
*options = (*options)^DBR_ENUM_STRS;/*Turn off option*/
break;
}
no_str = pdbDeviceMenu->nChoice;
papChoice = pdbDeviceMenu->papChoice;
goto choice_common;
choice_common:
i = sizeof(pdbr_enumStrs->strs)/
sizeof(pdbr_enumStrs->strs[0]);
if(i<no_str) no_str = i;
pdbr_enumStrs->no_str = no_str;
ptemp = &(pdbr_enumStrs->strs[0][0]);
for (i=0; i<no_str; i++) {
if(papChoice[i]==NULL) *ptemp=0;
else {
strncpy(ptemp,papChoice[i],
sizeof(pdbr_enumStrs->strs[0]));
*(ptemp+sizeof(pdbr_enumStrs->strs[0])-1) = 0;
}
ptemp += sizeof(pdbr_enumStrs->strs[0]);
}
break;
default:
*options = (*options)^DBR_ENUM_STRS;/*Turn off option*/
break;
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); /* availible > 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';
}
}
*ppbuffer = ((char *)*ppbuffer) + dbr_enumStrs_size;
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,

View File

@ -165,7 +165,7 @@ unsigned long dbChannelIO::nativeElementCount (
{
guard.assertIdenticalMutex ( this->mutex );
long elements = dbChannelElements ( this->dbch );
if ( elements >= 0u ) {
if ( elements >= 0 ) {
return static_cast < unsigned long > ( elements );
}
return 0u;

View File

@ -15,13 +15,21 @@ typedef struct dbCommonPvt {
/* Thread which is currently processing this record */
struct epicsThreadOSD* procThread;
struct dbCommon common;
/* actually followed by:
* struct dbCommon common;
*/
} dbCommonPvt;
static EPICS_ALWAYS_INLINE
dbCommonPvt* dbRec2Pvt(struct dbCommon *prec)
{
return CONTAINER(prec, dbCommonPvt, common);
return (dbCommonPvt*)((char*)prec - sizeof(dbCommonPvt));
}
static EPICS_ALWAYS_INLINE
dbCommon* dbPvt2Rec(struct dbCommonPvt *pvt)
{
return (dbCommon*)&pvt[1];
}
#endif // DBCOMMONPVT_H

View File

@ -94,7 +94,7 @@ static long cvt_st_UInt32(const char *from, void *pfield, const dbAddr *paddr)
status = epicsParseFloat64(from, &dval, &end);
if (!status &&
dval >=0 &&
dval <= ULONG_MAX)
dval <= UINT_MAX)
*to = dval;
}
return status;

View File

@ -302,7 +302,7 @@ static long getStringUlong(const dbAddr *paddr,
epicsFloat64 dval;
status = epicsParseFloat64(psrc, &dval, &end);
if (!status && 0 <= dval && dval <= ULONG_MAX)
if (!status && 0 <= dval && dval <= UINT_MAX)
*pdst = dval;
}
if (status)
@ -1052,7 +1052,7 @@ static long putStringUlong(dbAddr *paddr,
epicsFloat64 dval;
status = epicsParseFloat64(psrc, &dval, &end);
if (!status && 0 <= dval && dval <= ULONG_MAX)
if (!status && 0 <= dval && dval <= UINT_MAX)
*pdst = dval;
}
if (status)

View File

@ -181,7 +181,7 @@ static long cvt_st_ul(const void *f, void *t, const dbAddr *paddr)
status = epicsParseFloat64(from, &dval, &end);
if (!status &&
dval >=0 &&
dval <= ULONG_MAX)
dval <= UINT_MAX)
*to = dval;
}
return status;

View File

@ -1119,6 +1119,20 @@ static void dbRecordHead(char *recordType, char *name, int visible)
return;
}
if (recordType[0] == '#' && recordType[1] == 0) {
status = dbFindRecord(pdbentry, name);
if (status == 0) {
dbDeleteRecord(pdbentry);
} else {
fprintf(stderr, ERL_WARNING ": Unable to delete record \"%s\". Not found.\n"
" at file %s line %d\n",
name, pinputFileNow->filename, pinputFileNow->line_num);
}
popFirstTemp();
dbFreeEntry(pdbentry);
duplicate = TRUE;
return;
}
status = dbFindRecordType(pdbentry, recordType);
if (status) {
fprintf(stderr, "Record \"%s\" is of unknown type \"%s\"\n",
@ -1249,24 +1263,50 @@ static void dbRecordInfo(char *name, char *value)
}
}
static long createAlias(DBENTRY *pdbentry, const char *alias)
{
DBENTRY tempEntry;
long status;
dbRecordNode *precnode = pdbentry->precnode;
if (precnode->aliasedRecnode) precnode = precnode->aliasedRecnode;
dbInitEntry(pdbentry->pdbbase, &tempEntry);
status = dbFindRecord(&tempEntry, alias);
if (status == 0) {
if (tempEntry.precnode->aliasedRecnode != precnode) {
if (tempEntry.precnode->aliasedRecnode)
fprintf(stderr, ERL_ERROR ": Alias \"%s\" for \"%s\": name already used by an alias for \"%s\"\n",
alias, dbGetRecordName(pdbentry),
tempEntry.precnode->aliasedRecnode->recordname);
else
fprintf(stderr, ERL_ERROR ": Alias \"%s\" for \"%s\": name already used by a record\n",
alias, dbGetRecordName(pdbentry));
status = S_dbLib_recExists;
} else if (dbRecordsOnceOnly) {
fprintf(stderr, ERL_ERROR ": Alias \"%s\" already defined and dbRecordsOnceOnly set.\n",
alias);
status = S_dbLib_recExists;
}
} else {
status = dbCreateAlias(pdbentry, alias);
}
dbFinishEntry(&tempEntry);
return status;
}
static void dbRecordAlias(char *name)
{
DBENTRY *pdbentry;
tempListNode *ptempListNode;
long status;
if(dbRecordNameValidate(name))
return;
if (duplicate) return;
ptempListNode = (tempListNode *)ellFirst(&tempList);
pdbentry = ptempListNode->item;
status = dbCreateAlias(pdbentry, name);
if (status) {
fprintf(stderr, "Can't create alias \"%s\" for \"%s\"\n",
name, dbGetRecordName(pdbentry));
if (createAlias(pdbentry, name) != 0) {
yyerror(NULL);
return;
}
}
@ -1284,9 +1324,7 @@ static void dbAlias(char *name, char *alias)
alias, name);
yyerror(NULL);
}
else if (dbCreateAlias(pdbEntry, alias)) {
fprintf(stderr, "Can't create alias \"%s\" referring to \"%s\"\n",
alias, name);
else if (createAlias(pdbEntry, alias) != 0) {
yyerror(NULL);
}
dbFinishEntry(pdbEntry);

View File

@ -1219,7 +1219,9 @@ long dbNextRecordType(DBENTRY *pdbentry)
char * dbGetRecordTypeName(DBENTRY *pdbentry)
{
return(pdbentry->precordType->name);
dbRecordType *pdbRecordType = pdbentry->precordType;
if(!pdbRecordType) return NULL;
return(pdbRecordType->name);
}
int dbGetNRecordTypes(DBENTRY *pdbentry)
@ -1477,6 +1479,17 @@ long dbDeleteAliases(DBENTRY *pdbentry)
return 0;
}
static void dbDeleteRecordLinks(dbRecordType *rtyp, struct dbCommon *prec)
{
short i;
for (i=0; i<rtyp->no_links; i++) {
dbFldDes *pflddes = rtyp->papFldDes[rtyp->link_ind[i]];
DBLINK *plink = (DBLINK *)(((char *)prec) + pflddes->offset);
dbFreeLinkContents(plink);
}
}
long dbDeleteRecord(DBENTRY *pdbentry)
{
dbBase *pdbbase = pdbentry->pdbbase;
@ -1492,6 +1505,7 @@ long dbDeleteRecord(DBENTRY *pdbentry)
preclist = &precordType->recList;
ellDelete(preclist, &precnode->node);
dbPvdDelete(pdbbase, precnode);
dbDeleteRecordLinks(precordType, precnode->precord);
while (!dbFirstInfo(pdbentry)) {
dbDeleteInfo(pdbentry);
}

View File

@ -66,7 +66,17 @@ void devExtend(dsxt *pdsxt)
pthisDevSup->pdsxt = pdsxt;
}
}
/* Ensure that: sizeof(dbCommonPvt) + pdbRecordType->rec_size
* accounts for necessary alignment of record type struct
*/
typedef struct {
dbCommonPvt prefix;
/* ensure no padding needed... */
dbCommon suffix;
} dbCommonPvtAlignmentTest;
STATIC_ASSERT(offsetof(dbCommonPvtAlignmentTest, suffix)==sizeof(dbCommonPvt));
long dbAllocRecord(DBENTRY *pdbentry,const char *precordName)
{
dbRecordType *pdbRecordType = pdbentry->precordType;
@ -90,8 +100,8 @@ long dbAllocRecord(DBENTRY *pdbentry,const char *precordName)
precordName, pdbRecordType->name, pdbRecordType->rec_size);
return(S_dbLib_noRecSup);
}
ppvt = dbCalloc(1, offsetof(dbCommonPvt, common) + pdbRecordType->rec_size);
precord = &ppvt->common;
ppvt = dbCalloc(1, sizeof(dbCommonPvt) + pdbRecordType->rec_size);
precord = dbPvt2Rec(ppvt);
ppvt->recnode = precnode;
precord->rdes = pdbRecordType;
precnode->precord = precord;

View File

@ -378,6 +378,7 @@ static int yyerror(char *str)
dbIncludePrint();
yyFailed = TRUE;
}
fprintf(stderr, "\n %d | %s\n", pinputFileNow->line_num, my_buffer);
return(0);
}
static long pvt_yy_parse(void)

View File

@ -9,6 +9,7 @@
#include <set>
#include <map>
#include <stdint.h>
#include <string.h>
#include <stdint.h>

View File

@ -36,6 +36,7 @@ dbTestIoc_DBD += xLink.dbd
dbTestIoc_DBD += devx.dbd
dbTestIoc_DBD += jlinkz.dbd
dbTestIoc_DBD += dbLinkdset.dbd
dbTestIoc_DBD += dbCore.dbd
TESTFILES += $(COMMON_DIR)/dbTestIoc.dbd ../xRecord.db
testHarness_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
@ -183,6 +184,12 @@ testHarness_SRCS += dbStaticTest.c
TESTFILES += ../dbStaticTest.db
TESTFILES += ../dbStaticTestAlias1.db
TESTFILES += ../dbStaticTestAlias2.db
TESTFILES += ../dbStaticTestAliasAgain1.db
TESTFILES += ../dbStaticTestAliasAgain2.db
TESTFILES += ../dbStaticTestAliasAgain3.db
TESTFILES += ../dbStaticTestAliasAgainError1.db
TESTFILES += ../dbStaticTestAliasAgainError2.db
TESTFILES += ../dbStaticTestRemove.db
TESTS += dbStaticTest
# This runs all the test programs in a known working order:

View File

@ -82,12 +82,12 @@ void checkDoubleGet(dbChannel *chan, db_field_log* pfl)
testOk1(meta.status==UDF_ALARM);
testOk1(meta.acks==MAJOR_ALARM);
testOk1(meta.ackt==1);
testOk1(strncmp(meta.amsg, "oops", DB_UNITS_SIZE)==0);
testOk1(strncmp(meta.amsg, "oops", sizeof(meta.amsg))==0);
testOk1(meta.time.secPastEpoch==0x12345678);
testOk1(meta.time.nsec==0x90abcdef);
testOk1(meta.utag==0x10203040);
testOk1(meta.precision.dp==0x12345678);
testOk1(strncmp(meta.units, "arbitrary", DB_UNITS_SIZE)==0);
testOk1(strncmp(meta.units, "arbitrary", sizeof(meta.units))==0);
#define limitEq(UL, FL, VAL) testOk(meta.UL ## _ ## FL ## _limit == (VAL), #UL "_" #FL "_limit (%f) == %f", meta.UL ## _ ## FL ## _limit, VAL)
limitEq(lower, disp, 10000000.0-1.0);
limitEq(upper, disp, 10000000.0+1.0);
@ -114,6 +114,8 @@ void testdbMetaDoubleGet(void)
STATIC_ASSERT(sizeof(meta.amsg)==sizeof(prec->amsg));
STATIC_ASSERT(sizeof(meta.amsg)==sizeof(pfl->amsg));
testDiag("testdbMetaDoubleGet");
if(!chan)
testAbort("Missing recmeta OTST");
if((status=dbChannelOpen(chan))!=0)
@ -163,6 +165,8 @@ typedef struct {
DBRenumStrs
} dbMetaEnum;
enum {dbMetaMetaMask = DBR_STATUS | DBR_AMSG | DBR_ENUM_STRS | DBR_TIME | DBR_UTAG};
static
void testdbMetaEnumSizes(void)
{
@ -189,6 +193,66 @@ void testdbMetaEnumSizes(void)
testOk(sizeof(dbMetaEnum)==pos, "sizeof(dbMetaEnum), %u == %u", (unsigned)sizeof(dbMetaEnum), (unsigned)pos);
}
static
void checkEnumGet(dbChannel *chan, db_field_log* pfl)
{
dbMetaEnum meta;
long options = (long)dbMetaMetaMask;
long nReq = 0;
long status;
/* spoil */
meta.no_str = -1;
meta.strs[0][0] = '!';
status=dbChannelGet(chan, DBF_DOUBLE, &meta, &options, &nReq, pfl);
testOk(status==0, "dbGet OTST : %ld", status);
testOk1(meta.severity==INVALID_ALARM);
testOk1(meta.status==UDF_ALARM);
testOk1(meta.acks==MAJOR_ALARM);
testOk1(meta.ackt==1);
testOk1(strncmp(meta.amsg, "oops", sizeof(meta.amsg))==0);
testOk1(meta.time.secPastEpoch==0x12345678);
testOk1(meta.time.nsec==0x90abcdef);
testOk1(meta.utag==0x10203040);
if(testOk1(meta.no_str==3)) {
testOk1(strncmp(meta.strs[0], "Before", sizeof(meta.strs[0]))==0);
testOk1(strncmp(meta.strs[1], "After", sizeof(meta.strs[0]))==0);
testOk1(strncmp(meta.strs[2], "None", sizeof(meta.strs[0]))==0);
}
}
void testdbMetaEnumGet(void)
{
long status;
xRecord* prec = (xRecord*)testdbRecordPtr("recmeta");
dbChannel *chan = dbChannelCreate("recmeta.SFX");
testDiag("testdbMetaEnumGet");
if(!chan)
testAbort("Missing recmeta SFX");
if((status=dbChannelOpen(chan))!=0)
testAbort("can't open recmeta SFX : %ld", status);
dbScanLock((dbCommon*)prec);
/* ensure that all meta-data has different non-zero values */
prec->otst = 10000000.0;
prec->sevr = INVALID_ALARM;
prec->stat = UDF_ALARM;
strcpy(prec->amsg, "oops");
prec->acks = MAJOR_ALARM;
prec->time.secPastEpoch = 0x12345678;
prec->time.nsec = 0x90abcdef;
prec->utag = 0x10203040;
testDiag("dbGet directly from record");
checkEnumGet(chan, NULL);
dbScanUnlock((dbCommon*)prec);
}
static
void testdbGetStringEqual(const char *pv, const char *expected)
{
@ -347,7 +411,7 @@ void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(dbPutGet)
{
testPlan(124);
testPlan(137);
testdbPrepare();
testdbMetaDoubleSizes();
@ -366,6 +430,7 @@ MAIN(dbPutGet)
eltc(1);
testdbMetaDoubleGet();
testdbMetaEnumGet();
testLongLink();
testLongAttr();

View File

@ -13,6 +13,37 @@
#include <dbStaticPvt.h>
#include <dbUnitTest.h>
#include <testMain.h>
#include <epicsString.h>
#include <iocsh.h>
static void testEntryRemoved(const char *pv)
{
DBENTRY entry;
testDiag("testEntryRemoved(\"%s\")", pv);
dbInitEntry(pdbbase, &entry);
testOk(dbFindRecord(&entry, pv)==S_dbLib_recNotFound,
"Record '%s' not present", pv);
dbFinishEntry(&entry);
}
static void testEntryPresent(const char *pv)
{
DBENTRY entry;
testDiag("testEntryPresent(\"%s\")", pv);
dbInitEntry(pdbbase, &entry);
testOk(dbFindRecord(&entry, pv)==0,
"Record '%s' present", pv);
dbFinishEntry(&entry);
}
static void testEntry(const char *pv)
{
@ -291,16 +322,19 @@ static void testDbVerify(const char *record)
dbFinishEntry(&entry);
}
static void testWrongAliasRecord(const char *filename)
static void testReadDatabase(const char *filename, int expectToFail)
{
FILE *fp = NULL;
long status;
dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR "..");
dbOpenFile(pdbbase, filename, &fp);
if(!fp) {
testAbort("Unable to read %s", filename);
testAbort("Unable to open %s", filename);
}
testOk(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL) != 0,
"Wrong alias record in %s is expected to fail", filename);
status = dbReadDatabaseFP(&pdbbase, fp, NULL, NULL);
testOk(!status == !expectToFail,
"Reading %s%s", filename,
expectToFail ? " is expected to fail" : "");
}
void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
@ -308,9 +342,10 @@ void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(dbStaticTest)
{
const char *ldir;
char *ldirDup;
FILE *fp = NULL;
testPlan(312);
testPlan(350);
testdbPrepare();
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
@ -320,13 +355,40 @@ MAIN(dbStaticTest)
if(!fp) {
testAbort("Unable to read dbStaticTest.db");
}
ldirDup = epicsStrDup(ldir);
if(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL)) {
testAbort("Unable to load %s%sdbStaticTest.db",
ldir, OSI_PATH_LIST_SEPARATOR);
ldirDup, OSI_PATH_LIST_SEPARATOR);
}
free(ldirDup);
testWrongAliasRecord("dbStaticTestAlias1.db");
testWrongAliasRecord("dbStaticTestAlias2.db");
dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR "..");
ldir = dbOpenFile(pdbbase, "dbStaticTestRemove.db", &fp);
if(!fp) {
testAbort("Unable to read dbStaticTestRemove.db");
}
ldirDup = epicsStrDup(ldir);
if(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL)) {
testAbort("Unable to load %s%sdbStaticTestRemove.db",
ldirDup, OSI_PATH_LIST_SEPARATOR);
}
free(ldirDup);
testReadDatabase("dbStaticTestAlias1.db", 1);
testReadDatabase("dbStaticTestAlias2.db", 1);
/* Test re-defining aliases */
testReadDatabase("dbStaticTestAliasAgain1.db", 0);
testReadDatabase("dbStaticTestAliasAgain1.db", 0);
testReadDatabase("dbStaticTestAliasAgain2.db", 0);
testReadDatabase("dbStaticTestAliasAgain2.db", 0);
testReadDatabase("dbStaticTestAliasAgain3.db", 0);
testReadDatabase("dbStaticTestAliasAgain3.db", 0);
testReadDatabase("dbStaticTestAliasAgainError1.db", 1);
testReadDatabase("dbStaticTestAliasAgainError2.db", 1);
iocshCmd("var dbRecordsOnceOnly 1");
testReadDatabase("dbStaticTestAliasAgain2.db", 1);
testReadDatabase("dbStaticTestAliasAgain3.db", 1);
testEntry("testrec.VAL");
testEntry("testalias.VAL");
@ -341,6 +403,21 @@ MAIN(dbStaticTest)
testRec2Entry("testalias2");
testRec2Entry("testalias3");
testEntryPresent("testdelrec");
testEntryPresent("testdelrec6");
testEntryPresent("testdelalias66");
testEntryRemoved("testdelrec1");
testEntryRemoved("testdelrec2");
testEntryRemoved("testdelrec3");
testEntryRemoved("testdelrec4");
testEntryRemoved("testdelrec5");
testEntryRemoved("testdelalias6");
testEntryRemoved("testdelrec7");
testEntryRemoved("testdelalias7");
testEntryRemoved("testdelalias77");
testEntryRemoved("testdelrec8");
testEntryRemoved("testdelrec11");
eltc(0);
testIocInitOk();
eltc(1);
@ -358,6 +435,21 @@ MAIN(dbStaticTest)
testRec2Entry("testalias2");
testRec2Entry("testalias3");
testEntryPresent("testdelrec");
testEntryPresent("testdelrec6");
testEntryPresent("testdelalias66");
testEntryRemoved("testdelrec1");
testEntryRemoved("testdelrec2");
testEntryRemoved("testdelrec3");
testEntryRemoved("testdelrec4");
testEntryRemoved("testdelrec5");
testEntryRemoved("testdelalias6");
testEntryRemoved("testdelrec7");
testEntryRemoved("testdelalias7");
testEntryRemoved("testdelalias77");
testEntryRemoved("testdelrec8");
testEntryRemoved("testdelrec11");
testDbVerify("testrec");
testIocShutdownOk();

View File

@ -0,0 +1,4 @@
# Test re-load alias in record
record(x, "testrecAgain") {
alias("testaliasAgain1")
}

View File

@ -0,0 +1,2 @@
# Test re-load alias for record
alias("testrecAgain", "testaliasAgain2")

View File

@ -0,0 +1,2 @@
# Test re-load alias for alias
alias("testaliasAgain2", "testaliasAgain3")

View File

@ -0,0 +1,2 @@
# ERROR: alias using name of exising alias for different record
alias("testrec", "testaliasAgain1")

View File

@ -0,0 +1,2 @@
# ERROR: alias using name of exising record fails
alias("testrecAgain", "testrec")

View File

@ -0,0 +1,55 @@
record(x, "testdelrec") { }
record(x, "testdelrec1") { }
record(x, "testdelrec2") {
field("INP", "foobar")
}
record(x, "testdelrec3") {
info("foo", "bar")
}
record(x, "testdelrec4") {
field("INP", "testdelrec.VAL")
}
record(x, "testdelrec5") {
field("FLNK", "testdelrec.VAL")
}
record(x, "testdelrec6") { }
alias("testdelrec6", "testdelalias6")
alias("testdelrec6", "testdelalias66")
record(x, "testdelrec7") { }
alias("testdelrec7", "testdelalias7")
alias("testdelrec7", "testdelalias77")
record(x, "testdelrec8") {
field("INP", "{z:{good:1}}")
}
record("#", "no:such:record") { }
record("#", "also:non:existant") {
field(FOO, "5")
}
record("#", "testdelrec1") { }
record("#", "testdelrec2") {
field("INP", "foobar2")
}
record("#", "testdelrec3") {
field("INP", "foobar2")
field("VAL", "1")
}
record("#", "testdelrec4") { }
record("#", "testdelrec5") { }
record("#", "testdelalias6") { }
record("#", "testdelrec7") { }
record("#", "testdelrec8") { }
record("#", "testdelrec11") { }

View File

@ -109,7 +109,7 @@ osdReadline (const char *prompt, struct readlineContext *context)
line[linelen] = '\0';
}
context->line = line;
if (line && *line)
if (line && *line && !context->in)
add_history(line);
return line;
}