Files
epics-base/src/ioc/db/chfPlugin.c

680 lines
18 KiB
C

/*************************************************************************\
* Copyright (c) 2010 Brookhaven National Laboratory.
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
* für Materialien und Energie GmbH.
* Copyright (c) 2014 ITER Organization.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Author: Ralph Lange <Ralph.Lange@gmx.de>
*/
/* Based on the linkoptions utility by Michael Davidsaver (BNL) */
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#include <dbDefs.h>
#include <dbStaticLib.h>
#include <epicsTypes.h>
#include <epicsStdio.h>
#include <epicsStdlib.h>
#include <epicsString.h>
#include <errlog.h>
#include <shareLib.h>
#define epicsExportSharedSymbols
#include "chfPlugin.h"
/*
* Data for a chfPlugin
*/
typedef struct chfPlugin {
const chfPluginArgDef *opts;
size_t nopts;
epicsUInt32 *required;
const chfPluginIf *pif;
} chfPlugin;
/*
* Parser state data for a chfFilter (chfPlugin instance)
*/
typedef struct chfFilter {
const chfPlugin *plugin;
epicsUInt32 *found;
void *puser;
epicsInt16 nextParam;
} chfFilter;
/* Data types we get from the parser */
typedef enum chfPluginType {
chfPluginTypeBool,
chfPluginTypeInt,
chfPluginTypeDouble,
chfPluginTypeString
} chfPluginType;
/*
* Convert the (epicsInt32) integer value 'val' to the type named in
* 'opt->optType' and store the result at 'user + opt->offset'.
*/
static int
store_integer_value(const chfPluginArgDef *opt, char *user, epicsInt32 val)
{
epicsInt32 *ival;
int *eval;
const chfPluginEnumType *emap;
double *dval;
char *sval;
int ret;
char buff[22]; /* 2^64 = 1.8e+19, so 20 digits plus sign max */
#ifdef DEBUG_CHF
printf("Got an integer for %s (type %d): %ld\n",
opt->name, opt->optType, (long) val);
#endif
if (!opt->convert && opt->optType != chfPluginArgInt32) {
return -1;
}
switch (opt->optType) {
case chfPluginArgInt32:
ival = (epicsInt32 *) ((char *)user + opt->dataOffset);
*ival = val;
break;
case chfPluginArgBoolean:
sval = user + opt->dataOffset;
*sval = !!val;
break;
case chfPluginArgDouble:
dval = (double*) (user + opt->dataOffset);
*dval = val;
break;
case chfPluginArgString:
sval = user + opt->dataOffset;
ret = sprintf(buff, "%ld", (long)val);
if (ret < 0 || (unsigned) ret > opt->size - 1) {
return -1;
}
strncpy(sval, buff, opt->size-1);
sval[opt->size-1]='\0';
break;
case chfPluginArgEnum:
eval = (int*) (user + opt->dataOffset);
for (emap = opt->enums; emap && emap->name; emap++) {
if (val == emap->value) {
*eval = val;
break;
}
}
if (!emap || !emap->name) {
return -1;
}
break;
case chfPluginArgInvalid:
return -1;
}
return 0;
}
/*
* Convert the (int) boolean value 'val' to the type named in 'opt->optType'
* and store the result at 'user + opt->offset'.
*/
static int store_boolean_value(const chfPluginArgDef *opt, char *user, int val)
{
epicsInt32 *ival;
double *dval;
char *sval;
#ifdef DEBUG_CHF
printf("Got a boolean for %s (type %d): %d\n",
opt->name, opt->optType, val);
#endif
if (!opt->convert && opt->optType != chfPluginArgBoolean) {
return -1;
}
switch (opt->optType) {
case chfPluginArgInt32:
ival = (epicsInt32 *) (user + opt->dataOffset);
*ival = val;
break;
case chfPluginArgBoolean:
sval = user + opt->dataOffset;
*sval = val;
break;
case chfPluginArgDouble:
dval = (double*) (user + opt->dataOffset);
*dval = !!val;
break;
case chfPluginArgString:
sval = user + opt->dataOffset;
if ((unsigned) (val ? 4 : 5) > opt->size - 1) {
return -1;
}
strncpy(sval, val ? "true" : "false", opt->size - 1);
sval[opt->size - 1] = '\0';
break;
case chfPluginArgEnum:
case chfPluginArgInvalid:
return -1;
}
return 0;
}
/*
* Convert the double value 'val' to the type named in 'opt->optType'
* and store the result at 'user + opt->offset'.
*/
static int
store_double_value(const chfPluginArgDef *opt, void *vuser, double val)
{
char *user = vuser;
epicsInt32 *ival;
double *dval;
char *sval;
unsigned int i;
#ifdef DEBUG_CHF
printf("Got a double for %s (type %d, convert: %s): %g\n",
opt->name, opt->optType, opt->convert ? "yes" : "no", val);
#endif
if (!opt->convert && opt->optType != chfPluginArgDouble) {
return -1;
}
switch (opt->optType) {
case chfPluginArgInt32:
if (val < INT_MIN || val > INT_MAX) {
return -1;
}
ival = (epicsInt32 *) (user + opt->dataOffset);
*ival = (epicsInt32) val;
break;
case chfPluginArgBoolean:
sval = user + opt->dataOffset;
*sval = !!val;
break;
case chfPluginArgDouble:
dval = (double*) (user + opt->dataOffset);
*dval = val;
break;
case chfPluginArgString:
sval = user + opt->dataOffset;
if (opt->size <= 8) { /* Play it safe: 3 exp + 2 sign + 'e' + '.' */
return -1;
}
i = epicsSnprintf(sval, opt->size, "%.*g", (int) opt->size - 7, val);
if (i < 0 || (unsigned) i >= opt->size) {
return -1;
}
break;
case chfPluginArgEnum:
case chfPluginArgInvalid:
return -1;
}
return 0;
}
/*
* Convert the (char*) string value 'val' to the type named in 'opt->optType'
* and store the result at 'user + opt->offset'.
*/
static int
store_string_value(const chfPluginArgDef *opt, char *user, const char *val,
size_t len)
{
epicsInt32 *ival;
int *eval;
const chfPluginEnumType *emap;
double *dval;
char *sval;
char *end;
size_t i;
#ifdef DEBUG_CHF
printf("Got a string for %s (type %d): %.*s\n",
opt->name, opt->optType, (int) len, val);
#endif
if (!opt->convert && opt->optType != chfPluginArgString &&
opt->optType != chfPluginArgEnum) {
return -1;
}
switch (opt->optType) {
case chfPluginArgInt32:
ival = (epicsInt32 *) (user + opt->dataOffset);
return epicsParseInt32(val, ival, 0, &end);
case chfPluginArgBoolean:
sval = user + opt->dataOffset;
if (epicsStrnCaseCmp(val, "true", len) == 0) {
*sval = 1;
} else if (epicsStrnCaseCmp(val, "false", len) == 0) {
*sval = 0;
} else {
epicsInt8 i8;
if (epicsParseInt8(val, &i8, 0, &end))
return -1;
*sval = !!i8;
}
break;
case chfPluginArgDouble:
dval = (double*) (user + opt->dataOffset);
return epicsParseDouble(val, dval, &end);
case chfPluginArgString:
i = opt->size-1 < len ? opt->size-1 : (int) len;
sval = user + opt->dataOffset;
strncpy(sval, val, i);
sval[i] = '\0';
break;
case chfPluginArgEnum:
eval = (int*) (user + opt->dataOffset);
for (emap = opt->enums; emap && emap->name; emap++) {
if (strncmp(emap->name, val, len) == 0) {
*eval = emap->value;
break;
}
}
if( !emap || !emap->name ) {
return -1;
}
break;
case chfPluginArgInvalid:
return -1;
}
return 0;
}
static void freeInstanceData(chfFilter *f)
{
free(f->found);
free(f); /* FIXME: Use a free-list */
}
/*
* chFilterIf callbacks
*/
/* First entry point when a new filter instance is created.
* All per-instance allocations happen here.
*/
static parse_result parse_start(chFilter *filter)
{
chfPlugin *p = (chfPlugin*) filter->plug->puser;
chfFilter *f;
/* Filter context */
/* FIXME: Use a free-list */
f = calloc(1, sizeof(chfFilter));
if (!f) {
errlogPrintf("chfFilterCtx calloc failed\n");
goto errfctx;
}
f->nextParam = -1;
/* Bit array to find missing required keys */
f->found = calloc( (p->nopts/32)+1, sizeof(epicsUInt32) );
if (!f->found) {
errlogPrintf("chfConfigParseStart: bit array calloc failed\n");
goto errbitarray;
}
/* Call the plugin to allocate its structure, it returns NULL on error */
if (p->pif->allocPvt) {
if ((f->puser = p->pif->allocPvt()) == NULL)
goto errplugin;
}
filter->puser = (void*) f;
return parse_continue;
errplugin:
free(f->found);
errbitarray:
free(f); /* FIXME: Use a free-list */
errfctx:
return parse_stop;
}
static void parse_abort(chFilter *filter) {
chfPlugin *p = (chfPlugin*) filter->plug->puser;
chfFilter *f = (chfFilter*) filter->puser;
/* Call the plugin to tell it we're aborting */
if (p->pif->parse_error) p->pif->parse_error(f->puser);
if (p->pif->freePvt) p->pif->freePvt(f->puser);
freeInstanceData(f);
}
static parse_result parse_end(chFilter *filter)
{
chfPlugin *p = (chfPlugin*) filter->plug->puser;
chfFilter *f = (chfFilter*) filter->puser;
int i;
/* Check if all required arguments were supplied */
for(i = 0; i < (p->nopts/32)+1; i++) {
if ((f->found[i] & p->required[i]) != p->required[i]) {
if (p->pif->parse_error) p->pif->parse_error(f->puser);
if (p->pif->freePvt) p->pif->freePvt(f->puser);
freeInstanceData(f);
return parse_stop;
}
}
/* Call the plugin to tell it we're done */
if (p->pif->parse_ok) {
if (p->pif->parse_ok(f->puser)) {
if (p->pif->freePvt) p->pif->freePvt(f->puser);
freeInstanceData(f);
return parse_stop;
}
}
return parse_continue;
}
static parse_result parse_boolean(chFilter *filter, int boolVal)
{
const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts;
chfFilter *f = (chfFilter*)filter->puser;
if (f->nextParam < 0 ||
store_boolean_value(&opts[f->nextParam], f->puser, boolVal)) {
return parse_stop;
} else {
return parse_continue;
}
}
static parse_result parse_integer(chFilter *filter, long integerVal)
{
const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts;
chfFilter *f = (chfFilter*)filter->puser;
if(sizeof(long)>sizeof(epicsInt32)) {
epicsInt32 temp=integerVal;
if(integerVal !=temp)
return parse_stop;
}
if (f->nextParam < 0 ||
store_integer_value(&opts[f->nextParam], f->puser, integerVal)) {
return parse_stop;
} else {
return parse_continue;
}
}
static parse_result parse_double(chFilter *filter, double doubleVal)
{
const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts;
chfFilter *f = (chfFilter*)filter->puser;
if (f->nextParam < 0 ||
store_double_value(&opts[f->nextParam], f->puser, doubleVal)) {
return parse_stop;
} else {
return parse_continue;
}
}
static parse_result
parse_string(chFilter *filter, const char *stringVal, size_t stringLen)
{
const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts;
chfFilter *f = (chfFilter*)filter->puser;
if (f->nextParam < 0 ||
store_string_value(&opts[f->nextParam], f->puser, stringVal, stringLen)) {
return parse_stop;
} else {
return parse_continue;
}
}
static parse_result parse_start_map(chFilter *filter)
{
return parse_continue;
}
static parse_result
parse_map_key(chFilter *filter, const char *key, size_t stringLen)
{
const chfPluginArgDef *cur;
const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts;
chfFilter *f = (chfFilter*)filter->puser;
int *tag;
int i;
int j;
f->nextParam = -1;
for(cur = opts, i = 0; cur && cur->name; cur++, i++) {
if (strncmp(key, cur->name, stringLen) == 0) {
f->nextParam = i;
break;
}
}
if (f->nextParam == -1) {
return parse_stop;
}
f->found[i/32] |= 1<<(i%32);
/* For tagged parameters:
* set tag to this choice, and mark all other choices as found */
if (opts[i].tagged) {
tag = (int*) ((char*) f->puser + opts[i].tagOffset);
*tag = opts[i].choice;
for (cur = opts, j = 0; cur && cur->name; cur++, j++) {
if (cur->tagged && cur->tagOffset == opts[i].tagOffset) {
f->found[j/32] |= 1<<(j%32);
}
}
}
return parse_continue;
}
static parse_result parse_end_map(chFilter *filter)
{
return parse_continue;
}
static long channel_open(chFilter *filter)
{
chfPlugin *p = (chfPlugin*) filter->plug->puser;
chfFilter *f = (chfFilter*) filter->puser;
if (p->pif->channel_open)
return p->pif->channel_open(filter->chan, f->puser);
else
return 0;
}
static void
channel_register_pre(chFilter *filter, chPostEventFunc **cb_out,
void **arg_out, db_field_log *probe)
{
chfPlugin *p = (chfPlugin*) filter->plug->puser;
chfFilter *f = (chfFilter*) filter->puser;
if (p->pif->channelRegisterPre)
p->pif->channelRegisterPre(filter->chan, f->puser, cb_out, arg_out,
probe);
}
static void
channel_register_post(chFilter *filter, chPostEventFunc **cb_out,
void **arg_out, db_field_log *probe)
{
chfPlugin *p = (chfPlugin*) filter->plug->puser;
chfFilter *f = (chfFilter*) filter->puser;
if (p->pif->channelRegisterPost)
p->pif->channelRegisterPost(filter->chan, f->puser, cb_out, arg_out,
probe);
}
static void channel_report(chFilter *filter, int level,
const unsigned short indent)
{
chfPlugin *p = (chfPlugin*) filter->plug->puser;
chfFilter *f = (chfFilter*) filter->puser;
if (p->pif->channel_report)
p->pif->channel_report(filter->chan, f->puser, level, indent);
}
static void channel_close(chFilter *filter)
{
chfPlugin *p = (chfPlugin*) filter->plug->puser;
chfFilter *f = (chfFilter*) filter->puser;
if (p->pif->channel_close) p->pif->channel_close(filter->chan, f->puser);
if (p->pif->freePvt) p->pif->freePvt(f->puser);
free(f->found);
free(f); /* FIXME: Use a free-list */
}
static void plugin_free(void* puser)
{
chfPlugin *p=puser;
free(p->required);
free(p);
}
/*
* chFilterIf for the wrapper
* we just support a simple one-level map, and no arrays
*/
static chFilterIf wrapper_fif = {
plugin_free,
parse_start,
parse_abort,
parse_end,
NULL, /* parse_null, */
parse_boolean,
parse_integer,
parse_double,
parse_string,
parse_start_map,
parse_map_key,
parse_end_map,
NULL, /* parse_start_array, */
NULL, /* parse_end_array, */
channel_open,
channel_register_pre,
channel_register_post,
channel_report,
channel_close
};
const char*
chfPluginEnumString(const chfPluginEnumType *emap, int i, const char* def)
{
for(; emap && emap->name; emap++) {
if ( i == emap->value ) {
return emap->name;
}
}
return def;
}
int
chfPluginRegister(const char* key, const chfPluginIf *pif,
const chfPluginArgDef* opts)
{
chfPlugin *p;
size_t i;
const chfPluginArgDef *cur;
epicsUInt32 *reqd;
/* Check and count options */
for (i = 0, cur = opts; cur && cur->name; i++, cur++) {
switch(cur->optType) {
case chfPluginArgInt32:
if (cur->size < sizeof(epicsInt32)) {
errlogPrintf("Plugin %s: %d bytes too small for epicsInt32 %s\n",
key, cur->size, cur->name);
return -1;
}
break;
case chfPluginArgBoolean:
if (cur->size < 1) {
errlogPrintf("Plugin %s: %d bytes too small for boolean %s\n",
key, cur->size, cur->name);
return -1;
}
break;
case chfPluginArgDouble:
if (cur->size < sizeof(double)) {
errlogPrintf("Plugin %s: %d bytes too small for double %s\n",
key, cur->size, cur->name);
return -1;
}
break;
case chfPluginArgString:
if (cur->size < sizeof(char*)) {
/* Catch if someone has given us a char* instead of a char[]
* Also means that char buffers must be >=4.
*/
errlogPrintf("Plugin %s: %d bytes too small for string %s\n",
key, cur->size, cur->name);
return -1;
}
break;
case chfPluginArgEnum:
if (cur->size < sizeof(int)) {
errlogPrintf("Plugin %s: %d bytes too small for enum %s\n",
key, cur->size, cur->name);
return -1;
}
break;
case chfPluginArgInvalid:
errlogPrintf("Plugin %s: storage type for %s is not defined\n",
key, cur->name);
return -1;
break;
}
}
/* Bit array used to find missing required keys */
reqd = dbCalloc((i/32)+1, sizeof(epicsUInt32));
if (!reqd) {
errlogPrintf("Plugin %s: bit array calloc failed\n", key);
return -1;
}
for (i = 0, cur = opts; cur && cur->name; i++, cur++) {
if (cur->required) reqd[i/32] |= 1 << (i%32);
}
/* Plugin data */
p = dbCalloc(1, sizeof(chfPlugin));
p->pif = pif;
p->opts = opts;
p->nopts = i;
p->required = reqd;
dbRegisterFilter(key, &wrapper_fif, p);
return 0;
}