Merge remote-tracking branch 'lp-Com/iocsherr' into 7.0

* lp-Com/iocsherr:
  doc
  deprecate iocshFindCommand()
  iocshTest start
  iocshCmd() imply "on error break"
  iocsh more error handling
  iocsh handle redirect and similar early errors
  iocsh trap arg. parsing errors
  iocsh further on error
  Fixed logic errors and added some prompts.
  use iocshSetError()
  iocsh allow setting of error code
  iocsh control error behavior
  iocsh catch exceptions

# Conflicts:
#	documentation/RELEASE_NOTES.html
This commit is contained in:
Michael Davidsaver
2019-10-06 20:10:09 -07:00
16 changed files with 388 additions and 46 deletions
+20
View File
@@ -30,6 +30,26 @@ release.</p>
-->
<h3>Iocsh &quot;on error ...&quot;</h3>
<p>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 <pre>iocshSetError(int)</pre>
A script may be prefixed with eg. &quot;on error break&quot;
to stop at the failed command.</p>
<pre>
on error continue | break | wait [value] | halt
</pre>
<p>A suggested form for IOC shell commands is:</p>
<pre>
static void doSomethingCallFunc(const iocshArgBuf *args)
{
iocshSetError(doSomething(...)); /* return 0 == success */
}
</pre>
<h3>Channel Access Security: Check Hostname Against DNS</h3>
+1 -1
View File
@@ -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 */
+2 -2
View File
@@ -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 */
@@ -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));
}
@@ -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
@@ -275,7 +275,7 @@ static const iocshFuncDef rrddFuncDef =
{"$subname", 1, rrddArgs};
static void rrddCallFunc(const iocshArgBuf *)
{
$subname(*iocshPpdbbase);
iocshSetError($subname(*iocshPpdbbase));
}
} // extern "C"
+180 -28
View File
@@ -11,6 +11,8 @@
/* Heavily modified by Eric Norum Date: 03MAY2000 */
/* Adapted to C++ by Eric Norum Date: 18DEC2000 */
#include <exception>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
@@ -18,6 +20,7 @@
#include <errno.h>
#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, "<telnet>") == 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 <delay>]\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 <delay>]\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" */
+20 -1
View File
@@ -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);
+1 -1
View File
@@ -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");
}
}
+8
View File
@@ -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)
+131
View File
@@ -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 <string>
#include <fstream>
#include <set>
#include <osiUnistd.h>
#include <osiFileName.h>
#include <dbDefs.h>
#include <iocsh.h>
#include <libComRegister.h>
#include <epicsUnitTest.h>
#include <testMain.h>
namespace {
void findTestData()
{
const char *locations[] = {
".",
"..",
".." OSI_PATH_LIST_SEPARATOR "O.Common",
"O.Common",
};
for(size_t i=0; i<NELEMENTS(locations); i++) {
std::string path(locations[i]);
path += OSI_PATH_SEPARATOR "iocshTestSuccess.cmd";
std::ifstream strm(path.c_str());
if(strm.good()) {
testDiag("Found test data in %s", locations[i]);
if(chdir(locations[i])!=0)
testAbort("Unable to cd \"%s\"", locations[i]);
return;
}
}
testAbort("Failed to find test data");
}
void testFile(const char *fname, bool expect=true)
{
testDiag("run \"%s\"", fname);
int err = iocsh(fname);
testOk((err==0) ^ (!expect), "%s \"%s\" -> %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<std::string> 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("<iocshTestSuccess.cmd");
testPosition("success");
reached.clear();
testCmd("no_such_command", false);
testFile("no_such_file.cmd", false);
testCmd("<no_such_file.cmd", false);
testCmd("assert 0");
testCmd("assert 1", false);
testDiag("Check argument parsing errors");
// iocshArgDouble
testCmd("epicsThreadSleep foo", false);
// iocshArgInt
testCmd("epicsMutexShowAll foo", false);
testDiag("successful indirection");
testFile("iocshTestSuccessIndirect.cmd");
testPosition("success");
testPosition("after_success");
reached.clear();
testFile("iocshTestBadArgIndirect.cmd", false);
testPosition("before_error");
testPosition("after_error", false);
testPosition("after_error_1", false);
reached.clear();
return testDone();
}
+4
View File
@@ -0,0 +1,4 @@
on error break
position before_error
epicsThreadSleep bad
position after_error
@@ -0,0 +1,3 @@
on error break
<iocshTestBadArg.cmd
position after_error_1
+8
View File
@@ -0,0 +1,8 @@
on error break
help
echo This works
pwd
epicsThreadSleep 0.0
epicsThreadSleep 0
epicsThreadSleep
position success
@@ -0,0 +1,3 @@
on error break
<iocshTestSuccess.cmd
position after_success
-6
View File
@@ -1,6 +0,0 @@
#include "epicsMemFs.h"
/* no local files needed for these tests,
* so skip local FS setup
*/
const epicsMemFS *epicsRtemsFSImage = NULL;