diff --git a/GNUmakefile b/GNUmakefile index bf094678f0182c464d8355c7fd9161d95287e228..ceef4726e6597f0786e169033ca2070bbce0169a 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -7,6 +7,11 @@ DBDS += require.dbd SOURCES += runScript.c DBDS += runScript.dbd +SOURCES += dbLoadTemplate.y +DBDS += dbLoadTemplate.dbd + +dbLoadTemplate.c: dbLoadTemplate_lex.c ../dbLoadTemplate.h + #HEADERS += require.h SOURCES_T2 += strdup.c diff --git a/dbLoadTemplate.dbd b/dbLoadTemplate.dbd new file mode 100644 index 0000000000000000000000000000000000000000..721fa85fac3af44f051cea55c26ceea578340e8f --- /dev/null +++ b/dbLoadTemplate.dbd @@ -0,0 +1 @@ +registrar(dbLoadTemplateRegister) diff --git a/dbLoadTemplate.h b/dbLoadTemplate.h new file mode 100644 index 0000000000000000000000000000000000000000..0e96e749eff46db37156d669e01332ec8fbd72b8 --- /dev/null +++ b/dbLoadTemplate.h @@ -0,0 +1,18 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, 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. +\*************************************************************************/ +/* dbLoadTemplate.h */ + +#ifndef INCdbLoadTemplateh +#define INCdbLoadTemplateh + +#include "shareLib.h" +epicsShareFunc int dbLoadTemplate( + const char *sub_file, const char *cmd_collect, const char *path); + +#endif /*INCdbLoadTemplateh*/ diff --git a/dbLoadTemplate.y b/dbLoadTemplate.y new file mode 100644 index 0000000000000000000000000000000000000000..728913b26d0622127fb590ed2e9c80f3496282a9 --- /dev/null +++ b/dbLoadTemplate.y @@ -0,0 +1,489 @@ +%{ + +/*************************************************************************\ +* Copyright (c) 2006 UChicago, 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. +\*************************************************************************/ + +/* for vasprintf */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> + +#include "osiUnistd.h" +#include "macLib.h" +#include "dbmf.h" + +#include "dbAccess.h" +#include "dbLoadTemplate.h" +#include "osiFileName.h" +#include "epicsVersion.h" + +#if defined(vxWorks) || defined (_WIN32) +#include "asprintf.h" +#endif + +#ifdef BASE_VERSION +#define EPICS_3_13 +extern void dbLoadRecords(const char*, const char*); +#else +#include "iocsh.h" +#include "epicsExport.h" +#endif + +#if (EPICS_VERSION*10000+EPICS_REVISION*100+EPICS_MODIFICATION<31412) +#define dbmfStrdup(s) strcpy(dbmfMalloc(strlen((char*)(s))+1),(char*)(s)) +#endif + +static int line_num; +static int yyerror(char* str); + +static char *sub_collect = NULL; +static char *sub_locals; +static char **vars = NULL; +static char *db_file_name = NULL; +static int var_count, sub_count; + +/* We allocate MAX_VAR_FACTOR chars in the sub_collect string for each + * "variable=value," segment, and will accept at most dbTemplateMaxVars + * template variables. The user can adjust that variable to increase + * the number of variables or the length allocated for the buffer. + */ +#define MAX_VAR_FACTOR 50 + +int dbTemplateMaxVars = 100; + +%} + +%start substitution_file + +%token <Str> WORD QUOTE +%token DBFILE +%token PATTERN +%token GLOBAL +%token EQUALS COMMA +%left O_PAREN C_PAREN +%left O_BRACE C_BRACE + +%union +{ + int Int; + char Char; + char *Str; + double Real; +} + +%% + +substitution_file: global_or_template + | substitution_file global_or_template + ; + +global_or_template: global_definitions + | template_substitutions + ; + +global_definitions: GLOBAL O_BRACE C_BRACE + | GLOBAL O_BRACE variable_definitions C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "global_definitions: %s\n", sub_collect+1); + #endif + sub_locals += strlen(sub_locals); + } + ; + +template_substitutions: template_filename O_BRACE C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_substitutions: %s unused\n", db_file_name); + #endif + dbmfFree(db_file_name); + db_file_name = NULL; + } + | template_filename O_BRACE substitutions C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_substitutions: %s finished\n", db_file_name); + #endif + dbmfFree(db_file_name); + db_file_name = NULL; + } + ; + +template_filename: DBFILE WORD + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_filename: %s\n", $2); + #endif + var_count = 0; + db_file_name = dbmfMalloc(strlen($2)+1); + strcpy(db_file_name, $2); + dbmfFree($2); + } + | DBFILE QUOTE + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_filename: \"%s\"\n", $2); + #endif + var_count = 0; + db_file_name = dbmfMalloc(strlen($2)+1); + strcpy(db_file_name, $2); + dbmfFree($2); + } + ; + +substitutions: pattern_substitutions + | variable_substitutions + ; + +pattern_substitutions: PATTERN O_BRACE C_BRACE + | PATTERN O_BRACE C_BRACE pattern_definitions + | PATTERN O_BRACE pattern_names C_BRACE + | PATTERN O_BRACE pattern_names C_BRACE pattern_definitions + ; + +pattern_names: pattern_name + | pattern_names COMMA + | pattern_names pattern_name + ; + +pattern_name: WORD + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_name: [%d] = %s\n", var_count, $1); + #endif + if (var_count >= dbTemplateMaxVars) { + fprintf(stderr, + "More than dbTemplateMaxVars = %d macro variables used\n", + dbTemplateMaxVars); + yyerror(NULL); + } + else { + vars[var_count] = dbmfMalloc(strlen($1)+1); + strcpy(vars[var_count], $1); + var_count++; + dbmfFree($1); + } + } + ; + +pattern_definitions: pattern_definition + | pattern_definitions pattern_definition + ; + +pattern_definition: global_definitions + | O_BRACE C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_definition: pattern_values empty\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + } + | O_BRACE pattern_values C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_definition:\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + *sub_locals = '\0'; + sub_count = 0; + } + | WORD O_BRACE pattern_values C_BRACE + { /* DEPRECATED SYNTAX */ + fprintf(stderr, + "dbLoadTemplate: Substitution file uses deprecated syntax.\n" + " the string '%s' on line %d that comes just before the\n" + " '{' character is extraneous and should be removed.\n", + $1, line_num); + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_definition:\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + dbmfFree($1); + *sub_locals = '\0'; + sub_count = 0; + } + ; + +pattern_values: pattern_value + | pattern_values COMMA + | pattern_values pattern_value + ; + +pattern_value: QUOTE + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_value: [%d] = \"%s\"\n", sub_count, $1); + #endif + if (sub_count < var_count) { + strcat(sub_locals, ","); + strcat(sub_locals, vars[sub_count]); + strcat(sub_locals, "=\""); + strcat(sub_locals, $1); + strcat(sub_locals, "\""); + sub_count++; + } else { + fprintf(stderr, "dbLoadTemplate: Too many values given, line %d.\n", + line_num); + } + dbmfFree($1); + } + | WORD + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_value: [%d] = %s\n", sub_count, $1); + #endif + if (sub_count < var_count) { + strcat(sub_locals, ","); + strcat(sub_locals, vars[sub_count]); + strcat(sub_locals, "="); + strcat(sub_locals, $1); + sub_count++; + } else { + fprintf(stderr, "dbLoadTemplate: Too many values given, line %d.\n", + line_num); + } + dbmfFree($1); + } + ; + +variable_substitutions: variable_substitution + | variable_substitutions variable_substitution + ; + +variable_substitution: global_definitions + | O_BRACE C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "variable_substitution: variable_definitions empty\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + } + | O_BRACE variable_definitions C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "variable_substitution:\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + *sub_locals = '\0'; + } + | WORD O_BRACE variable_definitions C_BRACE + { /* DEPRECATED SYNTAX */ + fprintf(stderr, + "dbLoadTemplate: Substitution file uses deprecated syntax.\n" + " the string '%s' on line %d that comes just before the\n" + " '{' character is extraneous and should be removed.\n", + $1, line_num); + #ifdef ERROR_STUFF + fprintf(stderr, "variable_substitution:\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + dbmfFree($1); + *sub_locals = '\0'; + } + ; + +variable_definitions: variable_definition + | variable_definitions COMMA + | variable_definitions variable_definition + ; + +variable_definition: WORD EQUALS WORD + { + #ifdef ERROR_STUFF + fprintf(stderr, "variable_definition: %s = %s\n", $1, $3); + #endif + strcat(sub_locals, ","); + strcat(sub_locals, $1); + strcat(sub_locals, "="); + strcat(sub_locals, $3); + dbmfFree($1); dbmfFree($3); + } + | WORD EQUALS QUOTE + { + #ifdef ERROR_STUFF + fprintf(stderr, "variable_definition: %s = \"%s\"\n", $1, $3); + #endif + strcat(sub_locals, ","); + strcat(sub_locals, $1); + strcat(sub_locals, "=\""); + strcat(sub_locals, $3); + strcat(sub_locals, "\""); + dbmfFree($1); dbmfFree($3); + } + | QUOTE EQUALS QUOTE + { + #ifdef ERROR_STUFF + fprintf(stderr, "variable_definition: \"%s\" = \"%s\"\n", $1, $3); + #endif + strcat(sub_locals, ",\""); + strcat(sub_locals, $1); + strcat(sub_locals, "\"=\""); + strcat(sub_locals, $3); + strcat(sub_locals, "\""); + dbmfFree($1); dbmfFree($3); + } + ; + +%% + +#include "dbLoadTemplate_lex.c" + +static int yyerror(char* str) +{ + if (str) + fprintf(stderr, "Substitution file error: %s\n", str); + else + fprintf(stderr, "Substitution file error.\n"); + fprintf(stderr, "line %d: '%s'\n", line_num, yytext); + return 0; +} + +static int is_not_inited = 1; + +int dbLoadTemplate(const char *sub_file, const char *cmd_collect, const char *path) +{ + FILE *fp; + int i; + + line_num = 1; + + if (!sub_file || !*sub_file) { + fprintf(stderr, "must specify variable substitution file\n"); + return -1; + } + + if (dbTemplateMaxVars < 1) { + fprintf(stderr,"Error: dbTemplateMaxVars = %d, must be positive\n", + dbTemplateMaxVars); + return -1; + } + + fp = fopen(sub_file, "r"); + if (!fp && sub_file[0] != OSI_PATH_SEPARATOR[0]) { + const char *dirname, *end; + int dirlen; + char* filename; + + if (!path || !*path) { + path = getenv("EPICS_DB_INCLUDE_PATH"); + } + for(dirname = path; dirname != NULL; dirname = end) { + end = strchr(dirname, OSI_PATH_LIST_SEPARATOR[0]); + if (end) dirlen = (int)(end++ - dirname); + else dirlen = (int)strlen(dirname); + if (dirlen == 0) continue; /* ignore empty path elements */ + if (dirlen == 1 && dirname[0] == '.') continue; /* we had . already */ + filename = NULL; + if (asprintf(&filename, "%.*s" OSI_PATH_SEPARATOR "%s", dirlen, dirname, sub_file) < 0) + { + fprintf(stderr,"dbLoadTemplate: out of memory\n"); + break; + } + fp = fopen(filename, "r"); + free(filename); + if (fp) break; + } + } + if (!fp) { + fprintf(stderr, "dbLoadTemplate: error opening sub file %s: %s\n", sub_file, strerror(errno)); + return -1; + } + + vars = malloc(dbTemplateMaxVars * sizeof(char*)); + sub_collect = malloc(dbTemplateMaxVars * MAX_VAR_FACTOR); + if (!vars || !sub_collect) { + free(vars); + free(sub_collect); + fclose(fp); + fprintf(stderr, "dbLoadTemplate: Out of memory!\n"); + return -1; + } + strcpy(sub_collect, ","); + + if (cmd_collect && *cmd_collect) { + strcat(sub_collect, cmd_collect); + sub_locals = sub_collect + strlen(sub_collect); + } else { + sub_locals = sub_collect; + *sub_locals = '\0'; + } + var_count = 0; + sub_count = 0; + + if (is_not_inited) { + yyin = fp; + is_not_inited = 0; + } else { + yyrestart(fp); + } + + yyparse(); + + for (i = 0; i < var_count; i++) { + dbmfFree(vars[i]); + } + free(vars); + free(sub_collect); + vars = NULL; + fclose(fp); + if (db_file_name) { + dbmfFree(db_file_name); + db_file_name = NULL; + } + return 0; +} + +#ifndef EPICS_3_13 +#include "registry.h" +epicsExportAddress(int, dbTemplateMaxVars); + +static const iocshFuncDef dbLoadTemplateDef = { + "dbLoadTemplate", 3, (const iocshArg *[]) { + &(iocshArg) { "filename", iocshArgString }, + &(iocshArg) { "\"macro=value,...\"", iocshArgString }, + &(iocshArg) { "searchpath", iocshArgString }, +}}; + +#ifdef __GNUC__ +/* Without this I somehow always get the original dbLoadTemplate linked instead of my version */ +int __dbLoadTemplate(const char *sub_file, const char *cmd_collect, const char *path) __attribute__ ((weak, alias ("dbLoadTemplate"))); +#endif + +static void dbLoadTemplateFunc(const iocshArgBuf *args) +{ + __dbLoadTemplate(args[0].sval, args[1].sval, args[2].sval); +} + +typedef struct iocshCommand { + iocshFuncDef const *pFuncDef; + iocshCallFunc func; + struct iocshCommand *next; +}iocshCommand; + +static void dbLoadTemplateRegister(void) +{ + static int firstTime = 1; + if (firstTime) { + iocshRegister(&dbLoadTemplateDef, dbLoadTemplateFunc); + firstTime = 0; + } +} + +epicsExportRegistrar(dbLoadTemplateRegister); +#endif diff --git a/dbLoadTemplate_lex.l b/dbLoadTemplate_lex.l new file mode 100644 index 0000000000000000000000000000000000000000..afb72951729c104793d8d930377f5095f8ff6232 --- /dev/null +++ b/dbLoadTemplate_lex.l @@ -0,0 +1,57 @@ +/*************************************************************************\ +* Copyright (c) 2006 UChicago, 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. +\*************************************************************************/ + +newline "\n" +backslash "\\" +doublequote "\"" +singlequote "'" +comment "#" +whitespace [ \t\r] +escape {backslash}. +dstringchar [^"\n\\] +sstringchar [^'\n\\] +bareword [a-zA-Z0-9_\-+:./\\\[\]<>;] + +%% + +"pattern" { return(PATTERN); } +"file" { return(DBFILE); } +"global" { return(GLOBAL); } + +{doublequote}({dstringchar}|{escape})*{doublequote} | +{singlequote}({sstringchar}|{escape})*{singlequote} { + yylval.Str = dbmfStrdup(yytext+1); + yylval.Str[strlen(yylval.Str)-1] = '\0'; + return(QUOTE); +} + +{bareword}+ { + yylval.Str = dbmfStrdup(yytext); + return(WORD); +} + +"=" { return(EQUALS); } +"," { return(COMMA); } +"{" { return(O_BRACE); } +"}" { return(C_BRACE); } + +{comment}.* ; +{whitespace} ; +{newline} { line_num++; } + +. { + char message[40]; + + sprintf(message, "invalid character '%c'", yytext[0]); + yyerror(message); + + /* Suppress compiler warning messages */ + if (0) yyunput('c',NULL); + if (0) yy_switch_to_buffer(NULL); +} + +%%