This refactor simplifies and streamlines the code associated with server side filters. Apart from immediate benefits (clearer code, less duplication) it is also hoped that this will make it easier to add write filters. The data pointer dbfl_ref.field can now either point to a copy owned by a filter, or it can point to the original data owned by a record. In the latter case, the dbfl_ref.dtor is NULL. The dbExtractArray* functions are unified to the single function dbExtractArray and stripped of conversion functionality. This is redundant because we always call dbGet after applying filters, which takes care of conversion. Accordingly, dbChannelMakeArrayCopy is now obsolete and its single use (in the ts filter) replaced with dbExtractArray. Instead, we add the helper function dbChannelGetArrayInfo to wrap the common boilerplate around calls to the get_array_info method, used in both arr.c and ts.c.
782 lines
21 KiB
C
782 lines
21 KiB
C
/*************************************************************************\
|
|
* 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 <anj@aps.anl.gov>
|
|
* Ralph Lange <Ralph.Lange@bessy.de>
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#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"
|
|
|
|
#define epicsExportSharedSymbols
|
|
#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 */
|
|
probe.type = dbfl_type_val;
|
|
probe.ctx = dbfl_context_read;
|
|
probe.field_type = dbChannelExportType(chan);
|
|
probe.no_elements = dbChannelElements(chan);
|
|
probe.field_size = dbChannelFieldSize(chan);
|
|
probe.sevr = NO_ALARM;
|
|
probe.stat = NO_ALARM;
|
|
probe.time.secPastEpoch = 0;
|
|
probe.time.nsec = 0;
|
|
|
|
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;
|
|
}
|