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 */