diff options
Diffstat (limited to 'shell/dbshell.cpp')
-rw-r--r-- | shell/dbshell.cpp | 351 |
1 files changed, 225 insertions, 126 deletions
diff --git a/shell/dbshell.cpp b/shell/dbshell.cpp index 2bd8973..2e93682 100644 --- a/shell/dbshell.cpp +++ b/shell/dbshell.cpp @@ -39,14 +39,11 @@ jmp_buf jbuf; #include "../util/password.h" #include "../util/version.h" #include "../util/goodies.h" +#include "../db/repl/rs_member.h" using namespace std; using namespace boost::filesystem; - -using mongo::BSONObj; -using mongo::BSONObjBuilder; -using mongo::BSONObjIterator; -using mongo::BSONElement; +using namespace mongo; string historyFile; bool gotInterrupted = 0; @@ -59,20 +56,25 @@ bool autoKillOp = false; #define CTRLC_HANDLE #endif -mongo::Scope * shellMainScope; +namespace mongo { + + Scope * shellMainScope; + + extern bool dbexitCalled; +} -void generateCompletions( const string& prefix , vector<string>& all ){ +void generateCompletions( const string& prefix , vector<string>& all ) { if ( prefix.find( '"' ) != string::npos ) return; - shellMainScope->exec( "shellAutocomplete( \"" + prefix + "\" );" , "autocomplete help" , false , true , false ); - + + shellMainScope->invokeSafe("function(x) {shellAutocomplete(x)}", BSON("0" << prefix), 1000); BSONObjBuilder b; shellMainScope->append( b , "" , "__autocomplete__" ); BSONObj res = b.obj(); BSONObj arr = res.firstElement().Obj(); BSONObjIterator i(arr); - while ( i.more() ){ + while ( i.more() ) { BSONElement e = i.next(); all.push_back( e.String() ); } @@ -80,44 +82,43 @@ void generateCompletions( const string& prefix , vector<string>& all ){ } #ifdef USE_READLINE -static char** completionHook(const char* text , int start ,int end ){ +static char** completionHook(const char* text , int start ,int end ) { static map<string,string> m; - + vector<string> all; - - if ( start == 0 ){ - generateCompletions( string(text,end) , all ); - } - - if ( all.size() == 0 ){ - rl_bind_key('\t',0); + + generateCompletions( string(text,end) , all ); + + if ( all.size() == 0 ) { return 0; } - + string longest = all[0]; - for ( vector<string>::iterator i=all.begin(); i!=all.end(); ++i ){ + for ( vector<string>::iterator i=all.begin(); i!=all.end(); ++i ) { string s = *i; - for ( unsigned j=0; j<s.size(); j++ ){ + for ( unsigned j=0; j<s.size(); j++ ) { if ( longest[j] == s[j] ) continue; longest = longest.substr(0,j); break; } } - + char ** matches = (char**)malloc( sizeof(char*) * (all.size()+2) ); unsigned x=0; matches[x++] = strdup( longest.c_str() ); - for ( unsigned i=0; i<all.size(); i++ ){ + for ( unsigned i=0; i<all.size(); i++ ) { matches[x++] = strdup( all[i].c_str() ); } matches[x++] = 0; + rl_completion_append_character = '\0'; // don't add a space after completions + return matches; } #endif -void shellHistoryInit(){ +void shellHistoryInit() { #ifdef USE_READLINE stringstream ss; char * h = getenv( "HOME" ); @@ -128,19 +129,19 @@ void shellHistoryInit(){ using_history(); read_history( historyFile.c_str() ); - + rl_attempted_completion_function = completionHook; - + #else //cout << "type \"exit\" to exit" << endl; #endif } -void shellHistoryDone(){ +void shellHistoryDone() { #ifdef USE_READLINE write_history( historyFile.c_str() ); #endif } -void shellHistoryAdd( const char * line ){ +void shellHistoryAdd( const char * line ) { #ifdef USE_READLINE if ( line[0] == '\0' ) return; @@ -156,7 +157,7 @@ void shellHistoryAdd( const char * line ){ #endif } -void intr( int sig ){ +void intr( int sig ) { #ifdef CTRLC_HANDLE longjmp( jbuf , 1 ); #endif @@ -170,33 +171,40 @@ void killOps() { if ( atPrompt ) return; - if ( !autoKillOp ) { - cout << endl << "do you want to kill the current op on the server? (y/n): "; - cout.flush(); + sleepmillis(10); // give current op a chance to finish - char yn; - cin >> yn; + for( map< string, set<string> >::const_iterator i = shellUtils::_allMyUris.begin(); i != shellUtils::_allMyUris.end(); ++i ) { + string errmsg; + ConnectionString cs = ConnectionString::parse(i->first, errmsg); + if (!cs.isValid()) continue; + boost::scoped_ptr<DBClientWithCommands> conn (cs.connect(errmsg)); + if (!conn) continue; - if (yn != 'y' && yn != 'Y') - return; - } + const set<string>& uris = i->second; + BSONObj inprog = conn->findOne("admin.$cmd.sys.inprog", Query())["inprog"].embeddedObject().getOwned(); + BSONForEach(op, inprog) { + if ( uris.count(op["client"].String()) ) { + ONCE if ( !autoKillOp ) { + cout << endl << "do you want to kill the current op(s) on the server? (y/n): "; + cout.flush(); - vector< string > uris; - for( map< const void*, string >::iterator i = mongo::shellUtils::_allMyUris.begin(); i != mongo::shellUtils::_allMyUris.end(); ++i ) - uris.push_back( i->second ); - mongo::BSONObj spec = BSON( "" << uris ); - try { - auto_ptr< mongo::Scope > scope( mongo::globalScriptEngine->newScope() ); - scope->invoke( "function( x ) { killWithUris( x ); }", spec ); - } catch ( ... ) { - mongo::rawOut( "exception while cleaning up any db ops started by this shell\n" ); + char yn; + cin >> yn; + + if (yn != 'y' && yn != 'Y') + return; + } + + conn->findOne("admin.$cmd.sys.killop", QUERY("op"<< op["opid"])); + } + } } } -void quitNicely( int sig ){ - mongo::goingAway = true; - if ( sig == SIGINT && inMultiLine ){ +void quitNicely( int sig ) { + mongo::dbexitCalled = true; + if ( sig == SIGINT && inMultiLine ) { gotInterrupted = 1; return; } @@ -207,15 +215,16 @@ void quitNicely( int sig ){ exit(0); } #else -void quitNicely( int sig ){ - mongo::goingAway = true; +void quitNicely( int sig ) { + mongo::dbexitCalled = true; //killOps(); shellHistoryDone(); exit(0); } #endif -char * shellReadline( const char * prompt , int handlesigint = 0 ){ +char * shellReadline( const char * prompt , int handlesigint = 0 ) { + atPrompt = true; #ifdef USE_READLINE @@ -223,12 +232,12 @@ char * shellReadline( const char * prompt , int handlesigint = 0 ){ #ifdef CTRLC_HANDLE - if ( ! handlesigint ){ + if ( ! handlesigint ) { char* ret = readline( prompt ); atPrompt = false; return ret; } - if ( setjmp( jbuf ) ){ + if ( setjmp( jbuf ) ) { gotInterrupted = 1; sigrelse(SIGINT); signal( SIGINT , quitNicely ); @@ -269,6 +278,14 @@ void quitAbruptly( int sig ) { exit(14); } +// this will be called in certain c++ error cases, for example if there are two active +// exceptions +void myterminate() { + mongo::rawOut( "terminate() called in shell, printing stack:" ); + mongo::printStackTrace(); + exit(14); +} + void setupSignals() { signal( SIGINT , quitNicely ); signal( SIGTERM , quitNicely ); @@ -277,28 +294,29 @@ void setupSignals() { signal( SIGSEGV , quitAbruptly ); signal( SIGBUS , quitAbruptly ); signal( SIGFPE , quitAbruptly ); + set_terminate( myterminate ); } #else inline void setupSignals() {} #endif -string fixHost( string url , string host , string port ){ +string fixHost( string url , string host , string port ) { //cout << "fixHost url: " << url << " host: " << host << " port: " << port << endl; - if ( host.size() == 0 && port.size() == 0 ){ - if ( url.find( "/" ) == string::npos ){ + if ( host.size() == 0 && port.size() == 0 ) { + if ( url.find( "/" ) == string::npos ) { // check for ips if ( url.find( "." ) != string::npos ) return url + "/test"; if ( url.rfind( ":" ) != string::npos && - isdigit( url[url.rfind(":")+1] ) ) + isdigit( url[url.rfind(":")+1] ) ) return url + "/test"; } return url; } - if ( url.find( "/" ) != string::npos ){ + if ( url.find( "/" ) != string::npos ) { cerr << "url can't have host or port if you specify them individually" << endl; exit(-1); } @@ -309,7 +327,7 @@ string fixHost( string url , string host , string port ){ string newurl = host; if ( port.size() > 0 ) newurl += ":" + port; - else if (host.find(':') == string::npos){ + else if (host.find(':') == string::npos) { // need to add port with IPv6 addresses newurl += ":27017"; } @@ -319,14 +337,23 @@ string fixHost( string url , string host , string port ){ return newurl; } -bool isBalanced( string code ){ +static string OpSymbols = "~!%^&*-+=|:,<>/?"; + +bool isOpSymbol( char c ) { + for ( size_t i = 0; i < OpSymbols.size(); i++ ) + if ( OpSymbols[i] == c ) return true; + return false; +} + +bool isBalanced( string code ) { int brackets = 0; int parens = 0; + bool danglingOp = false; - for ( size_t i=0; i<code.size(); i++ ){ - switch( code[i] ){ + for ( size_t i=0; i<code.size(); i++ ) { + switch( code[i] ) { case '/': - if ( i+1 < code.size() && code[i+1] == '/' ){ + if ( i+1 < code.size() && code[i+1] == '/' ) { while ( i<code.size() && code[i] != '\n' ) i++; } @@ -343,17 +370,30 @@ bool isBalanced( string code ){ i++; while ( i < code.size() && code[i] != '\'' ) i++; break; + case '\\': + if ( i+1 < code.size() && code[i+1] == '/') i++; + break; + case '+': + case '-': + if ( i+1 < code.size() && code[i+1] == code[i]) { + i++; + continue; // postfix op (++/--) can't be a dangling op + } + break; } + + if ( isOpSymbol( code[i] )) danglingOp = true; + else if (! std::isspace( code[i] )) danglingOp = false; } - return brackets == 0 && parens == 0; + return brackets == 0 && parens == 0 && !danglingOp; } using mongo::asserted; struct BalancedTest : public mongo::UnitTest { public: - void run(){ + void run() { assert( isBalanced( "x = 5" ) ); assert( isBalanced( "function(){}" ) ); assert( isBalanced( "function(){\n}" ) ); @@ -362,12 +402,19 @@ public: assert( isBalanced( "// {" ) ); assert( ! isBalanced( "// \n {" ) ); assert( ! isBalanced( "\"//\" {" ) ); - + assert( isBalanced( "{x:/x\\//}" ) ); + assert( ! isBalanced( "{ \\/// }" ) ); + assert( isBalanced( "x = 5 + y ") ); + assert( ! isBalanced( "x = ") ); + assert( ! isBalanced( "x = // hello") ); + assert( ! isBalanced( "x = 5 +") ); + assert( isBalanced( " x ++") ); + assert( isBalanced( "-- x") ); } } balnaced_test; -string finishCode( string code ){ - while ( ! isBalanced( code ) ){ +string finishCode( string code ) { + while ( ! isBalanced( code ) ) { inMultiLine = 1; code += "\n"; char * line = shellReadline("... " , 1 ); @@ -375,6 +422,10 @@ string finishCode( string code ){ return ""; if ( ! line ) return ""; + + while (startsWith(line, "... ")) + line += 4; + code += line; } return code; @@ -395,18 +446,51 @@ void show_help_text(const char* name, po::options_description options) { << "unless --shell is specified" << endl; }; -bool fileExists( string file ){ +bool fileExists( string file ) { try { path p(file); return boost::filesystem::exists( file ); } - catch (...){ + catch (...) { return false; } } namespace mongo { extern bool isShell; + extern DBClientWithCommands *latestConn; +} + +string stateToString(MemberState s) { + if( s.s == MemberState::RS_STARTUP ) return "STARTUP"; + if( s.s == MemberState::RS_PRIMARY ) return "PRIMARY"; + if( s.s == MemberState::RS_SECONDARY ) return "SECONDARY"; + if( s.s == MemberState::RS_RECOVERING ) return "RECOVERING"; + if( s.s == MemberState::RS_FATAL ) return "FATAL"; + if( s.s == MemberState::RS_STARTUP2 ) return "STARTUP2"; + if( s.s == MemberState::RS_ARBITER ) return "ARBITER"; + if( s.s == MemberState::RS_DOWN ) return "DOWN"; + if( s.s == MemberState::RS_ROLLBACK ) return "ROLLBACK"; + return ""; +} +string sayReplSetMemberState() { + try { + if( latestConn ) { + BSONObj info; + if( latestConn->runCommand("admin", BSON( "replSetGetStatus" << 1 << "forShell" << 1 ) , info ) ) { + stringstream ss; + ss << info["set"].String() << ':'; + int s = info["myState"].Int(); + MemberState ms(s); + ss << stateToString(ms); + return ss.str(); + } + } + } + catch( std::exception& e ) { + log(1) << "error in sayReplSetMemberState:" << e.what() << endl; + } + return ""; } int _main(int argc, char* argv[]) { @@ -425,35 +509,36 @@ int _main(int argc, char* argv[]) { bool runShell = false; bool nodb = false; - + string script; po::options_description shell_options("options"); po::options_description hidden_options("Hidden options"); po::options_description cmdline_options("Command line options"); po::positional_options_description positional_options; - + shell_options.add_options() - ("shell", "run the shell after executing files") - ("nodb", "don't connect to mongod on startup - no 'db address' arg expected") - ("quiet", "be less chatty" ) - ("port", po::value<string>(&port), "port to connect to") - ("host", po::value<string>(&dbhost), "server to connect to") - ("eval", po::value<string>(&script), "evaluate javascript") - ("username,u", po::value<string>(&username), "username for authentication") - ("password,p", new mongo::PasswordValue(&password), - "password for authentication") - ("help,h", "show this usage information") - ("version", "show version information") - ("ipv6", "enable IPv6 support (disabled by default)") - ; + ("shell", "run the shell after executing files") + ("nodb", "don't connect to mongod on startup - no 'db address' arg expected") + ("quiet", "be less chatty" ) + ("port", po::value<string>(&port), "port to connect to") + ("host", po::value<string>(&dbhost), "server to connect to") + ("eval", po::value<string>(&script), "evaluate javascript") + ("username,u", po::value<string>(&username), "username for authentication") + ("password,p", new mongo::PasswordValue(&password), + "password for authentication") + ("help,h", "show this usage information") + ("version", "show version information") + ("verbose", "increase verbosity") + ("ipv6", "enable IPv6 support (disabled by default)") + ; hidden_options.add_options() - ("dbaddress", po::value<string>(), "dbaddress") - ("files", po::value< vector<string> >(), "files") - ("nokillop", "nokillop") // for testing, kill op will also be disabled automatically if the tests starts a mongo program - ("autokillop", "autokillop") // for testing, will kill op without prompting - ; + ("dbaddress", po::value<string>(), "dbaddress") + ("files", po::value< vector<string> >(), "files") + ("nokillop", "nokillop") // for testing, kill op will also be disabled automatically if the tests starts a mongo program + ("autokillop", "autokillop") // for testing, will kill op without prompting + ; positional_options.add("dbaddress", 1); positional_options.add("files", -1); @@ -474,17 +559,18 @@ int _main(int argc, char* argv[]) { positional(positional_options). style(command_line_style).run(), params); po::notify(params); - } catch (po::error &e) { + } + catch (po::error &e) { cout << "ERROR: " << e.what() << endl << endl; show_help_text(argv[0], shell_options); return mongo::EXIT_BADOPTIONS; } // hide password from ps output - for (int i=0; i < (argc-1); ++i){ - if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--password")){ + for (int i=0; i < (argc-1); ++i) { + if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--password")) { char* arg = argv[i+1]; - while (*arg){ + while (*arg) { *arg++ = 'x'; } } @@ -516,7 +602,7 @@ int _main(int argc, char* argv[]) { if (params.count("autokillop")) { autoKillOp = true; } - + /* This is a bit confusing, here are the rules: * * if nodb is set then all positional parameters are files @@ -528,41 +614,46 @@ int _main(int argc, char* argv[]) { string dbaddress = params["dbaddress"].as<string>(); if (nodb) { files.insert(files.begin(), dbaddress); - } else { + } + else { string basename = dbaddress.substr(dbaddress.find_last_of("/\\") + 1); if (basename.find_first_of('.') == string::npos || - (basename.find(".js", basename.size() - 3) == string::npos && !fileExists(dbaddress))) { + (basename.find(".js", basename.size() - 3) == string::npos && !fileExists(dbaddress))) { url = dbaddress; - } else { + } + else { files.insert(files.begin(), dbaddress); } } } - if (params.count("ipv6")){ + if (params.count("ipv6")) { mongo::enableIPv6(); } - - if ( ! mongo::cmdLine.quiet ) + if (params.count("verbose")) { + logLevel = 1; + } + + if ( ! mongo::cmdLine.quiet ) cout << "MongoDB shell version: " << mongo::versionString << endl; mongo::UnitTest::runTests(); if ( !nodb ) { // connect to db //if ( ! mongo::cmdLine.quiet ) cout << "url: " << url << endl; - + stringstream ss; if ( mongo::cmdLine.quiet ) ss << "__quiet = true;"; ss << "db = connect( \"" << fixHost( url , dbhost , port ) << "\")"; - + mongo::shellUtils::_dbConnect = ss.str(); if ( params.count( "password" ) - && ( password.empty() ) ) { + && ( password.empty() ) ) { password = mongo::askPassword(); } - if ( username.size() && password.size() ){ + if ( username.size() && password.size() ) { stringstream ss; ss << "if ( ! db.auth( \"" << username << "\" , \"" << password << "\" ) ){ throw 'login failed'; }"; mongo::shellUtils::_dbAuth = ss.str(); @@ -573,12 +664,12 @@ int _main(int argc, char* argv[]) { mongo::ScriptEngine::setConnectCallback( mongo::shellUtils::onConnect ); mongo::ScriptEngine::setup(); mongo::globalScriptEngine->setScopeInitCallback( mongo::shellUtils::initScope ); - auto_ptr< mongo::Scope > scope( mongo::globalScriptEngine->newScope() ); + auto_ptr< mongo::Scope > scope( mongo::globalScriptEngine->newScope() ); shellMainScope = scope.get(); if( runShell ) cout << "type \"help\" for help" << endl; - + if ( !script.empty() ) { mongo::shellUtils::MongoProgramScope s; if ( ! scope->exec( script , "(shell eval)" , true , true , false ) ) @@ -591,7 +682,7 @@ int _main(int argc, char* argv[]) { if ( files.size() > 1 ) cout << "loading file: " << files[i] << endl; - if ( ! scope->execFile( files[i] , false , true , false ) ){ + if ( ! scope->execFile( files[i] , false , true , false ) ) { cout << "failed to load: " << files[i] << endl; return -3; } @@ -601,7 +692,7 @@ int _main(int argc, char* argv[]) { runShell = true; } - if ( runShell ){ + if ( runShell ) { mongo::shellUtils::MongoProgramScope s; @@ -609,29 +700,38 @@ int _main(int argc, char* argv[]) { //v8::Handle<v8::Object> shellHelper = baseContext_->Global()->Get( v8::String::New( "shellHelper" ) )->ToObject(); - while ( 1 ){ + while ( 1 ) { inMultiLine = 0; gotInterrupted = 0; - char * line = shellReadline( "> " ); +// shellMainScope->localConnect; + //DBClientWithCommands *c = getConnection( JSContext *cx, JSObject *obj ); + + string prompt(sayReplSetMemberState()+"> "); + + char * line = shellReadline( prompt.c_str() ); + + if ( line ) { + while (startsWith(line, "> ")) + line += 2; - if ( line ) while ( line[0] == ' ' ) line++; + } - if ( ! line || ( strlen(line) == 4 && strstr( line , "exit" ) ) ){ + if ( ! line || ( strlen(line) == 4 && strstr( line , "exit" ) ) ) { cout << "bye" << endl; break; } string code = line; - if ( code == "exit" || code == "exit;" ){ + if ( code == "exit" || code == "exit;" ) { break; } if ( code.size() == 0 ) continue; code = finishCode( code ); - if ( gotInterrupted ){ + if ( gotInterrupted ) { cout << endl; continue; } @@ -645,40 +745,39 @@ int _main(int argc, char* argv[]) { if ( cmd.find( " " ) > 0 ) cmd = cmd.substr( 0 , cmd.find( " " ) ); - if ( cmd.find( "\"" ) == string::npos ){ + if ( cmd.find( "\"" ) == string::npos ) { try { scope->exec( (string)"__iscmd__ = shellHelper[\"" + cmd + "\"];" , "(shellhelp1)" , false , true , true ); - if ( scope->getBoolean( "__iscmd__" ) ){ + if ( scope->getBoolean( "__iscmd__" ) ) { scope->exec( (string)"shellHelper( \"" + cmd + "\" , \"" + code.substr( cmd.size() ) + "\");" , "(shellhelp2)" , false , true , false ); wascmd = true; } } - catch ( std::exception& e ){ - cout << "error2:" << e.what() << endl; + catch ( std::exception& e ) { + cout << "error2:" << e.what() << endl; wascmd = true; } } } - if ( ! wascmd ){ + if ( ! wascmd ) { try { if ( scope->exec( code.c_str() , "(shell)" , false , true , false ) ) scope->exec( "shellPrintHelper( __lastres__ );" , "(shell2)" , true , true , false ); } - catch ( std::exception& e ){ + catch ( std::exception& e ) { cout << "error:" << e.what() << endl; } } - shellHistoryAdd( line ); } shellHistoryDone(); } - mongo::goingAway = true; + mongo::dbexitCalled = true; return 0; } @@ -687,7 +786,7 @@ int main(int argc, char* argv[]) { try { return _main( argc , argv ); } - catch ( mongo::DBException& e ){ + catch ( mongo::DBException& e ) { cerr << "exception: " << e.what() << endl; return -1; } |