laforge
/
openbts-osmo
Archived
1
0
Fork 0

Merge branch 'remote-cli'

* remote-cli: (22 commits)
  Throw exception if usrp is not found. Don't crash transceiver.
  Update OpenBTS init script
  Correctly exit OpenBTS even if console is not started yet.
  Add option to run OpenBTS in a failsafe loop.
  Shutdown without core dump on a usual SocketError.
  Remove Transceiver PID file on OpenBTS exit.
  Save transceiver PID to a file and use it to kill old transceivers.
  Correct comment for Server.WritePID.
  Set FD_CLOEXEC flag on all sockets.
  Stupid bug fixed - can't move stack variables initialization to a separate function.
  Gracefully shutdown OpenBTS on transceiver timeout/error.
  Make functions and variables in OpenBTS.cpp static to avoid their use outside of the file.
  Remove PID file on program exit.
  Implemented daemon mode, configured from config.
  Better default name for OpenBTS control UNIX socket.
  Initialize logger right after loading config file.
  Use normal cout and cerr while we have only one thread.
  Better comments for previous commit.
  Catch SIGINT, SIGTERM and SIGHUP and shutdown the server gracefully.
  Uncomment real BTS start/stop.
  ...
This commit is contained in:
Thomas Tsou 2011-06-21 19:02:19 -07:00
commit 8301298f88
31 changed files with 3454 additions and 933 deletions

View File

@ -22,781 +22,89 @@
*/ */
#include <iostream> #include <errno.h>
#include <iomanip>
#include <fstream>
#include <iterator>
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <math.h>
#include <config.h> #include <config.h>
#include "CLI.h" #include "CLI.h"
#include <Logger.h> #include <Logger.h>
#include <Globals.h> #include <Globals.h>
#include <GSMConfig.h> #ifdef HAVE_LIBREADLINE // [
#include <GSMLogicalChannel.h> //# include <stdio.h>
#include <ControlCommon.h> # include <readline/readline.h>
#include <TRXManager.h> # include <readline/history.h>
#include <PowerManager.h> #endif // HAVE_LIBREADLINE ]
using namespace std; using namespace std;
using namespace CommandLine; using namespace CommandLine;
#define SUCCESS 0 void CommandLine::runCLI(ParserBase *processor)
#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
{ {
// escape to the shell? #ifdef HAVE_LIBREADLINE // [
if (line[0]=='!') { // start console
os << endl; using_history();
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;
}
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 if(home_dir) {
{ size_t home_dir_len = strlen(home_dir);
char *newLine = strdup(line); size_t history_file_len = strlen(history_file_name);
int retVal = execute(newLine,os,is); size_t history_len = home_dir_len + history_file_len + 1;
free(newLine); if(history_len > home_dir_len) {
if (retVal>0) os << standardResponses[retVal] << endl; if(!(history_name = (char *)malloc(history_len))) {
return retVal; LOG(ERROR) << "malloc failed: " << strerror(errno);
} exit(-1);
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 <cntrl-A>, <D> to detach from \"screen\", *not* <cntrl-C>." << 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)<finish) {
unsigned load = gBTS.SDCCHActive() + gBTS.TCHActive();
if (load==0) break;
sleep(1);
}
bool loads = false;
if (gBTS.SDCCHActive()>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 <imsiprefix>\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;
} }
++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; #endif // HAVE_LIBREADLINE ]
return SUCCESS;
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 #ifdef HAVE_LIBREADLINE // [
string val; if(history_name) {
for (int i=2; i<argc; i++) { int e = write_history(history_name);
val.append(argv[i]); if(e) {
if (i!=(argc-1)) val.append(" "); fprintf(stderr, "error: history: %s\n", strerror(e));
}
free(history_name);
history_name = 0;
} }
bool existing = gConfig.defines(argv[1]); #endif // HAVE_LIBREADLINE ]
if (!gConfig.set(argv[1],val)) {
os << argv[1] << " is static and connot be altered after initialization" << endl;
return BAD_VALUE;
}
if (!existing) {
os << "defined new config " << argv[1] << " as \"" << val << "\"" << endl;
// Anything created by the CLI is optional.
gConfig.makeOptional(argv[1]);
} else {
os << "changed " << argv[1] << " to \"" << val << "\"" << endl;
}
gBTS.regenerateBeacon();
return SUCCESS;
} }
/** Remove a configiuration value. */
int unconfig(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=2) return BAD_NUM_ARGS;
if (gConfig.unset(argv[1])) {
os << "\"" << argv[1] << "\" removed from the configuration table" << endl;
gBTS.regenerateBeacon();
return SUCCESS;
}
if (gConfig.defines(argv[1])) {
os << "\"" << argv[1] << "\" could not be removed" << endl;
} else {
os << "\"" << argv[1] << "\" was not in the table" << endl;
}
return BAD_VALUE;
}
/** Dump current configuration to a file. */
int configsave(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=2) return BAD_NUM_ARGS;
fstream f;
f.open(argv[1],fstream::out);
if (f.fail()) {
os << "cannot open " << argv[1] << " for writing" << endl;
return FAILURE;
}
f << "# OpenBTS configuration file" << endl;
time_t now = time(NULL);
f << "# " << ctime(&now) << endl;
gConfig.write(f);
f.close();
return SUCCESS;
}
/** Change the registration timers. */
int regperiod(int argc, char** argv, ostream& os, istream& is)
{
if (argc==1) {
os << "T3212 is " << gConfig.getNum("GSM.T3212") << " minutes" << endl;
os << "SIP registration period is " << gConfig.getNum("SIP.RegistrationPeriod")/60 << " minutes" << endl;
return SUCCESS;
}
if (argc>3) 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) << <text> */
int alarms(int argc, char** argv, ostream& os, istream& is)
{
std::ostream_iterator<std::string> output( os, "\n" );
std::list<std::string> 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, "<path> -- set the logging file to <path>.");
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, "<IMSI> <src> -- send SMS to <IMSI>, addressed from <src>, after prompting.");
addCommand("sendrrlp", sendrrlp, "<IMSI> <hexstring> -- send RRLP message <hexstring> to <IMSI>.");
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, "<path> -- 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 // vim: ts=4 sw=4

View File

@ -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. * This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details. * See the COPYING file in the main directory for details.
@ -29,53 +29,12 @@
#include <string> #include <string>
#include <map> #include <map>
#include <iostream> #include <iostream>
#include <CLIParserBase.h>
namespace CommandLine { namespace CommandLine {
void runCLI(ParserBase *processor);
/** A table for matching strings to actions. */
typedef std::map<std::string,int (*)(int,char**,std::ostream&,std::istream&)> ParseTable;
/** The help table. */
typedef std::map<std::string,std::string> 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;
};
} // CLI } // CLI

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <string>
#include <config.h>
#include "CLIClient.h"
#include <Globals.h>
#include <Logger.h>
#include <CLI.h>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSCLICLIENT_H
#define OPENBTSCLICLIENT_H
#include <string>
#include <iostream>
#include <CLIConnection.h>
#include <CLIParserBase.h>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "CLIConnection.h"
#include <Logger.h>
//#include <Globals.h>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSCLICONNECTION_H
#define OPENBTSCLICONNECTION_H
#include <string>
#include <Sockets.h>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <iomanip>
#include <fstream>
#include <iterator>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <config.h>
#include "CLIParser.h"
#include "Tokenizer.h"
#include <Logger.h>
#include <Globals.h>
#include <GSMConfig.h>
#include <GSMLogicalChannel.h>
#include <ControlCommon.h>
#include <TRXManager.h>
#include <PowerManager.h>
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)<finish) {
unsigned load = gBTS.SDCCHActive() + gBTS.TCHActive();
if (load==0) break;
sleep(1);
}
}
bool loads = false;
if (gBTS.SDCCHActive()>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 <Ctrl-D> or <Ctrl-C> to exit from the CLI. \"exit\" will shutdown BTS!" << endl;
} else {
os << "Use <cntrl-A>, <D> to detach from \"screen\", *not* <cntrl-C>." << 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 <imsiprefix>\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; i<argc; i++) {
val.append(argv[i]);
if (i!=(argc-1)) val.append(" ");
}
bool existing = gConfig.defines(argv[1]);
if (!gConfig.set(argv[1],val)) {
os << argv[1] << " is static and connot be altered after initialization" << endl;
return BAD_VALUE;
}
if (!existing) {
os << "defined new config " << argv[1] << " as \"" << val << "\"" << endl;
// Anything created by the CLI is optional.
gConfig.makeOptional(argv[1]);
} else {
os << "changed " << argv[1] << " to \"" << val << "\"" << endl;
}
gBTS.regenerateBeacon();
return SUCCESS;
}
/** Remove a configiuration value. */
int unconfig(int argc, char** argv, ostream& os)
{
if (argc!=2) return BAD_NUM_ARGS;
if (gConfig.unset(argv[1])) {
os << "\"" << argv[1] << "\" removed from the configuration table" << endl;
gBTS.regenerateBeacon();
return SUCCESS;
}
if (gConfig.defines(argv[1])) {
os << "\"" << argv[1] << "\" could not be removed" << endl;
} else {
os << "\"" << argv[1] << "\" was not in the table" << endl;
}
return BAD_VALUE;
}
/** Dump current configuration to a file. */
int configsave(int argc, char** argv, ostream& os)
{
if (argc!=2) return BAD_NUM_ARGS;
fstream f;
f.open(argv[1],fstream::out);
if (f.fail()) {
os << "cannot open " << argv[1] << " for writing" << endl;
return FAILURE;
}
f << "# OpenBTS configuration file" << endl;
time_t now = time(NULL);
f << "# " << ctime(&now) << endl;
gConfig.write(f);
f.close();
return SUCCESS;
}
/** Change the registration timers. */
int regperiod(int argc, char** argv, ostream& os)
{
if (argc==1) {
os << "T3212 is " << gConfig.getNum("GSM.T3212") << " minutes" << endl;
os << "SIP registration period is " << gConfig.getNum("SIP.RegistrationPeriod")/60 << " minutes" << endl;
return SUCCESS;
}
if (argc>3) 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) << <text> */
int alarms(int argc, char** argv, ostream& os)
{
std::ostream_iterator<std::string> output( os, "\n" );
std::list<std::string> 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, "<path> -- set the logging file to <path>.");
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, "<IMSI> <src> <text> -- send SMS to <IMSI>, addressed from <src>.");
addCommand("sendrrlp", sendrrlp, "<IMSI> <hexstring> -- send RRLP message <hexstring> to <IMSI>.");
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, "<path> -- 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, "<string> -- print <string> to the screen");
// HACK -- Comment out these until they are fixed.
// addCommand("endcall", endcall,"trans# -- terminate the given transaction");
}
// vim: ts=4 sw=4

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSCLIPARSER_H
#define OPENBTSCLIPARSER_H
#include <string>
#include <map>
#include <iostream>
#include <CLIParserBase.h>
#include <Threads.h>
namespace CommandLine {
typedef int (*CLICommandFunc)(int,char**,std::ostream&);
/** A table for matching strings to actions. */
typedef std::map<std::string,CLICommandFunc> ParseTable;
/** The help table. */
typedef std::map<std::string,std::string> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
//#include <vector>
#include <list>
#include <config.h>
#include <CLIServer.h>
#include <CLIConnection.h>
#include <CLIParser.h>
#include <Globals.h>
#include <Logger.h>
using namespace std;
using namespace CommandLine;
namespace CommandLine {
typedef std::list<CLIServerConnectionThread *> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPENBTSCLISERVER_H
#define OPENBTSCLISERVER_H
#include <string>
#include <map>
#include <iostream>
#include <Threads.h>
#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

View File

@ -26,10 +26,31 @@ EXTRA_DIST = \
AM_CXXFLAGS = -Wall AM_CXXFLAGS = -Wall
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
noinst_LTLIBRARIES = libcli.la noinst_LTLIBRARIES = libcli.la libcli_commands.la
noinst_PROGRAMS = \
TokenizerTest
libcli_la_SOURCES = \ 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 = \ noinst_HEADERS = \
CLI.h CLI.h \
CLIServer.h \
CLIClient.h \
CLIConnection.h \
CLIParser.h \
CLIParserBase.h \
Tokenizer.h

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "Tokenizer.h"
#include <stdio.h> // 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "Tokenizer.h"
#include <iostream>
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<argc; i++) {
cout << "|" << argv[i] << "|" << endl;
}
}
int main()
{
test("");
test(" ");
test("This software is distributed ");
test("This software is distributed under the terms ");
test(" This software is distributed under the terms ");
test(" This software \"is distributed\" under the terms ");
test(" This software \"is distributed \" under \"the terms\"");
test(" This software \"is distributed \"under \"the terms\"");
test(" This software \"is distributed \"under \"the terms");
test("software \"is distributed \" \"the terms");
test("\"software is\" distributed \" \"the terms");
test("\" ");
test("\"\" ");
test("\" \"");
}

View File

@ -0,0 +1,324 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "Sockets.h"
#include "Threads.h"
#include "Logger.h"
#include "Configuration.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>
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; i<gNumToSend; i++)
{
ConnectionSocketTCP conn(gIpBindPort, gIpBindHost);
if (!clientConnect(&conn)) continue;
if (!clientWrite(&conn, "ping")) continue;
if (!clientRead(&conn)) continue;
conn.close();
usleep(500*1000);
}
// Shutdown server
ConnectionSocketTCP conn(gIpBindPort, gIpBindHost);
if (!clientConnect(&conn)) return NULL;
if (!clientWrite(&conn, "exit")) return NULL;
conn.close();
return NULL;
}
void *unixClient(void *)
{
for (int i=0; i<gNumToSend; i++)
{
ConnectionSocketUnix conn(gUnixSocketPath);
if (!clientConnect(&conn)) continue;
if (!clientWrite(&conn, "ping")) continue;
if (!clientRead(&conn)) continue;
conn.close();
usleep(500*1000);
}
// Shutdown server
ConnectionSocketUnix conn(gUnixSocketPath);
if (!clientConnect(&conn)) return NULL;
if (!clientWrite(&conn, "exit")) return NULL;
conn.close();
return NULL;
}
struct ConnectionPair {
ConnectionPair(ConnectionServerSocket *serv, ConnectionSocket *conn)
: serverSocket(serv)
, connectionSocket(conn)
{};
ConnectionServerSocket *serverSocket;
ConnectionSocket *connectionSocket;
};
void *serverConnThread(void *data)
{
ConnectionPair *pConnPair = (ConnectionPair*)data;
ConnectionSocket *pConn = pConnPair->connectionSocket;
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<Thread *> 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

View File

@ -108,15 +108,13 @@ bool gSetLogFile(const char *name)
assert(name); assert(name);
LOG(DEEPDEBUG) << "setting log path to " << name; LOG(DEEPDEBUG) << "setting log path to " << name;
bool retVal = true; bool retVal = true;
gLogLock.lock();
FILE* newLoggingFile = fopen(name,"a+"); FILE* newLoggingFile = fopen(name,"a+");
if (!newLoggingFile) { if (!newLoggingFile) {
LOG(ERROR) << "cannot open \"" << name << "\" for logging."; LOG(ERROR) << "cannot open \"" << name << "\" for logging.";
retVal = false; retVal = false;
} else { } else {
gLoggingFile = newLoggingFile; gSetLogFile(newLoggingFile);
} }
gLogLock.unlock();
LOG(FORCE) << "new log path " << name; LOG(FORCE) << "new log path " << name;
return retVal; 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 // vim: ts=4 sw=4

View File

@ -103,18 +103,26 @@ std::ostringstream& operator<<(std::ostringstream& os, Log::Level);
std::list<std::string> gGetLoggerAlarms(); ///< Get a copy of the recent alarm list. std::list<std::string> 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. */ /**@name Global logging file control. */
//@{ //@{
void gSetLogFile(FILE*); void gSetLogFile(FILE*);
bool gSetLogFile(const char*); 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 #endif

View File

@ -41,6 +41,7 @@ libcommon_la_SOURCES = \
noinst_PROGRAMS = \ noinst_PROGRAMS = \
BitVectorTest \ BitVectorTest \
InterthreadTest \ InterthreadTest \
ConnectionSocketsTest \
SocketsTest \ SocketsTest \
TimevalTest \ TimevalTest \
RegexpTest \ RegexpTest \
@ -73,6 +74,10 @@ SocketsTest_SOURCES = SocketsTest.cpp
SocketsTest_LDADD = libcommon.la SocketsTest_LDADD = libcommon.la
SocketsTest_LDFLAGS = -lpthread SocketsTest_LDFLAGS = -lpthread
ConnectionSocketsTest_SOURCES = ConnectionSocketsTest.cpp
ConnectionSocketsTest_LDADD = libcommon.la
ConnectionSocketsTest_LDFLAGS = -lpthread
TimevalTest_SOURCES = TimevalTest.cpp TimevalTest_SOURCES = TimevalTest.cpp
TimevalTest_LDADD = libcommon.la TimevalTest_LDADD = libcommon.la

View File

@ -234,6 +234,11 @@ void UDPSocket::open(unsigned short localPort)
perror("socket() failed"); perror("socket() failed");
throw SocketError(); 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 // bind
struct sockaddr_in address; struct sockaddr_in address;
@ -280,6 +285,10 @@ void UDDSocket::open(const char* localPath)
perror("socket() failed"); perror("socket() failed");
throw SocketError(); 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 // bind
struct sockaddr_un address; 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 // vim: ts=4 sw=4

View File

@ -33,10 +33,9 @@
#include <sys/un.h> #include <sys/un.h>
#include <errno.h> #include <errno.h>
#include <list> #include <list>
#include <ostream>
#include <assert.h> #include <assert.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
@ -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 #endif

View File

@ -59,6 +59,3 @@ const char* gOpenBTSWelcome =
"All users of this software are expected to comply with applicable\n" "All users of this software are expected to comply with applicable\n"
"regulations and laws.\n" "regulations and laws.\n"
; ;
CommandLine::Parser gParser;

View File

@ -31,7 +31,6 @@
#define GLOBALS_H #define GLOBALS_H
#include <Configuration.h> #include <Configuration.h>
#include <CLI.h>
/** /**
Just about everything goes into the configuration table. Just about everything goes into the configuration table.
@ -42,7 +41,4 @@ extern ConfigurationTable gConfig;
/** The OpenBTS welcome message. */ /** The OpenBTS welcome message. */
extern const char* gOpenBTSWelcome; extern const char* gOpenBTSWelcome;
/** The central parser. */
extern CommandLine::Parser gParser;
#endif #endif

View File

@ -49,6 +49,7 @@ TRX_LA = $(top_builddir)/TRXManager/libtrxmanager.la
CONTROL_LA = $(top_builddir)/Control/libcontrol.la CONTROL_LA = $(top_builddir)/Control/libcontrol.la
GLOBALS_LA = $(top_builddir)/Globals/libglobals.la GLOBALS_LA = $(top_builddir)/Globals/libglobals.la
CLI_LA = $(top_builddir)/CLI/libcli.la CLI_LA = $(top_builddir)/CLI/libcli.la
CLI_COMMANDS_LA = $(top_builddir)/CLI/libcli_commands.la
HLR_LA = $(top_builddir)/HLR/libHLR.la HLR_LA = $(top_builddir)/HLR/libHLR.la
#SQL_LA = $(top_builddir)/SQL/libSQL.la #SQL_LA = $(top_builddir)/SQL/libSQL.la

View File

@ -32,6 +32,7 @@
#include "GSMConfig.h" #include "GSMConfig.h"
#include "GSML1FEC.h" #include "GSML1FEC.h"
#include <string.h> #include <string.h>
#include <stdexcept>
#include <Logger.h> #include <Logger.h>
@ -39,6 +40,8 @@
using namespace GSM; using namespace GSM;
using namespace std; using namespace std;
// From OpenBTS.cpp
extern void shutdownOpenbts();
TransceiverManager::TransceiverManager(int numARFCNs, TransceiverManager::TransceiverManager(int numARFCNs,
const char* wTRXAddress, int wBasePort) const char* wTRXAddress, int wBasePort)
@ -85,11 +88,13 @@ void TransceiverManager::clockHandler()
// Did the transceiver die?? // Did the transceiver die??
if (msgLen<0) { if (msgLen<0) {
LOG(ALARM) << "TRX clock interface timed out, assuming TRX is dead."; LOG(ALARM) << "TRX clock interface timed out, assuming TRX is dead.";
abort(); shutdownOpenbts();
return;
} }
if (msgLen==0) { if (msgLen==0) {
LOG(ALARM) << "read error on TRX clock interface, return " << msgLen; LOG(ALARM) << "read error on TRX clock interface, return " << msgLen;
shutdownOpenbts();
return; return;
} }

View File

@ -33,6 +33,7 @@
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdexcept>
#include "Threads.h" #include "Threads.h"
#include "USRPDevice.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, m_uRx = usrp_standard_rx_sptr(usrp_standard_rx::make(0,decimRate,1,-1,
usrp_standard_rx::FPGA_MODE_NORMAL, usrp_standard_rx::FPGA_MODE_NORMAL,
1024,16*8,rbf)); 1024,16*8,rbf));
if (!m_uRx) throw runtime_error("");
#ifdef HAVE_LIBUSRP_3_2 #ifdef HAVE_LIBUSRP_3_2
m_uRx->set_fpga_master_clock_freq(masterClockRate); m_uRx->set_fpga_master_clock_freq(masterClockRate);
#endif #endif
@ -104,6 +107,7 @@ bool USRPDevice::open()
try { try {
m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make(0,decimRate*2,1,-1, m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make(0,decimRate*2,1,-1,
1024,16*8,rbf)); 1024,16*8,rbf));
if (!m_uTx) throw runtime_error("");
#ifdef HAVE_LIBUSRP_3_2 #ifdef HAVE_LIBUSRP_3_2
m_uTx->set_fpga_master_clock_freq(masterClockRate); m_uTx->set_fpga_master_clock_freq(masterClockRate);
#endif #endif

View File

@ -24,7 +24,8 @@ AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
AM_CXXFLAGS = -Wall -g -pthread AM_CXXFLAGS = -Wall -g -pthread
noinst_PROGRAMS = \ noinst_PROGRAMS = \
OpenBTS OpenBTS \
OpenBTScli
OpenBTS_SOURCES = OpenBTS.cpp OpenBTS_SOURCES = OpenBTS.cpp
OpenBTS_LDADD = \ OpenBTS_LDADD = \
@ -36,10 +37,17 @@ OpenBTS_LDADD = \
$(TRX_LA) \ $(TRX_LA) \
$(CONTROL_LA) \ $(CONTROL_LA) \
$(SMS_LA) \ $(SMS_LA) \
$(CLI_COMMANDS_LA) \
$(CLI_LA) \ $(CLI_LA) \
$(OSIP_LIBS) \ $(OSIP_LIBS) \
$(ORTP_LIBS) $(ORTP_LIBS)
OpenBTScli_SOURCES = OpenBTScli.cpp
OpenBTScli_LDADD = \
$(GLOBALS_LA) \
$(COMMON_LA) \
$(CLI_LA)
EXTRA_DIST = \ EXTRA_DIST = \
OpenBTS.config.example \ OpenBTS.config.example \

View File

@ -23,6 +23,41 @@
# So be cafeful. # 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 # Logging and reporting parameters
# #
@ -88,6 +123,14 @@ TRX.Path ../Transceiver/transceiver
#TRX.Path ../Transceiver52M/transceiver #TRX.Path ../Transceiver52M/transceiver
$static TRX.Path $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. # TRX logging.
# Logging level. # Logging level.
# IF TRX.Path IS DEFINED, THIS MUST ALSO BE DEFINED. # IF TRX.Path IS DEFINED, THIS MUST ALSO BE DEFINED.
@ -514,8 +557,37 @@ GSM.MaxSpeechLatency 2
# CLI paramters # CLI paramters
# #
# A string, used as a CLI prompt
CLI.Prompt OpenBTS> 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

View File

@ -37,6 +37,8 @@
#include <Logger.h> #include <Logger.h>
#include <CLI.h> #include <CLI.h>
#include <CLIServer.h>
#include <CLIParser.h>
#include <PowerManager.h> #include <PowerManager.h>
#include <RRLPQueryController.h> #include <RRLPQueryController.h>
#include <Configuration.h> #include <Configuration.h>
@ -45,82 +47,468 @@
#include <unistd.h> #include <unistd.h>
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
#include <sys/stat.h>
#ifdef HAVE_LIBREADLINE // [
//# include <stdio.h>
# include <readline/readline.h>
# include <readline/history.h>
#endif // HAVE_LIBREADLINE ]
using namespace std; using namespace std;
using namespace GSM; 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"); 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 // All of the other globals that rely on the global configuration file need to
// be declared here. // be declared here.
// The global SIPInterface object. /// The global SIPInterface object.
SIP::SIPInterface gSIPInterface; SIP::SIPInterface gSIPInterface;
// Configure the BTS object based on the config file. /// Configure the BTS object based on the config file.
// So don't create this until AFTER loading the config file. /// So don't create this until AFTER loading the config file.
GSMConfig gBTS; 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")); 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;
/** Function to shutdown the process when something wrong happens. */
void shutdownOpenbts()
pid_t gTransceiverPid = 0;
void restartTransceiver()
{ {
// This is harmless - if someone is running OpenBTS they WANT no transceiver kill(SIGTERM, getpid());
// instance at the start anyway. }
if (gTransceiverPid > 0) {
LOG(INFO) << "RESTARTING TRANSCEIVER"; static int openPidFile(const std::string &lockfile)
kill(gTransceiverPid,SIGKILL); // TODO - call on ctrl-c (put in signal?) {
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<sizeof(tempBuf)?bytesRead:sizeof(tempBuf)-1] = '\0';
int res = sscanf(tempBuf, " %d", &pid);
if (res < 1) {
LOG(ERROR) << "Unable to parse PID from file " << lockfile << ", code="
<< errno << " (" << strerror(errno) << ")";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
static int startTransceiver()
{
// Start the transceiver binary, if the path is defined. // Start the transceiver binary, if the path is defined.
// If the path is not defined, the transceiver must be started by some other process. // If the path is not defined, the transceiver must be started by some other process.
const char *TRXPath = NULL; if (gConfig.defines("TRX.Path")) {
if (gConfig.defines("TRX.Path")) TRXPath=gConfig.getStr("TRX.Path");
if (TRXPath) { // Open and lock PID file, taking care of old transceiver instance.
sgTransceiverPidFile = gConfig.getStr("TRX.WritePID");
sgTransceiverPidFileFd = openPidFile(sgTransceiverPidFile);
if (sgTransceiverPidFileFd < 0) return EXIT_SUCCESS;
int pid;
if (lockPidFile(sgTransceiverPidFile, sgTransceiverPidFileFd, false) != EXIT_SUCCESS) {
// Another OpenBTS instance is running and blocking PID file.
return EXIT_FAILURE;
}
if (readPidFile(sgTransceiverPidFile, sgTransceiverPidFileFd, pid) == EXIT_SUCCESS) {
// There is no harm in this. Transceiver's owner is not
// running and could safely kill it.
kill(pid, SIGTERM);
}
// Start transceiver
const char *TRXPath = gConfig.getStr("TRX.Path");
const char *TRXLogLevel = gConfig.getStr("TRX.LogLevel"); const char *TRXLogLevel = gConfig.getStr("TRX.LogLevel");
const char *TRXLogFileName = NULL; const char *TRXLogFileName = NULL;
if (gConfig.defines("TRX.LogFileName")) TRXLogFileName=gConfig.getStr("TRX.LogFileName"); if (gConfig.defines("TRX.LogFileName")) TRXLogFileName=gConfig.getStr("TRX.LogFileName");
gTransceiverPid = vfork(); sgTransceiverPid = vfork();
LOG_ASSERT(gTransceiverPid>=0); LOG_ASSERT(sgTransceiverPid>=0);
if (gTransceiverPid==0) { if (sgTransceiverPid==0) {
// Pid==0 means this is the process that starts the transceiver. // Pid==0 means this is the process that starts the transceiver.
execl(TRXPath,"transceiver",TRXLogLevel,TRXLogFileName,NULL); execl(TRXPath,"transceiver",TRXLogLevel,TRXLogFileName,NULL);
LOG(ERROR) << "cannot start transceiver"; LOG(ERROR) << "cannot start transceiver";
_exit(0); _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 <program-name>.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[]) int main(int argc, char *argv[])
{ {
srandom(time(NULL)); srandom(time(NULL));
COUT("\n\n" << gOpenBTSWelcome << "\n"); // Catch signal to re-read config
COUT("\nStarting the system..."); 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")) { cout << endl << endl << gOpenBTSWelcome << endl;
gSetLogFile(gConfig.getStr("Log.FileName"));
} try {
#if 1
cout << endl << "Starting the system..." << endl;
if (gConfig.defines("Control.TMSITable.SavePath")) { if (gConfig.defines("Control.TMSITable.SavePath")) {
gTMSITable.load(gConfig.getStr("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__; LOG(ALARM) << "OpenBTS starting, ver " << VERSION << " build date " << __DATE__;
restartTransceiver(); startTransceiver();
// Start the SIP interface. // Start the SIP interface.
gSIPInterface.start(); gSIPInterface.start();
@ -196,9 +584,9 @@ int main(int argc, char *argv[])
// C-V C0T0 SDCCHs // C-V C0T0 SDCCHs
SDCCHLogicalChannel C0T0SDCCH[4] = { SDCCHLogicalChannel C0T0SDCCH[4] = {
SDCCHLogicalChannel(0,gSDCCH_4_0), SDCCHLogicalChannel(0,gSDCCH_4_0),
SDCCHLogicalChannel(0,gSDCCH_4_1), SDCCHLogicalChannel(0,gSDCCH_4_1),
SDCCHLogicalChannel(0,gSDCCH_4_2), SDCCHLogicalChannel(0,gSDCCH_4_2),
SDCCHLogicalChannel(0,gSDCCH_4_3), SDCCHLogicalChannel(0,gSDCCH_4_3),
}; };
Thread C0T0SDCCHControlThread[4]; Thread C0T0SDCCHControlThread[4];
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
@ -212,22 +600,22 @@ int main(int argc, char *argv[])
unsigned sCount = 1; unsigned sCount = 1;
bool halfDuplex = gConfig.defines("GSM.HalfDuplex"); bool halfDuplex = gConfig.defines("GSM.HalfDuplex");
if (halfDuplex) LOG(NOTICE) << "Configuring for half-duplex operation."; if (halfDuplex) { LOG(NOTICE) << "Configuring for half-duplex operation." ; }
else LOG(NOTICE) << "Configuring for full-duplex operation."; else { LOG(NOTICE) << "Configuring for full-duplex operation."; }
if (halfDuplex) sCount++; if (halfDuplex) sCount++;
// Create C-VII slots. // Create C-VII slots.
for (int i=0; i<gConfig.getNum("GSM.NumC7s"); i++) { for (int i=0; i<gConfig.getNum("GSM.NumC7s"); i++) {
gBTS.createCombinationVII(gTRX,sCount/8,sCount); gBTS.createCombinationVII(gTRX,sCount/8,sCount);
if (halfDuplex) sCount++; if (halfDuplex) sCount++;
sCount++; sCount++;
} }
// Create C-I slots. // Create C-I slots.
for (int i=0; i<gConfig.getNum("GSM.NumC1s"); i++) { for (int i=0; i<gConfig.getNum("GSM.NumC1s"); i++) {
gBTS.createCombinationI(gTRX,sCount/8,sCount); gBTS.createCombinationI(gTRX,sCount/8,sCount);
if (halfDuplex) sCount++; if (halfDuplex) sCount++;
sCount++; sCount++;
} }
@ -235,7 +623,7 @@ int main(int argc, char *argv[])
// Set up idle filling on C0 as needed. // Set up idle filling on C0 as needed.
while (sCount<8) { while (sCount<8) {
gBTS.createCombination0(gTRX,sCount/8,sCount); gBTS.createCombination0(gTRX,sCount/8,sCount);
if (halfDuplex) sCount++; if (halfDuplex) sCount++;
sCount++; sCount++;
} }
@ -260,79 +648,37 @@ int main(int argc, char *argv[])
// OK, now it is safe to start the BTS. // OK, now it is safe to start the BTS.
gBTS.start(); gBTS.start();
#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");
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);
}
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"; LOG(INFO) << "system ready";
COUT("\n\nWelcome to OpenBTS. Type \"help\" to see available commands."); #endif
// FIXME: We want to catch control-d (emacs keybinding for exit())
if (strcasecmp(gConfig.getStr("CLI.Type"),"TCP") == 0) {
// The logging parts were removed from this loop. ConnectionServerSocketTCP serverSock(gConfig.getNum("CLI.TCP.Port"),
// If we want them back, they will need to go into their own thread. gConfig.getStr("CLI.TCP.IP"));
while (1) { sgCLIServerSock = &serverSock;
#ifdef HAVE_LIBREADLINE // [ runCLIServer(&serverSock);
char *inbuf = readline(gConfig.getStr("CLI.Prompt")); sgCLIServerSock = NULL;
if (!inbuf) break; } else if (strcasecmp(gConfig.getStr("CLI.Type"),"Unix") == 0) {
if (*inbuf) { ConnectionServerSocketUnix serverSock(gConfig.getStr("CLI.Unix.Path"));
add_history(inbuf); sgCLIServerSock = &serverSock;
// The parser returns -1 on exit. runCLIServer(&serverSock);
if (gParser.process(inbuf, cout, cin)<0) { sgCLIServerSock = NULL;
free(inbuf); } else {
break; runCLI(&gParser);
}
}
free(inbuf);
#else // HAVE_LIBREADLINE ][
cout << endl << gConfig.getStr("CLI.Prompt");
cout.flush();
char inbuf[1024];
cin.getline(inbuf,1024,'\n');
// The parser returns -1 on exit.
if (gParser.process(inbuf,cout,cin)<0) break;
#endif // !HAVE_LIBREADLINE ]
} }
#ifdef HAVE_LIBREADLINE // [ } catch(SocketError) {
if(history_name) { // Shutdown without core dump.
int e = write_history(history_name); // SocketError is a usual case, e.g. it's fired when transceiver fails.
if(e) { LOG(ALARM) << "Uncaught exception. Shutting down.";
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"));
} }
if (gTransceiverPid) kill(gTransceiverPid, SIGKILL); if (!gBTS.hold()) {
exitBTS(0, cout);
}
serverCleanup();
return EXIT_SUCCESS;
} }
// vim: ts=4 sw=4 // vim: ts=4 sw=4

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <Globals.h>
#include <Sockets.h>
#include <Threads.h>
#include <Logger.h>
#include <CLIClient.h>
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

160
public-trunk/init/OpenBTS Executable file
View File

@ -0,0 +1,160 @@
#! /bin/sh
#
# A script to start/stop OpenBTS.
#
# Author: Alexander Chemeris <Alexander.Chemeris@gmail.com>
#
### 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
: