diff --git a/public-trunk/CLI/CLI.cpp b/public-trunk/CLI/CLI.cpp index ba3564f..4d9e7f8 100644 --- a/public-trunk/CLI/CLI.cpp +++ b/public-trunk/CLI/CLI.cpp @@ -22,781 +22,88 @@ */ -#include -#include -#include -#include +#include #include -#include -#include #include #include "CLI.h" #include #include -#include -#include -#include -#include -#include +#ifdef HAVE_LIBREADLINE // [ +//# include +# include +# include +#endif // HAVE_LIBREADLINE ] using namespace std; using namespace CommandLine; -#define SUCCESS 0 -#define BAD_NUM_ARGS 1 -#define BAD_VALUE 2 -#define NOT_FOUND 3 -#define TOO_MANY_ARGS 4 -#define FAILURE 5 - -extern TransceiverManager gTRX; - -/** 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 -}; - - - - -int Parser::execute(char* line, ostream& os, istream& is) const +void CommandLine::runCLI(ParserBase *processor) { - // escape to the shell? - if (line[0]=='!') { - os << endl; - int retVal = system(line+1); - os << endl << "External call returned " << retVal << endl; - return SUCCESS; - } - // tokenize - char *argv[mMaxArgs]; - int argc = 0; - char **ap; - // This is (almost) straight from the man page for strsep. - 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. - ParseTable::const_iterator cfp = mParseTable.find(argv[0]); - if (cfp == mParseTable.end()) { - return NOT_FOUND; - } - int (*func)(int,char**,ostream&,istream&); - func = cfp->second; - // Do it. - int retVal = (*func)(argc,argv,os,is); - // Give hint on bad # args. - if (retVal==BAD_NUM_ARGS) os << help(argv[0]) << endl; - return retVal; -} +#ifdef HAVE_LIBREADLINE // [ + // start console + using_history(); + static const char * const history_file_name = "/.openbts_history"; + char *history_name = 0; + char *home_dir = getenv("HOME"); -int Parser::process(const char* line, ostream& os, istream& is) const -{ - char *newLine = strdup(line); - int retVal = execute(newLine,os,is); - free(newLine); - if (retVal>0) os << standardResponses[retVal] << endl; - return retVal; -} - - -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(); -} - - - -/**@name Commands for the CLI. */ -//@{ - -// forward refs -int printStats(int argc, char** argv, ostream& os, istream& is); - -/* - A CLI command takes the argument in an array. - It returns 0 on success. -*/ - -/** Set the logging file. */ -int setlogfile(int argc, char** argv, ostream& os, istream& is) -{ - if (argc!=2) return BAD_NUM_ARGS; - if (gSetLogFile(argv[1])) { - gConfig.set("LogFile",argv[1]); - return SUCCESS; - } - os << "cannot open " << argv[1] << " for logging" << endl; - return FAILURE; -} - - -/** Display system uptime and current GSM frame number. */ -int uptime(int argc, char** argv, ostream& os, istream& is) -{ - if (argc!=1) return BAD_NUM_ARGS; - os.precision(2); - os << "Unix time " << time(NULL) << endl; - int seconds = gBTS.uptime(); - if (seconds<120) { - os << "uptime " << seconds << " seconds, frame " << gBTS.time() << endl; - return SUCCESS; - } - float minutes = seconds / 60.0F; - if (minutes<120) { - os << "uptime " << minutes << " minutes, frame " << gBTS.time() << endl; - return SUCCESS; - } - float hours = minutes / 60.0F; - if (hours<48) { - os << "uptime " << hours << " hours, frame " << gBTS.time() << endl; - return SUCCESS; - } - float days = hours / 24.0F; - os << "uptime " << days << " days, frame " << gBTS.time() << endl; - return SUCCESS; -} - - -/** Give a list of available commands or describe a specific command. */ -int showHelp(int argc, char** argv, ostream& os, istream& is) -{ - if (argc==2) { - os << argv[1] << " " << gParser.help(argv[1]) << endl; - return SUCCESS; - } - if (argc!=1) return BAD_NUM_ARGS; - ParseTable::const_iterator cp = gParser.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 != gParser.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; - os << endl << "Lines starting with '!' are escaped to the shell." << endl; - os << endl << "Use , to detach from \"screen\", *not* ." << endl << endl; - return SUCCESS; -} - - - -/** A function to return -1, the exit code for the caller. */ -int exit_function(int argc, char** argv, ostream& os, istream& is) -{ - unsigned wait =0; - if (argc>2) return BAD_NUM_ARGS; - if (argc==2) wait = atoi(argv[1]); - - if (wait!=0) - os << "waiting up to " << wait << " seconds for clearing of " - << gBTS.TCHActive() << " active calls" << endl; - - // Block creation of new channels. - gBTS.hold(true); - // Wait up to the timeout for active channels to release. - time_t finish = time(NULL) + wait; - while (time(NULL)0) { - LOG(WARN) << "dropping " << gBTS.SDCCHActive() << " control transactions on exit"; - loads = true; - } - if (gBTS.TCHActive()>0) { - LOG(WARN) << "dropping " << gBTS.TCHActive() << " calls on exit"; - loads = true; - } - if (loads) { - os << endl << "exiting with loads:" << endl; - printStats(1,NULL,os,is); - } - if (gConfig.defines("Control.TMSITable.SavePath")) { - gTMSITable.save(gConfig.getStr("Control.TMSITable.SavePath")); - } - os << endl << "exiting..." << endl; - return -1; -} - - - -// Forward ref. -int tmsis(int argc, char** argv, ostream& os, istream& is); - -/** Dump TMSI table to a text file. */ -int dumpTMSIs(const char* filename, istream& is) -{ - ofstream fileout; - fileout.open(filename, ios::out); // erases existing! - // FIXME -- Check that the file really opened. - // Fake an argument list to call printTMSIs. - char* subargv[] = {"tmsis", NULL}; - int subargc = 1; - return tmsis(subargc, subargv, fileout, is); -} - - - - -/** Print or clear the TMSI table. */ -int tmsis(int argc, char** argv, ostream& os, istream& is) -{ - if (argc>=2) { - // Clear? - if (strcmp(argv[1],"clear")==0) { - if (argc!=2) return BAD_NUM_ARGS; - os << "clearing TMSI table" << endl; - gTMSITable.clear(); - return SUCCESS; - } - // Dump? - if (strcmp(argv[1],"dump")==0) { - if (argc!=3) return BAD_NUM_ARGS; - os << "dumping TMSI table to " << argv[2] << endl; - return dumpTMSIs(argv[2],is); - } - return BAD_VALUE; - } - - if (argc!=1) return BAD_NUM_ARGS; - - os << "TMSI IMSI IMEI age used" << endl; - gTMSITable.dump(os); - os << endl << gTMSITable.size() << " TMSIs in table" << endl; - return SUCCESS; -} - - -/** Print the transactions table. */ -int trans(int argc, char** argv, ostream& os, istream& is) -{ - if (argc!=1) return BAD_NUM_ARGS; - -// os << "TMSI IMSI IMEI age used" << endl; - gTransactionTable.dump(os); - os << endl << gTransactionTable.size() << " transactions in table" << endl; - return SUCCESS; -} - - - - -int findimsi(int argc, char** argv, ostream& os, istream& is) -{ - if (argc!=2) { - os << "usage: findimsi \n"; - return BAD_VALUE; - } - // FIXME -- THis should be moved into the TMSITable object to make it thread-safe. - Control::TMSIMap::const_iterator tp = gTMSITable.begin(); - char buf[50]; // max size in decimal digits plus 1 plus RandomPositive - while (tp != gTMSITable.end()) { - std::string target; - sprintf(buf, "%d", tp->first); - std::string imsi = buf; - target.assign(imsi, 0, strlen(argv[1])); - if (target == argv[1]) - os << tp->second << " 0x" << std::hex << tp->first << std::dec << endl; - ++tp; - } - return SUCCESS; -} - - - -/** Submit an SMS for delivery to an IMSI. */ -int sendsms(int argc, char** argv, ostream& os, istream& is) -{ - if (argc!=3) return BAD_NUM_ARGS; - - os << "enter text to send: "; - char txtBuf[161]; - cin.getline(txtBuf,160,'\n'); - char *IMSI = argv[1]; - char *srcAddr = argv[2]; - - Control::TransactionEntry transaction( - GSM::L3MobileIdentity(IMSI), - GSM::L3CMServiceType::MobileTerminatedShortMessage, - GSM::L3CallingPartyBCDNumber(srcAddr), - txtBuf); - transaction.Q931State(Control::TransactionEntry::Paging); - Control::initiateMTTransaction(transaction,GSM::SDCCHType,30000); - os << "message submitted for delivery" << endl; - return SUCCESS; -} - -/** DEBUGGING: Sends a special sms that triggers a RRLP message to an IMSI. */ -int sendrrlp(int argc, char** argv, ostream& os, istream& is) -{ - if (argc!=3) return BAD_NUM_ARGS; - - char *IMSI = argv[1]; - - UDPSocket sock(0,"127.0.0.1",gConfig.getNum("SIP.Port")); - unsigned port = sock.port(); - unsigned callID = random(); - - // Just fake out a SIP message. - const char form[] = "MESSAGE sip:IMSI%s@localhost SIP/2.0\nVia: SIP/2.0/TCP localhost;branch=z9hG4bK776sgdkse\nMax-Forwards: 2\nFrom: RRLP@localhost:%d;tag=49583\nTo: sip:IMSI%s@localhost\nCall-ID: %d@127.0.0.1:5063\nCSeq: 1 MESSAGE\nContent-Type: text/plain\nContent-Length: %lu\n\n%s\n"; - - char txtBuf[161]; - sprintf(txtBuf,"RRLP%s",argv[2]); - char outbuf[2048]; - sprintf(outbuf,form,IMSI,port,IMSI,callID,strlen(txtBuf),txtBuf); - sock.write(outbuf); - sleep(2); - sock.write(outbuf); - sock.close(); - os << "RRLP Triggering message submitted for delivery" << endl; - - return SUCCESS; -} - - - -/** Print current usage loads. */ -int printStats(int argc, char** argv, ostream& os, istream& is) -{ - if (argc!=1) return BAD_NUM_ARGS; - os << "SDCCH load: " << gBTS.SDCCHActive() << '/' << gBTS.SDCCHTotal() << endl; - os << "TCH/F load: " << gBTS.TCHActive() << '/' << gBTS.TCHTotal() << endl; - os << "AGCH/PCH load: " << gBTS.AGCHLoad() << ',' << gBTS.PCHLoad() << endl; - // paging table size - os << "Paging table size: " << gBTS.pager().pagingEntryListSize() << endl; - os << "Transactions/TMSIs: " << gTransactionTable.size() << ',' << gTMSITable.size() << endl; - // 3122 timer current value (the number of seconds an MS should hold off the next RACH) - os << "T3122: " << gBTS.T3122() << " ms" << endl; - return SUCCESS; -} - - - -/** Get/Set MCC, MNC, LAC, CI. */ -int cellID(int argc, char** argv, ostream& os, istream& is) -{ - if (argc==1) { - os << "MCC=" << gConfig.getStr("GSM.MCC") - << " MNC=" << gConfig.getStr("GSM.MNC") - << " LAC=" << gConfig.getNum("GSM.LAC") - << " CI=" << gConfig.getNum("GSM.CI") - << endl; - return SUCCESS; - } - - if (argc!=5) return BAD_NUM_ARGS; - - // Safety check the args!! - char* MCC = argv[1]; - char* MNC = argv[2]; - if (strlen(MCC)!=3) { - os << "MCC must be three digits" << endl; - return BAD_VALUE; - } - int MNCLen = strlen(MNC); - if ((MNCLen<2)||(MNCLen>3)) { - os << "MNC must be two or three digits" << endl; - return BAD_VALUE; - } - gTMSITable.clear(); - gConfig.set("GSM.MCC",MCC); - gConfig.set("GSM.MNC",MNC); - gConfig.set("GSM.LAC",argv[3]); - gConfig.set("GSM.CI",argv[4]); - gBTS.regenerateBeacon(); - return SUCCESS; -} - - - - -/** Print table of current transactions. */ -int calls(int argc, char** argv, ostream& os, istream& is) -{ - if (argc!=1) return BAD_NUM_ARGS; - Control::TransactionMap::const_iterator trans = gTransactionTable.begin(); - int count = 0; - gTransactionTable.clearDeadEntries(); - while (trans != gTransactionTable.end()) { - os << trans->second << endl; - ++trans; - count++; - } - os << endl << count << " transactions in table" << endl; - return SUCCESS; -} - - - -/** Print or modify the global configuration table. */ -int config(int argc, char** argv, ostream& os, istream& is) -{ - // no args, just print - if (argc==1) { - gConfig.dump(os); - return SUCCESS; - } - - // one arg, pattern match and print - bool anything = false; - if (argc==2) { - StringMap::const_iterator p = gConfig.begin(); - while (p != gConfig.end()) { - if (strstr(p->first.c_str(),argv[1])) { - os << p->first << ": " << p->second << endl; - anything = true; + if(home_dir) { + size_t home_dir_len = strlen(home_dir); + size_t history_file_len = strlen(history_file_name); + size_t history_len = home_dir_len + history_file_len + 1; + if(history_len > home_dir_len) { + if(!(history_name = (char *)malloc(history_len))) { + LOG(ERROR) << "malloc failed: " << strerror(errno); + exit(-1); } - ++p; + memcpy(history_name, home_dir, home_dir_len); + memcpy(history_name + home_dir_len, history_file_name, + history_file_len + 1); + read_history(history_name); } - if (!anything) - os << "nothing matching \"" << argv[1] << "\"" << endl; - return SUCCESS; + } +#endif // HAVE_LIBREADLINE ] + + COUT("\n\nWelcome to OpenBTS. Type \"help\" to see available commands."); + // FIXME: We want to catch control-d (emacs keybinding for exit()) + + // The logging parts were removed from this loop. + // If we want them back, they will need to go into their own thread. + while (1) { +#ifdef HAVE_LIBREADLINE // [ + char *inbuf = readline(gConfig.getStr("CLI.Prompt")); + if (!inbuf) break; + if (*inbuf) { + add_history(inbuf); + // The parser returns -1 on exit. + if (processor->process(inbuf, cout)<0) { + free(inbuf); + break; + } + } + free(inbuf); +#else // HAVE_LIBREADLINE ][ + cout << endl << gConfig.getStr("CLI.Prompt"); + cout.flush(); + std::string inbuf; + getline(cin, inbuf, '\n'); + // The parser returns -1 on exit. + if (processor->process(inbuf,cout)<0) break; +#endif // !HAVE_LIBREADLINE ] } - // >1 args: set new value - string val; - for (int i=2; i3) return BAD_NUM_ARGS; - - unsigned newT3212 = strtol(argv[1],NULL,10); - if ((newT3212<6)||(newT3212>1530)) { - os << "valid T3212 range is 6..1530 minutes" << endl; - return BAD_VALUE; - } - - // By defuault, make SIP registration period 1.5x the GSM registration period. - unsigned SIPRegPeriod = newT3212*90; - if (argc==3) { - SIPRegPeriod = 60*strtol(argv[2],NULL,10); - } - - // Set the values in the table and on the GSM beacon. - gConfig.set("SIP.RegistrationPeriod",SIPRegPeriod); - gConfig.set("GSM.T3212",newT3212); - gBTS.regenerateBeacon(); - // Done. - return SUCCESS; -} - - -/** Print the list of alarms kept by the logger, i.e. the last LOG(ALARM) << */ -int alarms(int argc, char** argv, ostream& os, istream& is) -{ - std::ostream_iterator output( os, "\n" ); - std::list alarms = gGetLoggerAlarms(); - std::copy( alarms.begin(), alarms.end(), output ); - return SUCCESS; -} - - -/** Version string. */ -int version(int argc, char **argv, ostream& os, istream& is) -{ - if (argc!=1) return BAD_NUM_ARGS; - os << "release " VERSION " built " __DATE__ << endl; - return SUCCESS; -} - -/** Show start-up notices. */ -int notices(int argc, char **argv, ostream& os, istream& is) -{ - if (argc!=1) return BAD_NUM_ARGS; - os << endl << gOpenBTSWelcome << endl; - return SUCCESS; -} - -int page(int argc, char **argv, ostream& os, istream& is) -{ - if (argc==1) { - gBTS.pager().dump(os); - return SUCCESS; - } - if (argc!=3) return BAD_NUM_ARGS; - char *IMSI = argv[1]; - if (strlen(IMSI)>15) { - os << IMSI << " is not a valid IMSI" << endl; - return BAD_VALUE; - } - Control::TransactionEntry dummy; - gBTS.pager().addID(GSM::L3MobileIdentity(IMSI),GSM::SDCCHType,dummy,1000*atoi(argv[2])); - return SUCCESS; -} - - - -int testcall(int argc, char **argv, ostream& os, istream& is) -{ - if (argc!=3) return BAD_NUM_ARGS; - char *IMSI = argv[1]; - if (strlen(IMSI)!=15) { - os << IMSI << " is not a valid IMSI" << endl; - return BAD_VALUE; - } - Control::TransactionEntry transaction( - GSM::L3MobileIdentity(IMSI), - GSM::L3CMServiceType::TestCall, - GSM::L3CallingPartyBCDNumber("0")); - transaction.Q931State(Control::TransactionEntry::Paging); - Control::initiateMTTransaction(transaction,GSM::TCHFType,1000*atoi(argv[2])); - return SUCCESS; -} - - -int endcall(int argc, char **argv, ostream& os, istream& is) -{ - // FIXME -- This doesn't really work. - if (argc!=2) return BAD_NUM_ARGS; - unsigned transID = atoi(argv[1]); - Control::TransactionEntry target; - if (!gTransactionTable.find(transID,target)) { - os << transID << " not found in table"; - return BAD_VALUE; - } - target.Q931State(Control::TransactionEntry::ReleaseRequest); - gTransactionTable.update(target); - return SUCCESS; -} - - - - -void printChanInfo(const GSM::LogicalChannel* chan, ostream& os) -{ - os << setw(2) << chan->TN(); - os << " " << setw(9) << chan->typeAndOffset(); - char buffer[1024]; - sprintf(buffer,"%10d %5.2f %4d %5d %4d", - chan->transactionID(), - 100.0*chan->FER(), (int)round(chan->RSSI()), - chan->actualMSPower(), chan->actualMSTiming()); - os << " " << buffer; - const GSM::L3MeasurementResults& meas = chan->SACCH()->measurementResults(); - if (!meas.MEAS_VALID()) { - sprintf(buffer,"%5d %5.2f", - meas.RXLEV_FULL_SERVING_CELL_dBm(), - 100.0*meas.RXQUAL_FULL_SERVING_CELL_BER()); - os << " " << buffer; - } else { - os << " ----- ------"; - } - os << endl; -} - - - -int chans(int argc, char **argv, ostream& os, istream& is) -{ - if (argc!=1) return BAD_NUM_ARGS; - - os << "TN chan transaction UPFER RSSI TXPWR TXTA DNLEV DNBER" << endl; - os << "TN type id pct dB dBm sym dBm pct" << endl; - - // SDCCHs - GSM::SDCCHList::const_iterator sChanItr = gBTS.SDCCHPool().begin(); - while (sChanItr != gBTS.SDCCHPool().end()) { - const GSM::SDCCHLogicalChannel* sChan = *sChanItr; - if (sChan->active()) printChanInfo(sChan,os); - ++sChanItr; - } - - // TCHs - GSM::TCHList::const_iterator tChanItr = gBTS.TCHPool().begin(); - while (tChanItr != gBTS.TCHPool().end()) { - const GSM::TCHFACCHLogicalChannel* tChan = *tChanItr; - if (tChan->active()) printChanInfo(tChan,os); - ++tChanItr; - } - - os << endl; - - return SUCCESS; -} - - - - -int power(int argc, char **argv, ostream& os, istream& is) -{ - os << "current downlink power " << gBTS.powerManager().power() << " dB wrt full scale" << endl; - os << "current attenuation bounds " - << gConfig.getNum("GSM.PowerManager.MinAttenDB") - << " to " - << gConfig.getNum("GSM.PowerManager.MaxAttenDB") - << " dB" << endl; - - if (argc==1) return SUCCESS; - if (argc!=3) return BAD_NUM_ARGS; - - int min = atoi(argv[1]); - int max = atoi(argv[2]); - if (min>max) return BAD_VALUE; - - gConfig.set("GSM.PowerManager.MinAttenDB",argv[1]); - gConfig.set("GSM.PowerManager.MaxAttenDB",argv[2]); - - os << "new attenuation bounds " - << gConfig.getNum("GSM.PowerManager.MinAttenDB") - << " to " - << gConfig.getNum("GSM.PowerManager.MaxAttenDB") - << " dB" << endl; - - return SUCCESS; -} - - -int rxgain(int argc, char** argv, ostream& os, istream& is) -{ - os << "current RX gain is " << gConfig.getNum("GSM.RxGain") << " dB" << endl; - if (argc==1) return SUCCESS; - if (argc!=2) return BAD_NUM_ARGS; - - int newGain = gTRX.ARFCN(0)->setRxGain(atoi(argv[1])); - os << "new RX gain is " << newGain << " dB" << endl; - - gConfig.set("GSM.RxGain",newGain); - - return SUCCESS; -} - -int noise(int argc, char** argv, ostream& os, istream& is) -{ - if (argc!=1) return BAD_NUM_ARGS; - - int noise = gTRX.ARFCN(0)->getNoiseLevel(); - os << "noise RSSI is -" << noise << " dB wrt full scale" << endl; - os << "MS RSSI target is " << gConfig.getNum("GSM.RSSITarget") << " dB wrt full scale" << endl; - - return SUCCESS; -} - - -//@} // CLI commands - - - -Parser::Parser() -{ - // The constructor adds the commands. - addCommand("setlogfile", setlogfile, " -- set the logging file to ."); - addCommand("uptime", uptime, "-- show BTS uptime and BTS frame number."); - addCommand("help", showHelp, "[command] -- list available commands or gets help on a specific command."); - addCommand("exit", exit_function, "[wait] -- exit the application, either immediately, or waiting for existing calls to clear with a timeout in seconds"); - addCommand("tmsis", tmsis, "[\"clear\"] or [\"dump\" filename] -- print/clear the TMSI table or dump it to a file."); - addCommand("trans", trans, "-- print the transactions table."); - addCommand("findimsi", findimsi, "[IMSIPrefix] -- prints all imsi's that are prefixed by IMSIPrefix"); - addCommand("sendsms", sendsms, " -- send SMS to , addressed from , after prompting."); - addCommand("sendrrlp", sendrrlp, " -- send RRLP message to ."); - addCommand("load", printStats, "-- print the current activity loads."); - addCommand("cellid", cellID, "[MCC MNC LAC CI] -- get/set location area identity (MCC, MNC, LAC) and cell ID (CI)"); - addCommand("calls", calls, "-- print the transaction table"); - addCommand("config", config, "[] OR [patt] OR [key val(s)] -- print the current configuration, print configuration values matching a pattern, or set/change a configuration value"); - addCommand("configsave", configsave, " -- write the current configuration to a file"); - addCommand("regperiod", regperiod, "[GSM] [SIP] -- get/set the registration period (GSM T3212), in MINUTES"); - addCommand("alarms", alarms, "-- show latest alarms"); - addCommand("version", version,"-- print the version string"); - addCommand("page", page, "[IMSI time] -- dump the paging table or page the given IMSI for the given period"); - addCommand("testcall", testcall, "IMSI time -- initiate a test call to a given IMSI with a given paging time"); - addCommand("chans", chans, "-- report PHY status for active channels"); - addCommand("power", power, "[minAtten maxAtten] -- report current attentuation or set min/max bounds"); - addCommand("rxgain", rxgain, "[newRxgain] -- get/set the RX gain in dB"); - addCommand("noise", noise, "-- report receive noise level in RSSI dB"); - addCommand("unconfig", unconfig, "key -- remove a config value"); - addCommand("notices", notices, "-- show startup copyright and legal notices"); - // HACK -- Comment out these until they are fixed. - // addCommand("endcall", endcall,"trans# -- terminate the given transaction"); -} - - - - // vim: ts=4 sw=4 diff --git a/public-trunk/CLI/CLI.h b/public-trunk/CLI/CLI.h index 8c3481c..d64c7b6 100644 --- a/public-trunk/CLI/CLI.h +++ b/public-trunk/CLI/CLI.h @@ -1,5 +1,5 @@ /* -* Copyright 2009 Free Software Foundation, Inc. +* Copyright 2009, 2010 Free Software Foundation, Inc. * * This software is distributed under the terms of the GNU Affero Public License. * See the COPYING file in the main directory for details. @@ -29,53 +29,12 @@ #include #include #include +#include namespace CommandLine { - -/** A table for matching strings to actions. */ -typedef std::map ParseTable; - -/** The help table. */ -typedef std::map HelpTable; - -class Parser { - - private: - - ParseTable mParseTable; - HelpTable mHelpTable; - static const int mMaxArgs = 10; - - public: - - Parser(); - - /** - Process a command line. - @return 0 on sucess, -1 on exit request, error codes otherwise - */ - int process(const char* line, std::ostream& os, std::istream& is) const; - - /** Add a command to the parsing table. */ - void addCommand(const char* name, int (*func)(int,char**,std::ostream&,std::istream&), const char* helpString) - { mParseTable[name] = func; mHelpTable[name]=helpString; } - - ParseTable::const_iterator begin() const { return mParseTable.begin(); } - ParseTable::const_iterator end() const { return mParseTable.end(); } - - /** Return a help string. */ - const char *help(const std::string& cmd) const; - - private: - - /** Parse and execute a command string. */ - int execute(char* line, std::ostream& os, std::istream& is) const; - -}; - - +void runCLI(ParserBase *processor); } // CLI diff --git a/public-trunk/CLI/CLIClient.cpp b/public-trunk/CLI/CLIClient.cpp new file mode 100644 index 0000000..60029fb --- /dev/null +++ b/public-trunk/CLI/CLIClient.cpp @@ -0,0 +1,72 @@ +/* +* Copyright 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + +#include + +#include +#include "CLIClient.h" +#include +#include +#include + +using namespace std; +using namespace CommandLine; + +int CLIClientProcessor::process(const std::string &line, std::ostream& os) +{ + // Step 1 -- Send command + if (!mConnection.sendBlock(line)) + return -1; + + // Step 2 -- Receive response + std::string recvBlock; + if (!mConnection.receiveBlock(recvBlock)) + return -1; + + // Step 3 -- Print out response + os << recvBlock; + os << endl; + + return 0; +} + +void CommandLine::runCLIClient(ConnectionSocket *sock) +{ + // Connect to a server + int res = sock->connect(); + if (res < 0) { + int errsv = errno; + LOG(WARN) << "sock.connect() failed with errno=" + << errsv << " (0x" << hex << errsv << dec << "): " + << strerror(errsv); + sock->close(); + return; + } + + // Start the main loop + CLIClientProcessor proc(sock); + runCLI(&proc); +} + +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/CLIClient.h b/public-trunk/CLI/CLIClient.h new file mode 100644 index 0000000..1ef7679 --- /dev/null +++ b/public-trunk/CLI/CLIClient.h @@ -0,0 +1,65 @@ +/* +* Copyright 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + + +#ifndef OPENBTSCLICLIENT_H +#define OPENBTSCLICLIENT_H + +#include +#include +#include +#include + +namespace CommandLine { + +class CLIClientProcessor : public ParserBase { +public: + + CLIClientProcessor(ConnectionSocket *pSock) + : mConnection(pSock) + {}; + + ~CLIClientProcessor() {}; + + /** + Process a command line. + @return 0 on success, -1 on exit request, error codes otherwise + */ + virtual int process(const std::string &line, std::ostream& os); + +protected: + + CLIConnection mConnection; ///< Network connection to a server + +}; + +// Run CLI client +void runCLIClient(ConnectionSocket *sock); + +} // CLI + + + +#endif +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/CLIConnection.cpp b/public-trunk/CLI/CLIConnection.cpp new file mode 100644 index 0000000..2fc9fc4 --- /dev/null +++ b/public-trunk/CLI/CLIConnection.cpp @@ -0,0 +1,101 @@ +/* +* Copyright 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + +#include +#include "CLIConnection.h" +#include +//#include + +using namespace std; +using namespace CommandLine; + +bool CLIConnection::sendBlock(const std::string &block) +{ + // Step 1 -- Prepare block + std::string::size_type blockLen = block.length(); + uint32_t sendBlockLen = htonl(blockLen); + int bytesSent; + LOG(DEEPDEBUG) << "Sending: (" << blockLen << "/0x" << hex << blockLen << dec + << ") \"" << block << "\""; + + // Step 2 -- Send block length + bytesSent = mSocket->write((const char *)&sendBlockLen, 4); + if (bytesSent < 4) { + onError(errno, "mSocket.write()"); + return false; + } + + // Step 3 -- Send block data + bytesSent = mSocket->write(block.data(), blockLen); + if (bytesSent < blockLen) { + onError(errno, "mSocket.write()"); + return false; + } + + return true; +} + +bool CLIConnection::receiveBlock(std::string &block) +{ + uint32_t recvDataLen; + int bytesReceived; + + // Step 1 -- Read length of the block + bytesReceived = mSocket->read((char*)&recvDataLen, 4); + if (bytesReceived < 4) { + onError(errno, "mSocket.read()"); + return false; + } + recvDataLen = ntohl(recvDataLen); + + // Step 2 -- prepare buffer + block.resize(recvDataLen); + + // Step 3 -- Receive actual data + bytesReceived = mSocket->read((char*)block.data(), recvDataLen); + if (bytesReceived < recvDataLen) { + onError(errno, "mSocket.read()"); + return false; + } +// readBuf[bytesReceived>=sizeof(readBuf)?sizeof(readBuf)-1:bytesReceived]= '\0'; + LOG(DEEPDEBUG) << "Received: (" << recvDataLen << "/0x" << hex << recvDataLen << dec + << ") \"" << block << "\""; + + return true; +} + +void CLIConnection::onError(int errnum, const char *oper) +{ + if (errnum == 0) { + LOG(INFO) << "Connection has been closed by remote party for socket " << *mSocket; + } else { + LOG(WARN) << "Socket=" << *mSocket << " operation " << oper + << " failed with errno=" << errnum << " (0x" + << hex << errnum << dec << "): " + << strerror(errnum); + } + mSocket->close(); +} + +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/CLIConnection.h b/public-trunk/CLI/CLIConnection.h new file mode 100644 index 0000000..e34b54d --- /dev/null +++ b/public-trunk/CLI/CLIConnection.h @@ -0,0 +1,74 @@ +/* +* Copyright 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + + +#ifndef OPENBTSCLICONNECTION_H +#define OPENBTSCLICONNECTION_H + +#include +#include + + +namespace CommandLine { + +class CLIConnection { +public: + + /** Constructor */ + CLIConnection(ConnectionSocket *pSocket) + : mSocket(pSocket) + { + assert(mSocket); + } + + /** Return a reference to the actual socket. */ + ConnectionSocket *socket() { return mSocket; } + + /** + Send a block of data + @return true if sent successfully, false otherwise. + */ + bool sendBlock(const std::string &block); + + /** + Receive a block of data + @return true if received successfully, false otherwise. + */ + bool receiveBlock(std::string &block); + +protected: + + ConnectionSocket *mSocket; ///< Actual socket for the connection + + /** Logs the error in a readable form and closes socket. */ + void onError(int errnum, const char *oper); + +}; + +} // CLI + + + +#endif +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/CLIParser.cpp b/public-trunk/CLI/CLIParser.cpp new file mode 100644 index 0000000..d20b5c3 --- /dev/null +++ b/public-trunk/CLI/CLIParser.cpp @@ -0,0 +1,837 @@ +/* +* Copyright 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "CLIParser.h" +#include "Tokenizer.h" +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace CommandLine; + +enum erorrCode { + SUCCESS=0, + BAD_NUM_ARGS=1, + BAD_VALUE=2, + NOT_FOUND=3, + TOO_MANY_ARGS=4, + FAILURE=5 +}; + +/** Standard responses in the CLI, must match erorrCode enum. */ +static const char* errorCodeText[] = { + "success", // 0 + "wrong number of arguments", // 1 + "bad argument(s)", // 2 + "command not found", // 3 + "too many arguments for parser", // 4 + "command failed", // 5 +}; + +extern TransceiverManager gTRX; +CommandLine::Parser CommandLine::gParser; + + + +/**@name Commands for the CLI. */ +//@{ + +// forward refs +int printStats(int argc, char** argv, ostream& os); + +/* + A CLI command takes the argument in an array. + It returns 0 on success. +*/ + +/** Set the logging file. */ +int setlogfile(int argc, char** argv, ostream& os) +{ + if (argc!=2) return BAD_NUM_ARGS; + if (gSetLogFile(argv[1])) { + gConfig.set("LogFile",argv[1]); + return SUCCESS; + } + os << "cannot open " << argv[1] << " for logging" << endl; + return FAILURE; +} + + +/** Display system uptime and current GSM frame number. */ +int uptime(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + os.precision(2); + os << "Unix time " << time(NULL) << endl; + int seconds = gBTS.uptime(); + if (seconds<120) { + os << "uptime " << seconds << " seconds, frame " << gBTS.time() << endl; + return SUCCESS; + } + float minutes = seconds / 60.0F; + if (minutes<120) { + os << "uptime " << minutes << " minutes, frame " << gBTS.time() << endl; + return SUCCESS; + } + float hours = minutes / 60.0F; + if (hours<48) { + os << "uptime " << hours << " hours, frame " << gBTS.time() << endl; + return SUCCESS; + } + float days = hours / 24.0F; + os << "uptime " << days << " days, frame " << gBTS.time() << endl; + return SUCCESS; +} + + +/** Give a list of available commands or describe a specific command. */ +int showHelp(int argc, char** argv, ostream& os) +{ + if (argc==2) { + os << argv[1] << " " << gParser.help(argv[1]) << endl; + return SUCCESS; + } + if (argc!=1) return BAD_NUM_ARGS; + os << endl << "Type \"help\" followed by the command name for help on that command." << endl << endl; + gParser.printCommands(os, 3); + os << endl; + os << "Lines starting with '!' are escaped to the shell." << endl; + os << endl; + os << "To pass string with spaces to a command, use quotes. E.g.:" << endl; + os << " sendsms IMSI255025502132582 100 \"multi-word sms with spaces\"" << endl; + os << "Escaping is not yet available, so you can't pass \" symbol itself to a command." << endl; + os << endl; + if ( strcasecmp(gConfig.getStr("CLI.Type"),"TCP") == 0 + || strcasecmp(gConfig.getStr("CLI.Type"),"UNIX") == 0) { + os << "Use or to exit from the CLI. \"exit\" will shutdown BTS!" << endl; + } else { + os << "Use , to detach from \"screen\", *not* ." << endl << endl; + } + return SUCCESS; +} + + + +/** A function to return -1, the exit code for the caller. */ +int exit_function(int argc, char** argv, ostream& os) +{ + unsigned wait =0; + if (argc>2) return BAD_NUM_ARGS; + if (argc==2) wait = atoi(argv[1]); + + if (wait!=0) + os << "waiting up to " << wait << " seconds for clearing of " + << gBTS.TCHActive() << " active calls" << endl; + + // Block creation of new channels. + gBTS.hold(true); + // Wait up to the timeout for active channels to release. + time_t finish = time(NULL) + wait; + while (time(NULL)0) { + LOG(WARN) << "dropping " << gBTS.SDCCHActive() << " control transactions on exit"; + loads = true; + } + if (gBTS.TCHActive()>0) { + LOG(WARN) << "dropping " << gBTS.TCHActive() << " calls on exit"; + loads = true; + } + if (loads) { + os << endl << "exiting with loads:" << endl; + printStats(1,NULL,os); + } + if (gConfig.defines("Control.TMSITable.SavePath")) { + gTMSITable.save(gConfig.getStr("Control.TMSITable.SavePath")); + } + os << endl << "exiting..." << endl; + return -1; +} + + + +// Forward ref. +int tmsis(int argc, char** argv, ostream& os); + +/** Dump TMSI table to a text file. */ +int dumpTMSIs(const char* filename) +{ + ofstream fileout; + fileout.open(filename, ios::out); // erases existing! + // FIXME -- Check that the file really opened. + // Fake an argument list to call printTMSIs. + char* subargv[] = {"tmsis", NULL}; + int subargc = 1; + return tmsis(subargc, subargv, fileout); +} + + + + +/** Print or clear the TMSI table. */ +int tmsis(int argc, char** argv, ostream& os) +{ + if (argc>=2) { + // Clear? + if (strcmp(argv[1],"clear")==0) { + if (argc!=2) return BAD_NUM_ARGS; + os << "clearing TMSI table" << endl; + gTMSITable.clear(); + return SUCCESS; + } + // Dump? + if (strcmp(argv[1],"dump")==0) { + if (argc!=3) return BAD_NUM_ARGS; + os << "dumping TMSI table to " << argv[2] << endl; + return dumpTMSIs(argv[2]); + } + return BAD_VALUE; + } + + if (argc!=1) return BAD_NUM_ARGS; + + os << "TMSI IMSI IMEI age used" << endl; + gTMSITable.dump(os); + os << endl << gTMSITable.size() << " TMSIs in table" << endl; + return SUCCESS; +} + + +/** Print the transactions table. */ +int trans(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + +// os << "TMSI IMSI IMEI age used" << endl; + gTransactionTable.dump(os); + os << endl << gTransactionTable.size() << " transactions in table" << endl; + return SUCCESS; +} + + + + +int findimsi(int argc, char** argv, ostream& os) +{ + if (argc!=2) { + os << "usage: findimsi \n"; + return BAD_VALUE; + } + // FIXME -- THis should be moved into the TMSITable object to make it thread-safe. + Control::TMSIMap::const_iterator tp = gTMSITable.begin(); + char buf[50]; // max size in decimal digits plus 1 plus RandomPositive + while (tp != gTMSITable.end()) { + std::string target; + sprintf(buf, "%d", tp->first); + std::string imsi = buf; + target.assign(imsi, 0, strlen(argv[1])); + if (target == argv[1]) + os << tp->second << " 0x" << std::hex << tp->first << std::dec << endl; + ++tp; + } + return SUCCESS; +} + + + +/** Submit an SMS for delivery to an IMSI. */ +int sendsms(int argc, char** argv, ostream& os) +{ + if (argc!=4) return BAD_NUM_ARGS; + +// os << "enter text to send: "; +// char txtBuf[161]; +// cin.getline(txtBuf,160,'\n'); + char *IMSI = argv[1]; + char *srcAddr = argv[2]; + char *txtBuf = argv[3]; + + Control::TransactionEntry transaction( + GSM::L3MobileIdentity(IMSI), + GSM::L3CMServiceType::MobileTerminatedShortMessage, + GSM::L3CallingPartyBCDNumber(srcAddr), + txtBuf); + transaction.Q931State(Control::TransactionEntry::Paging); + Control::initiateMTTransaction(transaction,GSM::SDCCHType,30000); + os << "message submitted for delivery" << endl; + return SUCCESS; +} + +/** DEBUGGING: Sends a special sms that triggers a RRLP message to an IMSI. */ +int sendrrlp(int argc, char** argv, ostream& os) +{ + if (argc!=3) return BAD_NUM_ARGS; + + char *IMSI = argv[1]; + + UDPSocket sock(0,"127.0.0.1",gConfig.getNum("SIP.Port")); + unsigned port = sock.port(); + unsigned callID = random(); + + // Just fake out a SIP message. + const char form[] = "MESSAGE sip:IMSI%s@localhost SIP/2.0\nVia: SIP/2.0/TCP localhost;branch=z9hG4bK776sgdkse\nMax-Forwards: 2\nFrom: RRLP@localhost:%d;tag=49583\nTo: sip:IMSI%s@localhost\nCall-ID: %d@127.0.0.1:5063\nCSeq: 1 MESSAGE\nContent-Type: text/plain\nContent-Length: %lu\n\n%s\n"; + + char txtBuf[161]; + sprintf(txtBuf,"RRLP%s",argv[2]); + char outbuf[2048]; + sprintf(outbuf,form,IMSI,port,IMSI,callID,strlen(txtBuf),txtBuf); + sock.write(outbuf); + sleep(2); + sock.write(outbuf); + sock.close(); + os << "RRLP Triggering message submitted for delivery" << endl; + + return SUCCESS; +} + + + +/** Print current usage loads. */ +int printStats(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + os << "SDCCH load: " << gBTS.SDCCHActive() << '/' << gBTS.SDCCHTotal() << endl; + os << "TCH/F load: " << gBTS.TCHActive() << '/' << gBTS.TCHTotal() << endl; + os << "AGCH/PCH load: " << gBTS.AGCHLoad() << ',' << gBTS.PCHLoad() << endl; + // paging table size + os << "Paging table size: " << gBTS.pager().pagingEntryListSize() << endl; + os << "Transactions/TMSIs: " << gTransactionTable.size() << ',' << gTMSITable.size() << endl; + // 3122 timer current value (the number of seconds an MS should hold off the next RACH) + os << "T3122: " << gBTS.T3122() << " ms" << endl; + return SUCCESS; +} + + + +/** Get/Set MCC, MNC, LAC, CI. */ +int cellID(int argc, char** argv, ostream& os) +{ + if (argc==1) { + os << "MCC=" << gConfig.getStr("GSM.MCC") + << " MNC=" << gConfig.getStr("GSM.MNC") + << " LAC=" << gConfig.getNum("GSM.LAC") + << " CI=" << gConfig.getNum("GSM.CI") + << endl; + return SUCCESS; + } + + if (argc!=5) return BAD_NUM_ARGS; + + // Safety check the args!! + char* MCC = argv[1]; + char* MNC = argv[2]; + if (strlen(MCC)!=3) { + os << "MCC must be three digits" << endl; + return BAD_VALUE; + } + int MNCLen = strlen(MNC); + if ((MNCLen<2)||(MNCLen>3)) { + os << "MNC must be two or three digits" << endl; + return BAD_VALUE; + } + gTMSITable.clear(); + gConfig.set("GSM.MCC",MCC); + gConfig.set("GSM.MNC",MNC); + gConfig.set("GSM.LAC",argv[3]); + gConfig.set("GSM.CI",argv[4]); + gBTS.regenerateBeacon(); + return SUCCESS; +} + + + + +/** Print table of current transactions. */ +int calls(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + Control::TransactionMap::const_iterator trans = gTransactionTable.begin(); + int count = 0; + gTransactionTable.clearDeadEntries(); + while (trans != gTransactionTable.end()) { + os << trans->second << endl; + ++trans; + count++; + } + os << endl << count << " transactions in table" << endl; + return SUCCESS; +} + + + +/** Print or modify the global configuration table. */ +int config(int argc, char** argv, ostream& os) +{ + // no args, just print + if (argc==1) { + gConfig.dump(os); + return SUCCESS; + } + + // one arg, pattern match and print + bool anything = false; + if (argc==2) { + StringMap::const_iterator p = gConfig.begin(); + while (p != gConfig.end()) { + if (strstr(p->first.c_str(),argv[1])) { + os << p->first << ": " << p->second << endl; + anything = true; + } + ++p; + } + if (!anything) + os << "nothing matching \"" << argv[1] << "\"" << endl; + return SUCCESS; + } + + // >1 args: set new value + string val; + for (int i=2; i3) return BAD_NUM_ARGS; + + unsigned newT3212 = strtol(argv[1],NULL,10); + if ((newT3212<6)||(newT3212>1530)) { + os << "valid T3212 range is 6..1530 minutes" << endl; + return BAD_VALUE; + } + + // By defuault, make SIP registration period 1.5x the GSM registration period. + unsigned SIPRegPeriod = newT3212*90; + if (argc==3) { + SIPRegPeriod = 60*strtol(argv[2],NULL,10); + } + + // Set the values in the table and on the GSM beacon. + gConfig.set("SIP.RegistrationPeriod",SIPRegPeriod); + gConfig.set("GSM.T3212",newT3212); + gBTS.regenerateBeacon(); + // Done. + return SUCCESS; +} + + +/** Print the list of alarms kept by the logger, i.e. the last LOG(ALARM) << */ +int alarms(int argc, char** argv, ostream& os) +{ + std::ostream_iterator output( os, "\n" ); + std::list alarms = gGetLoggerAlarms(); + std::copy( alarms.begin(), alarms.end(), output ); + return SUCCESS; +} + + +/** Version string. */ +int version(int argc, char **argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + os << "release " VERSION " built " __DATE__ << endl; + return SUCCESS; +} + +/** Show start-up notices. */ +int notices(int argc, char **argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + os << endl << gOpenBTSWelcome << endl; + return SUCCESS; +} + +int page(int argc, char **argv, ostream& os) +{ + if (argc==1) { + gBTS.pager().dump(os); + return SUCCESS; + } + if (argc!=3) return BAD_NUM_ARGS; + char *IMSI = argv[1]; + if (strlen(IMSI)>15) { + os << IMSI << " is not a valid IMSI" << endl; + return BAD_VALUE; + } + Control::TransactionEntry dummy; + gBTS.pager().addID(GSM::L3MobileIdentity(IMSI),GSM::SDCCHType,dummy,1000*atoi(argv[2])); + return SUCCESS; +} + + + +int testcall(int argc, char **argv, ostream& os) +{ + if (argc!=3) return BAD_NUM_ARGS; + char *IMSI = argv[1]; + if (strlen(IMSI)!=15) { + os << IMSI << " is not a valid IMSI" << endl; + return BAD_VALUE; + } + Control::TransactionEntry transaction( + GSM::L3MobileIdentity(IMSI), + GSM::L3CMServiceType::TestCall, + GSM::L3CallingPartyBCDNumber("0")); + transaction.Q931State(Control::TransactionEntry::Paging); + Control::initiateMTTransaction(transaction,GSM::TCHFType,1000*atoi(argv[2])); + return SUCCESS; +} + + +int endcall(int argc, char **argv, ostream& os) +{ + // FIXME -- This doesn't really work. + if (argc!=2) return BAD_NUM_ARGS; + unsigned transID = atoi(argv[1]); + Control::TransactionEntry target; + if (!gTransactionTable.find(transID,target)) { + os << transID << " not found in table"; + return BAD_VALUE; + } + target.Q931State(Control::TransactionEntry::ReleaseRequest); + gTransactionTable.update(target); + return SUCCESS; +} + + + + +void printChanInfo(const GSM::LogicalChannel* chan, ostream& os) +{ + os << setw(2) << chan->TN(); + os << " " << setw(9) << chan->typeAndOffset(); + char buffer[1024]; + sprintf(buffer,"%10d %5.2f %4d %5d %4d", + chan->transactionID(), + 100.0*chan->FER(), (int)round(chan->RSSI()), + chan->actualMSPower(), chan->actualMSTiming()); + os << " " << buffer; + const GSM::L3MeasurementResults& meas = chan->SACCH()->measurementResults(); + if (!meas.MEAS_VALID()) { + sprintf(buffer,"%5d %5.2f", + meas.RXLEV_FULL_SERVING_CELL_dBm(), + 100.0*meas.RXQUAL_FULL_SERVING_CELL_BER()); + os << " " << buffer; + } else { + os << " ----- ------"; + } + os << endl; +} + + + +int chans(int argc, char **argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + + os << "TN chan transaction UPFER RSSI TXPWR TXTA DNLEV DNBER" << endl; + os << "TN type id pct dB dBm sym dBm pct" << endl; + + // SDCCHs + GSM::SDCCHList::const_iterator sChanItr = gBTS.SDCCHPool().begin(); + while (sChanItr != gBTS.SDCCHPool().end()) { + const GSM::SDCCHLogicalChannel* sChan = *sChanItr; + if (sChan->active()) printChanInfo(sChan,os); + ++sChanItr; + } + + // TCHs + GSM::TCHList::const_iterator tChanItr = gBTS.TCHPool().begin(); + while (tChanItr != gBTS.TCHPool().end()) { + const GSM::TCHFACCHLogicalChannel* tChan = *tChanItr; + if (tChan->active()) printChanInfo(tChan,os); + ++tChanItr; + } + + os << endl; + + return SUCCESS; +} + + + + +int power(int argc, char **argv, ostream& os) +{ + os << "current downlink power " << gBTS.powerManager().power() << " dB wrt full scale" << endl; + os << "current attenuation bounds " + << gConfig.getNum("GSM.PowerManager.MinAttenDB") + << " to " + << gConfig.getNum("GSM.PowerManager.MaxAttenDB") + << " dB" << endl; + + if (argc==1) return SUCCESS; + if (argc!=3) return BAD_NUM_ARGS; + + int min = atoi(argv[1]); + int max = atoi(argv[2]); + if (min>max) return BAD_VALUE; + + gConfig.set("GSM.PowerManager.MinAttenDB",argv[1]); + gConfig.set("GSM.PowerManager.MaxAttenDB",argv[2]); + + os << "new attenuation bounds " + << gConfig.getNum("GSM.PowerManager.MinAttenDB") + << " to " + << gConfig.getNum("GSM.PowerManager.MaxAttenDB") + << " dB" << endl; + + return SUCCESS; +} + + +int rxgain(int argc, char** argv, ostream& os) +{ + os << "current RX gain is " << gConfig.getNum("GSM.RxGain") << " dB" << endl; + if (argc==1) return SUCCESS; + if (argc!=2) return BAD_NUM_ARGS; + + int newGain = gTRX.ARFCN(0)->setRxGain(atoi(argv[1])); + os << "new RX gain is " << newGain << " dB" << endl; + + gConfig.set("GSM.RxGain",newGain); + + return SUCCESS; +} + +int noise(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + + int noise = gTRX.ARFCN(0)->getNoiseLevel(); + os << "noise RSSI is -" << noise << " dB wrt full scale" << endl; + os << "MS RSSI target is " << gConfig.getNum("GSM.RSSITarget") << " dB wrt full scale" << endl; + + return SUCCESS; +} + +int echofirst(int argc, char** argv, ostream& os) +{ + if (argc!=2) return BAD_NUM_ARGS; + + os << argv[1]; + + return SUCCESS; +} + +int shellexec(const char *line, ostream& os) +{ + os << endl; + FILE *pout = popen(line, "r"); + char buf[1024]; + while (fgets(buf, sizeof(buf), pout) != NULL) { + os << buf; + } + int retVal = pclose(pout); + os << endl << "External call returned " << retVal << endl; + return SUCCESS; +} + +//@} // CLI commands + + + + +int Parser::execute(std::string &line, ostream& os) const +{ + // escape to the shell? + if (line[0]=='!') { + return shellexec(line.data()+1, os); + } + + // Tokenize + char *argv[mMaxArgs]; + int argc = tokenize((char*)line.data(), argv, mMaxArgs); + + // Blank line? + if (!argc) return SUCCESS; + + // Find the command. + mMutex.lock(); + ParseTable::const_iterator cfp = mParseTable.find(argv[0]); + if (cfp == mParseTable.end()) { + mMutex.unlock(); + return NOT_FOUND; + } + mMutex.unlock(); + CLICommandFunc func; + func = cfp->second; + + // Do it. + int retVal = (*func)(argc,argv,os); + // Give hint on bad # args. + if (retVal==BAD_NUM_ARGS) os << help(argv[0]) << endl; + + return retVal; +} + + +int Parser::process(const std::string &line, ostream& os) +{ + std::string newLine = line; + int retVal = execute(newLine,os); + if (retVal>0) os << errorCodeText[retVal] << endl; + return retVal; +} + +void Parser::printCommands(std::ostream &os, int numCols) const +{ + mMutex.lock(); + ParseTable::const_iterator cp = mParseTable.begin(); + int c=0; + while (cp != mParseTable.end()) { + const string& wd = cp->first; + os << wd << '\t'; + if (wd.size()<8) os << '\t'; + ++cp; + c++; + if (c%numCols==0) os << endl; + } + if (c%numCols!=0) os << endl; + mMutex.unlock(); +} + +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(); +} + + +Parser::Parser() +{ + // The constructor adds the commands. + addCommand("setlogfile", setlogfile, " -- set the logging file to ."); + addCommand("uptime", uptime, "-- show BTS uptime and BTS frame number."); + addCommand("help", showHelp, "[command] -- list available commands or gets help on a specific command."); + addCommand("exit", exit_function, "[wait] -- exit the application, either immediately, or waiting for existing calls to clear with a timeout in seconds"); + addCommand("tmsis", tmsis, "[\"clear\"] or [\"dump\" filename] -- print/clear the TMSI table or dump it to a file."); + addCommand("trans", trans, "-- print the transactions table."); + addCommand("findimsi", findimsi, "[IMSIPrefix] -- prints all imsi's that are prefixed by IMSIPrefix"); + addCommand("sendsms", sendsms, " -- send SMS to , addressed from ."); + addCommand("sendrrlp", sendrrlp, " -- send RRLP message to ."); + addCommand("load", printStats, "-- print the current activity loads."); + addCommand("cellid", cellID, "[MCC MNC LAC CI] -- get/set location area identity (MCC, MNC, LAC) and cell ID (CI)"); + addCommand("calls", calls, "-- print the transaction table"); + addCommand("config", config, "[] OR [patt] OR [key val(s)] -- print the current configuration, print configuration values matching a pattern, or set/change a configuration value"); + addCommand("configsave", configsave, " -- write the current configuration to a file"); + addCommand("regperiod", regperiod, "[GSM] [SIP] -- get/set the registration period (GSM T3212), in MINUTES"); + addCommand("alarms", alarms, "-- show latest alarms"); + addCommand("version", version,"-- print the version string"); + addCommand("page", page, "[IMSI time] -- dump the paging table or page the given IMSI for the given period"); + addCommand("testcall", testcall, "IMSI time -- initiate a test call to a given IMSI with a given paging time"); + addCommand("chans", chans, "-- report PHY status for active channels"); + addCommand("power", power, "[minAtten maxAtten] -- report current attentuation or set min/max bounds"); + addCommand("rxgain", rxgain, "[newRxgain] -- get/set the RX gain in dB"); + addCommand("noise", noise, "-- report receive noise level in RSSI dB"); + addCommand("unconfig", unconfig, "key -- remove a config value"); + addCommand("notices", notices, "-- show startup copyright and legal notices"); + addCommand("echo", echofirst, " -- print to the screen"); + // HACK -- Comment out these until they are fixed. + // addCommand("endcall", endcall,"trans# -- terminate the given transaction"); +} + +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/CLIParser.h b/public-trunk/CLI/CLIParser.h new file mode 100644 index 0000000..70265de --- /dev/null +++ b/public-trunk/CLI/CLIParser.h @@ -0,0 +1,86 @@ +/* +* Copyright 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + + +#ifndef OPENBTSCLIPARSER_H +#define OPENBTSCLIPARSER_H + +#include +#include +#include +#include +#include + + +namespace CommandLine { + +typedef int (*CLICommandFunc)(int,char**,std::ostream&); + +/** A table for matching strings to actions. */ +typedef std::map ParseTable; + +/** The help table. */ +typedef std::map HelpTable; + +class Parser : public ParserBase { + +public: + + Parser(); + + /** + Process a command line. + @return 0 on success, -1 on exit request, error codes otherwise + */ + virtual int process(const std::string &line, std::ostream& os); + + /** Add a command to the parsing table. */ + void addCommand(const char* name, CLICommandFunc func, const char* helpString) + { mMutex.lock(); mParseTable[name] = func; mHelpTable[name]=helpString; mMutex.unlock(); } + + /** Print command list to ostream */ + void printCommands(std::ostream &os, int numCols=1) const; + + /** Return a help string. */ + const char *help(const std::string& cmd) const; + +protected: + + mutable Mutex mMutex; ///< Mutex to protect mParseTable and mHelpTable. + ParseTable mParseTable; + HelpTable mHelpTable; + static const int mMaxArgs = 10; + + /** Parse and execute a command string. */ + int execute(std::string &line, std::ostream& os) const; + +}; + +extern CommandLine::Parser gParser; + +} // CLI + + +#endif +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/CLIParserBase.h b/public-trunk/CLI/CLIParserBase.h new file mode 100644 index 0000000..849d505 --- /dev/null +++ b/public-trunk/CLI/CLIParserBase.h @@ -0,0 +1,47 @@ +/* +* Copyright 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + + +#ifndef OPENBTSCLIBASE_H +#define OPENBTSCLIBASE_H + +namespace CommandLine { + +class ParserBase { +public: + + /** + Process a command line. + @return 0 on success, -1 on exit request, error codes otherwise + */ + virtual int process(const std::string &line, std::ostream& os) =0; + +}; + +} // CLI + + + +#endif +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/CLIServer.cpp b/public-trunk/CLI/CLIServer.cpp new file mode 100644 index 0000000..9b55c9b --- /dev/null +++ b/public-trunk/CLI/CLIServer.cpp @@ -0,0 +1,172 @@ +/* +* Copyright 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + +//#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace CommandLine; + +namespace CommandLine { + +typedef std::list ServerThreadsList; + +/** Free all finished threads from the given list */ +void clearFinishedThreads(ServerThreadsList &threadsList) +{ + ServerThreadsList::iterator it; + for (it = threadsList.begin(); it != threadsList.end(); ) + { + CLIServerConnectionThread *pThread = *it; + if (pThread->state() == CLIServerConnectionThread::FINISHING) { + pThread->join(); + delete pThread; + // This moves iterator forward, so we don't need to increment it. + it = threadsList.erase(it); + } else { + it++; + } + } +} + +/** Stop all threads in the list */ +void stopAllThreads(ServerThreadsList &threadsList) +{ + while (threadsList.begin() != threadsList.end()) + { + // Pop an element from the list. + CLIServerConnectionThread *pThread = threadsList.front(); + threadsList.pop_front(); + + switch (pThread->state()) { + case CLIServerConnectionThread::IDLE: + delete pThread; + break; + + case CLIServerConnectionThread::STARTING: + case CLIServerConnectionThread::STARTED: + pThread->stop(); + // no break; + case CLIServerConnectionThread::FINISHING: + pThread->join(); + delete pThread; + break; + } + } +} + +}; + +void CommandLine::runCLIServer(ConnectionServerSocket *serverSock) +{ + ServerThreadsList serverThreads; + + // Step 1 -- Start listening at the given interface/port + if (!serverSock->bind()) { + int errsv = errno; + LOG(ERROR) << "serverSock->bind() failed with errno=" + << errsv << " (0x" << hex << errsv << dec << ")"; + return; + } + + // Step 2 -- Serve incoming connections + while (1) { + // Step 2.1 -- Wait for incoming connection + ConnectionSocket *newConn = serverSock->accept(); + if (newConn == NULL) + { + int errsv = errno; + if (errsv == EINVAL) { + LOG(INFO) << "serverSock has been closed."; + } else { + LOG(INFO) << "serverSock->accept() failed with errno=" + << errsv << " (0x" << hex << errsv << dec << ")"; + } + break; + } + LOG(INFO) << "New incoming connection: " << *newConn; + + // Step 2.2 -- Add thread to our threads list and start it. + CLIServerConnectionThread *ipServerThread + = new CLIServerConnectionThread(newConn, serverSock); + LOG(DEEPDEBUG) << "New server thread: " << hex << ipServerThread << dec; + serverThreads.push_back(ipServerThread); + ipServerThread->start(); + + // Step 2.3 -- Clean up finished threads + clearFinishedThreads(serverThreads); + } + + // Step 3 -- Cleanup + serverSock->close(); + stopAllThreads(serverThreads); +} + + +void *CLIServerConnectionThread::startFunc(void *data) +{ + CLIServerConnectionThread *threadObj = (CLIServerConnectionThread*)data; + // Simply forward the call to member function + threadObj->state(STARTED); + threadObj->run(); + threadObj->state(FINISHING); + return NULL; +} + +void CLIServerConnectionThread::run() +{ + assert(mConnSock != NULL); + CLIConnection conn(mConnSock); + + while (1) { + // Receive a command + std::string recvBlock; + if (!conn.receiveBlock(recvBlock)) + break; + + // Process command + ostringstream sendBlock; + int res = gParser.process(recvBlock, sendBlock); + if (res < 0) { + // Shutdown CLI server if process() returns -1 + mServerSock->close(); + break; + } + + // Send result back to the client + if (!conn.sendBlock(sendBlock.str())) + break; + } + + mConnSock->close(); +} + +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/CLIServer.h b/public-trunk/CLI/CLIServer.h new file mode 100644 index 0000000..9a7aff0 --- /dev/null +++ b/public-trunk/CLI/CLIServer.h @@ -0,0 +1,116 @@ +/* +* Copyright 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + + +#ifndef OPENBTSCLISERVER_H +#define OPENBTSCLISERVER_H + +#include +#include +#include +#include +#include "CLIConnection.h" + +class ConnectionServerSocket; + +namespace CommandLine { + +void runCLIServer(ConnectionServerSocket *serverSock); + +class CLIServerConnectionThread { +public: + + enum State { + IDLE, ///< Thread is not started + STARTING, ///< Thread start has been just requested + STARTED, ///< Thread has entered its main processing loop + FINISHING ///< Thread has exited from its main processing loop + }; + + /** Constructor */ + CLIServerConnectionThread(ConnectionSocket *connSock, + ConnectionServerSocket *serverSock) + : mConnSock(connSock) + , mServerSock(serverSock) + , mState(IDLE) + {} + + /** Destructor */ + ~CLIServerConnectionThread() + { + assert(state() == IDLE); + delete mConnSock; + } + + /** Request thread to start. */ + void start() { assert(state() == IDLE); state(STARTING); mThread.start(startFunc, this); } + + /** Request thread to stop. */ + void stop() { mConnSock->close(); } + + /** Wait for the thread to finish. */ + void join() { mThread.join(); state(IDLE); } + + /** Get internal state of the thread */ + State state() const + { + mStateMutex.lock(); + State retval = mState; + mStateMutex.unlock(); + return retval; + } + +protected: + + Thread mThread; ///< Actual thread instance. We're proxying requests to it. + ConnectionSocket * const mConnSock; ///< Connection to a client socket. + ///< It's thread-safe to access it as long as it's const. + ConnectionServerSocket * const mServerSock; ///< Listening server socket. + ///< It's thread-safe to access it as long as it's const. + mutable Mutex mStateMutex; ///< A mutex to protect mState variable. + State mState; ///< State of the thread - is it running or what. + + + /** Static function to pass to Thread::start() */ + static void *startFunc(void *data); + + /** Main entry for the thread */ + void run(); + + /** Change internal state of the thread. */ + void state(State newState) + { + mStateMutex.lock(); + mState = newState; + mStateMutex.unlock(); + } + +}; + +} // CLI + + + +#endif +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/Makefile.am b/public-trunk/CLI/Makefile.am index 456f72a..e3faac1 100644 --- a/public-trunk/CLI/Makefile.am +++ b/public-trunk/CLI/Makefile.am @@ -26,10 +26,31 @@ EXTRA_DIST = \ AM_CXXFLAGS = -Wall AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) -noinst_LTLIBRARIES = libcli.la +noinst_LTLIBRARIES = libcli.la libcli_commands.la + +noinst_PROGRAMS = \ + TokenizerTest libcli_la_SOURCES = \ - CLI.cpp + CLI.cpp \ + CLIServer.cpp \ + CLIClient.cpp \ + CLIConnection.cpp \ + Tokenizer.cpp + +libcli_commands_la_SOURCES = \ + CLIParser.cpp + +TokenizerTest_SOURCES = \ + TokenizerTest.cpp +TokenizerTest_LDADD = \ + $(CLI_LA) noinst_HEADERS = \ - CLI.h + CLI.h \ + CLIServer.h \ + CLIClient.h \ + CLIConnection.h \ + CLIParser.h \ + CLIParserBase.h \ + Tokenizer.h diff --git a/public-trunk/CLI/Tokenizer.cpp b/public-trunk/CLI/Tokenizer.cpp new file mode 100644 index 0000000..423600b --- /dev/null +++ b/public-trunk/CLI/Tokenizer.cpp @@ -0,0 +1,78 @@ +/* +* Copyright 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + +#include +#include "Tokenizer.h" + + +#include // For debug only + +using namespace CommandLine; + +int CommandLine::tokenize(char *line, char **argv, int maxArgs) +{ + char *lineData = line; + int argc; + const char *tokenEnd = " "; + + // Find the beginning of the first token + lineData += strspn(lineData, " "); + // Check if this is a start of quoting + if (*lineData == '"') { + tokenEnd = "\""; + lineData++; + } else { + tokenEnd = " "; + } + + for (argc = 0; argc < maxArgs && *(argv[argc] = lineData) != '\0'; argc++) { + // Find the end of the token + lineData = strpbrk(lineData, tokenEnd); + if (lineData == NULL) { + argc++; + break; + } + *lineData = '\0'; + + // Find the beginning of the next token + lineData++; + lineData += strspn(lineData, " "); + if (*lineData == '\0') { + argc++; + break; + } + + // Check if this is a start of quoting + if (*lineData == '"') { + tokenEnd = "\""; + lineData++; + } else { + tokenEnd = " "; + } + } + + return argc; +} + +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/Tokenizer.h b/public-trunk/CLI/Tokenizer.h new file mode 100644 index 0000000..95bfa17 --- /dev/null +++ b/public-trunk/CLI/Tokenizer.h @@ -0,0 +1,41 @@ +/* +* Copyright 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + + +#ifndef OPENBTSTOKENIZER_H +#define OPENBTSTOKENIZER_H + +namespace CommandLine { + +/** + Split given string into tokens. + WARNING: String will be changed during processing and argv will point + to parts of this string. +*/ +int tokenize(char *line, char **argv, int maxArgs); + +} + +#endif +// vim: ts=4 sw=4 diff --git a/public-trunk/CLI/TokenizerTest.cpp b/public-trunk/CLI/TokenizerTest.cpp new file mode 100644 index 0000000..0b9e351 --- /dev/null +++ b/public-trunk/CLI/TokenizerTest.cpp @@ -0,0 +1,64 @@ +/* +* Copyright 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + + + +#include "Tokenizer.h" +#include + +using namespace std; +using namespace CommandLine; + +const int gMaxArgs = 5; + +void test(const char *line) +{ + char *argv[gMaxArgs]; + std::string lineStr = line; + + cout << "Tokenizing string: |" << line << "|" << endl; + int argc = tokenize((char*)lineStr.data(), argv, gMaxArgs); + cout << "Found " << argc << " arguments" << endl; + for (int i=0; i. + +*/ + + + + +#include "Sockets.h" +#include "Threads.h" +#include "Logger.h" +#include "Configuration.h" +#include +#include +#include +#include + +using namespace std; + + +ConfigurationTable gConfig; + +static const int gNumToSend = 3; +static const int gIpBindPort = 22239; +static const char *gIpBindHost = "127.0.0.1"; +//static const char *gIpBindHost = "0.0.0.0"; +static const char *gUnixSocketPath = "/tmp/testUnixSock"; + +void reportError(const std::string &what, int errsv) +{ + LOG(ERROR) << what << " failed with errno=" + << errsv << " (0x" << hex << errsv << dec << "): " + << strerror(errsv); +} + +void *ipServerClose(void *data) +{ + ConnectionServerSocket *pServer = (ConnectionServerSocket*)data; + + sleep(1); + pServer->close(); + + return NULL; +} + +bool clientConnect(ConnectionSocket *conn) +{ + int res = conn->connect(); + if (res < 0) { + reportError("conn->connect()", errno); + conn->close(); + return false; + } + + return true; +} + +bool clientWrite(ConnectionSocket *conn, const char *text) +{ + std::string sendBuf = text; + LOG(INFO) << "Client sending: \"" << sendBuf << "\""; + uint32_t sendBufLength = htonl(sendBuf.length()); + int bytesSent; + bytesSent = conn->write((const char *)&sendBufLength, 4); + if (bytesSent < 4) { + reportError("conn->write()", errno); + conn->close(); + return false; + } + bytesSent = conn->write(sendBuf.data(), sendBuf.length()); + if (bytesSent < sendBuf.length()) { + reportError("conn->write()", errno); + conn->close(); + return false; + } + + return true; +} + +bool clientRead(ConnectionSocket *conn) +{ + uint32_t recvDataLen; + char readBuf[1024]; + int bytesReceived; + bytesReceived = conn->read(readBuf, 4); + if (bytesReceived < 4) { + reportError("conn->read()", errno); + conn->close(); + return false; + } + recvDataLen = ntohl(*(uint32_t*)readBuf); + if (recvDataLen > 1024) { + recvDataLen = 1024; + } + + bytesReceived = conn->read(readBuf, recvDataLen); + if (bytesReceived < recvDataLen) { + reportError("conn->read()", errno); + conn->close(); + return false; + } + readBuf[bytesReceived>=sizeof(readBuf)?sizeof(readBuf)-1:bytesReceived]= '\0'; + LOG(INFO) << "Client received: \"" << readBuf << "\""; + + return true; +} + +void *ipClient(void *) +{ + for (int i=0; iconnectionSocket; + ConnectionServerSocket *pServerSock = pConnPair->serverSocket; + delete pConnPair; + + uint32_t recvDataLen; + char readBuf[1024]; + int bytesReceived; + bytesReceived = pConn->read(readBuf, 4); + if (bytesReceived < 4) { + reportError("pConn->read()", errno); + pConn->close(); + return NULL; + } + recvDataLen = ntohl(*(uint32_t*)readBuf); + if (recvDataLen > 1024) { + recvDataLen = 1024; + } + + bytesReceived = pConn->read(readBuf, recvDataLen); + if (bytesReceived < recvDataLen) { + reportError("pConn->read()", errno); + pConn->close(); + return NULL; + } + readBuf[bytesReceived>=sizeof(readBuf)?sizeof(readBuf)-1:bytesReceived]= '\0'; + LOG(INFO) << "Server received: \"" << readBuf << "\""; + + // Shutdown server is requested so. + if (strcmp(readBuf, "exit")==0) { + pConn->close(); + pServerSock->close(); + return NULL; + } + + std::string sendBuf = "pong"; + LOG(INFO) << "Server sending: \"" << sendBuf << "\""; + uint32_t sendBufLength = htonl(sendBuf.length()); + int bytesSent; + bytesSent = pConn->write((const char *)&sendBufLength, 4); + if (bytesSent < 4) { + reportError("pConn->write()", errno); + pConn->close(); + return NULL; + } + bytesSent = pConn->write(sendBuf.data(), sendBuf.length()); + if (bytesSent < sendBuf.length()) { + reportError("pConn->write()", errno); + pConn->close(); + return NULL; + } + + // Wait for remote side to close connection to avoid TIME_WAIT on server side. + // Refer to http://hea-www.harvard.edu/~fine/Tech/addrinuse.html + bytesReceived = pConn->read(readBuf, 1); + pConn->close(); + + // This should be done in thread's destructor + delete pConn; + + return NULL; +} + +void serverBind(ConnectionServerSocket *serverSock) +{ + if (!serverSock->bind()) { + reportError("serverSock->bind()", errno); + return; + } +} + +void serverRun(ConnectionServerSocket *serverSock) +{ + std::vector serverThreads; + + while (1) { + ConnectionSocket *newConn = serverSock->accept(); + if (newConn == NULL) + { + int errsv = errno; + if (errsv == EINVAL) { + LOG(INFO) << "serverSock has been closed."; + } else { + reportError("serverSock->accept()", errsv); + } + break; + } + LOG(INFO) << "New incoming connection " << newConn; + + Thread *ipServerThread = new Thread(); + LOG(INFO) << "New server thread: " << hex << ipServerThread << dec; + serverThreads.push_back(ipServerThread); + ipServerThread->start(serverConnThread, new ConnectionPair(serverSock, newConn)); + } + + // Stop server + serverSock->close(); + + // Clean up + Thread *ipServerThread; + while (serverThreads.size()) { + ipServerThread = serverThreads.back(); + LOG(INFO) << "Removing server thread: " << hex << ipServerThread << dec; + ipServerThread->join(); + delete ipServerThread; + serverThreads.pop_back(); + } +} + +int main(int argc, char * argv[] ) +{ + gLogInit("DEBUG"); + + LOG(INFO) << "Testing TCP sockets"; + { + ConnectionServerSocketTCP serverSock(gIpBindPort, gIpBindHost); + Thread readerThreadIP; + + serverBind(&serverSock); + readerThreadIP.start(ipClient, NULL); + serverRun(&serverSock); + readerThreadIP.join(); + } + + LOG(INFO) << "Testing UNIX sockets"; + { + ConnectionServerSocketUnix serverSock(gUnixSocketPath); + Thread readerThreadIP; + + serverBind(&serverSock); + readerThreadIP.start(unixClient, NULL); + serverRun(&serverSock); + readerThreadIP.join(); + } +} + +// vim: ts=4 sw=4 diff --git a/public-trunk/CommonLibs/Makefile.am b/public-trunk/CommonLibs/Makefile.am index 7c2e68a..ac6bc77 100644 --- a/public-trunk/CommonLibs/Makefile.am +++ b/public-trunk/CommonLibs/Makefile.am @@ -41,6 +41,7 @@ libcommon_la_SOURCES = \ noinst_PROGRAMS = \ BitVectorTest \ InterthreadTest \ + ConnectionSocketsTest \ SocketsTest \ TimevalTest \ RegexpTest \ @@ -73,6 +74,10 @@ SocketsTest_SOURCES = SocketsTest.cpp SocketsTest_LDADD = libcommon.la SocketsTest_LDFLAGS = -lpthread +ConnectionSocketsTest_SOURCES = ConnectionSocketsTest.cpp +ConnectionSocketsTest_LDADD = libcommon.la +ConnectionSocketsTest_LDFLAGS = -lpthread + TimevalTest_SOURCES = TimevalTest.cpp TimevalTest_LDADD = libcommon.la diff --git a/public-trunk/CommonLibs/Sockets.cpp b/public-trunk/CommonLibs/Sockets.cpp index 0fe71e2..f311100 100644 --- a/public-trunk/CommonLibs/Sockets.cpp +++ b/public-trunk/CommonLibs/Sockets.cpp @@ -303,6 +303,226 @@ void UDDSocket::destination(const char* remotePath) } +ConnectionSocket::ConnectionSocket(int af, int type, int protocol) +: mSocketFD(-1) +{ + // Create the socket + mSocketFD = ::socket(af, type, protocol); + if (mSocketFD < 0) { +// int errsv = errno; +// LOG(ERROR) << "socket() failed with errno=" << errsv; + mSocketFD = -1; + } +} +ConnectionSocket::~ConnectionSocket() +{ + close(); +} + +int ConnectionSocket::write( const char * buffer, int length) +{ + // MSG_NOSIGNAL does not allow send() to emit signals on error, it will + // just return result. + ssize_t bytesSent = ::send(mSocketFD, buffer, length, MSG_NOSIGNAL); + if (bytesSent != length) { +// int errsv = errno; +// LOG(ERROR) << "send() has sent " << bytesSent << "bytes instead of " +// << length << " bytes, errno=" << errsv; + } + return bytesSent; +} + +int ConnectionSocket::read(char* buffer, size_t length) +{ + // MSG_NOSIGNAL does not allow recv() to emit signals on error, it will + // just return result. + int bytesRead = ::recv(mSocketFD, buffer, length, MSG_NOSIGNAL); + return bytesRead; +} + +void ConnectionSocket::nonblocking() +{ + fcntl(mSocketFD,F_SETFL,0); +} + +void ConnectionSocket::blocking() +{ + fcntl(mSocketFD,F_SETFL,0); +} + +void ConnectionSocket::close() +{ + // shutdown() forces all selects which are blocked on this socket to return + ::shutdown(mSocketFD, SHUT_RDWR); + ::close(mSocketFD); +} + +std::ostream& operator<<(std::ostream& os, const ConnectionSocket& conn) +{ + os << "(socket=" << conn.mSocketFD << ")"; + // TODO:: Add host/port information to output. + return os; +} + +ConnectionSocketTCP::ConnectionSocketTCP(int port, const char* address) +: ConnectionSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) +{ + if (!resolveAddress(&mRemote, address, port)) { + mRemote.sin_family = AF_UNSPEC; + } +} + +int ConnectionSocketTCP::connect() +{ + return ::connect(mSocketFD, (sockaddr*)&mRemote, sizeof(mRemote)); +} + +int ConnectionSocketUnix::connect() +{ + return ::connect(mSocketFD, (sockaddr*)&mRemote, sizeof(mRemote)); +} + +ConnectionServerSocket::ConnectionServerSocket(int af, int type, int protocol) +: mSocketFD(-1) +{ + // Create the socket + mSocketFD = ::socket(af, type, protocol); + if (mSocketFD < 0) { +// int errsv = errno; +// LOG(ERROR) << "socket() failed with errno=" << errsv; + mSocketFD = -1; + } +} + +bool ConnectionServerSocket::bindInternal(const sockaddr *addr, int addrlen, + int connectionQueueSize) +{ + int error; + + error = ::bind(mSocketFD, addr, addrlen); + if (error < 0) { +// int errsv = errno; +// LOG(ERROR) << "bind() to port " << bindPort<< " failed with errnp=" << errsv; + close(); + return false; + } + + // Setup the queue for connection requests + error = ::listen(mSocketFD, connectionQueueSize); + if (error < 0) { +// int errsv = errno; +// LOG(ERROR) << "listen() failed with errno=" << errsv; + close(); + return false; + } + + return true; +} + +int ConnectionServerSocket::acceptInternal(sockaddr *addr, socklen_t &addrlen) +{ + // Wait for a client to connect. + int newSocketFD = ::accept(mSocketFD, addr, &addrlen); + if (newSocketFD < 0) { +// int errsv = errno; +// LOG(INFO) << "accept() failed with errno=" << errsv; + mSocketFD = -1; + } + + return newSocketFD; +} + +void ConnectionServerSocket::close() +{ + // Under Linux we should call shutdown first to unblock blocking accept(). + ::shutdown(mSocketFD, SHUT_RDWR); + ::close(mSocketFD); +} + +ConnectionServerSocketTCP::ConnectionServerSocketTCP(int bindPort, + const char* bindAddress) +: ConnectionServerSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) +{ + const int one = 1; + if (setsockopt(mSocketFD, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one))) { +// int errsv = errno; +// LOG(ERROR) << "socket() failed with errno=" << errsv; + } + +#ifdef __APPLE__ + // We use SO_NOSIGPIPE here because MSG_NOSIGNAL is not supported + // for the write() call under MacOs X. + if (::setsockopt(mSocketFD, SOL_SOCKET, SO_NOSIGPIPE, (char *)&one, sizeof(one))) { +// int errsv = errno; +// LOG(ERROR) << "setsockopt() failed with errno=" << errsv; + close(); + return false; + } +#endif + + if (!resolveAddress(&mLocal, bindAddress, bindPort)) { + mLocal.sin_family = AF_UNSPEC; + } +} + +bool ConnectionServerSocketTCP::bind(int connectionQueueSize) +{ + return bindInternal((sockaddr*)&mLocal, sizeof(mLocal), connectionQueueSize); +} + +ConnectionSocket* ConnectionServerSocketTCP::accept() +{ + sockaddr_in newConnectionAddr; + socklen_t newConnectionAddrLen = sizeof(newConnectionAddr); + + int newSocketFD = acceptInternal((sockaddr*)&newConnectionAddr, newConnectionAddrLen); + if (newSocketFD < 0) { +// int errsv = errno; +// LOG(INFO) << "accept() failed with errno=" << errsv; + return NULL; + } + + return new ConnectionSocketTCP(newSocketFD, newConnectionAddr); +} + +ConnectionServerSocketUnix::ConnectionServerSocketUnix(const std::string &path) +: ConnectionServerSocket(AF_UNIX, SOCK_STREAM, 0) +, mBound(false) +{ + mLocal.sun_family = AF_UNIX; + strncpy(mLocal.sun_path, path.data(), 108); + mLocal.sun_path[108-1] = '\0'; +} + +bool ConnectionServerSocketUnix::bind(int connectionQueueSize) +{ + ConnectionSocketUnix testSocket(mLocal.sun_path); + if (testSocket.connect() == 0) { + // Socket is alive. Return error - we can't bind. + return false; + } else { + // Socket is not alive. Remove its file entry to avoid EINVAL. + ::unlink(mLocal.sun_path); + } + + mBound = bindInternal((sockaddr*)&mLocal, sizeof(mLocal), connectionQueueSize); + return mBound; +} + +ConnectionSocket* ConnectionServerSocketUnix::accept() +{ + sockaddr_un newConnectionAddr; + socklen_t newConnectionAddrLen = sizeof(newConnectionAddr); + + int newSocketFD = acceptInternal((sockaddr*)&newConnectionAddr, newConnectionAddrLen); + if (newSocketFD < 0) { +// int errsv = errno; +// LOG(INFO) << "accept() failed with errno=" << errsv; + return NULL; + } + + return new ConnectionSocketUnix(newSocketFD, newConnectionAddr); +} // vim: ts=4 sw=4 diff --git a/public-trunk/CommonLibs/Sockets.h b/public-trunk/CommonLibs/Sockets.h index 22a4dab..0b90f5f 100644 --- a/public-trunk/CommonLibs/Sockets.h +++ b/public-trunk/CommonLibs/Sockets.h @@ -33,10 +33,9 @@ #include #include #include +#include #include #include -#include - @@ -182,6 +181,233 @@ public: }; +/** A base class for connection-oriented sockets. */ +class ConnectionSocket { + +public: + + /** An almost-does-nothing constructor. */ + ConnectionSocket(int af, int type, int protocol); + +protected: + /** Constructor for the use with ConnectionServerSocket. */ + ConnectionSocket(int socketFD) : mSocketFD(socketFD) {} +public: + + virtual ~ConnectionSocket(); + + /** + Connect to the specified destination. + @return Same as system connect() function - 0 on success, -1 on error. + */ + virtual int connect() =0; + + /** + Send data. + @param buffer The data bytes to send. + @param length Number of bytes to send. + @return number of bytes written, or -1 on error. + */ + int write( const char * buffer, int length); + + /** + Receive data. + @param buffer A buffer to read data to. + @param length Length of the buffer. + @return The number of bytes received or -1 on non-blocking pass. + */ + int read(char* buffer, size_t length); + + /** Make the socket non-blocking. */ + void nonblocking(); + + /** Make the socket blocking (the default). */ + void blocking(); + + /** Close the socket. */ + void close(); + +friend std::ostream& operator<<(std::ostream& os, const ConnectionSocket& conn); +friend class ConnectionServerSocket; + +protected: + + int mSocketFD; ///< underlying file descriptor + +}; + +class ConnectionSocketTCP : public ConnectionSocket { + friend class ConnectionServerSocketTCP; +public: + + /** Constructor */ + ConnectionSocketTCP(const struct sockaddr_in &remote) + : ConnectionSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) + , mRemote(remote) + {} + + /** Constructor */ + ConnectionSocketTCP(int port, const char* address); + +protected: + /** Constructor for the use with ConnectionServerSocket. */ + ConnectionSocketTCP(int socketFD, const sockaddr_in &peerAddr) + : ConnectionSocket(socketFD), mRemote(peerAddr) {} +public: + + /** + Connect to the specified destination. + @return Same as system connect() function - 0 on success, -1 on error. + */ + virtual int connect(); + +protected: + + sockaddr_in mRemote; + +}; + +class ConnectionSocketUnix : public ConnectionSocket { +friend class ConnectionServerSocketUnix; +public: + + /** Constructor */ + ConnectionSocketUnix(const struct sockaddr_un &remote) + : ConnectionSocket(AF_UNIX, SOCK_STREAM, 0) + , mRemote(remote) + {} + + /** Constructor */ + ConnectionSocketUnix(const std::string &path) + : ConnectionSocket(AF_UNIX, SOCK_STREAM, 0) + { + mRemote.sun_family = AF_UNIX; + strncpy(mRemote.sun_path, path.data(), 108); + mRemote.sun_path[108-1] = '\0'; + } + +protected: + /** Constructor for the use with ConnectionServerSocket. */ + ConnectionSocketUnix(int socketFD, const sockaddr_un &peerAddr) + : ConnectionSocket(socketFD), mRemote(peerAddr) {} +public: + + /** + Connect to the specified destination. + @return Same as system connect() function - 0 on success, -1 on error. + */ + virtual int connect(); + +protected: + + sockaddr_un mRemote; + +}; + +/** Abstract class for a server side of connection-oriented sockets. */ +class ConnectionServerSocket { + +public: + + /** Constructor - creates a socket. */ + ConnectionServerSocket(int af, int type, int protocol); + + virtual ~ConnectionServerSocket() {}; + + /** + Perform bind. + @return True on success, false otherwise. + */ + virtual bool bind(int connectionQueueSize = 16) =0; + + /** + Wait for the next incoming connection request. + @return Incoming connection or NULL on error. + */ + virtual ConnectionSocket* accept() =0; + + /** Close the socket. */ + void close(); + +protected: + + int mSocketFD; ///< underlying file descriptor + + /** + Bind to given address. + @return True on success, false otherwise. + */ + bool bindInternal(const sockaddr *addr, int addrlen, int connectionQueueSize = 16); + + /** + Wait for the next incoming connection request. + @return Same as system accept() - descriptor on success, -1 on error. + */ + int acceptInternal(sockaddr *addr, socklen_t &addrlen); + +}; + +class ConnectionServerSocketTCP : public ConnectionServerSocket { +public: + + enum { + DEFAULT_PORT = 0 ///< Use this value for bindPort to let OS select port for you. + }; + + /** Constructor */ + ConnectionServerSocketTCP(int bindPort, const char* bindAddress); + + /** Destructor */ + virtual ~ConnectionServerSocketTCP() {close();} + + /** + Perform bind. + @return True on success, false otherwise. + */ + virtual bool bind(int connectionQueueSize = 16); + + /** + Wait for the next incoming connection request. + @return Incoming connection or NULL on error. + */ + virtual ConnectionSocket* accept(); + +protected: + + sockaddr_in mLocal; + +}; + +class ConnectionServerSocketUnix : public ConnectionServerSocket { +public: + + /** Constructor */ + ConnectionServerSocketUnix(const std::string &path); + + /** Destructor */ + virtual ~ConnectionServerSocketUnix() { + close(); + if (mBound) ::unlink(mLocal.sun_path); + } + + /** + Perform bind. + @return True on success, false otherwise. + */ + virtual bool bind(int connectionQueueSize = 16); + + /** + Wait for the next incoming connection request. + @return Incoming connection or NULL on error. + */ + virtual ConnectionSocket* accept(); + +protected: + + sockaddr_un mLocal; + bool mBound; ///< Set to true if we successfully bound the socket. + +}; #endif diff --git a/public-trunk/Globals/Globals.cpp b/public-trunk/Globals/Globals.cpp index cb5ed35..41cfb74 100644 --- a/public-trunk/Globals/Globals.cpp +++ b/public-trunk/Globals/Globals.cpp @@ -59,6 +59,3 @@ const char* gOpenBTSWelcome = "All users of this software are expected to comply with applicable\n" "regulations and laws.\n" ; - - -CommandLine::Parser gParser; diff --git a/public-trunk/Globals/Globals.h b/public-trunk/Globals/Globals.h index e4f9f84..3fc9a76 100644 --- a/public-trunk/Globals/Globals.h +++ b/public-trunk/Globals/Globals.h @@ -31,7 +31,6 @@ #define GLOBALS_H #include -#include /** Just about everything goes into the configuration table. @@ -42,7 +41,4 @@ extern ConfigurationTable gConfig; /** The OpenBTS welcome message. */ extern const char* gOpenBTSWelcome; -/** The central parser. */ -extern CommandLine::Parser gParser; - #endif diff --git a/public-trunk/Makefile.common b/public-trunk/Makefile.common index 9108e2d..b555209 100644 --- a/public-trunk/Makefile.common +++ b/public-trunk/Makefile.common @@ -49,6 +49,7 @@ TRX_LA = $(top_builddir)/TRXManager/libtrxmanager.la CONTROL_LA = $(top_builddir)/Control/libcontrol.la GLOBALS_LA = $(top_builddir)/Globals/libglobals.la CLI_LA = $(top_builddir)/CLI/libcli.la +CLI_COMMANDS_LA = $(top_builddir)/CLI/libcli_commands.la HLR_LA = $(top_builddir)/HLR/libHLR.la #SQL_LA = $(top_builddir)/SQL/libSQL.la diff --git a/public-trunk/apps/Makefile.am b/public-trunk/apps/Makefile.am index 8f412f5..0b67cba 100644 --- a/public-trunk/apps/Makefile.am +++ b/public-trunk/apps/Makefile.am @@ -24,7 +24,8 @@ AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) AM_CXXFLAGS = -Wall -g -pthread noinst_PROGRAMS = \ - OpenBTS + OpenBTS \ + OpenBTScli OpenBTS_SOURCES = OpenBTS.cpp OpenBTS_LDADD = \ @@ -36,10 +37,17 @@ OpenBTS_LDADD = \ $(TRX_LA) \ $(CONTROL_LA) \ $(SMS_LA) \ + $(CLI_COMMANDS_LA) \ $(CLI_LA) \ $(OSIP_LIBS) \ $(ORTP_LIBS) +OpenBTScli_SOURCES = OpenBTScli.cpp +OpenBTScli_LDADD = \ + $(GLOBALS_LA) \ + $(COMMON_LA) \ + $(CLI_LA) + EXTRA_DIST = \ OpenBTS.config.example \ diff --git a/public-trunk/apps/OpenBTS.config.example b/public-trunk/apps/OpenBTS.config.example index fd40c77..13511ef 100644 --- a/public-trunk/apps/OpenBTS.config.example +++ b/public-trunk/apps/OpenBTS.config.example @@ -514,8 +514,33 @@ GSM.MaxSpeechLatency 2 # CLI paramters # +# A string, used as a CLI prompt CLI.Prompt OpenBTS> +# CLI type has three possible values: +# Local - command line is provided by the same process. +# Useful when you don't daemonize OpenBTS process. +# Unix - command line is provided over a UNIX-socket. +# Useful for local access to command line when you run +# OpenBTS as a daemon. +# TCP - command line is provided by a TCP connection. +# Useful when you want to control your OpenBTS remotely. +# +# !!! WARNING !!! +# Current implementation of remote CLI doesn't support authentication +# and encryption. Use remote CLI with care! +CLI.Type Local +# Parameters for TCP remote CLI. +# Interface to bind to: +CLI.TCP.IP 127.0.0.1 +$static CLI.TCP.IP +# Port to bind to: +CLI.TCP.Port 22239 +$static CLI.TCP.Port +# Parameters for UNIX remote CLI. +# Path to the socket +CLI.Unix.Path /tmp/OpenBTScli.socket +$static CLI.Unix.Path diff --git a/public-trunk/apps/OpenBTS.cpp b/public-trunk/apps/OpenBTS.cpp index b929ee1..33336ab 100644 --- a/public-trunk/apps/OpenBTS.cpp +++ b/public-trunk/apps/OpenBTS.cpp @@ -37,6 +37,8 @@ #include #include +#include +#include #include #include #include @@ -46,14 +48,9 @@ #include #include -#ifdef HAVE_LIBREADLINE // [ -//# include -# include -# include -#endif // HAVE_LIBREADLINE ] - using namespace std; using namespace GSM; +using namespace CommandLine; // Load configuration from a file. ConfigurationTable gConfig("OpenBTS.config"); @@ -107,21 +104,10 @@ void restartTransceiver() } - - - - -int main(int argc, char *argv[]) +void startBTS() { - srandom(time(NULL)); - - COUT("\n\n" << gOpenBTSWelcome << "\n"); COUT("\nStarting the system..."); - if (gConfig.defines("Log.FileName")) { - gSetLogFile(gConfig.getStr("Log.FileName")); - } - if (gConfig.defines("Control.TMSITable.SavePath")) { gTMSITable.load(gConfig.getStr("Control.TMSITable.SavePath")); } @@ -196,9 +182,9 @@ int main(int argc, char *argv[]) // C-V C0T0 SDCCHs SDCCHLogicalChannel C0T0SDCCH[4] = { SDCCHLogicalChannel(0,gSDCCH_4_0), - SDCCHLogicalChannel(0,gSDCCH_4_1), - SDCCHLogicalChannel(0,gSDCCH_4_2), - SDCCHLogicalChannel(0,gSDCCH_4_3), + SDCCHLogicalChannel(0,gSDCCH_4_1), + SDCCHLogicalChannel(0,gSDCCH_4_2), + SDCCHLogicalChannel(0,gSDCCH_4_3), }; Thread C0T0SDCCHControlThread[4]; for (int i=0; i<4; i++) { @@ -212,22 +198,22 @@ int main(int argc, char *argv[]) unsigned sCount = 1; bool halfDuplex = gConfig.defines("GSM.HalfDuplex"); - if (halfDuplex) LOG(NOTICE) << "Configuring for half-duplex operation."; - else LOG(NOTICE) << "Configuring for full-duplex operation."; + if (halfDuplex) { LOG(NOTICE) << "Configuring for half-duplex operation." ; } + else { LOG(NOTICE) << "Configuring for full-duplex operation."; } - if (halfDuplex) sCount++; + if (halfDuplex) sCount++; // Create C-VII slots. for (int i=0; i home_dir_len) { - if(!(history_name = (char *)malloc(history_len))) { - LOG(ERROR) << "malloc failed: " << strerror(errno); - exit(-1); - } - memcpy(history_name, home_dir, home_dir_len); - memcpy(history_name + home_dir_len, history_file_name, - history_file_len + 1); - read_history(history_name); - } - } -#endif // HAVE_LIBREADLINE ] - - - LOG(INFO) << "system ready"; - COUT("\n\nWelcome to OpenBTS. Type \"help\" to see available commands."); - // FIXME: We want to catch control-d (emacs keybinding for exit()) - - - // The logging parts were removed from this loop. - // If we want them back, they will need to go into their own thread. - while (1) { -#ifdef HAVE_LIBREADLINE // [ - char *inbuf = readline(gConfig.getStr("CLI.Prompt")); - if (!inbuf) break; - if (*inbuf) { - add_history(inbuf); - // The parser returns -1 on exit. - if (gParser.process(inbuf, cout, cin)<0) { - free(inbuf); - break; - } - } - free(inbuf); -#else // HAVE_LIBREADLINE ][ - cout << endl << gConfig.getStr("CLI.Prompt"); - cout.flush(); - char inbuf[1024]; - cin.getline(inbuf,1024,'\n'); - // The parser returns -1 on exit. - if (gParser.process(inbuf,cout,cin)<0) break; -#endif // !HAVE_LIBREADLINE ] - } - -#ifdef HAVE_LIBREADLINE // [ - if(history_name) { - int e = write_history(history_name); - if(e) { - fprintf(stderr, "error: history: %s\n", strerror(e)); - } - free(history_name); - history_name = 0; - } -#endif // HAVE_LIBREADLINE ] +} +void stopBTS() +{ if (gConfig.defines("Control.TMSISavePath")) { gTMSITable.save(gConfig.getStr("Control.TMSISavePath")); } @@ -335,4 +258,32 @@ int main(int argc, char *argv[]) if (gTransceiverPid) kill(gTransceiverPid, SIGKILL); } + +int main(int argc, char *argv[]) +{ + srandom(time(NULL)); + + COUT("\n\n" << gOpenBTSWelcome << "\n"); + + if (gConfig.defines("Log.FileName")) { + gSetLogFile(gConfig.getStr("Log.FileName")); + } + +// startBTS(); + + if (strcasecmp(gConfig.getStr("CLI.Type"),"TCP") == 0) { + ConnectionServerSocketTCP serverSock(gConfig.getNum("CLI.TCP.Port"), + gConfig.getStr("CLI.TCP.IP")); + runCLIServer(&serverSock); + } else if (strcasecmp(gConfig.getStr("CLI.Type"),"Unix") == 0) { + ConnectionServerSocketUnix serverSock(gConfig.getStr("CLI.Unix.Path")); + runCLIServer(&serverSock); + } else { + runCLI(&gParser); + } + +// stopBTS(); + +} + // vim: ts=4 sw=4 diff --git a/public-trunk/apps/OpenBTScli.cpp b/public-trunk/apps/OpenBTScli.cpp new file mode 100644 index 0000000..62a0bac --- /dev/null +++ b/public-trunk/apps/OpenBTScli.cpp @@ -0,0 +1,64 @@ +/* +* Copyright 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +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. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +*/ + + + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace CommandLine; + +// Load configuration from a file. +ConfigurationTable gConfig("OpenBTS.config"); + + +int main(int argc, char * argv[]) +{ + srandom(time(NULL)); + COUT(endl << endl << gOpenBTSWelcome << endl); + + gLogInit("INFO"); + +// if (gConfig.defines("Log.FileName")) { +// gSetLogFile(gConfig.getStr("Log.FileName")); +// } + + if (strcasecmp(gConfig.getStr("CLI.Type"),"TCP") == 0) { + ConnectionSocketTCP sock(gConfig.getNum("CLI.TCP.Port"), + gConfig.getStr("CLI.TCP.IP")); + runCLIClient(&sock); + } else if (strcasecmp(gConfig.getStr("CLI.Type"),"Unix") == 0) { + ConnectionSocketUnix sock(gConfig.getStr("CLI.Unix.Path")); + runCLIClient(&sock); + } else { + COUT(endl << "ERROR -- OpenBTS not configured to use remote CLI" << endl); + } +} + +// vim: ts=4 sw=4