// -*- c-basic-offset: 4 -*-
/*
* script.{cc,hh} -- element provides scripting functionality
* Eddie Kohler
*
* Copyright (c) 2001 International Computer Science Institute
* Copyright (c) 2001 Mazu Networks, Inc.
* Copyright (c) 2005-2006 Regents of the University of California
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, subject to the conditions
* listed in the Click LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the Click LICENSE file; the license in that file is
* legally binding.
*/
#include <click/config.h>
#include "script.hh"
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/router.hh>
#include <click/straccum.hh>
#include <click/handlercall.hh>
#include <click/nameinfo.hh>
#if CLICK_USERLEVEL
# include <signal.h>
# include <click/master.hh>
#endif
CLICK_DECLS
static const StaticNameDB::Entry instruction_entries[] = {
{ "end", Script::INSN_END },
{ "exit", Script::INSN_EXIT },
{ "goto", Script::INSN_GOTO },
{ "init", Script::INSN_INIT },
{ "label", Script::INSN_LABEL },
{ "loop", Script::INSN_LOOP_PSEUDO },
{ "pause", Script::INSN_WAIT_STEP },
{ "print", Script::INSN_PRINT },
{ "read", Script::INSN_READ },
{ "return", Script::INSN_RETURN },
{ "set", Script::INSN_SET },
{ "stop", Script::INSN_STOP },
{ "wait", Script::INSN_WAIT_PSEUDO },
{ "wait_for", Script::INSN_WAIT_TIME },
{ "wait_step", Script::INSN_WAIT_STEP },
{ "wait_stop", Script::INSN_WAIT_STEP },
{ "wait_time", Script::INSN_WAIT_TIME },
{ "write", Script::INSN_WRITE }
};
#if CLICK_USERLEVEL
static const StaticNameDB::Entry signal_entries[] = {
{ "ABRT", SIGABRT },
{ "HUP", SIGHUP },
{ "INT", SIGINT },
{ "PIPE", SIGPIPE },
{ "QUIT", SIGQUIT },
{ "TERM", SIGTERM },
{ "TSTP", SIGTSTP },
{ "USR1", SIGUSR1 },
{ "USR2", SIGUSR2 }
};
#endif
static NameDB *dbs[2];
void
Script::static_initialize()
{
dbs[0] = new StaticNameDB(NameInfo::T_SCRIPT_INSN, String(), instruction_entries, sizeof(instruction_entries) / sizeof(instruction_entries[0]));
NameInfo::installdb(dbs[0], 0);
#if CLICK_USERLEVEL
dbs[1] = new StaticNameDB(NameInfo::T_SIGNO, String(), signal_entries, sizeof(signal_entries) / sizeof(signal_entries[0]));
NameInfo::installdb(dbs[1], 0);
#endif
}
void
Script::static_cleanup()
{
NameInfo::removedb(dbs[0]);
delete dbs[0];
#if CLICK_USERLEVEL
NameInfo::removedb(dbs[1]);
delete dbs[1];
#endif
}
Script::Script()
: _type(TYPE_ACTIVE), _write_status(0), _timer(this), _cur_steps(0)
{
}
Script::~Script()
{
}
void
Script::add_insn(int insn, int arg, int arg2, const String &arg3)
{
// first instruction must be WAIT or WAIT_STEP, so add INITIAL if
// necessary
if (_insns.size() == 0 && insn > INSN_WAIT_TIME)
add_insn(INSN_INITIAL, 0);
_insns.push_back(insn);
_args.push_back(arg);
_args2.push_back(arg2);
_args3.push_back(arg3);
}
int
Script::find_label(const String &label) const
{
for (int i = 0; i < _insns.size(); i++)
if (_insns[i] == INSN_LABEL && _args3[i] == label)
return i;
return _insns.size();
}
int
Script::find_variable(const String &varname) const
{
for (int i = 0; i < _vars.size(); i += 2)
if (_vars[i] == varname)
return i;
return _vars.size();
}
int
Script::configure(Vector<String> &conf, ErrorHandler *errh)
{
String type_arg;
if (cp_va_parse_remove_keywords
(conf, 0, this, errh,
"TYPE", cpArgument, "type of script", &type_arg,
cpEnd) < 0)
return -1;
int before = errh->nerrors();
String type_word = cp_pop_spacevec(type_arg);
if (type_word == "ACTIVE" && !type_arg)
_type = TYPE_ACTIVE;
else if (type_word == "PASSIVE" && !type_arg)
_type = TYPE_PASSIVE;
else if (type_word == "DRIVER" && !type_arg)
_type = TYPE_DRIVER;
#if CLICK_USERLEVEL
else if (type_word == "SIGNAL") {
_type = TYPE_SIGNAL;
int32_t signo;
while ((type_word = cp_pop_spacevec(type_arg))
&& NameInfo::query_int(NameInfo::T_SIGNO, this, type_word, &signo)
&& signo >= 0 && signo < 32)
_signos.push_back(signo);
if (type_word || !_signos.size())
return errh->error("bad signal number");
}
#endif
else if (type_word)
return errh->error("bad TYPE; expected 'ACTIVE', 'DRIVER', or (userlevel) 'SIGNAL'");
if (_type == TYPE_DRIVER) {
if (router()->attachment("Script"))
return errh->error("router has more than one Script element");
router()->set_attachment("Script", this);
}
for (int i = 0; i < conf.size(); i++) {
String insn_name = cp_pop_spacevec(conf[i]);
int32_t insn;
if (!insn_name) // ignore as benign
continue;
else if (!NameInfo::query_int(NameInfo::T_SCRIPT_INSN, this, insn_name, &insn)) {
errh->error("syntax error at '%s'", insn_name.c_str());
continue;
}
switch (insn) {
case INSN_WAIT_PSEUDO:
if (!conf[i])
goto wait_step;
else
goto wait_time;
wait_step:
case INSN_WAIT_STEP: {
unsigned n = 1;
if (!conf[i] || cp_unsigned(conf[i], &n))
add_insn(INSN_WAIT_STEP, n, 0);
else
goto syntax_error;
break;
}
case INSN_WRITE:
case INSN_READ:
case INSN_PRINT:
case INSN_GOTO:
add_insn(insn, 0, 0, conf[i]);
break;
case INSN_RETURN:
conf[i] = "_ " + conf[i];
/* fall through */
case INSN_INIT:
case INSN_SET: {
String word = cp_pop_spacevec(conf[i]);
if (!word || (insn == INSN_SET && !conf[i]))
goto syntax_error;
int x = find_variable(word);
if (x == _vars.size()) {
_vars.push_back(word);
_vars.push_back(String());
}
add_insn(insn, x, 0, conf[i]);
break;
}
wait_time:
case INSN_WAIT_TIME: {
Timestamp ts;
if (cp_time(conf[i], &ts))
add_insn(INSN_WAIT_TIME, ts.sec(), ts.subsec());
else
goto syntax_error;
break;
}
case INSN_LABEL: {
String word = cp_pop_spacevec(conf[i]);
if (!word || conf[i])
goto syntax_error;
add_insn(insn, 0, 0, word);
break;
}
case INSN_LOOP_PSEUDO:
insn = INSN_GOTO;
/* fallthru */
case INSN_END:
case INSN_EXIT:
case INSN_STOP:
if (conf[i])
goto syntax_error;
add_insn(insn, 0);
break;
default:
syntax_error:
errh->error("syntax error at '%s'", insn_name.c_str());
break;
}
}
// fix the gotos
for (int i = 0; i < _insns.size(); i++)
if (_insns[i] == INSN_GOTO && _args3[i]) {
String word = cp_pop_spacevec(_args3[i]);
if ((_args[i] = find_label(word)) >= _insns.size())
errh->error("no such label '%s'", word.c_str());
}
if (_insns.size() == 0 && _type == TYPE_DRIVER)
add_insn(INSN_WAIT_STEP, 1, 0);
add_insn(_type == TYPE_DRIVER ? INSN_STOP : INSN_END, 0);
return (errh->nerrors() == before ? 0 : -1);
}
int
Script::initialize(ErrorHandler *errh)
{
_insn_pos = 0;
_step_count = 0;
_timer.initialize(this);
Expander expander;
expander.script = this;
expander.errh = errh;
for (int i = 0; i < _insns.size(); i++)
if (_insns[i] == INSN_INIT)
_vars[_args[i] + 1] = cp_expand(_args3[i], expander);
int insn = _insns[_insn_pos];
assert(insn <= INSN_WAIT_TIME);
if (_type == TYPE_SIGNAL || _type == TYPE_PASSIVE)
/* passive, do nothing */;
else if (insn == INSN_WAIT_TIME)
_timer.schedule_after(Timestamp(_args[_insn_pos], _args2[_insn_pos]));
else if (insn == INSN_INITIAL) {
// get rid of the initial runcount so we get called right away
if (_type == TYPE_DRIVER)
router()->adjust_runcount(-1);
else
_timer.schedule_now();
_args[0] = 1;
}
#if CLICK_USERLEVEL
if (_type == TYPE_SIGNAL)
for (int i = 0; i < _signos.size(); i++)
master()->add_signal_handler(_signos[i], router(), name() + ".run");
#endif
return 0;
}
int
Script::step(int nsteps, int step_type, int njumps)
{
ErrorHandler *errh = ErrorHandler::default_handler();
ContextErrorHandler cerrh(errh, "While executing '" + declaration() + "':");
Expander expander;
expander.script = this;
expander.errh = &cerrh;
nsteps += _step_count;
while ((nsteps - _step_count) >= 0 && _insn_pos < _insns.size()
&& njumps < MAX_JUMPS) {
// process current instruction
// increment instruction pointer now, in case we call 'goto' directly
// or indirectly
int ipos = _insn_pos++;
int insn = _insns[ipos];
switch (insn) {
case INSN_STOP:
_step_count++;
_insn_pos--;
if (step_type != STEP_ROUTER)
router()->adjust_runcount(-1);
return njumps + 1;
case INSN_WAIT_STEP:
while (_step_count < nsteps && _args2[ipos] < _args[ipos]) {
_args2[ipos]++;
_step_count++;
}
if (_step_count == nsteps && _args2[ipos] < _args[ipos]) {
_insn_pos--;
goto done;
}
break;
case INSN_WAIT_TIME:
if (_step_count == nsteps) {
_timer.schedule_after(Timestamp(_args[ipos], _args2[ipos]));
_insn_pos--;
goto done;
}
_step_count++;
_timer.unschedule();
break;
case INSN_INITIAL:
if (_args[ipos]) {
_step_count++;
_args[ipos] = 0;
}
break;
case INSN_PRINT: {
String text = _args3[ipos];
#if CLICK_USERLEVEL || CLICK_TOOL
FILE *f = stdout;
if (text.length() && text[0] == '>') {
bool append = (text.length() > 1 && text[1] == '>');
text = text.substring(1 + append);
String filename = cp_pop_spacevec(text);
if (filename && filename != "-"
&& !(f = fopen(filename.c_str(), append ? "ab" : "wb"))) {
errh->error("%s: %s", filename.c_str(), strerror(errno));
break;
}
}
#endif
int before = cerrh.nerrors();
String result;
if (text && (isalpha(text[0]) || text[0] == '@' || text[0] == '_')) {
result = cp_expand(text, expander);
result = HandlerCall::call_read(result, this, &cerrh);
} else
result = cp_unquote(cp_expand(text, expander, true));
if (cerrh.nerrors() == before && (!result || result.back() != '\n'))
result += "\n";
#if CLICK_USERLEVEL || CLICK_TOOL
fwrite(result.data(), 1, result.length(), f);
if (f != stdout)
fclose(f);
#else
errh->message("%s", result.c_str());
#endif
break;
}
case INSN_READ: {
HandlerCall hc(cp_expand(_args3[ipos], expander));
if (hc.initialize_read(this, &cerrh) >= 0) {
String result = hc.call_read(errh);
errh->message("%s:\n%s\n", hc.handler()->unparse_name(hc.element()).c_str(), result.c_str());
}
break;
}
case INSN_WRITE: {
HandlerCall hc(cp_expand(_args3[ipos], expander));
if (hc.initialize_write(this, &cerrh) >= 0)
_write_status = hc.call_write(&cerrh);
break;
}
case INSN_RETURN:
case INSN_SET: {
expander.errh = errh;
_vars[_args[ipos] + 1] = cp_expand(_args3[ipos], expander);
if (insn == INSN_RETURN && _insn_pos == ipos + 1) {
_insn_pos--;
goto done;
}
break;
}
case INSN_GOTO: {
// reset intervening instructions
String cond_text = cp_expand(_args3[ipos], expander);
bool cond;
if (cond_text && !cp_bool(cond_text, &cond))
cerrh.error("bad condition '%s'", cond_text.c_str());
else if (!cond_text || cond) {
for (int i = _args[ipos]; i < ipos; i++)
if (_insns[i] == INSN_WAIT_STEP)
_args2[i] = 0;
_insn_pos = _args[ipos];
}
break;
}
case INSN_END:
#if CLICK_USERLEVEL
if (_type == TYPE_SIGNAL)
for (int i = 0; i < _signos.size(); i++)
master()->add_signal_handler(_signos[i], router(), name() + ".run");
#endif
/* fallthru */
case INSN_EXIT:
_insn_pos--;
goto done;
}
if (_insn_pos != ipos + 1)
njumps++;
}
done:
if (njumps >= MAX_JUMPS) {
ErrorHandler::default_handler()->error("%{element}: too many jumps, giving up", this);
_insn_pos = _insns.size();
_timer.unschedule();
}
if (step_type == STEP_ROUTER)
router()->adjust_runcount(1);
return njumps + 1;
}
void
Script::run_timer(Timer *)
{
// called when a timer expires
assert(_insns[_insn_pos] == INSN_WAIT_TIME || _insns[_insn_pos] == INSN_INITIAL);
step(1, STEP_TIMER, 0);
}
bool
Script::Expander::expand(const String &vname, int vartype, int quote, StringAccum &sa)
{
int x = script->find_variable(vname);
if (x < script->_vars.size()) {
sa << cp_expand_in_quotes(script->_vars[x + 1], quote);
return true;
}
if (vname.length() == 1 && vname[0] == '?') {
sa << script->_write_status;
return true;
}
if (vartype == '(') {
HandlerCall hc(vname);
if (hc.initialize_read(script, errh) >= 0) {
sa << cp_expand_in_quotes(hc.call_read(errh), quote);
return true;
}
}
return false;
}
enum {
ST_STEP = 0, ST_RUN, ST_GOTO,
AR_ADD = 0, AR_SUB,
AR_LT, AR_EQ, AR_GT, AR_GE, AR_NE, AR_LE, // order is important
AR_FIRST, AR_NOT, AR_SPRINTF
};
int
Script::step_handler(int, String &str, Element *e, const Handler *h, ErrorHandler *errh)
{
Script *scr = (Script *) e;
String data = cp_uncomment(str);
int nsteps, steptype, x;
int what = (uintptr_t) h->thunk1();
if (what == ST_GOTO) {
int step = scr->find_label(cp_uncomment(data));
if (step >= scr->_insns.size())
return errh->error("jump to nonexistent label");
for (int i = step; i < scr->_insns.size(); i++)
if (scr->_insns[i] == INSN_WAIT_STEP)
scr->_args2[i] = 0;
scr->_insn_pos = step;
nsteps = 0, steptype = STEP_JUMP;
} else if (what == ST_RUN) {
scr->_insn_pos = 0;
nsteps = 0, steptype = STEP_JUMP;
} else {
if (data == "router")
nsteps = 1, steptype = STEP_ROUTER;
else if (!data)
nsteps = 1, steptype = STEP_NORMAL;
else if (cp_integer(data, &nsteps))
steptype = STEP_NORMAL;
else
return errh->error("syntax error");
}
if (!scr->_cur_steps) {
int cur_steps = nsteps, njumps = 0;
scr->_cur_steps = &cur_steps;
while ((nsteps = cur_steps) >= 0) {
cur_steps = -1;
njumps = scr->step(nsteps, steptype, njumps);
steptype = ST_STEP;
}
scr->_cur_steps = 0;
} else if (what == ST_STEP)
*scr->_cur_steps += nsteps;
int last_insn = (scr->_insn_pos < scr->_insns.size() ? scr->_insns[scr->_insn_pos] : -1);
str = String();
if (last_insn == INSN_RETURN)
if ((x = scr->find_variable("_")) < scr->_vars.size())
str = scr->_vars[x + 1];
return (last_insn == INSN_STOP);
}
#if HAVE_INT64_TYPES
# define click_intmax_t int64_t
# define click_uintmax_t uint64_t
# define CLICK_INTMAX_CVT "ll"
#else
# define click_intmax_t int32_t
# define click_uintmax_t uint32_t
# define CLICK_INTMAX_CVT 'l'
#endif
int
Script::arithmetic_handler(int, String &str, Element *, const Handler *h, ErrorHandler *errh)
{
int what = (uintptr_t) h->thunk1();
switch (what) {
case AR_ADD:
case AR_SUB: {
click_intmax_t accum = 0, arg;
bool first = true;
while (1) {
String word = cp_pop_spacevec(str);
if (!word && cp_is_space(str))
break;
if (!cp_integer(word, &arg))
return errh->error("expected list of numbers");
accum += (what == AR_ADD || first ? arg : -arg);
first = false;
}
str = String(accum);
return 0;
}
case AR_FIRST:
str = cp_pop_spacevec(str);
return 0;
case AR_LT:
case AR_EQ:
case AR_GT:
case AR_LE:
case AR_NE:
case AR_GE: {
String astr = cp_pop_spacevec(str), bstr = cp_pop_spacevec(str);
click_intmax_t a, b;
if (str || !cp_integer(astr, &a) || !cp_integer(bstr, &b))
return errh->error("syntax error '%s' '%s'", astr.c_str(), bstr.c_str());
int x = (a < b ? AR_LT : (a == b ? AR_EQ : AR_GT));
str = cp_unparse_bool(what == x || (what >= AR_GE && what != x + 3));
return 0;
}
case AR_NOT: {
bool x;
if (!cp_bool(cp_uncomment(str), &x))
return errh->error("syntax error");
str = cp_unparse_bool(!x);
return 0;
}
case AR_SPRINTF: {
String format = cp_unquote(cp_pop_spacevec(str));
const char *s = format.begin(), *pct, *end = format.end();
StringAccum result;
while ((pct = find(s, end, '%')) < end) {
result << format.substring(s, pct);
StringAccum pf;
// flags
do {
pf << *pct++;
} while (pct < end && (*pct == '0' || *pct == '#'
|| *pct == '-' || *pct == ' ' || *pct == '+'));
// field width
int fw;
if (pct < end && *pct == '*') {
if (!cp_integer(cp_pop_spacevec(str), &fw))
return errh->error("syntax error");
pf << fw;
} else
while (pct < end && *pct >= '0' && *pct <= '9')
pf << *pct++;
// precision
if (pct < end && *pct == '.') {
pct++;
if (pct < end && *pct == '*') {
if (!cp_integer(cp_pop_spacevec(str), &fw) || fw < 0)
return errh->error("syntax error");
pf << '.' << fw;
} else if (pct < end && *pct >= '0' && *pct <= '9') {
pf << '.';
while (pct < end && *pct >= '0' && *pct <= '9')
pf << *pct++;
}
}
// width
int width_flag = 0;
while (1) {
if (pct < end && *pct == 'h')
width_flag = 'h', pct++;
else if (pct < end && *pct == 'l')
width_flag = (width_flag == 'l' ? 'q' : 'l'), pct++;
else if (pct < end && (*pct == 'L' || *pct == 'q'))
width_flag = 'q', pct++;
else
break;
}
// conversion
ErrorHandler *xerrh = ErrorHandler::silent_handler();
if (pct < end && (*pct == 'o' || *pct == 'x' || *pct == 'X' || *pct == 'u')) {
click_uintmax_t ival;
String x = cp_pop_spacevec(str);
if (!cp_unsigned(x, &ival))
return errh->error("syntax error");
if (width_flag == 'h')
ival = (unsigned short) ival;
else if (width_flag == 0 || width_flag == 'l')
ival = (unsigned long) ival;
pf << CLICK_INTMAX_CVT << *pct;
result << xerrh->make_text(ErrorHandler::ERR_MESSAGE, pf.c_str(), ival);
} else if (pct < end && (*pct == 'd' || *pct == 'i')) {
click_intmax_t ival;
if (!cp_integer(cp_pop_spacevec(str), &ival))
return errh->error("syntax error");
if (width_flag == 'h')
ival = (short) ival;
else if (width_flag == 0 || width_flag == 'l')
ival = (long) ival;
pf << CLICK_INTMAX_CVT << *pct;
result << xerrh->make_text(ErrorHandler::ERR_MESSAGE, pf.c_str(), ival);
} else if (pct < end && *pct == '%') {
pf << '%';
result << xerrh->make_text(ErrorHandler::ERR_MESSAGE, pf.c_str());
} else if (pct < end && *pct == 's') {
String s;
if (!cp_string(cp_pop_spacevec(str), &s))
return errh->error("syntax error");
pf << *pct;
result << xerrh->make_text(ErrorHandler::ERR_MESSAGE, pf.c_str(), s.c_str());
} else
return errh->error("syntax error");
s = pct + 1;
}
if (str)
return errh->error("syntax error");
result << format.substring(s, pct);
str = result.take_string();
return 0;
}
}
return -1;
}
void
Script::add_handlers()
{
set_handler("step", Handler::OP_WRITE | Handler::ONE_HOOK, step_handler, (void *) ST_STEP, 0);
set_handler("goto", Handler::OP_WRITE | Handler::ONE_HOOK, step_handler, (void *) ST_GOTO, 0);
set_handler("run", Handler::OP_READ | Handler::READ_PARAM | Handler::OP_WRITE | Handler::ONE_HOOK, step_handler, (void *) ST_RUN, 0);
set_handler("add", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_ADD, 0);
set_handler("sub", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_SUB, 0);
set_handler("eq", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_EQ, 0);
set_handler("ne", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_NE, 0);
set_handler("gt", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_GT, 0);
set_handler("ge", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_GE, 0);
set_handler("lt", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_LT, 0);
set_handler("le", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_LE, 0);
set_handler("not", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_NOT, 0);
set_handler("sprintf", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_SPRINTF, 0);
set_handler("first", Handler::OP_READ | Handler::READ_PARAM | Handler::ONE_HOOK, arithmetic_handler, (void *) AR_FIRST, 0);
}
EXPORT_ELEMENT(Script)
CLICK_ENDDECLS
syntax highlighted by Code2HTML, v. 0.9.1