diff --git a/src/ioc/db/Makefile b/src/ioc/db/Makefile index 0c874ccff..e758b6a5a 100644 --- a/src/ioc/db/Makefile +++ b/src/ioc/db/Makefile @@ -25,6 +25,7 @@ INC += dbConvertJSON.h INC += dbDbLink.h INC += dbExtractArray.h INC += dbEvent.h +INC += dbJLink.h INC += dbLink.h INC += dbLock.h INC += dbNotify.h @@ -73,6 +74,7 @@ dbCore_SRCS += dbConvertJSON.c dbCore_SRCS += dbDbLink.c dbCore_SRCS += dbFastLinkConv.c dbCore_SRCS += dbExtractArray.c +dbCore_SRCS += dbJLink.c dbCore_SRCS += dbLink.c dbCore_SRCS += dbNotify.c dbCore_SRCS += dbScan.c diff --git a/src/ioc/db/dbConstLink.c b/src/ioc/db/dbConstLink.c index a969addf3..821fc58da 100644 --- a/src/ioc/db/dbConstLink.c +++ b/src/ioc/db/dbConstLink.c @@ -13,6 +13,7 @@ */ #include +#include #include "dbDefs.h" @@ -46,28 +47,41 @@ void dbConstAddLink(struct link *plink) static long dbConstLoadScalar(struct link *plink, short dbrType, void *pbuffer) { - if (!plink->value.constantStr) - return S_db_badField; + const char *pstr = plink->value.constantStr; + size_t len; - /* Constant scalars are always numeric */ + if (!pstr) + return S_db_badField; + len = strlen(pstr); + + /* Choice values must be numeric */ if (dbrType == DBF_MENU || dbrType == DBF_ENUM || dbrType == DBF_DEVICE) dbrType = DBF_USHORT; + if (*pstr == '[' && pstr[len-1] == ']') { + /* Convert from JSON array */ + long nReq = 1; + + return dbPutConvertJSON(pstr, dbrType, pbuffer, &nReq); + } + return dbFastPutConvertRoutine[DBR_STRING][dbrType] - (plink->value.constantStr, pbuffer, NULL); + (pstr, pbuffer, NULL); } static long dbConstLoadArray(struct link *plink, short dbrType, void *pbuffer, long *pnReq) { - if (!plink->value.constantStr) + const char *pstr = plink->value.constantStr; + + if (!pstr) return S_db_badField; - /* No support for arrays of choice types */ + /* Choice values must be numeric */ if (dbrType == DBF_MENU || dbrType == DBF_ENUM || dbrType == DBF_DEVICE) - return S_db_badField; + dbrType = DBF_USHORT; - return dbPutConvertJSON(plink->value.constantStr, dbrType, pbuffer, pnReq); + return dbPutConvertJSON(pstr, dbrType, pbuffer, pnReq); } static long dbConstGetNelements(const struct link *plink, long *nelements) diff --git a/src/ioc/db/dbConvertJSON.c b/src/ioc/db/dbConvertJSON.c index cd7b7ca44..14a48866f 100644 --- a/src/ioc/db/dbConvertJSON.c +++ b/src/ioc/db/dbConvertJSON.c @@ -10,6 +10,7 @@ #include #include "dbDefs.h" +#include "errlog.h" #include "yajl_alloc.h" #include "yajl_parse.h" @@ -23,7 +24,7 @@ typedef struct parseContext { int depth; short dbrType; short dbrSize; - void *pdest; + char *pdest; int elems; } parseContext; @@ -67,8 +68,10 @@ static int dbcj_string(void *ctx, const unsigned char *val, unsigned int len) { /* Not attempting to handle char-array fields here, they need more * metadata about the field than we have available at the moment. */ - if (parser->dbrType != DBF_STRING) + if (parser->dbrType != DBF_STRING) { + errlogPrintf("dbPutConvertJSON: String provided, numeric value(s) expected\n"); return 0; /* Illegal */ + } if (parser->elems > 0) { if (len > parser->dbrSize - 1) @@ -82,6 +85,7 @@ static int dbcj_string(void *ctx, const unsigned char *val, unsigned int len) { } static int dbcj_start_map(void *ctx) { + errlogPrintf("dbPutConvertJSON: Map type not supported\n"); return 0; /* Illegal */ } @@ -96,7 +100,9 @@ static int dbcj_end_map(void *ctx) { static int dbcj_start_array(void *ctx) { parseContext *parser = (parseContext *) ctx; - parser->depth++; + if (++parser->depth > 1) + errlogPrintf("dbPutConvertJSON: Embedded arrays not supported\n"); + return (parser->depth == 1); } diff --git a/src/ioc/db/dbJLink.c b/src/ioc/db/dbJLink.c new file mode 100644 index 000000000..ed18a2894 --- /dev/null +++ b/src/ioc/db/dbJLink.c @@ -0,0 +1,370 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbJLink.c */ + +#include +#include + +#include "epicsAssert.h" +#include "errlog.h" +#include "yajl_alloc.h" +#include "yajl_parse.h" + +#define epicsExportSharedSybols +#include "dbAccessDefs.h" +#include "dbCommon.h" +#include "dbJLink.h" +#include "dbStaticLib.h" +#include "link.h" + +/* Change 'undef' to 'define' to turn on debug statements: */ +#undef DEBUG_JLINK + +#ifdef DEBUG_JLINK + int jlinkDebug = 10; +# define IFDEBUG(n) \ + if (jlinkDebug >= n) /* block or statement */ +#else +# define IFDEBUG(n) \ + if(0) /* Compiler will elide the block or statement */ +#endif + + +typedef struct parseContext { + jlink *pjlink; + jlink *product; + struct link *plink; + short dbfType; + short jsonDepth; + short linkDepth; + unsigned key_is_link:1; +} parseContext; + +#define CALLIF(routine) !routine ? jlif_stop : routine + + +static int dbjl_value(parseContext *parser, jlif_result result) { + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) { + printf("dbjl_value(%s@%p, %d)\n", pjlink->pif->name, pjlink, result); + printf(" jsonDepth=%d, linkDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->linkDepth, parser->key_is_link); + } + + if (result == jlif_stop || parser->linkDepth > 0) + return result; + + parser->product = pjlink; + parser->key_is_link = 0; + parser->pjlink = pjlink->parent; + + IFDEBUG(8) + printf("dbjl_value: product = %p\n", pjlink); + + return pjlink->pif->end_parse ? pjlink->pif->end_parse(pjlink) + : jlif_continue; +} + +static int dbjl_null(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) + printf("dbjl_null(%s@%p)\n", pjlink->pif->name, pjlink); + + assert(pjlink); + return dbjl_value(parser, CALLIF(pjlink->pif->parse_null)(pjlink)); +} + +static int dbjl_boolean(void *ctx, int val) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + assert(pjlink); + return dbjl_value(parser, CALLIF(pjlink->pif->parse_boolean)(pjlink, val)); +} + +static int dbjl_integer(void *ctx, long num) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) + printf("dbjl_integer(%s@%p, %ld)\n", pjlink->pif->name, pjlink, num); + + assert(pjlink); + return dbjl_value(parser, CALLIF(pjlink->pif->parse_integer)(pjlink, num)); +} + +static int dbjl_double(void *ctx, double num) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) + printf("dbjl_double(%s@%p, %g)\n", pjlink->pif->name, pjlink, num); + + assert(pjlink); + return dbjl_value(parser, CALLIF(pjlink->pif->parse_double)(pjlink, num)); +} + +static int dbjl_string(void *ctx, const unsigned char *val, unsigned len) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) + printf("dbjl_string(%s@%p, \"%.*s\")\n", pjlink->pif->name, pjlink, len, val); + + assert(pjlink); + return dbjl_value(parser, + CALLIF(pjlink->pif->parse_string)(pjlink, (const char *) val, len)); +} + +static int dbjl_start_map(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + jlif_key_result result; + + if (!pjlink) { + IFDEBUG(10) { + printf("dbjl_start_map(NULL)\n"); + printf(" jsonDepth=%d, linkDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->linkDepth, parser->key_is_link); + } + + assert(parser->jsonDepth == 0); + parser->jsonDepth++; + parser->key_is_link = 1; + return jlif_continue; /* Opening '{' */ + } + + IFDEBUG(10) { + printf("dbjl_start_map(%s@%p)\n", pjlink->pif->name, pjlink); + printf(" jsonDepth=%d, linkDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->linkDepth, parser->key_is_link); + } + + parser->linkDepth++; + parser->jsonDepth++; + result = CALLIF(pjlink->pif->parse_start_map)(pjlink); + if (result == jlif_key_embed_link) { + parser->key_is_link = 1; + result = jlif_continue; + } + + IFDEBUG(10) + printf("dbjl_start_map -> %d\n", result); + + return result; +} + +static int dbjl_map_key(void *ctx, const unsigned char *key, unsigned len) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + char link_name[MAX_LINK_NAME + 1]; + size_t lnlen = len; + linkSup *linkSup; + jlif *pjlif; + jlif_result result; + + if (!parser->key_is_link) { + if (!pjlink) { + errlogPrintf("dbJLinkInit: Illegal second link key '%.*s' seen for %s\n", + len, key, parser->plink->precord->name); + return jlif_stop; + } + + IFDEBUG(10) { + printf("dbjl_map_key(%s@%p, \"%.*s\")\n", + pjlink->pif->name, pjlink, len, key); + printf(" jsonDepth=%d, linkDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->linkDepth, parser->key_is_link); + } + + assert(parser->linkDepth > 0); + return CALLIF(pjlink->pif->parse_map_key)(pjlink, (const char *) key, len); + } + + IFDEBUG(10) { + printf("dbjl_map_key(NULL, \"%.*s\")\n", len, key); + printf(" jsonDepth=%d, linkDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->linkDepth, parser->key_is_link); + } + + if (lnlen > MAX_LINK_NAME) + lnlen = MAX_LINK_NAME; + strncpy(link_name, (const char *) key, lnlen); + link_name[lnlen] = '\0'; + + linkSup = dbFindLinkSup(pdbbase, link_name); + if (!linkSup) { + errlogPrintf("dbJLinkInit: Link type '%s' not found for %s\n", + link_name, parser->plink->precord->name); + return jlif_stop; + } + + pjlif = linkSup->pjlif; + if (!pjlif) { + errlogPrintf("dbJLinkInit: Support for Link type '%s' not loaded\n", + link_name); + return jlif_stop; + } + + pjlink = pjlif->alloc_jlink(parser->plink); + if (!pjlink) { + errlogPrintf("dbJLinkInit: Out of memory\n"); + return jlif_stop; + } + pjlink->pif = pjlif; + + if (parser->pjlink) { + /* This is an embedded link */ + pjlink->parent = parser->pjlink; + } + + result = pjlif->start_parse ? pjlif->start_parse(pjlink) : jlif_continue; + if (result == jlif_continue) { + parser->pjlink = pjlink; + + IFDEBUG(8) + printf("dbjl_map_key: New %s@%p\n", pjlink->pif->name, pjlink); + } + else { + pjlif->free_jlink(pjlink); + } + // FIXME Ensure link map has only one link key... + + IFDEBUG(10) + printf("dbjl_map_key -> %d\n", result); + + return result; +} + +static int dbjl_end_map(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + jlif_result result; + + IFDEBUG(10) { + printf("dbjl_end_map(%s@%p)\n", + pjlink ? pjlink->pif->name : "NULL", pjlink); + printf(" jsonDepth=%d, linkDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->linkDepth, parser->key_is_link); + } + + parser->jsonDepth--; + if (parser->linkDepth > 0) { + parser->linkDepth--; + + result = dbjl_value(parser, CALLIF(pjlink->pif->parse_end_map)(pjlink)); + } + else { + result = jlif_continue; + } + return result; +} + +static int dbjl_start_array(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) { + printf("dbjl_start_array(%s@%p)\n", pjlink->pif->name, pjlink); + printf(" jsonDepth=%d, linkDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->linkDepth, parser->key_is_link); + } + + assert(pjlink); + parser->linkDepth++; + parser->jsonDepth++; + return CALLIF(pjlink->pif->parse_start_array)(pjlink); +} + +static int dbjl_end_array(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) { + printf("dbjl_end_array(%s@%p)\n", pjlink->pif->name, pjlink); + printf(" jsonDepth=%d, linkDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->linkDepth, parser->key_is_link); + } + + assert(pjlink); + parser->linkDepth--; + parser->jsonDepth--; + return dbjl_value(parser, CALLIF(pjlink->pif->parse_end_array)(pjlink)); +} + +static yajl_callbacks dbjl_callbacks = { + dbjl_null, dbjl_boolean, dbjl_integer, dbjl_double, NULL, dbjl_string, + dbjl_start_map, dbjl_map_key, dbjl_end_map, dbjl_start_array, dbjl_end_array +}; + +static const yajl_parser_config dbjl_config = + { 0, 0 }; /* allowComments = NO, checkUTF8 = NO */ + +long dbJLinkInit(struct link *plink, short dbfType) +{ + parseContext context, *parser = &context; + yajl_alloc_funcs dbjl_allocs; + yajl_handle yh; + yajl_status ys; + const char *json = plink->value.json.string; + size_t jlen = strlen(json); + long status; + + IFDEBUG(10) + printf("dbJLinkInit(\"%s.????\", %d)\n", plink->precord->name, dbfType); + + parser->pjlink = NULL; + parser->product = NULL; + parser->plink = plink; + parser->dbfType = dbfType; + parser->jsonDepth = 0; + parser->linkDepth = 0; + parser->key_is_link = 0; + + IFDEBUG(10) + printf("dbJLinkInit: jsonDepth=%d, linkDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->linkDepth, parser->key_is_link); + + yajl_set_default_alloc_funcs(&dbjl_allocs); + yh = yajl_alloc(&dbjl_callbacks, &dbjl_config, &dbjl_allocs, parser); + if (!yh) + return S_db_noMemory; + + ys = yajl_parse(yh, (const unsigned char *) json, (unsigned) jlen); + if (ys == yajl_status_insufficient_data) + ys = yajl_parse_complete(yh); + + switch (ys) { + unsigned char *err; + jlink *pjlink; + + case yajl_status_ok: + assert(parser->jsonDepth == 0); + pjlink = parser->product; + assert(pjlink); + plink->value.json.jlink = pjlink; + plink->lset = pjlink->pif->get_lset(pjlink); + status = 0; + break; + + case yajl_status_error: + err = yajl_get_error(yh, 1, (const unsigned char *) json, (unsigned) jlen); + errlogPrintf("dbJLinkInit: %s\n", err); + yajl_free_error(yh, err); + /* fall through */ + default: + status = S_db_badField; + } + + yajl_free(yh); + return status; +} + + diff --git a/src/ioc/db/dbJLink.h b/src/ioc/db/dbJLink.h new file mode 100644 index 000000000..42d5e362c --- /dev/null +++ b/src/ioc/db/dbJLink.h @@ -0,0 +1,70 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbJLink.h */ + +#ifndef INC_dbJLink_H +#define INC_dbJLink_H + +#include +#include + +/* Limit for link name key length */ +#define MAX_LINK_NAME 15 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + jlif_stop = 0, + jlif_continue = 1 +} jlif_result; + +typedef enum { + jlif_key_stop = jlif_stop, + jlif_key_continue = jlif_continue, + jlif_key_embed_link +} jlif_key_result; + +struct link; +struct lset; +typedef struct jlink jlink; + +typedef struct jlif { + const char *name; + jlink* (*alloc_jlink)(struct link *); + void (*free_jlink)(jlink *); + jlif_result (*start_parse)(jlink *); + jlif_result (*parse_null)(jlink *); + jlif_result (*parse_boolean)(jlink *, int val); + jlif_result (*parse_integer)(jlink *, long num); + jlif_result (*parse_double)(jlink *, double num); + jlif_result (*parse_string)(jlink *, const char *val, size_t len); + jlif_key_result (*parse_start_map)(jlink *); + jlif_result (*parse_map_key)(jlink *, const char *key, size_t len); + jlif_result (*parse_end_map)(jlink *); + jlif_result (*parse_start_array)(jlink *); + jlif_result (*parse_end_array)(jlink *); + jlif_result (*end_parse)(jlink *); + struct lset* (*get_lset)(const jlink *); + void (*report)(const jlink *); +} jlif; + +typedef struct jlink { + jlif *pif; + jlink *parent; + /* Link types extend or embed this structure for private storage */ +} jlink; + +epicsShareFunc long dbJLinkInit(struct link *plink, short dbfType); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbJLink_H */ + diff --git a/src/ioc/db/dbLink.c b/src/ioc/db/dbLink.c index c2ae385d0..7e1cceb03 100644 --- a/src/ioc/db/dbLink.c +++ b/src/ioc/db/dbLink.c @@ -37,6 +37,7 @@ #include "dbDbLink.h" #include "db_field_log.h" #include "dbFldTypes.h" +#include "dbJLink.h" #include "dbLink.h" #include "dbLock.h" #include "dbScan.h" @@ -77,6 +78,11 @@ void dbInitLink(struct link *plink, short dbfType) return; } + if (plink->type == JSON_LINK) { + dbJLinkInit(plink, dbfType); + return; + } + if (plink->type != PV_LINK) return; @@ -120,6 +126,15 @@ void dbAddLink(struct dbLocker *locker, struct link *plink, short dbfType, return; } + if (plink->type == JSON_LINK) { + /* + * FIXME: Can't create DB links as dbJLink types yet, + * dbLock.c doesn't have any way to find/track them. + */ + dbJLinkInit(plink, dbfType); + return; + } + if (plink->type != PV_LINK) return; diff --git a/src/ioc/db/dbTest.c b/src/ioc/db/dbTest.c index d7d778d7f..69628cd5d 100644 --- a/src/ioc/db/dbTest.c +++ b/src/ioc/db/dbTest.c @@ -941,7 +941,9 @@ static void printBuffer( } else { for (i = 0; i < no_elements; i+= MAXLINE - 5) { - sprintf(pmsg, " \"%.*s\"", MAXLINE - 5, (char *)pbuffer + i); + int width = no_elements - i; + if (width > MAXLINE - 5) width = MAXLINE - 5; + sprintf(pmsg, " \"%.*s\"", width, (char *)pbuffer + i); if (i + MAXLINE - 5 < no_elements) strcat(pmsg, " +"); dbpr_msgOut(pMsgBuff, tab_size); } diff --git a/src/ioc/dbStatic/dbBase.h b/src/ioc/dbStatic/dbBase.h index de543a1e7..84d41c2fe 100644 --- a/src/ioc/dbStatic/dbBase.h +++ b/src/ioc/dbStatic/dbBase.h @@ -47,8 +47,8 @@ typedef struct devSup { typedef struct linkSup { ELLNODE node; char *name; - char *lset_name; - struct lset *lset; + char *jlif_name; + struct jlif *pjlif; } linkSup; typedef struct dbDeviceMenu { diff --git a/src/ioc/dbStatic/dbLexRoutines.c b/src/ioc/dbStatic/dbLexRoutines.c index 380844dec..660c02669 100644 --- a/src/ioc/dbStatic/dbLexRoutines.c +++ b/src/ioc/dbStatic/dbLexRoutines.c @@ -75,7 +75,7 @@ static void dbRecordtypeFieldItem(char *name,char *value); static void dbDevice(char *recordtype,char *linktype, char *dsetname,char *choicestring); static void dbDriver(char *name); -static void dbLinkType(char *name, char *lset_name); +static void dbLinkType(char *name, char *jlif_name); static void dbRegistrar(char *name); static void dbFunction(char *name); static void dbVariable(char *name, char *type); @@ -786,7 +786,7 @@ static void dbDriver(char *name) ellAdd(&pdbbase->drvList,&pdrvSup->node); } -static void dbLinkType(char *name, char *lset_name) +static void dbLinkType(char *name, char *jlif_name) { linkSup *pLinkSup; GPHENTRY *pgphentry; @@ -797,7 +797,7 @@ static void dbLinkType(char *name, char *lset_name) } pLinkSup = dbCalloc(1,sizeof(linkSup)); pLinkSup->name = epicsStrDup(name); - pLinkSup->lset_name = epicsStrDup(lset_name); + pLinkSup->jlif_name = epicsStrDup(jlif_name); pgphentry = gphAdd(pdbbase->pgpHash, pLinkSup->name, &pdbbase->linkList); if (!pgphentry) { yyerrorAbort("gphAdd failed"); @@ -1053,7 +1053,7 @@ static void dbRecordField(char *name,char *value) value++; value[strlen(value) - 1] = 0; } - dbTranslateEscape(value, value); /* yuck: in-place, but safe */ + dbTranslateEscape(value, value); /* in-place; safe & legal */ status = dbPutString(pdbentry,value); if(status) { epicsPrintf("Can't set \"%s.%s\" to \"%s\"\n", diff --git a/src/ioc/dbStatic/dbStaticLib.c b/src/ioc/dbStatic/dbStaticLib.c index e147374e5..b9532516b 100644 --- a/src/ioc/dbStatic/dbStaticLib.c +++ b/src/ioc/dbStatic/dbStaticLib.c @@ -46,6 +46,7 @@ #include "special.h" #include "dbCommon.h" +#include "dbJLink.h" int dbStaticDebug = 0; static char *pNullString = ""; @@ -122,7 +123,11 @@ void dbFreeLinkContents(struct link *plink) case CONSTANT: free((void *)plink->value.constantStr); break; case MACRO_LINK: free((void *)plink->value.macro_link.macroStr); break; case PV_LINK: free((void *)plink->value.pv_link.pvname); break; - case JSON_LINK: parm = plink->value.json.string; break; + case JSON_LINK: + if (plink->value.json.jlink) + plink->value.json.jlink->pif->free_jlink(plink->value.json.jlink); + parm = plink->value.json.string; + break; case VME_IO: parm = plink->value.vmeio.parm; break; case CAMAC_IO: parm = plink->value.camacio.parm; break; case AB_IO: parm = plink->value.abio.parm; break; @@ -560,7 +565,7 @@ void dbFreeBase(dbBase *pdbbase) pdrvSup = pdrvSupNext; } while ((plinkSup = (linkSup *) ellGet(&pdbbase->linkList))) { - free(plinkSup->lset_name); + free(plinkSup->jlif_name); free(plinkSup->name); free(plinkSup); } @@ -1086,7 +1091,7 @@ long dbWriteLinkFP(DBBASE *pdbbase, FILE *fp) } for (plinkSup = (linkSup *) ellFirst(&pdbbase->linkList); plinkSup; plinkSup = (linkSup *) ellNext(&plinkSup->node)) { - fprintf(fp, "link(%s,%s)\n", plinkSup->name, plinkSup->lset_name); + fprintf(fp, "link(%s,%s)\n", plinkSup->name, plinkSup->jlif_name); } return 0; } @@ -2267,9 +2272,8 @@ long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo) /* Check for braces => JSON */ if (*str == '{' && str[len-1] == '}') { - /* FIXME Parse JSON object here */ pinfo->ltype = JSON_LINK; - return 0; + return 0; } /* Check for other HW link types */ @@ -2337,7 +2341,6 @@ long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo) /* Link may be an array constant */ if (pstr[0] == '[' && pstr[len-1] == ']' && (strchr(pstr, ',') || strchr(pstr, '"'))) { - /* FIXME Parse JSON array here */ pinfo->ltype = CONSTANT; return 0; } diff --git a/src/ioc/dbStatic/link.h b/src/ioc/dbStatic/link.h index dc9ccacbc..ab1d7a54d 100644 --- a/src/ioc/dbStatic/link.h +++ b/src/ioc/dbStatic/link.h @@ -87,9 +87,10 @@ struct pv_link { short lastGetdbrType; /* last dbrType for DB or CA get */ }; +struct jlink; struct json_link { char *string; - /* ... */ + struct jlink *jlink; }; /* structure of a VME io channel */ diff --git a/src/ioc/misc/iocInit.c b/src/ioc/misc/iocInit.c index d0483e55a..09558c1fb 100644 --- a/src/ioc/misc/iocInit.c +++ b/src/ioc/misc/iocInit.c @@ -66,6 +66,7 @@ #include "recSup.h" #include "registryDeviceSupport.h" #include "registryDriverSupport.h" +#include "registryJLinks.h" #include "registryRecordType.h" #include "rsrv.h" @@ -79,6 +80,7 @@ static enum { /* define forward references*/ static int checkDatabase(dbBase *pdbbase); static void initDrvSup(void); +static void initJLinks(void); static void initRecSup(void); static void initDevSup(void); static void finishDevSup(void); @@ -145,6 +147,7 @@ static int iocBuild_2(void) { initHookAnnounce(initHookAfterCaLinkInit); + initJLinks(); initDrvSup(); initHookAnnounce(initHookAfterInitDrvSup); @@ -374,6 +377,22 @@ static void initDrvSup(void) /* Locate all driver support entry tables */ } } +static void initJLinks(void) +{ + linkSup *plinkSup; + + for (plinkSup = (linkSup *)ellFirst(&pdbbase->linkList); plinkSup; + plinkSup = (linkSup *)ellNext(&plinkSup->node)) { + jlif *pjlif = registryJLinkFind(plinkSup->name); + + if (!pjlif) { + errlogPrintf("iocInit: JLink %s not found\n", plinkSup->name); + continue; + } + plinkSup->pjlif = pjlif; + } +} + static void initRecSup(void) { dbRecordType *pdbRecordType; diff --git a/src/ioc/registry/Makefile b/src/ioc/registry/Makefile index ce285a0c6..a85320a9d 100644 --- a/src/ioc/registry/Makefile +++ b/src/ioc/registry/Makefile @@ -14,6 +14,7 @@ SRC_DIRS += $(IOCDIR)/registry INC += registryRecordType.h INC += registryDeviceSupport.h INC += registryDriverSupport.h +INC += registryJLinks.h INC += registryFunction.h INC += registryCommon.h INC += registryIocRegister.h @@ -21,6 +22,7 @@ INC += registryIocRegister.h dbCore_SRCS += registryRecordType.c dbCore_SRCS += registryDeviceSupport.c dbCore_SRCS += registryDriverSupport.c +dbCore_SRCS += registryJLinks.c dbCore_SRCS += registryFunction.c dbCore_SRCS += registryCommon.c dbCore_SRCS += registryIocRegister.c diff --git a/src/ioc/registry/registryCommon.c b/src/ioc/registry/registryCommon.c index e5d5768af..f4ec4af55 100644 --- a/src/ioc/registry/registryCommon.c +++ b/src/ioc/registry/registryCommon.c @@ -19,6 +19,7 @@ #include "registryCommon.h" #include "registryDeviceSupport.h" #include "registryDriverSupport.h" +#include "registryJLinks.h" void registerRecordTypes(DBBASE *pbase, int nRecordTypes, @@ -76,3 +77,16 @@ void registerDrivers(DBBASE *pbase, int nDrivers, } } +void registerJLinks(DBBASE *pbase, int nLinks, jlif * const *jlifsl) +{ + int i; + for (i = 0; i < nLinks; i++) { + if (registryJLinkFind(jlifsl[i]->name)) continue; + if (!registryJLinkAdd(jlifsl[i])) { + errlogPrintf("registryJLinkAdd failed %s\n", + jlifsl[i]->name); + continue; + } + } +} + diff --git a/src/ioc/registry/registryCommon.h b/src/ioc/registry/registryCommon.h index c4b601665..51b32dee3 100644 --- a/src/ioc/registry/registryCommon.h +++ b/src/ioc/registry/registryCommon.h @@ -12,6 +12,7 @@ #include "dbStaticLib.h" #include "devSup.h" +#include "dbJLink.h" #include "registryRecordType.h" #include "shareLib.h" @@ -28,6 +29,8 @@ epicsShareFunc void registerDevices( epicsShareFunc void registerDrivers( DBBASE *pbase, int nDrivers, const char * const *driverSupportNames, struct drvet * const *drvsl); +epicsShareFunc void registerJLinks( + DBBASE *pbase, int nDrivers, jlif * const *jlifsl); #ifdef __cplusplus } diff --git a/src/ioc/registry/registryJLinks.c b/src/ioc/registry/registryJLinks.c new file mode 100644 index 000000000..04c827098 --- /dev/null +++ b/src/ioc/registry/registryJLinks.c @@ -0,0 +1,27 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* registryJLinks.c */ + +#define epicsExportSharedSymbols +#include "registry.h" +#include "registryJLinks.h" +#include "dbJLink.h" + +static void *registryID = "JSON link types"; + + +epicsShareFunc int registryJLinkAdd(struct jlif *pjlif) +{ + return registryAdd(registryID, pjlif->name, pjlif); +} + +epicsShareFunc jlif * registryJLinkFind(const char *name) +{ + return registryFind(registryID, name); +} diff --git a/src/ioc/registry/registryJLinks.h b/src/ioc/registry/registryJLinks.h new file mode 100644 index 000000000..3f304c6d5 --- /dev/null +++ b/src/ioc/registry/registryJLinks.h @@ -0,0 +1,28 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_registryJLinks_H +#define INC_registryJLinks_H + +#include "dbJLink.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc int registryJLinkAdd(jlif *pjlif); +epicsShareFunc jlif * registryJLinkFind(const char *name); + +#ifdef __cplusplus +} +#endif + + +#endif /* INC_registryDriverSupport_H */ diff --git a/src/std/link/Makefile b/src/std/link/Makefile index d2e158475..56b56b8be 100644 --- a/src/std/link/Makefile +++ b/src/std/link/Makefile @@ -11,7 +11,7 @@ SRC_DIRS += $(STDDIR)/link DBD += links.dbd -# dbRecStd_SRCS += ... +dbRecStd_SRCS += lnkConst.c HTMLS += links.html diff --git a/src/std/link/links.dbd.pod b/src/std/link/links.dbd.pod index c4a5a45cd..335808097 100644 --- a/src/std/link/links.dbd.pod +++ b/src/std/link/links.dbd.pod @@ -1,7 +1,7 @@ -=head1 Link Types +=head1 JSON Links -Links are an extensible mechanism for adding new kinds of database link, -using JSON for link addresses. +JSON Links are an extensible mechanism for adding new kinds of database link, +using JSON for the link address. The following link types are available in this release: =over @@ -12,6 +12,8 @@ The following link types are available in this release: =item * L +=item * L + =back =head2 Using Links @@ -21,35 +23,36 @@ must appear inside a pair of braces C< {} > expressed as a JSON (L) object, which allows link parameters to be defined as needed. -Note that due to the required presence of the double-quote characters in the -JSON strings in a link field value string in a database file, it will usually -be necessary to escape all double-quote characters in the JSON object by -preceding them with a backslash C< \ > character. -Database configuration tools that support this link mechanism must be careful -to handle these escapes correctly on reading and writing string values from/to -a .db file. -=head2 Filter Reference +=head2 Link Type Reference =cut -link(const,lsetConst) +link(const, lnkConstIf) =head3 Constant Link C<"const"> -... +Constant links provide one or more values at link initalization time, but do not return +any data when their C routine is called. Most record types support the use of +constant links by calling C at initialization, which results in +the constant value being loaded into the target field at that time. =head4 Parameters -... +A const link takes a parameter which may be an integer, double or string, or an array of +those types. If an array contains both integers and double values the integers will be +promoted to doubles. Mixing strings and numbers in an array will result in an error. -=head4 Example +=head4 Examples - ... + {const: 3.14159265358979} + {const: "Pi"} + {const: [1, 2.718281828459, 3.14159265358979]} + {const: ["One", "e", "Pi"]} =cut -link(db,lsetDatabase) +#link(db, lnkDbIf) =head3 Database Link C<"db"> @@ -65,7 +68,7 @@ link(db,lsetDatabase) =cut -link(ca,lsetChannelAccess) +#link(ca, lnkCaIf) =head3 Channel Access Link C<"ca"> @@ -90,3 +93,19 @@ link(ca,lsetChannelAccess) ... =cut + +#link(calc, lnkCalcIf) + +=head3 Calculation Link C<"calc"> + +... + +=head4 Parameters + +... + +=head4 Example + + ... + +=cut diff --git a/src/std/link/lnkConst.c b/src/std/link/lnkConst.c new file mode 100644 index 000000000..6ed4e5e9a --- /dev/null +++ b/src/std/link/lnkConst.c @@ -0,0 +1,412 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* lnkConst.c */ + +#include +#include +#include + +#include "dbDefs.h" +#include "errlog.h" +#include "epicsAssert.h" +#include "epicsTypes.h" +#include "dbAccessDefs.h" +#include "dbConvertFast.h" +#include "dbLink.h" +#include "dbJLink.h" +#include "epicsExport.h" + + +/* Change 'undef' to 'define' to turn on debug statements: */ +#undef DEBUG_LINK + +#ifdef DEBUG_LINK + int lnkConstDebug = 10; +# define IFDEBUG(n) \ + if (lnkConstDebug >= n) /* block or statement */ +#else +# define IFDEBUG(n) \ + if(0) /* Compiler will elide the block or statement */ +#endif + +typedef long (*FASTCONVERT)(); + +typedef struct clink { + struct jlink jlink; /* embedded */ + int nElems; + enum {s0, si32, sf64, sc40, a0, ai32, af64, ac40} type; + union { + epicsInt32 scalar_integer; + epicsFloat64 scalar_double; + char * scalar_string; + void *pmem; + epicsInt32 *pintegers; + epicsFloat64 *pdoubles; + char **pstrings; + } value; +} clink; + +static lset lnkConst_lset; + + +/*************************** jlif Routines **************************/ + +static jlink* lnkConst_alloc(struct link *plink) { + clink *clink = calloc(1, sizeof(struct clink)); + + IFDEBUG(10) + printf("lnkConst_alloc()\n"); + + clink->type = s0; + clink->nElems = 0; + clink->value.pmem = NULL; + + IFDEBUG(10) + printf("lnkConst_alloc -> const@%p\n", clink); + + return &clink->jlink; +} + +static void lnkConst_free(jlink *pjlink) { + clink *clink = CONTAINER(pjlink, struct clink, jlink); + + IFDEBUG(10) + printf("lnkConst_free(const@%p)\n", pjlink); + + switch (clink->type) { + int i; + case ac40: + for (i=0; inElems; i++) + free(clink->value.pstrings[i]); + /* fall through */ + case ai32: + case af64: + free(clink->value.pmem); + default: + break; + } + free(clink); +} + +static jlif_result lnkConst_integer(jlink *pjlink, long num) { + clink *clink = CONTAINER(pjlink, struct clink, jlink); + + IFDEBUG(10) + printf("lnkConst_integer(const@%p, %ld)\n", pjlink, num); + + switch (clink->type) { + void *buf; + int nElems; + + case s0: + clink->nElems = 1; + clink->type = si32; + clink->value.scalar_integer = num; + break; + + case a0: + clink->type = ai32; + /* fall thorough */ + case ai32: + nElems = clink->nElems + 1; + buf = realloc(clink->value.pmem, nElems * sizeof(epicsInt32)); + if (!buf) break; + clink->value.pmem = buf; + clink->value.pintegers[clink->nElems] = num; + clink->nElems = nElems; + break; + + case af64: + nElems = clink->nElems + 1; + buf = realloc(clink->value.pmem, nElems * sizeof(epicsFloat64)); + if (!buf) break; + clink->value.pmem = buf; + clink->value.pdoubles[clink->nElems++] = num; + break; + + case ac40: + errlogPrintf("lnkConst: Mixed data types in array\n"); + /* FIXME ??? */ + default: + return jlif_stop; + } + return jlif_continue; +} + +static jlif_result lnkConst_boolean(jlink *pjlink, int val) { + IFDEBUG(10) + printf("lnkConst_boolean(const@%p, %d)\n", pjlink, val); + + return lnkConst_integer(pjlink, val); +} + +static jlif_result lnkConst_double(jlink *pjlink, double num) { + clink *clink = CONTAINER(pjlink, struct clink, jlink); + + IFDEBUG(10) + printf("lnkConst_double(const@%p, %g)\n", pjlink, num); + + switch (clink->type) { + epicsFloat64 *f64buf; + int nElems, i; + + case s0: + clink->nElems = 1; + clink->type = sf64; + clink->value.scalar_double = num; + break; + + case a0: + clink->type = af64; + /* fall thorough */ + case af64: + nElems = clink->nElems + 1; + f64buf = realloc(clink->value.pmem, nElems * sizeof(epicsFloat64)); + if (!f64buf) break; + clink->value.pdoubles = f64buf; + clink->value.pdoubles[clink->nElems++] = num; + break; + + case ai32: /* promote earlier ai32 values to af64 */ + f64buf = calloc(clink->nElems + 1, sizeof(epicsFloat64)); + if (!f64buf) break; + for (i = 0; i < clink->nElems; i++) { + f64buf[i] = clink->value.pintegers[i]; + } + f64buf[clink->nElems++] = num; + free(clink->value.pmem); + clink->value.pdoubles = f64buf; + clink->type = af64; + break; + + case ac40: + errlogPrintf("lnkConst: Mixed data types in array\n"); + /* FIXME ??? */ + default: + return jlif_stop; + } + + return jlif_continue; +} + +static jlif_result lnkConst_string(jlink *pjlink, const char *val, size_t len) { + clink *clink = CONTAINER(pjlink, struct clink, jlink); + + IFDEBUG(10) + printf("lnkConst_string(const@%p, \"%.*s\")\n", clink, (int) len, val); + + if (len > MAX_STRING_SIZE) + len = MAX_STRING_SIZE; + + if (clink->type == s0) { + char *buf = malloc(len + 1); + + if (!buf) + return jlif_stop; + + strncpy(buf, val, len); + buf[len] = '\0'; + + clink->nElems = 1; + clink->type = sc40; + clink->value.scalar_string = buf; + return jlif_continue; + } + /* FIXME Implement a0 and ac40 */ + /* FIXME How to handle ai32 and af64? */ + errlogPrintf("lnkConst: Mixed data types in array\n"); + + return jlif_stop; +} + +static jlif_result lnkConst_start_array(jlink *pjlink) { + clink *clink = CONTAINER(pjlink, struct clink, jlink); + + IFDEBUG(10) + printf("lnkConst_start_array(const@%p)\n", pjlink); + + if (clink->type != s0) { + errlogPrintf("lnkConst: Embedded array value\n"); + return jlif_stop; + } + + clink->type = a0; + return jlif_continue; +} + +static jlif_result lnkConst_end_array(jlink *pjlink) { + IFDEBUG(10) + printf("lnkConst_end_array(const@%p)\n", pjlink); + + return jlif_continue; +} + +static struct lset* lnkConst_get_lset(const jlink *pjlink) { + IFDEBUG(10) + printf("lnkConst_get_lset(const@%p)\n", pjlink); + + return &lnkConst_lset; +} + +static void lnkConst_report(const jlink *pjlink) { + clink *clink = CONTAINER(pjlink, struct clink, jlink); + + IFDEBUG(10) + printf("lnkConst_report(const@%p)\n", clink); + + /* FIXME Implement! */ +} + +/*************************** lset Routines **************************/ + +static long lnkConst_loadScalar(struct link *plink, short dbrType, void *pbuffer) +{ + clink *clink = CONTAINER(plink->value.json.jlink, struct clink, jlink); + long status; + + IFDEBUG(10) + printf("lnkConst_loadScalar(const@%p, %d, %p)\n", + clink, dbrType, pbuffer); + + switch (clink->type) { + case si32: + status = dbFastPutConvertRoutine[DBF_LONG][dbrType] + (&clink->value.scalar_integer, pbuffer, NULL); + break; + + case sf64: + status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType] + (&clink->value.scalar_double, pbuffer, NULL); + break; + + case sc40: + status = dbFastPutConvertRoutine[DBF_STRING][dbrType] + (clink->value.scalar_string, pbuffer, NULL); + break; + + case ai32: + status = dbFastPutConvertRoutine[DBF_LONG][dbrType] + (clink->value.pintegers, pbuffer, NULL); + break; + + case af64: + status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType] + (clink->value.pdoubles, pbuffer, NULL); + break; + + default: + status = S_db_badField; + break; + } + + return status; +} + +static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer, + long *pnReq) +{ + clink *clink = CONTAINER(plink->value.json.jlink, struct clink, jlink); + short dbrSize = dbValueSize(dbrType); + char *pdest = pbuffer; + int nElems = clink->nElems; + FASTCONVERT conv; + long status; + + IFDEBUG(10) + printf("lnkConst_loadArray(const@%p, %d, %p, (%ld))\n", + clink, dbrType, pbuffer, *pnReq); + + if (nElems > *pnReq) + nElems = *pnReq; + + switch (clink->type) { + int i; + + case si32: + status = dbFastPutConvertRoutine[DBF_LONG][dbrType] + (&clink->value.scalar_integer, pdest, NULL); + break; + + case sf64: + status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType] + (&clink->value.scalar_double, pdest, NULL); + break; + + case sc40: + status = dbFastPutConvertRoutine[DBF_STRING][dbrType] + (clink->value.scalar_string, pbuffer, NULL); + break; + + case ai32: + conv = dbFastPutConvertRoutine[DBF_LONG][dbrType]; + for (i = 0; i < nElems; i++) { + conv(&clink->value.pintegers[i], pdest, NULL); + pdest += dbrSize; + } + status = 0; + break; + + case af64: + conv = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]; + for (i = 0; i < nElems; i++) { + conv(&clink->value.pdoubles[i], pdest, NULL); + pdest += dbrSize; + } + status = 0; + break; + + default: + status = S_db_badField; + } + *pnReq = nElems; + return status; +} + +static long lnkConst_getNelements(const struct link *plink, long *nelements) +{ + IFDEBUG(10) + printf("lnkConst_getNelements(const@%p, (%ld))\n", + plink->value.json.jlink, *nelements); + + *nelements = 0; + return 0; +} + +static long lnkConst_getValue(struct link *plink, short dbrType, void *pbuffer, + epicsEnum16 *pstat, epicsEnum16 *psevr, long *pnRequest) +{ + IFDEBUG(10) + printf("lnkConst_loadScalar(const@%p, %d, %p, ... (%ld))\n", + plink->value.json.jlink, dbrType, pbuffer, *pnRequest); + + if (pnRequest) + *pnRequest = 0; + return 0; +} + + +/************************* Interface Tables *************************/ + +static lset lnkConst_lset = { + 1, 0, /* Constant, not Volatile */ + NULL, lnkConst_loadScalar, lnkConst_loadArray, NULL, + NULL, lnkConst_getNelements, lnkConst_getValue, + NULL, NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL +}; + +static jlif lnkConstIf = { + "const", lnkConst_alloc, lnkConst_free, NULL, + NULL, lnkConst_boolean, lnkConst_integer, lnkConst_double, lnkConst_string, + NULL, NULL, NULL, lnkConst_start_array, lnkConst_end_array, + NULL, lnkConst_get_lset, lnkConst_report +}; +epicsExportAddress(jlif, lnkConstIf); + diff --git a/src/tools/DBD/Link.pm b/src/tools/DBD/Link.pm index ac1a14452..4a4568e82 100644 --- a/src/tools/DBD/Link.pm +++ b/src/tools/DBD/Link.pm @@ -3,8 +3,8 @@ use DBD::Base; @ISA = qw(DBD::Base); sub init { - my ($this, $name, $lset) = @_; - $this->SUPER::init($lset, "link support (lset)"); + my ($this, $name, $jlif) = @_; + $this->SUPER::init($jlif, "link support (jlif)"); $this->{KEY} = $name; return $this; } diff --git a/src/tools/registerRecordDeviceDriver.pl b/src/tools/registerRecordDeviceDriver.pl index 34f1b59cb..18b522772 100644 --- a/src/tools/registerRecordDeviceDriver.pl +++ b/src/tools/registerRecordDeviceDriver.pl @@ -159,6 +159,20 @@ if (%drivers) { print $out "};\n\n"; } +my %links = %{$dbd->links}; +if (%links) { + my @links = sort keys %links; + + # Declare the link interfaces + print $out wrap('epicsShareExtern jlif ', ' ', + join(', ', map {"*pvar_jlif_$_"} @links)), ";\n\n"; + + # List of pointers to each link interface + print $out "static struct jlif *jlifsl[] = {\n"; + print $out join(",\n", map {" pvar_jlif_$_"} @links); + print $out "};\n\n"; +} + my @registrars = sort keys %{$dbd->registrars}; my @functions = sort keys %{$dbd->functions}; push @registrars, map {"register_func_$_"} @functions; @@ -234,6 +248,10 @@ print $out (<< 'END') if %drivers; registerDrivers(pbase, NELEMENTS(drvsl), driverSupportNames, drvsl); END +print $out (<< 'END') if %links; + registerJLinks(pbase, NELEMENTS(jlifsl), jlifsl); +END + print $out (<< "END") for @registrars; pvar_func_$_(); END