diff --git a/src/ioc/db/dbChannel.c b/src/ioc/db/dbChannel.c index eabf71350..f9a995a6a 100644 --- a/src/ioc/db/dbChannel.c +++ b/src/ioc/db/dbChannel.c @@ -210,6 +210,27 @@ static const yajl_callbacks chf_callbacks = static const yajl_parser_config chf_config = { 0, 1 }; /* allowComments = NO , checkUTF8 = YES */ +static long json_validate(const char *json) +{ + yajl_handle yh = yajl_alloc(NULL, &chf_config, NULL, NULL); + size_t jlen = strlen(json); + yajl_status ys; + + if (!yh) + return S_db_noMemory; + + ys = yajl_parse(yh, (const unsigned char *) json, jlen); + if (ys == yajl_status_insufficient_data) + ys = yajl_parse_complete(yh); + + yajl_free(yh); + + if (ys == yajl_status_ok) + return 0; + + return S_db_notFound; +} + static long chf_parse(dbChannel *chan, const char *json) { parseContext parser = @@ -251,6 +272,68 @@ static long chf_parse(dbChannel *chan, const char *json) return status; } +static long pvNameLookup(DBENTRY *pdbe, const char **ppname) +{ + long status; + + dbInitEntry(pdbbase, pdbe); + + status = dbFindRecordPart(pdbe, ppname); + if (status) + return status; + + if (**ppname == '.') + ++*ppname; + + status = dbFindFieldPart(pdbe, ppname); + if (status == S_dbLib_fieldNotFound) + status = dbGetAttributePart(pdbe, ppname); + + return status; +} + +long dbChannelTest(const char *pname) +{ + DBENTRY dbEntry; + long status; + + if (!pname || !*pname || !pdbbase) + return S_db_notFound; + + status = pvNameLookup(&dbEntry, &pname); + if (status) + goto finish; + + /* Test field modifiers */ + while (*pname) { + switch (*pname) { + case '$': + switch (dbEntry.pflddes->field_type) { + case DBF_STRING: + case DBF_INLINK: + case DBF_OUTLINK: + case DBF_FWDLINK: + break; + default: + status = S_dbLib_fieldNotFound; + goto finish; + } + break; + case '{': + status = json_validate(pname); + goto finish; + default: + status = S_dbLib_fieldNotFound; + goto finish; + } + pname++; + } + + finish: + dbFinishEntry(&dbEntry); + return status; +} + /* Stolen from dbAccess.c: */ static short mapDBFToDBR[DBF_NTYPES] = { @@ -271,41 +354,29 @@ static short mapDBFToDBR[DBF_NTYPES] = /* DBF_FWDLINK => */DBR_STRING, /* DBF_NOACCESS => */DBR_NOACCESS }; -long dbChannelFind(dbChannel *chan, const char *pname) +dbChannel * dbChannelCreate(const char *name) { - DBADDR *paddr; DBENTRY dbEntry; + dbChannel *chan = NULL; + DBADDR *paddr; dbFldDes *pflddes; + const char *pname; long status; short dbfType; - if (!chan || !pname || !*pname || !pdbbase) - return S_db_notFound; + if (!name || !*name || !pdbbase) + return NULL; + pname = name; - if (chan->magic == DBCHANNEL_MAGIC) { - chFilter *filter; - - while ((filter = (chFilter *) ellGet(&chan->filters))) { - filter->fif->channel_close(filter); - free(filter); - } - } else { - ellInit(&chan->filters); - chan->magic = DBCHANNEL_MAGIC; - } - - dbInitEntry(pdbbase, &dbEntry); - status = dbFindRecordPart(&dbEntry, &pname); + status = pvNameLookup(&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; + // FIXME: Use free-list + chan = (dbChannel *) callocMustSucceed(1, sizeof(*chan), "dbChannelCreate"); + chan->magic = DBCHANNEL_MAGIC; + chan->name = strdup(name); // FIXME? + ellInit(&chan->filters); paddr = &chan->addr; pflddes = dbEntry.pflddes; @@ -350,8 +421,13 @@ long dbChannelFind(dbChannel *chan, const char *pname) pname++; } - finish: dbFinishEntry(&dbEntry); - return status; +finish: + dbFinishEntry(&dbEntry); + if (status && chan) { + dbChannelDelete(chan); + chan = NULL; + } + return chan; } long dbChannelOpen(dbChannel *chan) @@ -392,6 +468,9 @@ void dbChannelReport(dbChannel *chan, int level) if (chan->magic != DBCHANNEL_MAGIC) return; + printf("Channel '%s': %d filter(s)\n", chan->name, + ellCount(&chan->filters)); + filter = (chFilter *) ellFirst(&chan->filters); while (filter) { filter->fif->channel_report(filter, level); @@ -399,7 +478,7 @@ void dbChannelReport(dbChannel *chan, int level) } } -long dbChannelClose(dbChannel *chan) +long dbChannelDelete(dbChannel *chan) { chFilter *filter; @@ -410,7 +489,10 @@ long dbChannelClose(dbChannel *chan) filter->fif->channel_close(filter); free(filter); } + free(chan->name); // FIXME? chan->magic = 0; + free(chan); // FIXME: Use free-list + return 0; } diff --git a/src/ioc/db/dbChannel.h b/src/ioc/db/dbChannel.h index 902245fe6..740165b17 100644 --- a/src/ioc/db/dbChannel.h +++ b/src/ioc/db/dbChannel.h @@ -26,6 +26,7 @@ /* A dbChannel points to a record field, and can have multiple filters */ typedef struct dbChannel { epicsUInt32 magic; + const char *name; dbAddr addr; ELLLIST filters; } dbChannel; @@ -85,10 +86,11 @@ struct chFilter { void *puser; }; -epicsShareFunc long dbChannelFind(dbChannel *chan, const char *pname); +epicsShareFunc long dbChannelTest(const char *pname); +epicsShareFunc dbChannel * dbChannelCreate(const char *pname); epicsShareFunc long dbChannelOpen(dbChannel *chan); epicsShareFunc void dbChannelReport(dbChannel *chan, int level); -epicsShareFunc long dbChannelClose(dbChannel *chan); +epicsShareFunc long dbChannelDelete(dbChannel *chan); epicsShareFunc void dbRegisterFilter(const char *key, const chFilterIf *fif); epicsShareFunc const chFilterIf * dbFindFilter(const char *key, size_t len); diff --git a/src/ioc/db/test/dbChannelTest.c b/src/ioc/db/test/dbChannelTest.c index 06b979ede..9a8245f55 100644 --- a/src/ioc/db/test/dbChannelTest.c +++ b/src/ioc/db/test/dbChannelTest.c @@ -130,72 +130,85 @@ chFilterIf testIf = MAIN(dbChannelTest) { - dbChannel ch; + dbChannel *pch; - testPlan(59); + testPlan(64); testOk1(!dbReadDatabase(&pdbbase, "dbChannelTest.dbx", ".:..", NULL)); testOk(!!pdbbase, "pdbbase was set"); r = e = 0; - testOk1(!dbChannelFind(&ch, "x.NAME$")); - testOk1(ch.addr.no_elements> 1); + /* Regular record and field names */ + testOk1(!dbChannelTest("x")); + testOk1(!dbChannelTest("x.NAME")); - testOk1(!dbChannelFind(&ch, "x.{}")); + /* Long string field modifier */ + testOk1(!dbChannelTest("x.NAME$")); + testOk1(!!(pch = dbChannelCreate("x.NAME$"))); + testOk1(pch->addr.no_elements > 1); + testOk1(!dbChannelDelete(pch)); - testOk1(dbChannelFind(&ch, "y")); - testOk1(dbChannelFind(&ch, "x.{\"none\":null}")); + /* JSON field modifier validation */ + testOk1(!dbChannelTest("x.{\"json\":true}")); + + /* Ensure bad PVs get rejected */ + testOk(dbChannelTest("y"), "Test nonexistent record"); + testOk(dbChannelCreate("y") == NULL, "Create nonexistent record"); + + testOk1(dbChannelTest("x.{not-json}")); + testOk1(!dbChannelCreate("x.{\"none\":null}")); dbRegisterFilter("any", &testIf); + /* Parser event rejection by filter */ e = e_start; - testOk1(dbChannelFind(&ch, "x.{\"any\":null}")); + testOk1(!dbChannelCreate("x.{\"any\":null}")); r = e_start; e = e_start | e_null | e_abort; - testOk1(dbChannelFind(&ch, "x.{\"any\":null}")); + testOk1(!dbChannelCreate("x.{\"any\":null}")); r = e_start | e_null; e = e_start | e_null | e_end; - testOk1(dbChannelFind(&ch, "x.{\"any\":null}")); + testOk1(!dbChannelCreate("x.{\"any\":null}")); + /* Successful parsing... */ r = r_any; e = e_start | e_null | e_end; - testOk1(!dbChannelFind(&ch, "x.{\"any\":null}")); + testOk1(!!(pch = dbChannelCreate("x.{\"any\":null}"))); e = e_close; - testOk1(!dbChannelClose(&ch)); + testOk1(!dbChannelDelete(pch)); dbRegisterFilter("scalar", &testIf); e = e_start | e_null | e_end; - testOk1(!dbChannelFind(&ch, "x.{\"scalar\":null}")); + testOk1(!!(pch = dbChannelCreate("x.{\"scalar\":null}"))); e = e_report; - dbChannelReport(&ch, 0); + dbChannelReport(pch, 0); e = e_close; - testOk1(!dbChannelClose(&ch)); + testOk1(!dbChannelDelete(pch)); e = e_start | e_start_array | e_boolean | e_integer | e_end_array | e_end; - testOk1(!dbChannelFind(&ch, "x.{\"any\":[true,1]}")); + testOk1(!!(pch = dbChannelCreate("x.{\"any\":[true,1]}"))); e = e_close; - testOk1(!dbChannelClose(&ch)); + testOk1(!dbChannelDelete(pch)); e = e_start | e_start_map | e_map_key | e_double | e_string | e_end_map | e_end; - testOk1(!dbChannelFind(&ch, "x.{\"any\":{\"a\":2.7183,\"b\":\"c\"}}")); + testOk1(!!(pch = dbChannelCreate("x.{\"any\":{\"a\":2.7183,\"b\":\"c\"}}"))); + e = e_close; + testOk1(!dbChannelDelete(pch)); + /* More event rejection */ r = r_scalar; - e = e_close | e_start | e_start_array | e_abort; - testOk1(dbChannelFind(&ch, "x.{\"scalar\":[null]}")); - e = 0; - testOk1(!dbChannelClose(&ch)); + e = e_start | e_start_array | e_abort; + testOk1(!dbChannelCreate("x.{\"scalar\":[null]}")); e = e_start | e_start_map | e_abort; - testOk1(dbChannelFind(&ch, "x.{\"scalar\":{}}")); - e = 0; - testOk1(!dbChannelClose(&ch)); + testOk1(!dbChannelCreate("x.{\"scalar\":{}}")); dbFreeBase(pdbbase);