/*************************************************************************\ * Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne * National Laboratory. * Copyright (c) 2010 Brookhaven National Laboratory. * Copyright (c) 2010 Helmholtz-Zentrum Berlin * fuer Materialien und Energie GmbH. * SPDX-License-Identifier: EPICS * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ /* * Author: Andrew Johnson * Ralph Lange */ #include #include #include #define EPICS_PRIVATE_API #include "cantProceed.h" #include "epicsAssert.h" #include "epicsString.h" #include "epicsStdio.h" #include "errlog.h" #include "freeList.h" #include "gpHash.h" #include "yajl_parse.h" #include "dbAccessDefs.h" #include "dbBase.h" #include "dbChannel.h" #include "dbCommon.h" #include "dbEvent.h" #include "dbLock.h" #include "dbStaticLib.h" #include "link.h" #include "recSup.h" #include "special.h" #include "alarm.h" typedef struct parseContext { dbChannel *chan; chFilter *filter; int depth; } parseContext; #define CALLIF(rtn) !rtn ? parse_stop : rtn static void *dbChannelFreeList; static void *chFilterFreeList; void dbChannelExit(void) { freeListCleanup(dbChannelFreeList); freeListCleanup(chFilterFreeList); dbChannelFreeList = chFilterFreeList = NULL; } void dbChannelInit (void) { if(dbChannelFreeList) return; freeListInitPvt(&dbChannelFreeList, sizeof(dbChannel), 128); freeListInitPvt(&chFilterFreeList, sizeof(chFilter), 64); db_init_event_freelists(); } static void chf_value(parseContext *parser, parse_result *presult) { chFilter *filter = parser->filter; if (*presult == parse_stop || parser->depth > 0) return; parser->filter = NULL; if (filter->plug->fif->parse_end(filter) == parse_continue) { ellAdd(&parser->chan->filters, &filter->list_node); } else { freeListFree(chFilterFreeList, filter); *presult = parse_stop; } } static int chf_null(void * ctx) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; parse_result result; assert(filter); result = CALLIF(filter->plug->fif->parse_null)(filter ); chf_value(parser, &result); return result; } static int chf_boolean(void * ctx, int boolVal) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; parse_result result; assert(filter); result = CALLIF(filter->plug->fif->parse_boolean)(filter , boolVal); chf_value(parser, &result); return result; } static int chf_integer(void * ctx, long long integerVal) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; parse_result result; assert(filter); result = CALLIF(filter->plug->fif->parse_integer)(filter , integerVal); chf_value(parser, &result); return result; } static int chf_double(void * ctx, double doubleVal) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; parse_result result; assert(filter); result = CALLIF(filter->plug->fif->parse_double)(filter , doubleVal); chf_value(parser, &result); return result; } static int chf_string(void * ctx, const unsigned char * stringVal, size_t stringLen) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; parse_result result; assert(filter); result = CALLIF(filter->plug->fif->parse_string)(filter , (const char *) stringVal, stringLen); chf_value(parser, &result); return result; } static int chf_start_map(void * ctx) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; if (!filter) { assert(parser->depth == 0); return parse_continue; /* Opening '{' */ } ++parser->depth; return CALLIF(filter->plug->fif->parse_start_map)(filter ); } static int chf_map_key(void * ctx, const unsigned char * key, size_t stringLen) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; const chFilterPlugin *plug; parse_result result; if (filter) { assert(parser->depth > 0); return CALLIF(filter->plug->fif->parse_map_key)(filter , (const char *) key, stringLen); } assert(parser->depth == 0); plug = dbFindFilter((const char *) key, stringLen); if (!plug) { errlogPrintf("dbChannelCreate: Channel filter '%.*s' not found\n", (int) stringLen, key); return parse_stop; } filter = freeListCalloc(chFilterFreeList); if (!filter) { errlogPrintf("dbChannelCreate: Out of memory\n"); return parse_stop; } filter->chan = parser->chan; filter->plug = plug; filter->puser = NULL; result = plug->fif->parse_start(filter); if (result == parse_continue) { parser->filter = filter; } else { freeListFree(chFilterFreeList, filter); } return result; } static int chf_end_map(void * ctx) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; parse_result result; if (!filter) { assert(parser->depth == 0); return parse_continue; /* Final closing '}' */ } assert(parser->depth > 0); result = CALLIF(filter->plug->fif->parse_end_map)(filter ); --parser->depth; chf_value(parser, &result); return result; } static int chf_start_array(void * ctx) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; assert(filter); ++parser->depth; return CALLIF(filter->plug->fif->parse_start_array)(filter ); } static int chf_end_array(void * ctx) { parseContext *parser = (parseContext *) ctx; chFilter *filter = parser->filter; parse_result result; assert(filter); result = CALLIF(filter->plug->fif->parse_end_array)(filter ); --parser->depth; chf_value(parser, &result); return result; } static const yajl_callbacks chf_callbacks = { chf_null, chf_boolean, chf_integer, chf_double, NULL, chf_string, chf_start_map, chf_map_key, chf_end_map, chf_start_array, chf_end_array }; static void * chf_malloc(void *ctx, size_t sz) { return malloc(sz); } static void * chf_realloc(void *ctx, void *ptr, size_t sz) { return realloc(ptr, sz); } static void chf_free(void *ctx, void *ptr) { free(ptr); } static yajl_alloc_funcs chf_alloc = { chf_malloc, chf_realloc, chf_free }; static long chf_parse(dbChannel *chan, const char **pjson) { parseContext parser = { chan, NULL, 0 }; yajl_handle yh = yajl_alloc(&chf_callbacks, &chf_alloc, &parser); const char *json = *pjson; size_t jlen = strlen(json), ylen; yajl_status ys; long status; if (!yh) return S_db_noMemory; ys = yajl_parse(yh, (const unsigned char *) json, jlen); ylen = yajl_get_bytes_consumed(yh); if (ys == yajl_status_ok) ys = yajl_complete_parse(yh); switch (ys) { case yajl_status_ok: *pjson += ylen; status = 0; break; case yajl_status_error: { unsigned char *err; err = yajl_get_error(yh, 1, (const unsigned char *) json, jlen); printf("dbChannelCreate: %s\n", err); yajl_free_error(yh, err); } /* fall through */ default: status = S_db_notFound; } if (parser.filter) { assert(status); parser.filter->plug->fif->parse_abort(parser.filter); freeListFree(chFilterFreeList, parser.filter); } yajl_free(yh); 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 *name) { DBENTRY dbEntry; long status; if (!name || !*name || !pdbbase) return S_db_notFound; status = pvNameLookup(&dbEntry, &name); dbFinishEntry(&dbEntry); return status; } #define TRY(Func, Arg) \ if (Func) { \ result = Func Arg; \ if (result != parse_continue) goto failure; \ } static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppnext) { epicsInt32 start = 0; epicsInt32 end = -1; epicsInt32 incr = 1; epicsInt32 l; char *pnext; ptrdiff_t exist; chFilter *filter; const chFilterPlugin *plug; parse_result result; long status = 0; /* If no number is present, strtol() returns 0 and sets pnext=pname, else pnext points to the first char after the number */ pname++; l = strtol(pname, &pnext, 0); exist = pnext - pname; if (exist) start = l; pname = pnext; if (*pname == ']' && exist) { end = start; goto insertplug; } if (*pname != ':') { status = S_dbLib_fieldNotFound; goto finish; } pname++; l = strtol(pname, &pnext, 0); exist = pnext - pname; pname = pnext; if (*pname == ']') { if (exist) end = l; goto insertplug; } if (exist) incr = l; if (*pname != ':') { status = S_dbLib_fieldNotFound; goto finish; } pname++; l = strtol(pname, &pnext, 0); exist = pnext - pname; if (exist) end = l; pname = pnext; if (*pname != ']') { status = S_dbLib_fieldNotFound; goto finish; } insertplug: pname++; *ppnext = pname; plug = dbFindFilter("arr", 3); if (!plug) { status = S_dbLib_fieldNotFound; goto finish; } filter = freeListCalloc(chFilterFreeList); if (!filter) { status = S_db_noMemory; goto finish; } filter->chan = chan; filter->plug = plug; filter->puser = NULL; TRY(filter->plug->fif->parse_start, (filter)); TRY(filter->plug->fif->parse_start_map, (filter)); if (start != 0) { TRY(filter->plug->fif->parse_map_key, (filter, "s", 1)); TRY(filter->plug->fif->parse_integer, (filter, start)); } if (incr != 1) { TRY(filter->plug->fif->parse_map_key, (filter, "i", 1)); TRY(filter->plug->fif->parse_integer, (filter, incr)); } if (end != -1) { TRY(filter->plug->fif->parse_map_key, (filter, "e", 1)); TRY(filter->plug->fif->parse_integer, (filter, end)); } TRY(filter->plug->fif->parse_end_map, (filter)); TRY(filter->plug->fif->parse_end, (filter)); ellAdd(&chan->filters, &filter->list_node); return 0; failure: freeListFree(chFilterFreeList, filter); status = S_dbLib_fieldNotFound; finish: return status; } dbChannel * dbChannelCreate(const char *name) { const char *pname = name; DBENTRY dbEntry; dbChannel *chan = NULL; char *cname; dbAddr *paddr; long status; if (!name || !*name || !pdbbase) return NULL; status = pvNameLookup(&dbEntry, &pname); if (status) goto finish; chan = freeListCalloc(dbChannelFreeList); if (!chan) goto finish; cname = malloc(strlen(name) + 1); if (!cname) goto finish; strcpy(cname, name); chan->name = cname; ellInit(&chan->filters); ellInit(&chan->pre_chain); ellInit(&chan->post_chain); paddr = &chan->addr; status = dbEntryToAddr(&dbEntry, paddr); if (status) goto finish; /* Handle field modifiers */ if (*pname) { short dbfType = paddr->field_type; if (*pname == '$') { /* Some field types can be accessed as char arrays */ if (dbfType == DBF_STRING) { paddr->no_elements = paddr->field_size; paddr->field_type = DBF_CHAR; paddr->field_size = 1; paddr->dbr_field_type = DBR_CHAR; } else if (dbfType >= DBF_INLINK && dbfType <= DBF_FWDLINK) { /* Clients see a char array, but keep original dbfType */ paddr->no_elements = PVLINK_STRINGSZ; paddr->field_size = 1; paddr->dbr_field_type = DBR_CHAR; } else { status = S_dbLib_fieldNotFound; goto finish; } pname++; } if (*pname == '[') { status = parseArrayRange(chan, pname, &pname); if (status) goto finish; } /* JSON may follow */ if (*pname == '{') { status = chf_parse(chan, &pname); if (status) goto finish; } /* Make sure there's nothing else */ if (*pname) { status = S_dbLib_fieldNotFound; goto finish; } } finish: if (status && chan) { dbChannelDelete(chan); chan = NULL; } dbFinishEntry(&dbEntry); return chan; } db_field_log* dbChannelRunPreChain(dbChannel *chan, db_field_log *pLogIn) { chFilter *filter; ELLNODE *node; db_field_log *pLog = pLogIn; for (node = ellFirst(&chan->pre_chain); node && pLog; node = ellNext(node)) { filter = CONTAINER(node, chFilter, pre_node); pLog = filter->pre_func(filter->pre_arg, chan, pLog); } return pLog; } db_field_log* dbChannelRunPostChain(dbChannel *chan, db_field_log *pLogIn) { chFilter *filter; ELLNODE *node; db_field_log *pLog = pLogIn; for (node = ellFirst(&chan->post_chain); node && pLog; node = ellNext(node)) { filter = CONTAINER(node, chFilter, post_node); pLog = filter->post_func(filter->post_arg, chan, pLog); } return pLog; } long dbChannelOpen(dbChannel *chan) { chFilter *filter; chPostEventFunc *func; void *arg; long status; ELLNODE *node; db_field_log probe; db_field_log p; for (node = ellFirst(&chan->filters); node; node = ellNext(node)) { filter = CONTAINER(node, chFilter, list_node); /* Call channel_open */ status = 0; if (filter->plug->fif->channel_open) status = filter->plug->fif->channel_open(filter); if (status) return status; } /* Set up type probe */ memset(&probe, 0, sizeof(probe)); probe.field_type = dbChannelExportType(chan); probe.no_elements = dbChannelElements(chan); probe.field_size = dbChannelFieldSize(chan); p = probe; /* * Build up the pre- and post-event-queue filter chains * Separate loops because the probe must reach the filters in the right order. */ for (node = ellFirst(&chan->filters); node; node = ellNext(node)) { filter = CONTAINER(node, chFilter, list_node); func = NULL; arg = NULL; if (filter->plug->fif->channel_register_pre) { filter->plug->fif->channel_register_pre(filter, &func, &arg, &p); if (func) { ellAdd(&chan->pre_chain, &filter->pre_node); filter->pre_func = func; filter->pre_arg = arg; probe = p; } } } for (node = ellFirst(&chan->filters); node; node = ellNext(node)) { filter = CONTAINER(node, chFilter, list_node); func = NULL; arg = NULL; if (filter->plug->fif->channel_register_post) { filter->plug->fif->channel_register_post(filter, &func, &arg, &p); if (func) { ellAdd(&chan->post_chain, &filter->post_node); filter->post_func = func; filter->post_arg = arg; probe = p; } } } /* Save probe results */ chan->final_no_elements = probe.no_elements; chan->final_field_size = probe.field_size; chan->final_type = probe.field_type; return 0; } /* Only use dbChannelGet() if the record is already locked. */ long dbChannelGet(dbChannel *chan, short type, void *pbuffer, long *options, long *nRequest, void *pfl) { return dbGet(&chan->addr, type, pbuffer, options, nRequest, pfl); } long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer, long *options, long *nRequest, void *pfl) { dbCommon *precord = chan->addr.precord; long status = 0; dbScanLock(precord); status = dbChannelGet(chan, dbrType, pbuffer, options, nRequest, pfl); dbScanUnlock(precord); return status; } /* Only use dbChannelPut() if the record is already locked. * This routine doesn't work on link fields, ignores DISP, and * doesn't trigger record processing on PROC or pp(TRUE). */ long dbChannelPut(dbChannel *chan, short type, const void *pbuffer, long nRequest) { return dbPut(&chan->addr, type, pbuffer, nRequest); } long dbChannelPutField(dbChannel *chan, short type, const void *pbuffer, long nRequest) { return dbPutField(&chan->addr, type, pbuffer, nRequest); } void dbChannelShow(dbChannel *chan, int level, const unsigned short indent) { long elems = chan->addr.no_elements; long felems = chan->final_no_elements; int count = ellCount(&chan->filters); int pre = ellCount(&chan->pre_chain); int post = ellCount(&chan->post_chain); printf("%*sChannel: '%s'\n", indent, "", chan->name); if (level > 0) { printf("%*sfield_type=%s (%d bytes), dbr_type=%s, %ld element%s", indent + 4, "", dbGetFieldTypeString(chan->addr.field_type), chan->addr.field_size, dbGetFieldTypeString(chan->addr.dbr_field_type), elems, elems == 1 ? "" : "s"); if (count) printf("\n%*s%d filter%s (%d pre eventq, %d post eventq)\n", indent + 4, "", count, count == 1 ? "" : "s", pre, post); else printf(", no filters\n"); if (level > 1) dbChannelFilterShow(chan, level - 2, indent + 8); if (count) { printf("%*sfinal field_type=%s (%dB), %ld element%s\n", indent + 4, "", dbGetFieldTypeString(chan->final_type), chan->final_field_size, felems, felems == 1 ? "" : "s"); } } } void dbChannelFilterShow(dbChannel *chan, int level, const unsigned short indent) { chFilter *filter = (chFilter *) ellFirst(&chan->filters); while (filter) { filter->plug->fif->channel_report(filter, level, indent); filter = (chFilter *) ellNext(&filter->list_node); } } void dbChannelDelete(dbChannel *chan) { chFilter *filter; /* Close filters in reverse order */ while ((filter = (chFilter *) ellPop(&chan->filters))) { filter->plug->fif->channel_close(filter); freeListFree(chFilterFreeList, filter); } free((char *) chan->name); freeListFree(dbChannelFreeList, chan); } /* * Helper function to adjust no_elements, offset, and pfield * when copying an array from a record. */ void dbChannelGetArrayInfo(dbChannel *chan, void **pfield, long *no_elements, long *offset) { rset *prset; if (dbChannelSpecial(chan) == SPC_DBADDR && (prset = dbGetRset(&chan->addr)) && prset->get_array_info) { void *pfieldsave = dbChannelField(chan); /* it is expected that this call always succeeds */ prset->get_array_info(&chan->addr, no_elements, offset); *pfield = dbChannelField(chan); dbChannelField(chan) = pfieldsave; } } /* FIXME: Do these belong in a different file? */ void dbRegisterFilter(const char *name, const chFilterIf *fif, void *puser) { GPHENTRY *pgph; chFilterPlugin *pfilt; if (!pdbbase) { printf("dbRegisterFilter: pdbbase not set!\n"); return; } pgph = gphFind(pdbbase->pgpHash, name, &pdbbase->filterList); if (pgph) return; pfilt = dbCalloc(1, sizeof(chFilterPlugin)); pfilt->name = epicsStrDup(name); pfilt->fif = fif; pfilt->puser = puser; ellAdd(&pdbbase->filterList, &pfilt->node); pgph = gphAdd(pdbbase->pgpHash, pfilt->name, &pdbbase->filterList); if (!pgph) { free((void *) pfilt->name); free(pfilt); printf("dbRegisterFilter: gphAdd failed\n"); return; } pgph->userPvt = pfilt; } const chFilterPlugin * dbFindFilter(const char *name, size_t len) { GPHENTRY *pgph = gphFindParse(pdbbase->pgpHash, name, len, &pdbbase->filterList); if (!pgph) return NULL; return (chFilterPlugin *) pgph->userPvt; }