diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index b17f000c8..e6e3fbdc1 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -30,6 +30,26 @@ release.

--> +

Iocsh "on error ..."

+ +

A new statement is added to enable IOC shell commands +to signal error conditions, and for scripts to respond. +This first is through the new function

iocshSetError(int)
+A script may be prefixed with eg. "on error break" +to stop at the failed command.

+ +
+on error continue | break | wait [value] | halt
+
+ +

A suggested form for IOC shell commands is:

+ +
+static void doSomethingCallFunc(const iocshArgBuf *args)
+{
+    iocshSetError(doSomething(...)); /* return 0 == success */
+}
+

Channel Access Security: Check Hostname Against DNS

diff --git a/modules/database/src/ioc/as/asIocRegister.c b/modules/database/src/ioc/as/asIocRegister.c index 16cba90c6..d5926a551 100644 --- a/modules/database/src/ioc/as/asIocRegister.c +++ b/modules/database/src/ioc/as/asIocRegister.c @@ -39,7 +39,7 @@ static void asSetSubstitutionsCallFunc(const iocshArgBuf *args) static const iocshFuncDef asInitFuncDef = {"asInit",0}; static void asInitCallFunc(const iocshArgBuf *args) { - asInit(); + iocshSetError(asInit()); } /* asdbdump */ diff --git a/modules/database/src/ioc/db/dbIocRegister.c b/modules/database/src/ioc/db/dbIocRegister.c index c40af92c1..afb31151d 100644 --- a/modules/database/src/ioc/db/dbIocRegister.c +++ b/modules/database/src/ioc/db/dbIocRegister.c @@ -39,7 +39,7 @@ static const iocshFuncDef dbLoadDatabaseFuncDef = {"dbLoadDatabase",3,dbLoadDatabaseArgs}; static void dbLoadDatabaseCallFunc(const iocshArgBuf *args) { - dbLoadDatabase(args[0].sval,args[1].sval,args[2].sval); + iocshSetError(dbLoadDatabase(args[0].sval,args[1].sval,args[2].sval)); } /* dbLoadRecords */ @@ -49,7 +49,7 @@ static const iocshArg * const dbLoadRecordsArgs[2] = {&dbLoadRecordsArg0,&dbLoad static const iocshFuncDef dbLoadRecordsFuncDef = {"dbLoadRecords",2,dbLoadRecordsArgs}; static void dbLoadRecordsCallFunc(const iocshArgBuf *args) { - dbLoadRecords(args[0].sval,args[1].sval); + iocshSetError(dbLoadRecords(args[0].sval,args[1].sval)); } /* dbb */ diff --git a/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c b/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c index 201a32398..879f67e19 100644 --- a/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c +++ b/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c @@ -22,7 +22,7 @@ static const iocshFuncDef dbLoadTemplateFuncDef = {"dbLoadTemplate", 2, dbLoadTemplateArgs}; static void dbLoadTemplateCallFunc(const iocshArgBuf *args) { - dbLoadTemplate(args[0].sval, args[1].sval); + iocshSetError(dbLoadTemplate(args[0].sval, args[1].sval)); } diff --git a/modules/database/src/ioc/misc/miscIocRegister.c b/modules/database/src/ioc/misc/miscIocRegister.c index 6c08ef0c9..4dffdfca0 100644 --- a/modules/database/src/ioc/misc/miscIocRegister.c +++ b/modules/database/src/ioc/misc/miscIocRegister.c @@ -22,28 +22,28 @@ static const iocshFuncDef iocInitFuncDef = {"iocInit",0,NULL}; static void iocInitCallFunc(const iocshArgBuf *args) { - iocInit(); + iocshSetError(iocInit()); } /* iocBuild */ static const iocshFuncDef iocBuildFuncDef = {"iocBuild",0,NULL}; static void iocBuildCallFunc(const iocshArgBuf *args) { - iocBuild(); + iocshSetError(iocBuild()); } /* iocRun */ static const iocshFuncDef iocRunFuncDef = {"iocRun",0,NULL}; static void iocRunCallFunc(const iocshArgBuf *args) { - iocRun(); + iocshSetError(iocRun()); } /* iocPause */ static const iocshFuncDef iocPauseFuncDef = {"iocPause",0,NULL}; static void iocPauseCallFunc(const iocshArgBuf *args) { - iocPause(); + iocshSetError(iocPause()); } /* coreRelease */ @@ -77,7 +77,7 @@ static const iocshArg * const systemArgs[] = {&systemArg0}; static const iocshFuncDef systemFuncDef = {"system",1,systemArgs}; static void systemCallFunc(const iocshArgBuf *args) { - system(args[0].sval); + iocshSetError(system(args[0].sval)); } #endif diff --git a/modules/database/src/tools/registerRecordDeviceDriver.pl b/modules/database/src/tools/registerRecordDeviceDriver.pl index acbcc517c..9e81c9875 100644 --- a/modules/database/src/tools/registerRecordDeviceDriver.pl +++ b/modules/database/src/tools/registerRecordDeviceDriver.pl @@ -275,7 +275,7 @@ static const iocshFuncDef rrddFuncDef = {"$subname", 1, rrddArgs}; static void rrddCallFunc(const iocshArgBuf *) { - $subname(*iocshPpdbbase); + iocshSetError($subname(*iocshPpdbbase)); } } // extern "C" diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 0de90c87a..a795c9b70 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -11,6 +11,8 @@ /* Heavily modified by Eric Norum Date: 03MAY2000 */ /* Adapted to C++ by Eric Norum Date: 18DEC2000 */ +#include + #include #include #include @@ -18,6 +20,7 @@ #include #define epicsExportSharedSymbols +#include "epicsMath.h" #include "errlog.h" #include "macLib.h" #include "epicsStdio.h" @@ -56,7 +59,7 @@ static char iocshVarID[] = "iocshVar"; extern "C" { static void varCallFunc(const iocshArgBuf *); } static epicsMutexId iocshTableMutex; static epicsThreadOnceId iocshOnceId = EPICS_THREAD_ONCE_INIT; -static epicsThreadPrivateId iocshMacroHandleId; +static epicsThreadPrivateId iocshContextId; /* * I/O redirection @@ -76,7 +79,7 @@ struct iocshRedirect { static void iocshOnce (void *) { iocshTableMutex = epicsMutexMustCreate (); - iocshMacroHandleId = epicsThreadPrivateCreate(); + iocshContextId = epicsThreadPrivateCreate(); } static void iocshInit (void) @@ -496,6 +499,39 @@ static void helpCallFunc(const iocshArgBuf *args) } } +typedef enum { + Continue, + Break, + Halt +} OnError; + +// per call to iocshBody() +struct iocshScope { + iocshScope *outer; + OnError onerr; + double timeout; + bool errored; + bool interactive; + iocshScope() :outer(0), onerr(Continue), timeout(0.0), errored(false), interactive(false) {} +}; + +// per thread executing iocshBody() +struct iocshContext { + MAC_HANDLE *handle; + iocshScope *scope; +}; + +int iocshSetError(int err) +{ + iocshContext *ctxt; + if (err && iocshContextId) { + ctxt = (iocshContext *) epicsThreadPrivateGet(iocshContextId); + + if(ctxt && ctxt->scope) ctxt->scope->errored = 1; + } + return err; +} + /* * The body of the command interpreter */ @@ -524,8 +560,10 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) void *readlineContext = NULL; int wasOkToBlock; static const char * pairs[] = {"", "environ", NULL, NULL}; - MAC_HANDLE *handle; + iocshScope scope; + iocshContext *context; char ** defines = NULL; + int ret = 0; iocshInit(); @@ -534,8 +572,10 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) */ if (commandLine == NULL) { if ((pathname == NULL) || (strcmp (pathname, "") == 0)) { - if ((prompt = envGetConfigParamPtr(&IOCSH_PS1)) == NULL) + if ((prompt = envGetConfigParamPtr(&IOCSH_PS1)) == NULL) { prompt = "epics> "; + } + scope.interactive = true; } else { fp = fopen (pathname, "r"); @@ -560,6 +600,10 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) fclose(fp); return -1; } + + } else { + // use of iocshCmd() implies "on error break" + scope.onerr = Break; } /* @@ -583,21 +627,25 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } } - /* - * Check for existing macro context or construct a new one. - */ - handle = (MAC_HANDLE *) epicsThreadPrivateGet(iocshMacroHandleId); - - if (handle == NULL) { - if (macCreateHandle(&handle, pairs)) { + // Check for existing context or construct a new one. + context = (iocshContext *) epicsThreadPrivateGet(iocshContextId); + + if (!context) { + context = (iocshContext*)calloc(1, sizeof(*context)); + if (!context || macCreateHandle(&context->handle, pairs)) { errlogMessage("iocsh: macCreateHandle failed."); free(redirects); + free(context); return -1; } - epicsThreadPrivateSet(iocshMacroHandleId, (void *) handle); + epicsThreadPrivateSet(iocshContextId, (void *) context); } - + MAC_HANDLE *handle = context->handle; + + scope.outer = context->scope; + context->scope = &scope; + macPushScope(handle); macInstallMacros(handle, defines); @@ -608,6 +656,29 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) * Read commands till EOF or exit */ for (;;) { + if(!scope.interactive && scope.errored) { + if(scope.onerr==Continue) { + /* do nothing */ + + } else if(scope.onerr==Break) { + ret = -1; + fprintf(epicsGetStderr(), "iocsh Error: Break\n" ); + break; + + } else if(scope.onerr==Halt) { + ret = -1; + if(scope.timeout<=0.0 || isinf(scope.timeout)) { + fprintf(epicsGetStderr(), "iocsh Error: Halt\n" ); + epicsThreadSuspendSelf(); + break; + + } else { + fprintf(epicsGetStderr(), "iocsh Error: Waiting %.1f sec ...\n", scope.timeout); + epicsThreadSleep(scope.timeout); + } + } + } + /* * Read a line */ @@ -646,8 +717,10 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) * Expand macros */ free(line); - if ((line = macDefExpand(raw, handle)) == NULL) + if ((line = macDefExpand(raw, handle)) == NULL) { + scope.errored = true; continue; + } /* * Skip leading white-space coming from a macro @@ -660,9 +733,11 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) * Echo non-empty lines read from a script. * Comments delineated with '#-' aren't echoed. */ - if ((prompt == NULL) && *line && (commandLine == NULL)) - if ((c != '#') || (line[icin + 1] != '-')) + if ((prompt == NULL) && *line && (commandLine == NULL)) { + if ((c != '#') || (line[icin + 1] != '-')) { puts(line); + } + } /* * Ignore lines that became a comment or empty after macro expansion @@ -686,6 +761,7 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) if (newv == NULL) { fprintf (epicsGetStderr(), "Out of memory!\n"); argc = -1; + scope.errored = true; break; } argv = newv; @@ -759,8 +835,9 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } else { if (!sep) { - if (((c == '"') || (c == '\'')) && !backslash) + if (((c == '"') || (c == '\'')) && !backslash) { quote = c; + } if (redirect != NULL) { if (redirect->name != NULL) { argc = -1; @@ -781,16 +858,20 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } if (redirect != NULL) { showError(filename, lineno, "Illegal redirection."); + scope.errored = true; continue; } - if (argc < 0) + if (argc < 0) { break; + } if (quote != EOF) { showError(filename, lineno, "Unbalanced quote."); + scope.errored = true; continue; } if (backslash) { showError(filename, lineno, "Trailing backslash."); + scope.errored = true; continue; } if (inword) @@ -807,7 +888,8 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) if (openRedirect(filename, lineno, redirects) < 0) continue; startRedirect(filename, lineno, redirects); - iocshBody(commandFile, NULL, macros); + if(iocshBody(commandFile, NULL, macros)) + scope.errored = true; stopRedirect(filename, lineno, redirects); continue; } @@ -822,6 +904,9 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) * Set up redirection */ if ((openRedirect(filename, lineno, redirects) == 0) && (argc > 0)) { + // error unless a function is actually called. + // handles command not-found and arg parsing errors. + scope.errored = true; /* * Look up command */ @@ -834,7 +919,17 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) for (int iarg = 0 ; ; ) { if (iarg == piocshFuncDef->nargs) { startRedirect(filename, lineno, redirects); - (*found->def.func)(argBuf); + /* execute */ + scope.errored = false; + try { + (*found->def.func)(argBuf); + } catch(std::exception& e){ + fprintf(epicsGetStderr(), "c++ error: %s\n", e.what()); + scope.errored = true; + } catch(...) { + fprintf(epicsGetStderr(), "c++ error unknown\n"); + scope.errored = true; + } break; } if (iarg >= argBufCapacity) { @@ -874,9 +969,12 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } macPopScope(handle); - if (handle->level == 0) { + if (!scope.outer) { macDeleteHandle(handle); - epicsThreadPrivateSet(iocshMacroHandleId, NULL); + free(context); + epicsThreadPrivateSet(iocshContextId, NULL); + } else { + context->scope = scope.outer; } if (fp && (fp != stdin)) fclose (fp); @@ -891,7 +989,7 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) if (readlineContext) epicsReadlineEnd(readlineContext); epicsThreadSetOkToBlock(wasOkToBlock); - return 0; + return ret; } /* @@ -943,13 +1041,13 @@ iocshRun(const char *cmd, const char *macros) void epicsShareAPI iocshEnvClear(const char *name) { - MAC_HANDLE *handle; + iocshContext *context; - if (iocshMacroHandleId) { - handle = (MAC_HANDLE *) epicsThreadPrivateGet(iocshMacroHandleId); + if (iocshContextId) { + context = (iocshContext *) epicsThreadPrivateGet(iocshContextId); - if (handle != NULL) { - macPutValue(handle, name, NULL); + if (context != NULL) { + macPutValue(context->handle, name, NULL); } } } @@ -1054,6 +1152,59 @@ static void iocshRunCallFunc(const iocshArgBuf *args) iocshRun(args[0].sval, args[1].sval); } +/* on */ +static const iocshArg onArg0 = { "'error' 'continue' | 'break' | 'wait' [value] | 'halt'", iocshArgArgv }; +static const iocshArg *onArgs[1] = {&onArg0}; +static const iocshFuncDef onFuncDef = {"on", 1, onArgs}; +static void onCallFunc(const iocshArgBuf *args) +{ + iocshContext *context = (iocshContext *) epicsThreadPrivateGet(iocshContextId); + +#define USAGE() fprintf(epicsGetStderr(), "Usage: on error [continue | break | halt | wait ]\n") + + if(!context || !context->scope) { + // we are not called through iocshBody()... + + } else if(args->aval.ac<3 || strcmp(args->aval.av[1], "error")!=0) { + USAGE(); + + } else if(context->scope->interactive) { + fprintf(epicsGetStderr(), "Interactive shell ignores on error ...\n"); + + } else { + // don't fault on previous, ignored, errors + context->scope->errored = false; + + if(strcmp(args->aval.av[2], "continue")==0) { + context->scope->onerr = Continue; + + } else if(strcmp(args->aval.av[2], "break")==0) { + context->scope->onerr = Break; + + } else if(strcmp(args->aval.av[2], "halt")==0) { + context->scope->onerr = Halt; + context->scope->timeout = 0.0; + + } else if(strcmp(args->aval.av[2], "wait")==0) { + context->scope->onerr = Halt; + if(args->aval.ac<=3) { + USAGE(); + } else if(epicsParseDouble(args->aval.av[3], &context->scope->timeout, NULL)) { + context->scope->timeout = 5.0; + } else { + USAGE(); + fprintf(epicsGetStderr(), "Unable to parse 'on error wait' time %s\n", args->aval.av[3]); + } + + } else { + fprintf(epicsGetStderr(), "Usage: on error [continue | break | halt | wait ]\n"); + context->scope->errored = true; + } + } + +#undef USAGE +} + /* * Dummy internal commands -- register and install in command table * so they show up in the help display @@ -1083,6 +1234,7 @@ static void localRegister (void) iocshRegister(&iocshCmdFuncDef,iocshCmdCallFunc); iocshRegister(&iocshLoadFuncDef,iocshLoadCallFunc); iocshRegister(&iocshRunFuncDef,iocshRunCallFunc); + iocshRegister(&onFuncDef, onCallFunc); } } /* extern "C" */ diff --git a/modules/libcom/src/iocsh/iocsh.h b/modules/libcom/src/iocsh/iocsh.h index 84b38f224..a63af64ce 100644 --- a/modules/libcom/src/iocsh/iocsh.h +++ b/modules/libcom/src/iocsh/iocsh.h @@ -76,7 +76,7 @@ epicsShareFunc void epicsShareAPI iocshRegister( epicsShareFunc void epicsShareAPI iocshRegisterVariable ( const iocshVarDef *piocshVarDef); epicsShareFunc const iocshCmdDef * epicsShareAPI iocshFindCommand( - const char* name); + const char* name) EPICS_DEPRECATED; epicsShareFunc const iocshVarDef * epicsShareAPI iocshFindVariable( const char* name); @@ -84,11 +84,30 @@ epicsShareFunc const iocshVarDef * epicsShareAPI iocshFindVariable( /* This should only be called when iocsh is no longer needed*/ epicsShareFunc void epicsShareAPI iocshFree(void); +/** shorthand for @code iocshLoad(pathname, NULL) @endcode */ epicsShareFunc int epicsShareAPI iocsh(const char *pathname); +/** shorthand for @code iocshRun(cmd, NULL) @endcode */ epicsShareFunc int epicsShareAPI iocshCmd(const char *cmd); +/** Read and evaluate IOC shell commands from the given file. + * @param pathname Path to script file + * @param macros NULL or a comma seperated list of macro definitions. eg. "VAR1=x,VAR2=y" + * @return 0 on success, non-zero on error + */ epicsShareFunc int epicsShareAPI iocshLoad(const char *pathname, const char* macros); +/** Evaluate a single IOC shell command + * @param cmd Command string. eg. "echo \"something or other\"" + * @param macros NULL or a comma seperated list of macro definitions. eg. "VAR1=x,VAR2=y" + * @return 0 on success, non-zero on error + */ epicsShareFunc int epicsShareAPI iocshRun(const char *cmd, const char* macros); +/** @brief Signal error from an IOC shell function. + * + * @param err 0 - success (no op), !=0 - error + * @return The err argument value. + */ +epicsShareFunc int iocshSetError(int err); + /* Makes macros that shadow environment variables work correctly with epicsEnvSet */ epicsShareFunc void epicsShareAPI iocshEnvClear(const char *name); diff --git a/modules/libcom/src/iocsh/libComRegister.c b/modules/libcom/src/iocsh/libComRegister.c index 04628bb53..c67804aef 100644 --- a/modules/libcom/src/iocsh/libComRegister.c +++ b/modules/libcom/src/iocsh/libComRegister.c @@ -77,7 +77,7 @@ static const iocshFuncDef chdirFuncDef = {"cd",1,chdirArgs}; static void chdirCallFunc(const iocshArgBuf *args) { if (args[0].sval == NULL || - chdir(args[0].sval)) { + iocshSetError(chdir(args[0].sval))) { fprintf(stderr, "Invalid directory path, ignored\n"); } } diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile index 7fde7313e..5b8ab6046 100755 --- a/modules/libcom/test/Makefile +++ b/modules/libcom/test/Makefile @@ -246,6 +246,11 @@ TESTS += yajlTest endif endif +TESTPROD_HOST += iocshTest +iocshTest_SRCS += iocshTest.cpp +TESTS += iocshTest +TESTFILES += $(wildcard ../iocshTest*.cmd) + # The testHarness runs all the test programs in a known working order. testHarness_SRCS += epicsRunLibComTests.c @@ -305,3 +310,6 @@ endif endif include $(TOP)/configure/RULES + +rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl + $(PERL) $(TOOLS)/epicsMakeMemFs.pl $@ epicsRtemsFSImage $(TESTFILES) diff --git a/modules/libcom/test/iocshTest.cpp b/modules/libcom/test/iocshTest.cpp new file mode 100644 index 000000000..1216ed579 --- /dev/null +++ b/modules/libcom/test/iocshTest.cpp @@ -0,0 +1,131 @@ +/*************************************************************************\ +* Copyright (c) 2019 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace { +void findTestData() +{ + const char *locations[] = { + ".", + "..", + ".." OSI_PATH_LIST_SEPARATOR "O.Common", + "O.Common", + }; + + for(size_t i=0; i %d", expect ? "ran" : "expected error from", fname, err); +} + +void testCmd(const char *cmd, bool expect=true) +{ + testDiag("eval \"%s\"", cmd); + int err = iocshCmd(cmd); + testOk((err==0) ^ (!expect), "%s \"%s\" -> %d", expect ? "eval'd" : "expected error from", cmd, err); +} + +std::set reached; +const iocshArg positionArg0 = {"position", iocshArgString}; +const iocshArg * const positionArgs[1] = { &positionArg0 }; +const iocshFuncDef positionFuncDef = {"position",1,positionArgs}; +void positionCallFunc(const iocshArgBuf *args) +{ + testDiag("Reaching \"%s\"", args[0].sval); + reached.insert(args[0].sval); +} + +void testPosition(const std::string& pos, bool expect=true) +{ + testOk((reached.find(pos)!=reached.end()) ^ !expect, + "%sreached position %s", expect ? "" : "not ", pos.c_str()); +} + +const iocshArg assertArg0 = {"condition", iocshArgInt}; +const iocshArg * const assertArgs[1] = {&assertArg0}; +const iocshFuncDef assertFuncDef = {"assert",1,assertArgs}; +void assertCallFunc(const iocshArgBuf *args) +{ + iocshSetError(args[0].ival); +} + +} // namespace + +MAIN(iocshTest) +{ + testPlan(19); + libComRegister(); + iocshRegister(&positionFuncDef, &positionCallFunc); + iocshRegister(&assertFuncDef, &assertCallFunc); + findTestData(); + + testFile("iocshTestSuccess.cmd"); + testPosition("success"); + reached.clear(); + testPosition("success", false); + + testCmd("