diff --git a/public-trunk/CLI/CLI.cpp b/public-trunk/CLI/CLI.cpp index ba3564f..7d9ce07 100644 --- a/public-trunk/CLI/CLI.cpp +++ b/public-trunk/CLI/CLI.cpp @@ -22,781 +22,89 @@ */ -#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'); + if (!cin.good()) break; + // 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..07d9811 --- /dev/null +++ b/public-trunk/CLI/CLIParser.cpp @@ -0,0 +1,849 @@ +/* +* 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 Helper functions. */ +//@{ + +void CommandLine::exitBTS(unsigned waitSec, ostream& os) +{ + // Block creation of new channels. + gBTS.hold(true); + + if (waitSec!=0) { + os << "waiting up to " << waitSec << " seconds for clearing of " + << gBTS.TCHActive() << " active calls" << endl; + + // Wait up to the timeout for active channels to release. + time_t finish = time(NULL) + waitSec; + 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(os); + } + if (gConfig.defines("Control.TMSITable.SavePath")) { + gTMSITable.save(gConfig.getStr("Control.TMSITable.SavePath")); + } + os << endl << "exiting..." << endl; +} + +void CommandLine::printStats(ostream& os) +{ + 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; +} + +//@} + +/**@name Commands for the CLI. */ +//@{ + +/* + 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]); + + exitBTS(wait, os); + + 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 cliPrintStats(int argc, char** argv, ostream& os) +{ + if (argc!=1) return BAD_NUM_ARGS; + printStats(os); + 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", cliPrintStats, "-- 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..37eee77 --- /dev/null +++ b/public-trunk/CLI/CLIParser.h @@ -0,0 +1,93 @@ +/* +* 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; + +/** Exit BTS, waiting up to waitSec seconds for active channels to release. */ +void exitBTS(unsigned waitSec, std::ostream& os); + +/** Print current usage loads. */ +void printStats(std::ostream& os); + + +} // 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/Logger.cpp b/public-trunk/CommonLibs/Logger.cpp index 3abb4f9..c044639 100644 --- a/public-trunk/CommonLibs/Logger.cpp +++ b/public-trunk/CommonLibs/Logger.cpp @@ -108,15 +108,13 @@ bool gSetLogFile(const char *name) assert(name); LOG(DEEPDEBUG) << "setting log path to " << name; bool retVal = true; - gLogLock.lock(); FILE* newLoggingFile = fopen(name,"a+"); if (!newLoggingFile) { LOG(ERROR) << "cannot open \"" << name << "\" for logging."; retVal = false; } else { - gLoggingFile = newLoggingFile; + gSetLogFile(newLoggingFile); } - gLogLock.unlock(); LOG(FORCE) << "new log path " << name; return retVal; } @@ -205,7 +203,17 @@ void gLogInit(const char* defaultLevel) } } - +LogInitializer::LogInitializer(const char *logFile) +{ + gLogInit("INFO"); + if (logFile != NULL) { + gSetLogFile(logFile); + } else if (gConfig.defines("Log.FileName")) { + gSetLogFile(gConfig.getStr("Log.FileName")); + } else { + gSetLogFile(stdout); + } +} // vim: ts=4 sw=4 diff --git a/public-trunk/CommonLibs/Logger.h b/public-trunk/CommonLibs/Logger.h index 6c9d2e5..f25a781 100644 --- a/public-trunk/CommonLibs/Logger.h +++ b/public-trunk/CommonLibs/Logger.h @@ -103,18 +103,26 @@ std::ostringstream& operator<<(std::ostringstream& os, Log::Level); std::list gGetLoggerAlarms(); ///< Get a copy of the recent alarm list. -/**@ Global control and initialization of the logging system. */ -//@{ -void gLogInit(const char* defaultLevel = DEFAULT_LOGGING_LEVEL); -Log::Level gLoggingLevel(const char *filename); -//@} - /**@name Global logging file control. */ //@{ void gSetLogFile(FILE*); bool gSetLogFile(const char*); //@} +/**@ Global control and initialization of the logging system. */ +//@{ +void gLogInit(const char* defaultLevel = DEFAULT_LOGGING_LEVEL); +Log::Level gLoggingLevel(const char *filename); + +/** Class to initialize Logger during static variables initialization. */ +class LogInitializer { +public: + + LogInitializer(const char *logFile=NULL); + +}; +//@} + #endif 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..00cb9f1 100644 --- a/public-trunk/CommonLibs/Sockets.cpp +++ b/public-trunk/CommonLibs/Sockets.cpp @@ -234,6 +234,11 @@ void UDPSocket::open(unsigned short localPort) perror("socket() failed"); throw SocketError(); } + // Set "close on exec" flag to avoid open sockets inheritance by + // child processes, like 'transceiver'. + int flags = fcntl(mSocketFD, F_GETFD); + if (flags >= 0) fcntl(mSocketFD, F_SETFD, flags | FD_CLOEXEC); + // bind struct sockaddr_in address; @@ -280,6 +285,10 @@ void UDDSocket::open(const char* localPath) perror("socket() failed"); throw SocketError(); } + // Set "close on exec" flag to avoid open sockets inheritance by + // child processes, like 'transceiver'. + int flags = fcntl(mSocketFD, F_GETFD); + if (flags >= 0) fcntl(mSocketFD, F_SETFD, flags | FD_CLOEXEC); // bind struct sockaddr_un address; @@ -303,6 +312,234 @@ 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; + } + // Set "close on exec" flag to avoid open sockets inheritance by + // child processes, like 'transceiver'. + int flags = fcntl(mSocketFD, F_GETFD); + if (flags >= 0) fcntl(mSocketFD, F_SETFD, flags | FD_CLOEXEC); +} +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; + } + // Set "close on exec" flag to avoid open sockets inheritance by + // child processes, like 'transceiver'. + int flags = fcntl(mSocketFD, F_GETFD); + if (flags >= 0) fcntl(mSocketFD, F_SETFD, flags | FD_CLOEXEC); +} + +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/TRXManager/TRXManager.cpp b/public-trunk/TRXManager/TRXManager.cpp index bd8f40a..c378300 100644 --- a/public-trunk/TRXManager/TRXManager.cpp +++ b/public-trunk/TRXManager/TRXManager.cpp @@ -32,6 +32,7 @@ #include "GSMConfig.h" #include "GSML1FEC.h" #include +#include #include @@ -39,6 +40,8 @@ using namespace GSM; using namespace std; +// From OpenBTS.cpp +extern void shutdownOpenbts(); TransceiverManager::TransceiverManager(int numARFCNs, const char* wTRXAddress, int wBasePort) @@ -85,11 +88,13 @@ void TransceiverManager::clockHandler() // Did the transceiver die?? if (msgLen<0) { LOG(ALARM) << "TRX clock interface timed out, assuming TRX is dead."; - abort(); + shutdownOpenbts(); + return; } if (msgLen==0) { LOG(ALARM) << "read error on TRX clock interface, return " << msgLen; + shutdownOpenbts(); return; } diff --git a/public-trunk/Transceiver52M/USRPDevice.cpp b/public-trunk/Transceiver52M/USRPDevice.cpp index 110a006..4abc4b0 100644 --- a/public-trunk/Transceiver52M/USRPDevice.cpp +++ b/public-trunk/Transceiver52M/USRPDevice.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include "Threads.h" #include "USRPDevice.h" @@ -81,6 +82,8 @@ bool USRPDevice::open() m_uRx = usrp_standard_rx_sptr(usrp_standard_rx::make(0,decimRate,1,-1, usrp_standard_rx::FPGA_MODE_NORMAL, 1024,16*8,rbf)); + if (!m_uRx) throw runtime_error(""); + #ifdef HAVE_LIBUSRP_3_2 m_uRx->set_fpga_master_clock_freq(masterClockRate); #endif @@ -104,6 +107,7 @@ bool USRPDevice::open() try { m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make(0,decimRate*2,1,-1, 1024,16*8,rbf)); + if (!m_uTx) throw runtime_error(""); #ifdef HAVE_LIBUSRP_3_2 m_uTx->set_fpga_master_clock_freq(masterClockRate); #endif 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..9064d28 100644 --- a/public-trunk/apps/OpenBTS.config.example +++ b/public-trunk/apps/OpenBTS.config.example @@ -23,6 +23,41 @@ # So be cafeful. +# +# Daemonization options. +# + +# Define Server.Daemonize to run OpenBTS in background. +#Server.Daemonize +$optional Server.Daemonize + +# If Server.RestartOnCrash is defined, OpenBTS forks a child +# and restart it if child dies. This is kind of failsafe. +#Server.RestartOnCrash +$optional Server.RestartOnCrash + +# Path to OpenBTS PID file. +# Filesystem Hierarchy Standard specifies it daemons should write PID +# to /var/run, but you may specify any other directory if you run +# OpenBTS manually. +#Server.WritePID /var/run/OpenBTS.pid +Server.WritePID OpenBTS.pid +$static Server.WritePID + +# Set umask. +# 027(oct)=23(dec) which equals to file creation mode 0750(oct) +Server.umask 23 +$static Server.umask + +# Define Server.ChdirToRoot to change directory to / on daemon startup. +# This is the recommended behaviour if daemon runs system-wide, because +# prevents the current directory from being locked; hence not being able +# to remove it. But when you run manually this behaviour may be inconvenient +# for you, because all paths should be absolute. +#Server.ChdirToRoot +$optional Server.ChdirToRoot + + # # Logging and reporting parameters # @@ -88,6 +123,14 @@ TRX.Path ../Transceiver/transceiver #TRX.Path ../Transceiver52M/transceiver $static TRX.Path +# Path to transceiver PID file. +# Filesystem Hierarchy Standard specifies it daemons should write PID +# to /var/run, but you may specify any other directory if you run +# OpenBTS manually. +#TRX.WritePID /var/run/transceiver.pid +TRX.WritePID transceiver.pid +$static TRX.WritePID + # TRX logging. # Logging level. # IF TRX.Path IS DEFINED, THIS MUST ALSO BE DEFINED. @@ -514,8 +557,37 @@ 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 +# Filesystem Hierarchy Standard specifies it daemons should store runtime +# files in /var/run, but you may specify any other directory if you run +# OpenBTS manually. +#CLI.Unix.Path /var/run/OpenBTS.control +CLI.Unix.Path OpenBTS.control +$static CLI.Unix.Path diff --git a/public-trunk/apps/OpenBTS.cpp b/public-trunk/apps/OpenBTS.cpp index b929ee1..f742b1f 100644 --- a/public-trunk/apps/OpenBTS.cpp +++ b/public-trunk/apps/OpenBTS.cpp @@ -37,6 +37,8 @@ #include #include +#include +#include #include #include #include @@ -45,82 +47,468 @@ #include #include #include - -#ifdef HAVE_LIBREADLINE // [ -//# include -# include -# include -#endif // HAVE_LIBREADLINE ] +#include using namespace std; using namespace GSM; +using namespace CommandLine; -// Load configuration from a file. +static int daemonize(std::string &lockfile, int &lfp); +static int forkLoop(); + +class DaemonInitializer +{ +public: + DaemonInitializer(bool doDaemonize) + : mLockFileFD(-1) + { + // Start in daemon mode? + if (doDaemonize) + if (daemonize(mLockFileName, mLockFileFD) != EXIT_SUCCESS) + exit(EXIT_FAILURE); + } + + ~DaemonInitializer() + { + if (mLockFileFD >= 0) close(mLockFileFD); + if (mLockFileName.size() > 0) { + if (unlink(mLockFileName.data()) == 0) { + LOG(INFO) << "Deleted lock file " << mLockFileName; + } else { + LOG(INFO) << "Error while deleting lock file " << mLockFileName + << " code=" << errno << ": " << strerror(errno); + } + } + } + +protected: + std::string mLockFileName; + int mLockFileFD; +}; + +class Restarter +{ +public: + Restarter(bool restartOnCrash) + { + if (restartOnCrash) + if (forkLoop() != EXIT_SUCCESS) + exit(EXIT_FAILURE); + } +}; + +/// Load configuration from a file. ConfigurationTable gConfig("OpenBTS.config"); +/// Initialize Logger form the config. +static LogInitializer sgLogInitializer; +/// Fork daemon if needed. +static DaemonInitializer sgDaemonInitializer(gConfig.defines("Server.Daemonize")); +/// Fork a child and restart it if it crash. Kind of failsafe. +static Restarter sgRestarter(gConfig.defines("Server.RestartOnCrash")); // All of the other globals that rely on the global configuration file need to // be declared here. -// The global SIPInterface object. +/// The global SIPInterface object. SIP::SIPInterface gSIPInterface; -// Configure the BTS object based on the config file. -// So don't create this until AFTER loading the config file. +/// Configure the BTS object based on the config file. +/// So don't create this until AFTER loading the config file. GSMConfig gBTS; -// Our interface to the software-defined radio. +/// Our interface to the software-defined radio. TransceiverManager gTRX(1, gConfig.getStr("TRX.IP"), gConfig.getNum("TRX.Port")); +/// Pointer to the server socket if we run remote CLI. +static ConnectionServerSocket *sgCLIServerSock = NULL; +/// We store Transceiver PID if we start it. +static pid_t sgTransceiverPid = 0; +static int sgTransceiverPidFileFd = -1; +static std::string sgTransceiverPidFile; - - -pid_t gTransceiverPid = 0; - -void restartTransceiver() +/** Function to shutdown the process when something wrong happens. */ +void shutdownOpenbts() { - // This is harmless - if someone is running OpenBTS they WANT no transceiver - // instance at the start anyway. - if (gTransceiverPid > 0) { - LOG(INFO) << "RESTARTING TRANSCEIVER"; - kill(gTransceiverPid,SIGKILL); // TODO - call on ctrl-c (put in signal?) + kill(SIGTERM, getpid()); +} + +static int openPidFile(const std::string &lockfile) +{ + int lfp = open(lockfile.data(), O_RDWR|O_CREAT, 0640); + if (lfp < 0) { + LOG(ERROR) << "Unable to create PID file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + } else { + LOG(INFO) << "Created PID file " << lockfile; + } + return lfp; +} + +static int lockPidFile(const std::string &lockfile, int lfp, bool block=false) +{ + if (lockf(lfp, block?F_LOCK:F_TLOCK,0) < 0) { + LOG(ERROR) << "Unable to lock PID file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +static int writePidFile(const std::string &lockfile, int lfp, int pid) +{ + // Clear old file content first + if (ftruncate(lfp, 0) < 0) { + LOG(ERROR) << "Unable to clear PID file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; } + // Write PID + char tempBuf[64]; + snprintf(tempBuf, sizeof(tempBuf), "%d\n", pid); + ssize_t tempDataLen = strlen(tempBuf); + lseek(lfp, 0, SEEK_SET); + if (write(lfp, tempBuf, tempDataLen) != tempDataLen) { + LOG(ERROR) << "Unable to write PID to file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +static int readPidFile(const std::string &lockfile, int lfp, int &pid) +{ + char tempBuf[64]; + lseek(lfp, 0, SEEK_SET); + int bytesRead = read(lfp, tempBuf, sizeof(tempBuf)); + if (bytesRead <= 0) { + LOG(ERROR) << "Unable to read PID from file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + tempBuf[bytesRead=0); - if (gTransceiverPid==0) { + sgTransceiverPid = vfork(); + LOG_ASSERT(sgTransceiverPid>=0); + if (sgTransceiverPid==0) { // Pid==0 means this is the process that starts the transceiver. execl(TRXPath,"transceiver",TRXLogLevel,TRXLogFileName,NULL); LOG(ERROR) << "cannot start transceiver"; _exit(0); } + // Now we can finally write transceiver PID to the file. + if (writePidFile(sgTransceiverPidFile, sgTransceiverPidFileFd, sgTransceiverPid) != EXIT_SUCCESS) return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +static void serverCleanup() +{ + if (sgTransceiverPid) { + kill(sgTransceiverPid, SIGTERM); + if (sgTransceiverPidFileFd >= 0) { + close(sgTransceiverPidFileFd); + } + if (sgTransceiverPidFile.size() > 0) { + if (unlink(sgTransceiverPidFile.data()) == 0) { + LOG(INFO) << "Deleted lock file " << sgTransceiverPidFile; + } else { + LOG(INFO) << "Error while deleting lock file " << sgTransceiverPidFile + << " code=" << errno << ": " << strerror(errno); + } + } } } +static void exitCLI() +{ + if (sgCLIServerSock == NULL) { + serverCleanup(); + _exit(EXIT_SUCCESS); + } else { + // Closing server sock + sgCLIServerSock->close(); + sgCLIServerSock = NULL; + } + // Closing server standard input to shutdown local CLI + // Following functions are not async-signal-safe, but I don't have + // better idea how to do this. + cin.setstate(ios::eofbit); +// cin.putback('\n'); + fclose(stdin); +} +static void daemonChildHandler(int signum) +{ + LOG(INFO) << "Handling signal " << signum; + switch(signum) { + case SIGALRM: + // alarm() fired. + exit(EXIT_FAILURE); + break; + case SIGUSR1: + //Child sent us a signal. Good sign! + exit(EXIT_SUCCESS); + break; + case SIGCHLD: + // Child has died + exit(EXIT_FAILURE); + break; + } +} +static int daemonize(std::string &lockfile, int &lfp) +{ + // Already a daemon + if ( getppid() == 1 ) return EXIT_SUCCESS; + // Sanity checks + if (strcasecmp(gConfig.getStr("CLI.Type"),"Local") == 0) { + LOG(ERROR) << "OpenBTS runs in daemon mode, but CLI is set to Local!"; + return EXIT_FAILURE; + } + if (!gConfig.defines("Server.WritePID")) { + LOG(ERROR) << "OpenBTS runs in daemon mode, but Server.WritePID is not set in config!"; + return EXIT_FAILURE; + } + + // According to the Filesystem Hierarchy Standard 5.13.2: + // "The naming convention for PID files is .pid." + // The same standard specifies that PID files should be placed + // in /var/run, but we make this configurable. + lockfile = gConfig.getStr("Server.WritePID"); + + // Create the PID file as the current user + if ((lfp=openPidFile(lockfile)) < 0) return EXIT_FAILURE; + + // Drop user if there is one, and we were run as root +/* if ( getuid() == 0 || geteuid() == 0 ) { + struct passwd *pw = getpwnam(RUN_AS_USER); + if ( pw ) { + syslog( LOG_NOTICE, "setting user to " RUN_AS_USER ); + setuid( pw->pw_uid ); + } + } +*/ + + // Trap signals that we expect to receive + signal(SIGCHLD, daemonChildHandler); + signal(SIGUSR1, daemonChildHandler); + signal(SIGALRM, daemonChildHandler); + + // Fork off the parent process + pid_t pid = fork(); + if (pid < 0) { + LOG(ERROR) << "Unable to fork daemon, code=" << errno + << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + // If we got a good PID, then we can exit the parent process. + if (pid > 0) { + // Wait for confirmation from the child via SIGUSR1 or SIGCHLD. + LOG(INFO) << "Forked child process with PID " << pid; + // Some recommend to add timeout here too (it will signal SIGALRM), + // but I don't think it's a good idea if we start on a slow system. + // Or may be we should make timeout value configurable and set it + // a big enough value. +// alarm(2); + // pause() should not return. + pause(); + LOG(ERROR) << "Executing code after pause()!"; + return EXIT_FAILURE; + } + + // Now lock our PID file and write our PID to it + if (lockPidFile(lockfile, lfp) != EXIT_SUCCESS) return EXIT_FAILURE; + if (writePidFile(lockfile, lfp, getpid()) != EXIT_SUCCESS) return EXIT_FAILURE; + + // At this point we are executing as the child process + pid_t parent = getppid(); + + // Return signals to default handlers + signal(SIGCHLD, SIG_DFL); + signal(SIGUSR1, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + // Change the file mode mask + // This will restrict file creation mode to 750 (complement of 027). + umask(gConfig.getNum("Server.umask")); + + // Create a new SID for the child process + pid_t sid = setsid(); + if (sid < 0) { + LOG(ERROR) << "Unable to create a new session, code=" << errno + << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + + // Change the current working directory. This prevents the current + // directory from being locked; hence not being able to remove it. + if (gConfig.defines("Server.ChdirToRoot")) { + if (chdir("/") < 0) { + LOG(ERROR) << "Unable to change directory to %s, code" << errno + << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } else { + LOG(INFO) << "Changed current directory to \"/\""; + } + } + + // Redirect standard files to /dev/null + if (freopen( "/dev/null", "r", stdin) == NULL) + LOG(WARN) << "Error redirecting stdin to /dev/null"; + if (freopen( "/dev/null", "w", stdout) == NULL) + LOG(WARN) << "Error redirecting stdout to /dev/null"; + if (freopen( "/dev/null", "w", stderr) == NULL) + LOG(WARN) << "Error redirecting stderr to /dev/null"; + + // Tell the parent process that we are okay + kill(parent, SIGUSR1); + + return EXIT_SUCCESS; +} + +static int forkLoop() +{ + bool shouldExit = false; + sigset_t chldSignalSet; + sigemptyset(&chldSignalSet); + sigaddset(&chldSignalSet, SIGCHLD); + sigaddset(&chldSignalSet, SIGTERM); + sigaddset(&chldSignalSet, SIGINT); + sigaddset(&chldSignalSet, SIGKILL); + + // Block signals to avoid race condition. + // It will be delivered to us in sigwait() when we are ready to handle it. + sigprocmask(SIG_BLOCK, &chldSignalSet, NULL); + + while (1) { + // Fork off the parent process + pid_t pid = fork(); + if (pid < 0) { + // fork() failed. + LOG(ERROR) << "Unable to fork child, code=" << errno + << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } else if (pid > 0) { + // Parent process + // Wait for child process to exit (SIGCHLD). + LOG(INFO) << "Forked child process with PID " << pid; + int signum = -1; + while (signum != SIGCHLD) { + sigwait(&chldSignalSet, &signum); + switch(signum) { + case SIGCHLD: + LOG(ERROR) << "Child with PID " << pid << " died."; + if (shouldExit) exit(EXIT_SUCCESS); + break; + case SIGTERM: + case SIGINT: + case SIGKILL: + // Forward signal to the child. + kill(pid, signum); + // We will exit child exits and send us SIGCHLD. + shouldExit = true; + } + } + } else { + // Child process + // Unblock signals we blocked. + sigprocmask(SIG_UNBLOCK, &chldSignalSet, NULL); + return EXIT_SUCCESS; + } + } + + return EXIT_SUCCESS; +} + +static void signalHandler(int sig) +{ + COUT("Handling signal " << sig); + LOG(INFO) << "Handling signal " << sig; + switch(sig){ + case SIGHUP: + // re-read the config + // TODO:: + break; + case SIGTERM: + case SIGINT: + // finalize the server + exitCLI(); + break; + default: + break; + } +} int main(int argc, char *argv[]) { srandom(time(NULL)); - COUT("\n\n" << gOpenBTSWelcome << "\n"); - COUT("\nStarting the system..."); + // Catch signal to re-read config + if (signal(SIGHUP, signalHandler) == SIG_ERR) { + cerr << "Error while setting handler for SIGHUP."; + return EXIT_FAILURE; + } + // Catch signal to shutdown gracefully + if (signal(SIGTERM, signalHandler) == SIG_ERR) { + cerr << "Error while setting handler for SIGTERM."; + return EXIT_FAILURE; + } + // Catch Ctrl-C signal + if (signal(SIGINT, signalHandler) == SIG_ERR) { + cerr << "Error while setting handler for SIGINT."; + return EXIT_FAILURE; + } + // Various TTY signals + // We don't really care about return values of these. + signal(SIGTSTP,SIG_IGN); + signal(SIGTTOU,SIG_IGN); + signal(SIGTTIN,SIG_IGN); - if (gConfig.defines("Log.FileName")) { - gSetLogFile(gConfig.getStr("Log.FileName")); - } + cout << endl << endl << gOpenBTSWelcome << endl; + + try { + +#if 1 + cout << endl << "Starting the system..." << endl; if (gConfig.defines("Control.TMSITable.SavePath")) { gTMSITable.load(gConfig.getStr("Control.TMSITable.SavePath")); @@ -128,7 +516,7 @@ int main(int argc, char *argv[]) LOG(ALARM) << "OpenBTS starting, ver " << VERSION << " build date " << __DATE__; - restartTransceiver(); + startTransceiver(); // Start the SIP interface. gSIPInterface.start(); @@ -196,9 +584,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 +600,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()) +#endif - - // 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 ] + if (strcasecmp(gConfig.getStr("CLI.Type"),"TCP") == 0) { + ConnectionServerSocketTCP serverSock(gConfig.getNum("CLI.TCP.Port"), + gConfig.getStr("CLI.TCP.IP")); + sgCLIServerSock = &serverSock; + runCLIServer(&serverSock); + sgCLIServerSock = NULL; + } else if (strcasecmp(gConfig.getStr("CLI.Type"),"Unix") == 0) { + ConnectionServerSocketUnix serverSock(gConfig.getStr("CLI.Unix.Path")); + sgCLIServerSock = &serverSock; + runCLIServer(&serverSock); + sgCLIServerSock = NULL; + } else { + runCLI(&gParser); } -#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 ] - - if (gConfig.defines("Control.TMSISavePath")) { - gTMSITable.save(gConfig.getStr("Control.TMSISavePath")); + } catch(SocketError) { + // Shutdown without core dump. + // SocketError is a usual case, e.g. it's fired when transceiver fails. + LOG(ALARM) << "Uncaught exception. Shutting down."; } - if (gTransceiverPid) kill(gTransceiverPid, SIGKILL); + if (!gBTS.hold()) { + exitBTS(0, cout); + } + + serverCleanup(); + + return EXIT_SUCCESS; } // 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 diff --git a/public-trunk/init/OpenBTS b/public-trunk/init/OpenBTS new file mode 100755 index 0000000..b086535 --- /dev/null +++ b/public-trunk/init/OpenBTS @@ -0,0 +1,160 @@ +#! /bin/sh +# +# A script to start/stop OpenBTS. +# +# Author: Alexander Chemeris +# +### BEGIN INIT INFO +# Provides: openbts +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Should-Start: asterisk $network +# Should-Stop: asterisk $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Control OpenBTS daemon. +# Description: A script to control OpenBTS running in daemon mode. +### END INIT INFO + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:/bin:/usr/bin +DESC="OpenBTS" +NAME=OpenBTS +DAEMON=/usr/bin/$NAME +DAEMON_ARGS="" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ +# test -d ${XL2TPD_RUN_DIR:-/var/run/xl2tpd} || mkdir -p ${XL2TPD_RUN_DIR:-/var/run/xl2tpd} + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_ARGS \ + || return 2 + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + # But we do. +# rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: