Files
openbts/CLI/CLI.cpp
2014-07-16 23:57:22 +02:00

245 lines
7.4 KiB
C++

/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2011, 2012, 2014 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <Globals.h>
#include <Logger.h>
#include "CLI.h"
namespace CommandLine {
using std::map; using std::string; using std::ostream;
/** Standard responses in the CLI, much mach erorrCode enum. */
static const char* standardResponses[] = {
"success", // 0
"wrong number of arguments", // 1
"bad argument(s)", // 2
"command not found", // 3
"too many arguments for parser", // 4
"command failed", // 5
};
CLIStatus Parser::execute(char* line, ostream& os) const
{
LOG(INFO) << "executing console command: " << line;
// tokenize
char *argv[mMaxArgs];
int argc = 0;
// This is (almost) straight from the man page for strsep.
while (line && argc < mMaxArgs) {
while (*line == ' ') { line++; }
if (! *line) { break; }
char *anarg = line;
if (*line == '"') { // We allow a quoted string as a single argument. Quotes themselves are removed.
line++; anarg++;
char *endquote = strchr(line,'"');
if (endquote == NULL) {
os << "error: Missing quote."<<endl;
return FAILURE;
}
if (!(endquote[1] == 0 || endquote[1] == ' ')) {
os << "error: Embedded quotes not allowed." << endl;
return FAILURE;
}
*endquote = 0;
line = endquote+1;
} else if (strsep(&line," ") == NULL) {
break;
}
argv[argc++] = anarg;
}
//for (ap=argv; (*ap=strsep(&line," ")) != NULL; ) {
// if (**ap != '\0') {
// if (++ap >= &argv[mMaxArgs]) break;
// else argc++;
// }
//}
// Blank line?
if (!argc) return SUCCESS;
// Find the command.
//printf("args=%d\n",argc);
//for (int i = 0; i < argc; i++) { printf("argv[%d]=%s\n",i,argv[i]); }
ParseTable::const_iterator cfp = mParseTable.find(argv[0]);
if (cfp == mParseTable.end()) {
return NOT_FOUND;
}
CLICommand func;
func = cfp->second;
// Do it.
CLIStatus retVal;
try {
retVal = (*func)(argc,argv,os);
} catch (CLIParseError &pe) {
os << pe.msg << endl;
retVal = SUCCESS; // Dont print any further messages.
}
// Give hint on bad # args.
if (retVal==BAD_NUM_ARGS) os << help(argv[0]) << endl;
return retVal;
}
// This is called from a runloop in apps/OpenBTS.cpp
// If it returns a negative number OpenBTS exists.
CLIStatus Parser::process(const char* line, ostream& os) const
{
static Mutex oneCommandAtATime;
ScopedLock lock(oneCommandAtATime);
char *newLine = strdup(line);
LOG(INFO) << "CLI executing command:" <<(line?line:"null");
CLIStatus retVal = execute(newLine,os);
free(newLine);
if (retVal < CLI_EXIT || retVal > FAILURE) {
os << "Unrecognized CLI command exit status: "<<retVal << endl;
} else if (retVal != SUCCESS) {
os << standardResponses[retVal] << endl;
}
return retVal;
}
// (pat) This is no longer used - see CLIServer.
static void *commandLineFunc(Parser *parser)
{
const char *prompt = "OpenBTS> "; //gConfig.getStr("CLI.Prompt"); CLI.Prompt no longer defined.
try {
#ifdef HAVE_READLINE
using_history();
while (true) {
clearerr(stdin); // Control-D may set the eof bit which causes getline to return immediately. Fix it.
char *inbuf = readline(prompt);
if (inbuf) {
if (*inbuf) {
add_history(inbuf);
// The parser returns -1 on exit.
if (parser->process(inbuf, cout, cin)<0) {
free(inbuf);
break;
}
}
free(inbuf);
} else {
printf("EOF ignored\n");
}
sleep(1); // in case something goofs up here, dont steal all the cpu cycles.
}
#else
while (true) {
//cout << endl <<
cout << endl << prompt;
cout.flush();
char inbuf[1024];
cin.clear(); // Control-D may set the eof bit which causes getline to return immediately. Fix it.
cin.getline(inbuf,1024,'\n'); // istream::getline
// The parser returns -1 on exit.
if (parser->process(inbuf,cout)<0) break;
sleep(1); // in case something goofs up here, dont steal all the cpu cycles.
}
#endif
} catch (ConfigurationTableKeyNotFound e) {
LOG(EMERG) << "required configuration parameter " << e.key() << " not defined, aborting";
gReports.incr("OpenBTS.Exit.Error.ConfigurationParamterNotFound");
}
exit(0); // Exit OpenBTS
return NULL;
}
void Parser::startCommandLine() // (pat) Start a simple command line processor as a separate thread.
{
static Thread commandLineThread;
commandLineThread.start( (void*(*)(void*)) commandLineFunc,this);
}
const char * Parser::help(const string& cmd) const
{
HelpTable::const_iterator hp = mHelpTable.find(cmd);
if (hp==mHelpTable.end()) return "no help available";
return hp->second.c_str();
}
void Parser::printHelp(ostream &os) const
{
ParseTable::const_iterator cp = this->begin();
os << endl << "Type \"help\" followed by the command name for help on that command." << endl << endl;
int c=0;
const int cols = 3;
while (cp != this->end()) {
const string& wd = cp->first;
os << wd << '\t';
if (wd.size()<8) os << '\t';
++cp;
c++;
if (c%cols==0) os << endl;
}
if (c%cols!=0) os << endl;
}
// Parse options in optstring out of argc,argv.
// The optstring is a space separated list of options. The options need not start with '-'. To recognize just "-" or "--" just add it in.
// Return a map containing the options found; if option in optstring was followed by ':', map value will be the next argv argument, otherwise "true".
// Leave argc,argv pointing at the first argument after the options, ie, on return argc is the number of non-option arguments remaining in argv.
// This routine does not allow combining options, ie, -a -b != -ab
map<string,string> cliParse(int &argc, char **&argv, ostream &os, const char *optstring)
{
map<string,string> options; // The result
// Skip the command name.
argc--, argv++;
// Parse args.
for ( ; argc > 0; argc--, argv++ ) {
char *arg = argv[0];
// The argv to match may not contain ':' to prevent the pathological case, for example, where optionlist contains "-a:" and command line arg is "-a:"
if (strchr(arg,':')) { return options; } // Can't parse this, too dangerous.
const char *op = strstr(optstring,arg);
if (op && (op == optstring || op[-1] == ' ')) {
const char *ep = op + strlen(arg);
if (*ep == ':') {
// This valid option requires an argument.
argc--, argv++;
if (argc <= 0) { throw CLIParseError(format("expected argument after: %s",arg)); }
options[arg] = string(argv[0]);
continue;
} else if (*ep == 0 || *ep == ' ') {
// This valid option does not require an argument.
options[arg] = string("true");
continue;
} else {
// Partial match of something in optstring; drop through to treat it like any other argument.
}
} else {
break; // Return when we find the first non-option.
}
// An argument beginning with - and not in optstring is an unrecognized option and is an error.
if (*arg == '-') { throw CLIParseError(format("unrecognized argument: %s",arg)); }
return options;
}
return options;
}
};