From 2e80a97da9dd6384ad6992f711569d3b72e298df Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 2 May 2019 09:25:19 -0700 Subject: [PATCH 01/13] iocsh catch exceptions --- modules/libcom/src/iocsh/iocsh.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 0de90c87a..23e363ad4 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 @@ -834,7 +836,14 @@ 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 */ + try { + (*found->def.func)(argBuf); + } catch(std::exception& e){ + fprintf(epicsGetStderr(), "c++ error: %s\n", e.what()); + } catch(...) { + fprintf(epicsGetStderr(), "c++ error unknown\n"); + } break; } if (iarg >= argBufCapacity) { From 89c269e2d5ba5f7775dd557ae919000ba199b9da Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 2 May 2019 10:38:47 -0700 Subject: [PATCH 02/13] iocsh control error behavior --- modules/libcom/src/iocsh/iocsh.cpp | 101 +++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 13 deletions(-) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 23e363ad4..57e8be671 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -58,7 +58,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 iocshScopeId; /* * I/O redirection @@ -78,7 +78,7 @@ struct iocshRedirect { static void iocshOnce (void *) { iocshTableMutex = epicsMutexMustCreate (); - iocshMacroHandleId = epicsThreadPrivateCreate(); + iocshScopeId = epicsThreadPrivateCreate(); } static void iocshInit (void) @@ -498,6 +498,19 @@ static void helpCallFunc(const iocshArgBuf *args) } } +typedef enum { + Continue, + Break, + Halt +} OnError; + +typedef struct { + MAC_HANDLE *handle; + OnError onerr; + double timeout; + bool errored; +} Scope; + /* * The body of the command interpreter */ @@ -526,8 +539,10 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) void *readlineContext = NULL; int wasOkToBlock; static const char * pairs[] = {"", "environ", NULL, NULL}; + Scope *scope; MAC_HANDLE *handle; char ** defines = NULL; + int ret = 0; iocshInit(); @@ -588,17 +603,20 @@ 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); + scope = (Scope *) epicsThreadPrivateGet(iocshScopeId); - if (handle == NULL) { - if (macCreateHandle(&handle, pairs)) { + if (!scope) { + scope = (Scope*)calloc(1, sizeof(*scope)); + if (!scope || macCreateHandle(&scope->handle, pairs)) { errlogMessage("iocsh: macCreateHandle failed."); free(redirects); + free(scope); return -1; } - epicsThreadPrivateSet(iocshMacroHandleId, (void *) handle); + epicsThreadPrivateSet(iocshScopeId, (void *) scope); } + handle = scope->handle; macPushScope(handle); macInstallMacros(handle, defines); @@ -837,12 +855,15 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) if (iarg == piocshFuncDef->nargs) { startRedirect(filename, lineno, redirects); /* 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; } @@ -877,15 +898,33 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } else { showError(filename, lineno, "Command %s not found.", argv[0]); + scope->errored = true; } } stopRedirect(filename, lineno, redirects); + if(!commandLine && !scope->errored) { + if(scope->onerr==Continue) { + } else if(scope->onerr==Break) { + ret = -1; + break; + } else if(scope->onerr==Halt) { + ret = -1; + if(scope->timeout<=0.0) { + epicsThreadSuspendSelf(); + } else { + fprintf(epicsGetStderr(), "Wait %f sec\n", scope->timeout); + epicsThreadSleep(scope->timeout); + } + break; + } + } } macPopScope(handle); if (handle->level == 0) { macDeleteHandle(handle); - epicsThreadPrivateSet(iocshMacroHandleId, NULL); + free(scope); + epicsThreadPrivateSet(iocshScopeId, NULL); } if (fp && (fp != stdin)) fclose (fp); @@ -900,7 +939,7 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) if (readlineContext) epicsReadlineEnd(readlineContext); epicsThreadSetOkToBlock(wasOkToBlock); - return 0; + return ret; } /* @@ -952,13 +991,13 @@ iocshRun(const char *cmd, const char *macros) void epicsShareAPI iocshEnvClear(const char *name) { - MAC_HANDLE *handle; + Scope *scope; - if (iocshMacroHandleId) { - handle = (MAC_HANDLE *) epicsThreadPrivateGet(iocshMacroHandleId); + if (iocshScopeId) { + scope = (Scope *) epicsThreadPrivateGet(iocshScopeId); - if (handle != NULL) { - macPutValue(handle, name, NULL); + if (scope != NULL) { + macPutValue(scope->handle, name, NULL); } } } @@ -1063,6 +1102,41 @@ static void iocshRunCallFunc(const iocshArgBuf *args) iocshRun(args[0].sval, args[1].sval); } +/* on */ +static const iocshArg onArg0 = { "...", iocshArgArgv }; +static const iocshArg *onArgs[1] = {&onArg0}; +static const iocshFuncDef onFuncDef = {"on", 1, onArgs}; +static void onCallFunc(const iocshArgBuf *args) +{ + Scope *scope = (Scope *) epicsThreadPrivateGet(iocshScopeId); + + if(!scope || args->aval.ac<=2) { + } else if(strcmp(args->aval.av[1], "error")==0) { + if(args->aval.ac==2) { + } else if(strcmp(args->aval.av[2], "continue")==0) { + scope->onerr = Continue; + return; + + } else if(strcmp(args->aval.av[2], "break")==0) { + scope->onerr = Break; + return; + + } else if(strcmp(args->aval.av[2], "halt")==0) { + scope->onerr = Halt; + scope->timeout = 0.0; + return; + + } else if(strcmp(args->aval.av[2], "wait")==0) { + scope->onerr = Halt; + if(args->aval.ac==3 || !epicsParseDouble(args->aval.av[3], &scope->timeout, NULL)) { + scope->timeout = 5.0; + } + return; + } + } + fprintf(epicsGetStderr(), "Invalid 'on'\n"); +} + /* * Dummy internal commands -- register and install in command table * so they show up in the help display @@ -1092,6 +1166,7 @@ static void localRegister (void) iocshRegister(&iocshCmdFuncDef,iocshCmdCallFunc); iocshRegister(&iocshLoadFuncDef,iocshLoadCallFunc); iocshRegister(&iocshRunFuncDef,iocshRunCallFunc); + iocshRegister(&onFuncDef, onCallFunc); } } /* extern "C" */ From eba8a13a2c1ba9fdfdea1380de636fc3c88824be Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 2 May 2019 20:18:12 -0700 Subject: [PATCH 03/13] iocsh allow setting of error code --- modules/libcom/src/iocsh/iocsh.cpp | 11 +++++++++++ modules/libcom/src/iocsh/iocsh.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 57e8be671..2e64e8b05 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -511,6 +511,17 @@ typedef struct { bool errored; } Scope; +int iocshSetError(int err) +{ + Scope *scope; + if (err && iocshScopeId) { + scope = (Scope *) epicsThreadPrivateGet(iocshScopeId); + + if(scope) scope->errored = 1; + } + return err; +} + /* * The body of the command interpreter */ diff --git a/modules/libcom/src/iocsh/iocsh.h b/modules/libcom/src/iocsh/iocsh.h index 84b38f224..2e8dc225e 100644 --- a/modules/libcom/src/iocsh/iocsh.h +++ b/modules/libcom/src/iocsh/iocsh.h @@ -89,6 +89,8 @@ epicsShareFunc int epicsShareAPI iocshCmd(const char *cmd); epicsShareFunc int epicsShareAPI iocshLoad(const char *pathname, const char* macros); epicsShareFunc int epicsShareAPI iocshRun(const char *cmd, const char* macros); +epicsShareFunc int iocshSetError(int err); + /* Makes macros that shadow environment variables work correctly with epicsEnvSet */ epicsShareFunc void epicsShareAPI iocshEnvClear(const char *name); From 4d5a677239ecc2f0224c0dcc2bbbc92c5134f1a4 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 2 May 2019 20:24:47 -0700 Subject: [PATCH 04/13] use iocshSetError() --- modules/database/src/ioc/as/asIocRegister.c | 2 +- modules/database/src/ioc/db/dbIocRegister.c | 4 ++-- .../database/src/ioc/dbtemplate/dbtoolsIocRegister.c | 2 +- modules/database/src/ioc/misc/miscIocRegister.c | 10 +++++----- .../database/src/tools/registerRecordDeviceDriver.pl | 2 +- modules/libcom/src/iocsh/libComRegister.c | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) 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 02bb9b772..10147db4b 100644 --- a/modules/database/src/tools/registerRecordDeviceDriver.pl +++ b/modules/database/src/tools/registerRecordDeviceDriver.pl @@ -277,7 +277,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/libComRegister.c b/modules/libcom/src/iocsh/libComRegister.c index b105ea12f..11396e984 100644 --- a/modules/libcom/src/iocsh/libComRegister.c +++ b/modules/libcom/src/iocsh/libComRegister.c @@ -76,7 +76,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"); } } From 76506991da5ba163f26de43bff367ae336f2fe2b Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 24 May 2019 14:15:40 -0700 Subject: [PATCH 05/13] Fixed logic errors and added some prompts. --- modules/libcom/src/iocsh/iocsh.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 2e64e8b05..6fea9c4c4 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -913,20 +913,22 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } } stopRedirect(filename, lineno, redirects); - if(!commandLine && !scope->errored) { + if(!commandLine && scope->errored) { if(scope->onerr==Continue) { } else if(scope->onerr==Break) { + fprintf(epicsGetStderr(), "iocsh Error: Break\n" ); ret = -1; break; } else if(scope->onerr==Halt) { ret = -1; if(scope->timeout<=0.0) { + fprintf(epicsGetStderr(), "iocsh Error: Halt\n" ); epicsThreadSuspendSelf(); + break; } else { - fprintf(epicsGetStderr(), "Wait %f sec\n", scope->timeout); + fprintf(epicsGetStderr(), "iocsh Error: Waiting %f sec ...\n", scope->timeout); epicsThreadSleep(scope->timeout); } - break; } } } @@ -1114,7 +1116,7 @@ static void iocshRunCallFunc(const iocshArgBuf *args) } /* on */ -static const iocshArg onArg0 = { "...", iocshArgArgv }; +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) @@ -1133,15 +1135,15 @@ static void onCallFunc(const iocshArgBuf *args) return; } else if(strcmp(args->aval.av[2], "halt")==0) { - scope->onerr = Halt; scope->timeout = 0.0; + scope->onerr = Halt; return; } else if(strcmp(args->aval.av[2], "wait")==0) { - scope->onerr = Halt; - if(args->aval.ac==3 || !epicsParseDouble(args->aval.av[3], &scope->timeout, NULL)) { + if(args->aval.ac<=3 || epicsParseDouble(args->aval.av[3], &scope->timeout, NULL)) { scope->timeout = 5.0; } + scope->onerr = Halt; return; } } From 52b9c8b947a817ea31de767da3c9c413a5fd6945 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 27 May 2019 21:24:18 -0700 Subject: [PATCH 06/13] iocsh further on error Split Scope into iocshContext and iocshScope to separate per-thread and per-call state. Error handling is per-call. --- modules/libcom/src/iocsh/iocsh.cpp | 149 +++++++++++++++++------------ 1 file changed, 87 insertions(+), 62 deletions(-) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 6fea9c4c4..26ecb5b01 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -20,6 +20,7 @@ #include #define epicsExportSharedSymbols +#include "epicsMath.h" #include "errlog.h" #include "macLib.h" #include "epicsStdio.h" @@ -58,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 iocshScopeId; +static epicsThreadPrivateId iocshContextId; /* * I/O redirection @@ -78,7 +79,7 @@ struct iocshRedirect { static void iocshOnce (void *) { iocshTableMutex = epicsMutexMustCreate (); - iocshScopeId = epicsThreadPrivateCreate(); + iocshContextId = epicsThreadPrivateCreate(); } static void iocshInit (void) @@ -504,20 +505,29 @@ typedef enum { Halt } OnError; -typedef struct { - MAC_HANDLE *handle; +// per call to iocshBody() +struct iocshScope { + iocshScope *outer; OnError onerr; double timeout; bool errored; -} Scope; + 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) { - Scope *scope; - if (err && iocshScopeId) { - scope = (Scope *) epicsThreadPrivateGet(iocshScopeId); + iocshContext *ctxt; + if (err && iocshContextId) { + ctxt = (iocshContext *) epicsThreadPrivateGet(iocshContextId); - if(scope) scope->errored = 1; + if(ctxt && ctxt->scope) ctxt->scope->errored = 1; } return err; } @@ -550,8 +560,8 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) void *readlineContext = NULL; int wasOkToBlock; static const char * pairs[] = {"", "environ", NULL, NULL}; - Scope *scope; - MAC_HANDLE *handle; + iocshScope scope; + iocshContext *context; char ** defines = NULL; int ret = 0; @@ -562,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"); @@ -614,21 +626,24 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) /* * Check for existing macro context or construct a new one. */ - scope = (Scope *) epicsThreadPrivateGet(iocshScopeId); - - if (!scope) { - scope = (Scope*)calloc(1, sizeof(*scope)); - if (!scope || macCreateHandle(&scope->handle, pairs)) { + 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(scope); + free(context); return -1; } - epicsThreadPrivateSet(iocshScopeId, (void *) scope); + epicsThreadPrivateSet(iocshContextId, (void *) context); } - handle = scope->handle; - + MAC_HANDLE *handle = context->handle; + + scope.outer = context->scope; + context->scope = &scope; + macPushScope(handle); macInstallMacros(handle, defines); @@ -866,15 +881,15 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) if (iarg == piocshFuncDef->nargs) { startRedirect(filename, lineno, redirects); /* execute */ - scope->errored = false; + scope.errored = false; try { (*found->def.func)(argBuf); } catch(std::exception& e){ fprintf(epicsGetStderr(), "c++ error: %s\n", e.what()); - scope->errored = true; + scope.errored = true; } catch(...) { fprintf(epicsGetStderr(), "c++ error unknown\n"); - scope->errored = true; + scope.errored = true; } break; } @@ -909,35 +924,42 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } else { showError(filename, lineno, "Command %s not found.", argv[0]); - scope->errored = true; + scope.errored = true; } } stopRedirect(filename, lineno, redirects); - if(!commandLine && scope->errored) { - if(scope->onerr==Continue) { - } else if(scope->onerr==Break) { + + if(!scope.interactive && scope.errored) { + if(scope.onerr==Continue) { + /* do nothing */ + + } else if(scope.onerr==Break) { + ret = -1; fprintf(epicsGetStderr(), "iocsh Error: Break\n" ); - ret = -1; break; - } else if(scope->onerr==Halt) { + + } else if(scope.onerr==Halt) { ret = -1; - if(scope->timeout<=0.0) { + if(scope.timeout<=0.0 || isinf(scope.timeout)) { fprintf(epicsGetStderr(), "iocsh Error: Halt\n" ); epicsThreadSuspendSelf(); break; + } else { - fprintf(epicsGetStderr(), "iocsh Error: Waiting %f sec ...\n", scope->timeout); - epicsThreadSleep(scope->timeout); + fprintf(epicsGetStderr(), "iocsh Error: Waiting %f sec ...\n", scope.timeout); + epicsThreadSleep(scope.timeout); } } } } macPopScope(handle); - if (handle->level == 0) { + if (!scope.outer) { macDeleteHandle(handle); - free(scope); - epicsThreadPrivateSet(iocshScopeId, NULL); + free(context); + epicsThreadPrivateSet(iocshContextId, NULL); + } else { + context->scope = scope.outer; } if (fp && (fp != stdin)) fclose (fp); @@ -1004,13 +1026,13 @@ iocshRun(const char *cmd, const char *macros) void epicsShareAPI iocshEnvClear(const char *name) { - Scope *scope; + iocshContext *context; - if (iocshScopeId) { - scope = (Scope *) epicsThreadPrivateGet(iocshScopeId); + if (iocshContextId) { + context = (iocshContext *) epicsThreadPrivateGet(iocshContextId); - if (scope != NULL) { - macPutValue(scope->handle, name, NULL); + if (context != NULL) { + macPutValue(context->handle, name, NULL); } } } @@ -1121,33 +1143,36 @@ static const iocshArg *onArgs[1] = {&onArg0}; static const iocshFuncDef onFuncDef = {"on", 1, onArgs}; static void onCallFunc(const iocshArgBuf *args) { - Scope *scope = (Scope *) epicsThreadPrivateGet(iocshScopeId); + iocshContext *context = (iocshContext *) epicsThreadPrivateGet(iocshContextId); - if(!scope || args->aval.ac<=2) { - } else if(strcmp(args->aval.av[1], "error")==0) { - if(args->aval.ac==2) { - } else if(strcmp(args->aval.av[2], "continue")==0) { - scope->onerr = Continue; - return; + if(!context || !context->scope) { + // we are not called through iocshBody()... - } else if(strcmp(args->aval.av[2], "break")==0) { - scope->onerr = Break; - return; + } else if(args->aval.ac<3 || strcmp(args->aval.av[1], "error")!=0) { + fprintf(epicsGetStderr(), "Usage: on error [continue | break | halt | wait ]\n"); - } else if(strcmp(args->aval.av[2], "halt")==0) { - scope->timeout = 0.0; - scope->onerr = Halt; - return; + } else if(context->scope->interactive) { + fprintf(epicsGetStderr(), "Interactive shell ignores on error ...\n"); - } else if(strcmp(args->aval.av[2], "wait")==0) { - if(args->aval.ac<=3 || epicsParseDouble(args->aval.av[3], &scope->timeout, NULL)) { - scope->timeout = 5.0; - } - scope->onerr = Halt; - return; + } else 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 || epicsParseDouble(args->aval.av[3], &context->scope->timeout, NULL)) { + context->scope->timeout = 5.0; } + + } else { + fprintf(epicsGetStderr(), "Usage: on error [continue | break | halt | wait ]\n"); } - fprintf(epicsGetStderr(), "Invalid 'on'\n"); } /* From a81e261e23ac744c297a6ebc882b473a5e46cdf0 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 23 Jun 2019 19:43:34 -0700 Subject: [PATCH 07/13] iocsh trap arg. parsing errors --- modules/libcom/src/iocsh/iocsh.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 26ecb5b01..7392bca42 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -868,6 +868,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 */ @@ -924,7 +927,6 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } else { showError(filename, lineno, "Command %s not found.", argv[0]); - scope.errored = true; } } stopRedirect(filename, lineno, redirects); From a625acbb182f5c90810983aed74ecd4df4359d2e Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sat, 24 Aug 2019 20:30:47 -0700 Subject: [PATCH 08/13] iocsh handle redirect and similar early errors --- modules/libcom/src/iocsh/iocsh.cpp | 73 +++++++++++++++++------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 7392bca42..b3fe936cb 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -623,9 +623,7 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } } - /* - * Check for existing macro context or construct a new one. - */ + // Check for existing context or construct a new one. context = (iocshContext *) epicsThreadPrivateGet(iocshContextId); if (!context) { @@ -654,6 +652,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 */ @@ -692,8 +713,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 @@ -706,9 +729,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 @@ -732,6 +757,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; @@ -805,8 +831,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; @@ -827,16 +854,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) @@ -853,7 +884,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; } @@ -930,29 +962,6 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros) } } stopRedirect(filename, lineno, redirects); - - 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 %f sec ...\n", scope.timeout); - epicsThreadSleep(scope.timeout); - } - } - } } macPopScope(handle); From 58473e825c76e859d9785a3fc4c7aee404adb95e Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 25 Aug 2019 16:29:55 -0700 Subject: [PATCH 09/13] iocsh more error handling sooo many ways to fail... --- modules/libcom/src/iocsh/iocsh.cpp | 51 +++++++++++++++++++----------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index b3fe936cb..1ba1de635 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -1156,34 +1156,49 @@ 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) { - fprintf(epicsGetStderr(), "Usage: on error [continue | break | halt | wait ]\n"); + USAGE(); } else if(context->scope->interactive) { fprintf(epicsGetStderr(), "Interactive shell ignores on error ...\n"); - } else 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 || epicsParseDouble(args->aval.av[3], &context->scope->timeout, NULL)) { - context->scope->timeout = 5.0; - } - } else { - fprintf(epicsGetStderr(), "Usage: on error [continue | break | halt | wait ]\n"); + // 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 } /* From 2557d147853c944f4fd3b5e8baa21b78e67d88dc Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 25 Aug 2019 19:37:47 -0700 Subject: [PATCH 10/13] iocshCmd() imply "on error break" --- modules/libcom/src/iocsh/iocsh.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 1ba1de635..a795c9b70 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -600,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; } /* From 85517d761d028822ddae546aea20e235a4b3749c Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 25 Aug 2019 17:40:26 -0700 Subject: [PATCH 11/13] iocshTest start --- modules/libcom/test/Makefile | 8 ++ modules/libcom/test/iocshTest.cpp | 131 ++++++++++++++++++ modules/libcom/test/iocshTestBadArg.cmd | 4 + .../libcom/test/iocshTestBadArgIndirect.cmd | 3 + modules/libcom/test/iocshTestSuccess.cmd | 8 ++ .../libcom/test/iocshTestSuccessIndirect.cmd | 3 + modules/libcom/test/rtemsTestData.c | 6 - 7 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 modules/libcom/test/iocshTest.cpp create mode 100644 modules/libcom/test/iocshTestBadArg.cmd create mode 100644 modules/libcom/test/iocshTestBadArgIndirect.cmd create mode 100644 modules/libcom/test/iocshTestSuccess.cmd create mode 100644 modules/libcom/test/iocshTestSuccessIndirect.cmd delete mode 100644 modules/libcom/test/rtemsTestData.c diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile index f2e495a90..27577f012 100755 --- a/modules/libcom/test/Makefile +++ b/modules/libcom/test/Makefile @@ -241,6 +241,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 @@ -300,3 +305,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(" Date: Mon, 26 Aug 2019 09:45:23 -0700 Subject: [PATCH 12/13] deprecate iocshFindCommand() This function, and struct iocshFuncDef, expose internal details. Specifically iocshCmdDef::func . Which prevents changing/extending the iocsh function signature. Deprecate in favor of iocshCmd() and iocshRun(). --- modules/libcom/src/iocsh/iocsh.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/libcom/src/iocsh/iocsh.h b/modules/libcom/src/iocsh/iocsh.h index 2e8dc225e..6781e2404 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); From c63a564ad455ae94235b1ce0b65c426acf411a58 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 25 Aug 2019 20:30:14 -0700 Subject: [PATCH 13/13] doc --- documentation/RELEASE_NOTES.html | 21 +++++++++++++++++++++ modules/libcom/src/iocsh/iocsh.h | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index df783606d..bdefbe0b8 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -30,6 +30,27 @@ 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 */
+}
+
+

EPICS Release 7.0.2.2

Build System changes

diff --git a/modules/libcom/src/iocsh/iocsh.h b/modules/libcom/src/iocsh/iocsh.h index 6781e2404..a63af64ce 100644 --- a/modules/libcom/src/iocsh/iocsh.h +++ b/modules/libcom/src/iocsh/iocsh.h @@ -84,11 +84,28 @@ 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 */