Files
epics-base/modules/libcom/src/iocsh/iocsh.cpp
2025-11-28 11:51:43 +01:00

1621 lines
49 KiB
C++

/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* iocsh.cpp */
/* Author: Marty Kraimer Date: 27APR2000 */
/* Heavily modified by Eric Norum Date: 03MAY2000 */
/* Adapted to C++ by Eric Norum Date: 18DEC2000 */
#include <exception>
#include <vector>
#include <map>
#include <string>
#include <sstream>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#define EPICS_PRIVATE_API
// Recent readline.h uses printf in an attribute
#define epicsStdioStdStreams
#define epicsStdioStdPrintfEtc
#include "epicsReadline.h"
#include "epicsReadlinePvt.h"
#if EPICS_COMMANDLINE_LIBRARY == EPICS_COMMANDLINE_LIBRARY_READLINE
# include <readline/readline.h>
# include <readline/history.h>
# define USE_READLINE
/* libedit also provides readline.h, but isn't fully compatible with
* GNU readline. It also doesn't specifically identify itself.
*
* libedit pretends to be GNU readline 4.2 (circa 2001), but lacks
* some definitions found in that GNU release, but has others not
* added until later GNU releases.
*/
# if RL_READLINE_VERSION == 0x402 && !defined(RL_STATE_NONE)
/* actual GNU readline 4.2 defines RL_STATE_NONE, but libedit.h
* circa libedit-39 (aka. CVS 1.33) does not. It also does not
* provide rl_basic_quote_characters
*/
static const char *rl_basic_quote_characters;
# endif
#endif
#include "epicsMath.h"
#include "errlog.h"
#include "macLib.h"
#include "epicsStdio.h"
#include "epicsString.h"
#include "epicsStdlib.h"
#include "epicsThread.h"
#include "epicsMutex.h"
#include "envDefs.h"
#include "registry.h"
#include "cantProceed.h"
#include "iocsh.h"
extern "C" {
/*
* Global link to pdbbase
*/
struct dbBase **iocshPpdbbase;
/*
* File-local information
*/
struct iocshCommand {
iocshCmdDef def;
struct iocshCommand *next;
};
static struct iocshCommand *iocshCommandHead;
static char iocshCmdID[] = "iocshCmd";
struct iocshVariable {
iocshVarDef const *pVarDef;
struct iocshVariable *next;
};
static struct iocshVariable *iocshVariableHead;
static char iocshVarID[] = "iocshVar";
extern "C" { static void varCallFunc(const iocshArgBuf *); }
static epicsMutexId iocshTableMutex;
static epicsThreadPrivateId iocshContextId;
static void iocshInit (void);
/*
* I/O redirection
*/
namespace {
struct iocshRedirect {
// pointer within the current line buffer
const char *name;
// a static string constant: "a", "r", or "w"
const char *mode;
FILE *fp;
FILE *oldFp;
bool mustRestore;
iocshRedirect()
:name(NULL)
,mode(NULL)
,fp(NULL)
,oldFp(NULL)
,mustRestore(false)
{}
~iocshRedirect() {
close();
}
void close() {
if(fp) {
(void)fclose(fp);
fp = NULL;
}
}
};
} // namespace
/*
* Lock the table mutex
*/
static void
iocshTableLock (void)
{
iocshInit();
epicsMutexMustLock (iocshTableMutex);
}
/*
* Unlock the table mutex
*/
static void
iocshTableUnlock (void)
{
epicsMutexUnlock (iocshTableMutex);
}
/*
* Register a command
*/
static
void iocshRegisterImpl (const iocshFuncDef *piocshFuncDef,
iocshCallFunc func)
{
struct iocshCommand *l, *p, *n;
int i;
for (l = NULL, p = iocshCommandHead ; p != NULL ; l = p, p = p->next) {
i = strcmp (piocshFuncDef->name, p->def.pFuncDef->name);
if (i == 0) {
p->def.pFuncDef = piocshFuncDef;
p->def.func = func;
return;
}
if (i < 0)
break;
}
n = (struct iocshCommand *) callocMustSucceed (1, sizeof *n,
"iocshRegister");
if (!registryAdd(iocshCmdID, piocshFuncDef->name, (void *)n)) {
free (n);
errlogPrintf ("iocshRegister failed to add %s\n", piocshFuncDef->name);
return;
}
if (l == NULL) {
n->next = iocshCommandHead;
iocshCommandHead = n;
}
else {
n->next = l->next;
l->next = n;
}
n->def.pFuncDef = piocshFuncDef;
n->def.func = func;
}
void epicsStdCall iocshRegister (const iocshFuncDef *piocshFuncDef,
iocshCallFunc func)
{
iocshTableLock ();
iocshRegisterImpl (piocshFuncDef, func);
iocshTableUnlock ();
}
/*
* Retrieves a previously registered function with the given name.
*/
const iocshCmdDef * epicsStdCall iocshFindCommand(const char *name)
{
return (iocshCmdDef *) registryFind(iocshCmdID, name);
}
static void
showError (const char *filename, int lineno, const char *msg, ...)
{
va_list ap;
va_start (ap, msg);
if (filename)
fprintf(epicsGetStderr(), ERL_ERROR " %s line %d: ", filename, lineno);
vfprintf (epicsGetStderr(), msg, ap);
fputc ('\n', epicsGetStderr());
va_end (ap);
}
char** (*iocshCompleteRecord)(const char *word) = NULL;
namespace {
struct Tokenize {
// holds pointers to the most recently split() line buffer
std::vector<const char*> argv;
typedef std::map<int, iocshRedirect> redirects_t;
redirects_t redirects;
iocshRedirect* redirect;
bool noise;
Tokenize()
:redirect(NULL)
,noise(true)
{}
~Tokenize()
{
stopRedirect();
}
// if split()==true then argv.size()>=1 where
// argv[size()-1] is always a NULL.
size_t size() const {
return argv.empty() ? 0u : argv.size()-1u;
}
bool empty() const {
return argv.size()<=1u;
}
bool is_redirected(int fd) const {
redirects_t::const_iterator it = redirects.find(fd);
return it!=redirects.end() && !!it->second.name;
}
/* Break line into words.
* Stores pointers to line buffer this->argv and this->redirects[].name
*/
bool split(const char *filename, int lineno, char *line, int& icin) {
argv.clear();
redirects.clear();
redirect = NULL;
int icout = 0;
bool inword = false;
bool backslash = false;
char quote = 0;
for (;;) {
char c = line[icin++];
if (c == '\0')
break;
bool sep = !quote && !backslash && (strchr (" \t(),\r", c));
if (!quote && !backslash) {
int redirectFd = 1;
if (c == '\\') {
backslash = true;
continue;
}
if (c == '<') {
if (redirect != NULL) {
break;
}
redirect = &redirects[0];
sep = 1;
redirect->mode = "r";
}
if ((c >= '1') && (c <= '9') && (line[icin] == '>')) {
redirectFd = c - '0';
c = '>';
icin++;
}
if (c == '>') {
if (redirect != NULL)
break;
redirect = &redirects[redirectFd];
sep = 1;
if (line[icin] == '>') {
icin++;
redirect->mode = "a";
}
else {
redirect->mode = "w";
}
}
}
if (inword) {
if (c == quote && !backslash) {
quote = 0;
}
else {
if (!quote && !backslash) {
if (sep) {
inword = false;
// this "closes" a sub-string which was previously
// stored in argv[] or redirects[].name
line[icout++] = '\0';
}
else if ((c == '"') || (c == '\'')) {
quote = c;
}
else {
line[icout++] = c;
}
}
else {
line[icout++] = c;
if (c == '\\' && !backslash) {
backslash = true;
continue;
}
}
}
}
else {
if (!sep) {
if (((c == '"') || (c == '\'')) && !backslash) {
quote = c;
}
if (redirect != NULL) {
if (redirect->name) { // double redirect?
return false;
}
redirect->name = line + icout;
redirect = NULL;
}
else {
argv.push_back(line + icout);
}
if (!quote)
line[icout++] = c;
inword = true;
}
}
backslash = false;
}
if (inword)
line[icout++] = '\0';
if (redirect != NULL) {
if(noise)
showError(filename, lineno, ANSI_RED("Invalid redirection."));
return true;
}
if (quote) {
if(noise)
showError(filename, lineno, ANSI_RED("Unbalanced quote."));
return true;
}
if (backslash) {
if(noise)
showError(filename, lineno, ANSI_RED("Trailing backslash."));
return true;
}
argv.push_back(NULL);
return false;
}
int openRedirect(const char *filename, int lineno)
{
for(redirects_t::iterator it = redirects.begin(), end = redirects.end();
it!=end; ++it)
{
iocshRedirect *redirect = &it->second;
if(!redirect->name)
continue;
redirect->fp = fopen(redirect->name, redirect->mode);
if (redirect->fp == NULL) {
int err = errno;
showError(filename, lineno, ANSI_RED("Can't open '%s': %s"),
redirect->name, strerror(err));
redirect->name = NULL;
// caller will clear tok.redirects
return -1;
}
redirect->mustRestore = false;
}
return 0;
}
void startRedirect()
{
for(redirects_t::iterator it = redirects.begin(), end = redirects.end();
it!=end; ++it)
{
iocshRedirect *redirect = &it->second;
if(!redirect->fp)
continue;
switch(it->first) {
case 0:
redirect->oldFp = epicsGetThreadStdin();
epicsSetThreadStdin(redirect->fp);
redirect->mustRestore = true;
break;
case 1:
redirect->oldFp = epicsGetThreadStdout();
epicsSetThreadStdout(redirect->fp);
redirect->mustRestore = true;
break;
case 2:
redirect->oldFp = epicsGetThreadStderr();
epicsSetThreadStderr(redirect->fp);
redirect->mustRestore = true;
break;
}
}
}
void stopRedirect()
{
for(redirects_t::iterator it = redirects.begin(), end = redirects.end();
it!=end; ++it)
{
iocshRedirect *redirect = &it->second;
if(!redirect->fp)
continue;
if (redirect->mustRestore) {
switch(it->first) {
case 0: epicsSetThreadStdin(redirect->oldFp); break;
case 1: epicsSetThreadStdout(redirect->oldFp); break;
case 2: epicsSetThreadStderr(redirect->oldFp); break;
}
redirect->mustRestore = false;
}
redirect->close();
redirect->name = NULL;
}
}
};
#ifdef USE_READLINE
char* iocsh_complete_command(const char* word, int notfirst)
{
// ick! ... readline is not re-entrant anyway
static const iocshCommand *next;
if(!notfirst) { // aka. first call
next = iocshCommandHead;
}
const size_t wlen = strlen(word);
while(next) {
const iocshCommand *cur = next;
next = next->next;
if(strncmp(word, cur->def.pFuncDef->name, wlen)==0) {
return strdup(cur->def.pFuncDef->name);
}
}
return NULL;
}
char* iocsh_complete_variable(const char* word, int notfirst)
{
// ick! ... readline is not re-entrant anyway
static const iocshVariable *next;
if(!notfirst) { // aka. first call
next = iocshVariableHead;
}
const size_t wlen = strlen(word);
while(next) {
const iocshVariable *cur = next;
next = next->next;
if(strncmp(word, cur->pVarDef->name, wlen)==0) {
return strdup(cur->pVarDef->name);
}
}
return NULL;
}
char** iocsh_attempt_completion(const char* word, int start, int end)
{
const char *line = rl_line_buffer;
if(!line || !word || start<0 || end <0 || start>end)
return NULL; // paranoia
// skip any leading space
while(isspace(*line)) {
line++;
start--;
end--;
}
if(start==0) { // complete command name
return rl_completion_matches(word, iocsh_complete_command);
} else { // complete some argument
// make a copy as split() will insert nils
size_t linelen = strlen(line);
std::vector<char> lbuf(linelen+1u);
memcpy(&lbuf[0], line, linelen);
lbuf[linelen] = '\0';
int pos = 0;
while(isspace(lbuf[pos]))
pos++;
Tokenize tokenize;
// don't complain about "Unbalanced quote when completing
tokenize.noise = false;
bool err = tokenize.split(NULL, -1, &lbuf[0], pos);
if(!err)
err = tokenize.empty() && tokenize.redirects.empty();
// look up command name
iocshCmdDef *def = NULL;
if(!err)
err = !(def = (iocshCmdDef *) registryFind(iocshCmdID, tokenize.argv[0]));
// Find out which argument 'start' is.
// arg is index into tokenize.argv[]
// argv[0] is command name, argv[1] would be first argument.
size_t arg;
if(!err) {
for(arg=1; arg<tokenize.size(); arg++) {
// BUG: does not work with eg. 'dbpr("X")' as split()
// has rewritten lbuf to make this 'dbpr "X"'
size_t soffset = tokenize.argv[arg]-&lbuf[0];
size_t eoffset = soffset + strlen(tokenize.argv[arg]);
if(soffset >= size_t(start) && eoffset <= size_t(end)) {
break;
}
}
if(arg-1u >= size_t(def->pFuncDef->nargs)) {
if(def->pFuncDef->arg
&& def->pFuncDef->nargs
&& def->pFuncDef->arg[def->pFuncDef->nargs-1u]->type == iocshArgArgv)
{
// last argument is variable length
arg = def->pFuncDef->nargs;
} else {
err = true;
}
}
}
if(!err) {
// we are being asked to complete a valid command,
// for which we have split argument strings.
const iocshArg * argdef = def->pFuncDef->arg[arg-1u];
// known argument type
rl_attempted_completion_over = 1;
if(argdef->type==iocshArgStringRecord && iocshCompleteRecord) {
return (*iocshCompleteRecord)(word);
} else if(argdef->type==iocshArgStringPath) {
// use default completion (filesystem)
rl_attempted_completion_over = 0;
} else if(argdef->type==iocshArgPdbbase) {
char **ret = (char**)calloc(2, sizeof(*ret));
if(ret)
ret[0] = strdup("pdbbase");
return ret;
} else if(arg==1 && strcmp(def->pFuncDef->name, "help")==0) {
return rl_completion_matches(word, iocsh_complete_command);
} else if(arg==1 && strcmp(def->pFuncDef->name, "var")==0) {
return rl_completion_matches(word, iocsh_complete_variable);
}
}
return NULL;
}
}
#endif
struct ReadlineContext {
void *context;
#ifdef USE_READLINE
// readline on BSD/OSX missing 'rl_completion_func_t' typedef, and 'const'
char* prev_rl_readline_name;
char* prev_rl_basic_word_break_characters;
char* prev_rl_completer_word_break_characters;
char* prev_rl_basic_quote_characters;
char* prev_rl_completer_quote_characters;
//rl_completion_func_t* prev_rl_attempted_completion_function;
char** (*prev_rl_attempted_completion_function)(const char* word, int start, int end);
#endif
ReadlineContext()
:context(NULL)
{}
bool setup(FILE *fp) {
context = epicsReadlineBegin(fp);
#ifdef USE_READLINE
if(context) {
prev_rl_readline_name = (char*)rl_readline_name;
prev_rl_basic_word_break_characters = (char*)rl_basic_word_break_characters;
prev_rl_completer_word_break_characters = (char*)rl_completer_word_break_characters;
prev_rl_basic_quote_characters = (char*)rl_basic_quote_characters;
prev_rl_completer_quote_characters = (char*)rl_completer_quote_characters;
prev_rl_attempted_completion_function = rl_attempted_completion_function;
rl_readline_name = (char*)"iocsh";
rl_basic_word_break_characters = (char*)"\t (),";
rl_completer_word_break_characters = (char*)"\t (),";
rl_basic_quote_characters = (char*)"\"";
rl_completer_quote_characters = (char*)"\"";
rl_attempted_completion_function = &iocsh_attempt_completion;
rl_bind_key('\t', rl_complete);
compute_hist_file();
if(!hist_file.empty()) {
if(int err = read_history(hist_file.c_str())) {
if(err!=ENOENT)
fprintf(epicsGetStderr(),
ERL_ERROR " %s (%d) loading '%s'\n",
strerror(err), err, hist_file.c_str());
}
stifle_history(1024); // some limit...
}
}
#endif
return context;
}
~ReadlineContext() {
if(context) {
#ifdef USE_READLINE
if(!hist_file.empty()) {
if(int err = write_history(hist_file.c_str())) {
fprintf(epicsGetStderr(),
ERL_ERROR " %s (%d) writing '%s'\n",
strerror(err), err, hist_file.c_str());
}
}
rl_readline_name = prev_rl_readline_name;
rl_basic_word_break_characters = prev_rl_basic_word_break_characters;
rl_completer_word_break_characters = prev_rl_completer_word_break_characters;
rl_basic_quote_characters = prev_rl_basic_quote_characters;
rl_completer_quote_characters = prev_rl_completer_quote_characters;
rl_attempted_completion_function = prev_rl_attempted_completion_function;
// cf. osdReadlineBegin() in gnuReadline.c
rl_bind_key('\t', rl_insert);
#endif
epicsReadlineEnd(context);
}
}
#ifdef USE_READLINE
std::string hist_file;
void compute_hist_file() {
std::string scratch;
if(const char *env = getenv("EPICS_IOCSH_HISTFILE")) {
scratch = env;
} else {
scratch = ".iocsh_history";
}
const char *home = getenv("HOME");
if(home && scratch.size()>=2 && scratch[0]=='~' && scratch[1]=='/') {
std::ostringstream strm;
strm<<home<<'/'<<scratch.substr(2);
scratch = strm.str();
}
hist_file.swap(scratch);
}
#endif // USE_READLINE
};
} // namespace
/*
* Register the "var" command and any variable(s)
*/
static const iocshArg varCmdArg0 = { "[variable", iocshArgString};
static const iocshArg varCmdArg1 = { "[value]]", iocshArgString};
static const iocshArg *varCmdArgs[2] = {&varCmdArg0, &varCmdArg1};
static const iocshFuncDef varFuncDef = {"var", 2, varCmdArgs,
"Print all, print single variable or set value to single variable\n"
" (default) - print all variables and their values"
" defined in database definitions files\n"
" variable - if only parameter print value for this variable\n"
" value - set the value to variable\n"};
void epicsStdCall iocshRegisterVariable (const iocshVarDef *piocshVarDef)
{
struct iocshVariable *l, *p, *n;
int i;
int found;
iocshTableLock ();
while ((piocshVarDef != NULL)
&& (piocshVarDef->name != NULL)
&& (*piocshVarDef->name != '\0')) {
if (iocshVariableHead == NULL)
iocshRegister(&varFuncDef,varCallFunc);
found = 0;
for (l = NULL, p = iocshVariableHead ; p != NULL ; l = p, p = p->next) {
i = strcmp (piocshVarDef->name, p->pVarDef->name);
if (i == 0) {
if ((p->pVarDef->type != piocshVarDef->type) && (p->pVarDef->pval != piocshVarDef->pval)) {
errlogPrintf("Warning: iocshRegisterVariable redefining %s.\n",
piocshVarDef->name);
p->pVarDef = piocshVarDef;
}
found = 1;
break;
}
if (i < 0)
break;
}
if (!found) {
n = (struct iocshVariable *) callocMustSucceed(1, sizeof *n,
"iocshRegisterVariable");
if (!registryAdd(iocshVarID, piocshVarDef->name, (void *)n)) {
free(n);
iocshTableUnlock();
errlogPrintf("iocshRegisterVariable failed to add %s.\n",
piocshVarDef->name);
return;
}
if (l == NULL) {
n->next = iocshVariableHead;
iocshVariableHead = n;
}
else {
n->next = l->next;
l->next = n;
}
n->pVarDef = piocshVarDef;
}
piocshVarDef++;
}
iocshTableUnlock ();
}
/*
* Retrieves a previously registered variable with the given name.
*/
const iocshVarDef * epicsStdCall iocshFindVariable(const char *name)
{
struct iocshVariable *temp = (iocshVariable *) registryFind(iocshVarID, name);
return temp ? temp->pVarDef : 0;
}
/*
* Free storage created by iocshRegister/iocshRegisterVariable
*/
void epicsStdCall iocshFree(void)
{
struct iocshCommand *pc;
struct iocshVariable *pv;
iocshTableLock ();
for (pc = iocshCommandHead ; pc != NULL ; ) {
struct iocshCommand * nc = pc->next;
free (pc);
pc = nc;
}
for (pv = iocshVariableHead ; pv != NULL ; ) {
struct iocshVariable *nv = pv->next;
free (pv);
pv = nv;
}
iocshCommandHead = NULL;
iocshVariableHead = NULL;
iocshTableUnlock ();
}
/*
* Parse argument input based on the arg type specified.
* It is worth noting that depending on type this argument may
* be defaulted if a value is not specified. For example, a
* double/int with no value will default to 0 which may allow
* you to add optional arguments to the end of your argument list.
*/
static int
cvtArg (const char *filename, int lineno, char *arg, iocshArgBuf *argBuf,
const iocshArg *piocshArg)
{
char *endp;
switch (piocshArg->type) {
case iocshArgInt:
if (arg && *arg) {
errno = 0;
argBuf->ival = strtol (arg, &endp, 0);
if (errno == ERANGE) {
errno = 0;
argBuf->ival = strtoul (arg, &endp, 0);
if (errno == ERANGE) {
showError(filename, lineno,
ANSI_RED("Integer '%s' out of range."), arg);
return 0;
}
}
if (*endp) {
showError(filename, lineno,
ANSI_RED("Invalid integer '%s'."), arg);
return 0;
}
}
else {
argBuf->ival = 0;
}
break;
case iocshArgDouble:
if (arg && *arg) {
argBuf->dval = epicsStrtod (arg, &endp);
if (*endp) {
showError(filename, lineno,
ANSI_RED("Invalid double '%s'."), arg);
return 0;
}
}
else {
argBuf->dval = 0.0;
}
break;
case iocshArgString:
case iocshArgStringRecord:
case iocshArgStringPath:
argBuf->sval = arg;
break;
case iocshArgPersistentString:
if (arg != NULL) {
size_t slen = strlen(arg);
argBuf->sval = (char *) malloc(slen + 1);
if (argBuf->sval == NULL) {
showError(filename, lineno, ANSI_RED("Out of memory!"));
return 0;
}
strncpy(argBuf->sval, arg, slen);
} else {
argBuf->sval = NULL;
}
break;
case iocshArgPdbbase:
/* Argument must be missing or 0 or pdbbase */
if(!arg || !*arg || (*arg == '0') || (strcmp(arg, "pdbbase") == 0)) {
if(!iocshPpdbbase || !*iocshPpdbbase) {
showError(filename, lineno, ANSI_RED("pdbbase not set!"));
return 0;
}
argBuf->vval = *iocshPpdbbase;
break;
}
showError(filename, lineno,
ANSI_RED("Expecting 'pdbbase' got '%s'."), arg);
return 0;
default:
showError(filename, lineno,
ANSI_RED("Invalid argument type %d."), piocshArg->type);
return 0;
}
return 1;
}
/*
* "help" command
*/
static const iocshArg helpArg0 = { "[command ...]",iocshArgArgv};
static const iocshArg *helpArgs[1] = {&helpArg0};
static const iocshFuncDef helpFuncDef =
{"help",1,helpArgs,
"With no arguments, list available command names.\n"
"With arguments, list arguments and usage for command(s).\n"
"Command names may contain wildcards\n"};
static void helpCallFunc(const iocshArgBuf *args)
{
int argc = args[0].aval.ac;
const char * const * argv = args[0].aval.av;
struct iocshFuncDef const *piocshFuncDef;
struct iocshCommand *pcmd;
if (argc == 1) {
int l, col = 0;
iocshTableLock ();
for (pcmd = iocshCommandHead ; pcmd != NULL ; pcmd = pcmd->next) {
piocshFuncDef = pcmd->def.pFuncDef;
l = strlen (piocshFuncDef->name);
if ((l + col) >= 79) {
fputc('\n', epicsGetStdout());
col = 0;
}
fputs(piocshFuncDef->name, epicsGetStdout());
col += l;
if (col >= 64) {
fputc('\n', epicsGetStdout());
col = 0;
}
else {
do {
fputc(' ', epicsGetStdout());
col++;
} while ((col % 16) != 0);
}
}
if (col)
fputc('\n', epicsGetStdout());
iocshTableUnlock ();
fprintf(epicsGetStdout(), "\n"
"Type 'help <glob>' for information about commands matching\n"
"the name or pattern <glob>, e.g. 'help db*'\n");
}
else {
bool firstFunction = true;
for (int iarg = 1 ; iarg < argc ; iarg++) {
for (pcmd = iocshCommandHead ; pcmd != NULL ; pcmd = pcmd->next) {
piocshFuncDef = pcmd->def.pFuncDef;
if (epicsStrGlobMatch(piocshFuncDef->name, argv[iarg]) != 0) {
if (! firstFunction) {
fprintf(epicsGetStdout(),
ANSI_UNDERLINE(" ")
"\n");
}
fprintf(epicsGetStdout(), "\n" ANSI_BOLD("%s"),
piocshFuncDef->name);
for (int a = 0 ; a < piocshFuncDef->nargs ; a++) {
const char *cp = piocshFuncDef->arg[a]->name;
if ((piocshFuncDef->arg[a]->type == iocshArgArgv)
|| (strchr (cp, ' ') == NULL)) {
fprintf(epicsGetStdout(), " %s", cp);
}
else {
fprintf(epicsGetStdout(), " '%s'", cp);
}
}
fprintf(epicsGetStdout(),"\n");
if(piocshFuncDef->usage) {
fprintf(epicsGetStdout(), "\n%s", piocshFuncDef->usage);
}
firstFunction = false;
}
}
}
}
}
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
*/
static int
iocshBody (const char *pathname, const char *commandLine, const char *macros)
{
FILE *fp = NULL;
const char *filename = NULL;
int icin;
char c;
const char *raw = NULL;;
char *line = NULL;
int lineno = 0;
const char *prompt = NULL;
std::vector<iocshArgBuf> argBuf;
struct iocshCommand *found;
ReadlineContext readline;
int wasOkToBlock;
static const char * pairs[] = {"", "environ", NULL, NULL};
iocshScope scope;
iocshContext *context;
char ** defines = NULL;
int ret = 0;
Tokenize tokenize;
iocshInit();
/*
* See if command interpreter is interactive
*/
if (commandLine == NULL) {
if ((pathname == NULL) || (strcmp (pathname, "<telnet>") == 0)) {
if ((prompt = envGetConfigParamPtr(&IOCSH_PS1)) == NULL) {
prompt = ANSI_GREEN("epics> ");
}
scope.interactive = true;
}
else {
fp = fopen (pathname, "r");
if (fp == NULL) {
fprintf(epicsGetStderr(),
ANSI_RED("Can't open %s: %s") "\n", pathname,
strerror (errno));
return -1;
}
if ((filename = strrchr (pathname, '/')) == NULL)
filename = pathname;
else
filename++;
prompt = NULL;
}
/*
* Create a command-line input context
*/
if (!readline.setup(fp)) {
fprintf(epicsGetStderr(),
ANSI_RED("Can't allocate command-line object!") "\n");
if (fp)
fclose(fp);
return -1;
}
} else {
// use of iocshCmd() implies "on error break"
scope.onerr = Break;
}
/*
* Parse macro definitions, this check occurs before creating the
* macro handle to simplify cleanup.
*/
if (macros) {
if (macParseDefns(NULL, macros, &defines) < 0) {
return -1;
}
}
// 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(context);
return -1;
}
epicsThreadPrivateSet(iocshContextId, (void *) context);
}
MAC_HANDLE *handle = context->handle;
scope.outer = context->scope;
context->scope = &scope;
macPushScope(handle);
macInstallMacros(handle, defines);
wasOkToBlock = epicsThreadIsOkToBlock();
epicsThreadSetOkToBlock(1);
/*
* 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
*/
if (commandLine) {
if (raw != NULL)
break;
raw = commandLine;
}
else {
if ((raw = epicsReadline(prompt, readline.context)) == NULL)
break;
}
lineno++;
/*
* Skip leading white-space
*/
icin = 0;
while ((c = raw[icin]) && isspace(c)) {
icin++;
}
/*
* Ignore comment lines other than to echo
* them if they came from a script (disable echoing
* with '#-'). This avoids macLib errors from comments.
*/
if (c == '#') {
if ((prompt == NULL) && (commandLine == NULL))
if (raw[icin + 1] != '-') {
fprintf(epicsGetStdout(), ANSI_BLUE("%s") "\n", raw);
}
continue;
}
/*
* Expand macros
*/
free(line);
if ((line = macDefExpand(raw, handle)) == NULL) {
scope.errored = true;
continue;
}
/*
* Skip leading white-space coming from a macro
*/
while ((c = line[icin]) && isspace(c)) {
icin++;
}
/*
* 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] != '-')) {
fprintf(epicsGetStdout(), ANSI_BOLD("%s") "\n", line);
}
}
/*
* Ignore lines that became a comment or empty after macro expansion
*/
if (!c || c == '#')
continue;
/*
* Break line into words
*/
if (tokenize.split(filename, lineno, line, icin)) {
scope.errored = true;
continue;
} else if (tokenize.empty() && tokenize.redirects.empty()) {
break;
}
/*
* Special case -- Redirected input but no command
* Treat as if 'iocsh filename'.
*/
if (tokenize.empty() && tokenize.is_redirected(0)) {
const char* commandFile = tokenize.redirects[0].name;
tokenize.redirects[0].name = NULL;
if (tokenize.openRedirect(filename, lineno) < 0)
continue;
tokenize.startRedirect();
if(iocshBody(commandFile, NULL, macros))
scope.errored = true;
tokenize.stopRedirect();
continue;
}
/*
* Special command?
*/
if (!tokenize.empty() && (strcmp(tokenize.argv[0], "exit") == 0))
break;
/*
* Set up redirection
*/
if ((tokenize.openRedirect(filename, lineno) == 0) && !tokenize.empty()) {
// error unless a function is actually called.
// handles command not-found and arg parsing errors.
scope.errored = true;
/*
* Look up command
*/
found = (iocshCommand *)registryFind (iocshCmdID, tokenize.argv[0]);
if (found) {
/*
* Process arguments and call function
*/
struct iocshFuncDef const *piocshFuncDef = found->def.pFuncDef;
// argBuf resize() with one extra so that &argBuf[0] doesn't trigger
// windows debug assert.
argBuf.resize(size_t(piocshFuncDef->nargs)+1u);
for (size_t iarg = 0 ; ; ) {
if (iarg == size_t(piocshFuncDef->nargs)) {
tokenize.startRedirect();
/* execute */
scope.errored = false;
try {
(*found->def.func)(&argBuf[0]);
} catch(std::exception& e){
fprintf(epicsGetStderr(),
ANSI_RED("C++ error: %s") "\n", e.what());
scope.errored = true;
} catch(...) {
fprintf(epicsGetStderr(),
ANSI_RED("C++ error unknown.") "\n");
scope.errored = true;
}
break;
}
if (piocshFuncDef->arg[iarg]->type == iocshArgArgv) {
argBuf[iarg].aval.ac = tokenize.size()-iarg;
argBuf[iarg].aval.av = const_cast<char**>(&tokenize.argv[iarg]);
iarg = piocshFuncDef->nargs;
}
else {
if (!cvtArg (filename, lineno,
((iarg < tokenize.size()) ? const_cast<char*>(tokenize.argv[iarg+1]) : NULL),
&argBuf[iarg], piocshFuncDef->arg[iarg]))
break;
iarg++;
}
}
if ((prompt != NULL) && (strcmp(tokenize.argv[0], "epicsEnvSet") == 0)) {
const char *newPrompt;
if ((newPrompt = envGetConfigParamPtr(&IOCSH_PS1)) != NULL)
prompt = newPrompt;
}
}
else {
showError(filename, lineno,
ANSI_RED("Command '%s' not registered."), tokenize.argv[0]);
}
}
tokenize.stopRedirect();
}
macPopScope(handle);
if (!scope.outer) {
macDeleteHandle(handle);
free(context);
epicsThreadPrivateSet(iocshContextId, NULL);
} else {
context->scope = scope.outer;
}
if (fp && (fp != stdin))
fclose (fp);
free(line);
errlogFlush();
epicsThreadSetOkToBlock(wasOkToBlock);
return ret;
}
/*
* External access to the command interpreter
*/
int epicsStdCall
iocsh (const char *pathname)
{
return iocshLoad(pathname, NULL);
}
int epicsStdCall
iocshCmd (const char *cmd)
{
return iocshRun(cmd, NULL);
}
int epicsStdCall
iocshLoad(const char *pathname, const char *macros)
{
if (pathname && !getenv("IOCSH_STARTUP_SCRIPT"))
epicsEnvSet("IOCSH_STARTUP_SCRIPT", pathname);
return iocshBody(pathname, NULL, macros);
}
int epicsStdCall
iocshRun(const char *cmd, const char *macros)
{
if (cmd == NULL)
return 0;
return iocshBody(NULL, cmd, macros);
}
/*
* Needed to work around the necessary limitations of macLib and
* environment variables. In every other case of macro expansion
* it is the expected outcome that defined macros override any
* environment variables.
*
* iocshLoad/Run turn this on its head as it is very likely that
* an epicsEnvSet command may be run within the context of their
* calls. Thus, it would be expected that the new value would be
* returned in any future macro expansion.
*
* To do so, the epicsEnvSet command needs to be able to access
* and update the shared MAC_HANDLE that the iocsh uses. Which is
* what this function is provided for.
*/
void epicsStdCall
iocshEnvClear(const char *name)
{
iocshContext *context;
if (iocshContextId) {
context = (iocshContext *) epicsThreadPrivateGet(iocshContextId);
if (context != NULL) {
macPutValue(context->handle, name, NULL);
}
}
}
/*
* Internal commands
*/
static void varHandler(const iocshVarDef *v, const char *setString)
{
switch(v->type) {
default:
fprintf(epicsGetStderr(),
ANSI_RED("Can't handle variable '%s' of type %d.") "\n",
v->name, v->type);
return;
case iocshArgInt: break;
case iocshArgDouble: break;
}
if(setString == NULL) {
switch(v->type) {
default: break;
case iocshArgInt:
fprintf(epicsGetStdout(), "int %s = %d\n", v->name, *(int *)v->pval);
break;
case iocshArgDouble:
fprintf(epicsGetStdout(), "double %s = %g\n", v->name, *(double *)v->pval);
break;
}
}
else {
switch(v->type) {
default: break;
case iocshArgInt:
{
char *endp;
long ltmp = strtol(setString, &endp, 0);
if((*setString != '\0') && (*endp == '\0'))
*(int *)v->pval = ltmp;
else
fprintf(epicsGetStderr(),
ANSI_RED("Invalid integer, var '%s' not changed.") "\n",
v->name);
break;
}
case iocshArgDouble:
{
char *endp;
double dtmp = epicsStrtod(setString, &endp);
if((*setString != '\0') && (*endp == '\0'))
*(double *)v->pval = dtmp;
else
fprintf(epicsGetStderr(),
ANSI_RED("Invalid double, var '%s' not changed.") "\n",
v->name);
break;
}
}
}
}
static void varCallFunc(const iocshArgBuf *args)
{
struct iocshVariable *v;
const char *name = args[0].sval;
const char *value = args[1].sval;
if (!value) {
int found = 0;
for (v = iocshVariableHead ; v != NULL ; v = v->next)
if (!name || epicsStrGlobMatch(v->pVarDef->name, name) != 0) {
varHandler(v->pVarDef, NULL);
found = 1;
}
if (!found && name != NULL) {
fprintf(epicsGetStderr(),
ANSI_RED("No known vars match '%s'.") "\n", name);
iocshSetError(1);
}
}
else {
v = (iocshVariable *)registryFind(iocshVarID, args[0].sval);
if (v == NULL) {
fprintf(epicsGetStderr(),
ANSI_RED("No known var '%s'.") "\n", name);
iocshSetError(1);
}
else {
varHandler(v->pVarDef, value);
}
}
}
/* iocshCmd */
static const iocshArg iocshCmdArg0 = { "command",iocshArgString};
static const iocshArg *iocshCmdArgs[1] = {&iocshCmdArg0};
static const iocshFuncDef iocshCmdFuncDef = {"iocshCmd",1,iocshCmdArgs,
"Takes a single IOC shell command and executes it\n"
" * This function is most useful to execute a single IOC shell command\n"
" from vxWorks or RTEMS startup script (or command line)\n"};
static void iocshCmdCallFunc(const iocshArgBuf *args)
{
iocshSetError(iocshCmd(args[0].sval));
}
/* iocshLoad */
static const iocshArg iocshLoadArg0 = { "pathname",iocshArgString};
static const iocshArg iocshLoadArg1 = { "macros", iocshArgString};
static const iocshArg *iocshLoadArgs[2] = {&iocshLoadArg0, &iocshLoadArg1};
static const iocshFuncDef iocshLoadFuncDef = {"iocshLoad",2,iocshLoadArgs,
"Execute IOC shell commands provided in file from first parameter\n"
" * (optional) replace macros within the file with provided values\n"};
static void iocshLoadCallFunc(const iocshArgBuf *args)
{
iocshSetError(iocshLoad(args[0].sval, args[1].sval));
}
/* iocshRun */
static const iocshArg iocshRunArg0 = { "command",iocshArgString};
static const iocshArg iocshRunArg1 = { "macros", iocshArgString};
static const iocshArg *iocshRunArgs[2] = {&iocshRunArg0, &iocshRunArg1};
static const iocshFuncDef iocshRunFuncDef = {"iocshRun",2,iocshRunArgs,
"Takes a single IOC shell command, replaces macros and executes it\n"
" * This function is most useful to execute a single IOC shell command\n"
" from vxWorks or RTEMS startup script (or command line)\n"};
static void iocshRunCallFunc(const iocshArgBuf *args)
{
iocshSetError(iocshRun(args[0].sval, args[1].sval));
}
/* on */
static const iocshArg onArg0 = { ANSI_BOLD("error") " [continue | break | halt | wait <delay>]", iocshArgArgv };
static const iocshArg *onArgs[1] = {&onArg0};
static const iocshFuncDef onFuncDef = {"on", 1, onArgs,
"Change IOC shell error handling.\n"
" continue (default) - Ignores error and continue with next commands.\n"
" break - Return to caller without executing futher commands.\n"
" halt - Suspend process.\n"
" wait - stall process for <delay> seconds, then continue.\n"};
static void onCallFunc(const iocshArgBuf *args)
{
iocshContext *context = (iocshContext *) epicsThreadPrivateGet(iocshContextId);
#define USAGE() fprintf(epicsGetStderr(), "Usage: " \
ANSI_BOLD("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(),
ANSI_RED("Interactive shell, 'on error' ignored.") "\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(),
ANSI_RED("Invalid 'on error wait' delay '%s'.") "\n",
args->aval.av[3]);
}
} else {
USAGE();
context->scope->errored = true;
}
}
#undef USAGE
}
/*
* Dummy internal commands -- register and install in command table
* so they show up in the help display
*/
/* comment */
static const iocshArg commentArg0 = { "newline-terminated comment",
iocshArgArgv};
static const iocshArg *commentArgs[1] = {&commentArg0};
static const iocshFuncDef commentFuncDef = {"#",1,commentArgs};
static void commentCallFunc(const iocshArgBuf *)
{
}
/* exit */
static const iocshFuncDef exitFuncDef =
{"exit",0,0,
"Return to caller. IOCs exit() from process.\n"};
static void exitCallFunc(const iocshArgBuf *)
{
}
static void iocshOnce (void *)
{
iocshTableMutex = epicsMutexMustCreate ();
iocshContextId = epicsThreadPrivateCreate();
epicsMutexMustLock (iocshTableMutex);
iocshRegisterImpl(&commentFuncDef,commentCallFunc);
iocshRegisterImpl(&exitFuncDef,exitCallFunc);
iocshRegisterImpl(&helpFuncDef,helpCallFunc);
iocshRegisterImpl(&iocshCmdFuncDef,iocshCmdCallFunc);
iocshRegisterImpl(&iocshLoadFuncDef,iocshLoadCallFunc);
iocshRegisterImpl(&iocshRunFuncDef,iocshRunCallFunc);
iocshRegisterImpl(&onFuncDef, onCallFunc);
iocshTableUnlock();
}
static void iocshInit (void)
{
static epicsThreadOnceId iocshOnceId = EPICS_THREAD_ONCE_INIT;
epicsThreadOnce (&iocshOnceId, iocshOnce, NULL);
}
} /* extern "C" */