// dbshell.cpp /* * Copyright 2010 10gen Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "pch.h" #include #if defined(_WIN32) # if defined(USE_READLINE) # define USE_READLINE_STATIC # endif #endif #ifdef USE_READLINE #include #include #include jmp_buf jbuf; #endif #include "../scripting/engine.h" #include "../client/dbclient.h" #include "../util/unittest.h" #include "../db/cmdline.h" #include "utils.h" #include "../util/password.h" #include "../util/version.h" #include "../util/goodies.h" using namespace std; using namespace boost::filesystem; using mongo::BSONObj; using mongo::BSONObjBuilder; using mongo::BSONObjIterator; using mongo::BSONElement; string historyFile; bool gotInterrupted = 0; bool inMultiLine = 0; static volatile bool atPrompt = false; // can eval before getting to prompt bool autoKillOp = false; #if defined(USE_READLINE) && !defined(__freebsd__) && !defined(__openbsd__) && !defined(_WIN32) #define CTRLC_HANDLE #endif mongo::Scope * shellMainScope; void generateCompletions( const string& prefix , vector& all ){ if ( prefix.find( '"' ) != string::npos ) return; shellMainScope->exec( "shellAutocomplete( \"" + prefix + "\" );" , "autocomplete help" , false , true , false ); BSONObjBuilder b; shellMainScope->append( b , "" , "__autocomplete__" ); BSONObj res = b.obj(); BSONObj arr = res.firstElement().Obj(); BSONObjIterator i(arr); while ( i.more() ){ BSONElement e = i.next(); all.push_back( e.String() ); } } #ifdef USE_READLINE static char** completionHook(const char* text , int start ,int end ){ static map m; vector all; if ( start == 0 ){ generateCompletions( string(text,end) , all ); } if ( all.size() == 0 ){ rl_bind_key('\t',0); return 0; } string longest = all[0]; for ( vector::iterator i=all.begin(); i!=all.end(); ++i ){ string s = *i; for ( unsigned j=0; j> yn; if (yn != 'y' && yn != 'Y') return; } 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" ); } } void quitNicely( int sig ){ mongo::goingAway = true; if ( sig == SIGINT && inMultiLine ){ gotInterrupted = 1; return; } if ( sig == SIGPIPE ) mongo::rawOut( "mongo got signal SIGPIPE\n" ); killOps(); shellHistoryDone(); exit(0); } #else void quitNicely( int sig ){ mongo::goingAway = true; //killOps(); shellHistoryDone(); exit(0); } #endif char * shellReadline( const char * prompt , int handlesigint = 0 ){ atPrompt = true; #ifdef USE_READLINE rl_bind_key('\t',rl_complete); #ifdef CTRLC_HANDLE if ( ! handlesigint ){ char* ret = readline( prompt ); atPrompt = false; return ret; } if ( setjmp( jbuf ) ){ gotInterrupted = 1; sigrelse(SIGINT); signal( SIGINT , quitNicely ); return 0; } signal( SIGINT , intr ); #endif char * ret = readline( prompt ); signal( SIGINT , quitNicely ); atPrompt = false; return ret; #else printf("%s", prompt); cout.flush(); char * buf = new char[1024]; char * l = fgets( buf , 1024 , stdin ); int len = strlen( buf ); if ( len ) buf[len-1] = 0; atPrompt = false; return l; #endif } #if !defined(_WIN32) #include void quitAbruptly( int sig ) { ostringstream ossSig; ossSig << "mongo got signal " << sig << " (" << strsignal( sig ) << "), stack trace: " << endl; mongo::rawOut( ossSig.str() ); ostringstream ossBt; mongo::printStackTrace( ossBt ); mongo::rawOut( ossBt.str() ); mongo::shellUtils::KillMongoProgramInstances(); exit(14); } void setupSignals() { signal( SIGINT , quitNicely ); signal( SIGTERM , quitNicely ); signal( SIGPIPE , quitNicely ); // Maybe just log and continue? signal( SIGABRT , quitAbruptly ); signal( SIGSEGV , quitAbruptly ); signal( SIGBUS , quitAbruptly ); signal( SIGFPE , quitAbruptly ); } #else inline void setupSignals() {} #endif 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 ){ // check for ips if ( url.find( "." ) != string::npos ) return url + "/test"; if ( url.rfind( ":" ) != string::npos && isdigit( url[url.rfind(":")+1] ) ) return url + "/test"; } return url; } if ( url.find( "/" ) != string::npos ){ cerr << "url can't have host or port if you specify them individually" << endl; exit(-1); } if ( host.size() == 0 ) host = "127.0.0.1"; string newurl = host; if ( port.size() > 0 ) newurl += ":" + port; else if (host.find(':') == string::npos){ // need to add port with IPv6 addresses newurl += ":27017"; } newurl += "/" + url; return newurl; } bool isBalanced( string code ){ int brackets = 0; int parens = 0; for ( size_t i=0; i namespace po = boost::program_options; void show_help_text(const char* name, po::options_description options) { cout << "MongoDB shell version: " << mongo::versionString << endl; cout << "usage: " << name << " [options] [db address] [file names (ending in .js)]" << endl << "db address can be:" << endl << " foo foo database on local machine" << endl << " 192.169.0.5/foo foo database on 192.168.0.5 machine" << endl << " 192.169.0.5:9999/foo foo database on 192.168.0.5 machine on port 9999" << endl << options << endl << "file names: a list of files to run. files have to end in .js and will exit after " << "unless --shell is specified" << endl; }; bool fileExists( string file ){ try { path p(file); return boost::filesystem::exists( file ); } catch (...){ return false; } } namespace mongo { extern bool isShell; } int _main(int argc, char* argv[]) { mongo::isShell = true; setupSignals(); mongo::shellUtils::RecordMyLocation( argv[ 0 ] ); string url = "test"; string dbhost; string port; vector files; string username; string password; 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(&port), "port to connect to") ("host", po::value(&dbhost), "server to connect to") ("eval", po::value(&script), "evaluate javascript") ("username,u", po::value(&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)") ; hidden_options.add_options() ("dbaddress", po::value(), "dbaddress") ("files", po::value< vector >(), "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); cmdline_options.add(shell_options).add(hidden_options); po::variables_map params; /* using the same style as db.cpp uses because eventually we're going * to merge some of this stuff. */ int command_line_style = (((po::command_line_style::unix_style ^ po::command_line_style::allow_guessing) | po::command_line_style::allow_long_disguise) ^ po::command_line_style::allow_sticky); try { po::store(po::command_line_parser(argc, argv).options(cmdline_options). positional(positional_options). style(command_line_style).run(), params); po::notify(params); } 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")){ char* arg = argv[i+1]; while (*arg){ *arg++ = 'x'; } } } if (params.count("shell")) { runShell = true; } if (params.count("nodb")) { nodb = true; } if (params.count("help")) { show_help_text(argv[0], shell_options); return mongo::EXIT_CLEAN; } if (params.count("files")) { files = params["files"].as< vector >(); } if (params.count("version")) { cout << "MongoDB shell version: " << mongo::versionString << endl; return mongo::EXIT_CLEAN; } if (params.count("quiet")) { mongo::cmdLine.quiet = true; } if (params.count("nokillop")) { mongo::shellUtils::_nokillop = true; } 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 * otherwise the first positional parameter might be a dbaddress, but * only if one of these conditions is met: * - it contains no '.' after the last appearance of '\' or '/' * - it doesn't end in '.js' and it doesn't specify a path to an existing file */ if (params.count("dbaddress")) { string dbaddress = params["dbaddress"].as(); if (nodb) { files.insert(files.begin(), dbaddress); } 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))) { url = dbaddress; } else { files.insert(files.begin(), dbaddress); } } } if (params.count("ipv6")){ mongo::enableIPv6(); } 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 = mongo::askPassword(); } if ( username.size() && password.size() ){ stringstream ss; ss << "if ( ! db.auth( \"" << username << "\" , \"" << password << "\" ) ){ throw 'login failed'; }"; mongo::shellUtils::_dbAuth = ss.str(); } } mongo::ScriptEngine::setConnectCallback( mongo::shellUtils::onConnect ); mongo::ScriptEngine::setup(); mongo::globalScriptEngine->setScopeInitCallback( mongo::shellUtils::initScope ); 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 ) ) return -4; } for (size_t i = 0; i < files.size(); i++) { mongo::shellUtils::MongoProgramScope s; if ( files.size() > 1 ) cout << "loading file: " << files[i] << endl; if ( ! scope->execFile( files[i] , false , true , false ) ){ cout << "failed to load: " << files[i] << endl; return -3; } } if ( files.size() == 0 && script.empty() ) { runShell = true; } if ( runShell ){ mongo::shellUtils::MongoProgramScope s; shellHistoryInit(); //v8::Handle shellHelper = baseContext_->Global()->Get( v8::String::New( "shellHelper" ) )->ToObject(); while ( 1 ){ inMultiLine = 0; gotInterrupted = 0; char * line = shellReadline( "> " ); if ( line ) while ( line[0] == ' ' ) line++; if ( ! line || ( strlen(line) == 4 && strstr( line , "exit" ) ) ){ cout << "bye" << endl; break; } string code = line; if ( code == "exit" || code == "exit;" ){ break; } if ( code.size() == 0 ) continue; code = finishCode( code ); if ( gotInterrupted ){ cout << endl; continue; } if ( code.size() == 0 ) break; bool wascmd = false; { string cmd = line; if ( cmd.find( " " ) > 0 ) cmd = cmd.substr( 0 , cmd.find( " " ) ); if ( cmd.find( "\"" ) == string::npos ){ try { scope->exec( (string)"__iscmd__ = shellHelper[\"" + cmd + "\"];" , "(shellhelp1)" , false , true , true ); 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; wascmd = true; } } } 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 ){ cout << "error:" << e.what() << endl; } } shellHistoryAdd( line ); } shellHistoryDone(); } mongo::goingAway = true; return 0; } int main(int argc, char* argv[]) { static mongo::StaticObserver staticObserver; try { return _main( argc , argv ); } catch ( mongo::DBException& e ){ cerr << "exception: " << e.what() << endl; return -1; } }