/* * Copyright 2010 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault #include "mongo/platform/basic.h" #include #include #include #include #include #include #include #include "mongo/base/init.h" #include "mongo/base/initializer.h" #include "mongo/base/status.h" #include "mongo/client/mongo_uri.h" #include "mongo/db/auth/sasl_command_constants.h" #include "mongo/db/client.h" #include "mongo/db/log_process_details.h" #include "mongo/db/server_options.h" #include "mongo/logger/console_appender.h" #include "mongo/logger/logger.h" #include "mongo/logger/message_event_utf8_encoder.h" #include "mongo/platform/atomic_word.h" #include "mongo/scripting/engine.h" #include "mongo/shell/linenoise.h" #include "mongo/shell/shell_options.h" #include "mongo/shell/shell_utils.h" #include "mongo/shell/shell_utils_launcher.h" #include "mongo/stdx/utility.h" #include "mongo/transport/transport_layer_asio.h" #include "mongo/util/exit.h" #include "mongo/util/file.h" #include "mongo/util/log.h" #include "mongo/util/net/ssl_options.h" #include "mongo/util/password.h" #include "mongo/util/quick_exit.h" #include "mongo/util/scopeguard.h" #include "mongo/util/signal_handlers.h" #include "mongo/util/stacktrace.h" #include "mongo/util/startup_test.h" #include "mongo/util/stringutils.h" #include "mongo/util/text.h" #include "mongo/util/version.h" #ifdef _WIN32 #include #include #define isatty _isatty #define fileno _fileno #else #include #endif using namespace std; using namespace std::literals::string_literals; using namespace mongo; string historyFile; bool gotInterrupted = false; bool inMultiLine = false; static AtomicBool atPrompt(false); // can eval before getting to prompt namespace { const auto kDefaultMongoURL = "mongodb://127.0.0.1:27017"_sd; // Initialize the featureCompatibilityVersion server parameter since the mongo shell does not have a // featureCompatibilityVersion document from which to initialize the parameter. The parameter is set // to the latest version because there is no feature gating that currently occurs at the mongo shell // level. The server is responsible for rejecting usages of new features if its // featureCompatibilityVersion is lower. MONGO_INITIALIZER_WITH_PREREQUISITES(SetFeatureCompatibilityVersion42, ("EndStartupOptionSetup")) (InitializerContext* context) { mongo::serverGlobalParams.featureCompatibility.setVersion( ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42); return Status::OK(); } const auto kAuthParam = "authSource"s; } // namespace namespace mongo { enum ShellExitCode : int { kDBException = 1, kInputFileError = -3, kEvalError = -4, kMongorcError = -5, kUnterminatedProcess = -6, kProcessTerminationError = -7, }; Scope* shellMainScope; } void generateCompletions(const string& prefix, vector& all) { if (prefix.find('"') != string::npos) return; try { BSONObj args = BSON("0" << prefix); shellMainScope->invokeSafe( "function callShellAutocomplete(x) {shellAutocomplete(x)}", &args, NULL); 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()); } } catch (...) { } } void completionHook(const char* text, linenoiseCompletions* lc) { vector all; generateCompletions(text, all); for (unsigned i = 0; i < all.size(); ++i) linenoiseAddCompletion(lc, (char*)all[i].c_str()); } void shellHistoryInit() { stringstream ss; const char* h = shell_utils::getUserDir(); if (h) ss << h << "/"; ss << ".dbshell"; historyFile = ss.str(); Status res = linenoiseHistoryLoad(historyFile.c_str()); if (!res.isOK()) { error() << "Error loading history file: " << res; } linenoiseSetCompletionCallback(completionHook); } void shellHistoryDone() { Status res = linenoiseHistorySave(historyFile.c_str()); if (!res.isOK()) { error() << "Error saving history file: " << res; } linenoiseHistoryFree(); } void shellHistoryAdd(const char* line) { if (line[0] == '\0') return; // dont record duplicate lines static string lastLine; if (lastLine == line) return; lastLine = line; // We don't want any .auth() or .createUser() shell helpers added, but we want to // be able to add things like `.author`, so be smart about how this is // detected by using regular expresions. This is so we can avoid storing passwords // in the history file in plaintext. static pcrecpp::RE hiddenHelpers( "\\.\\s*(auth|createUser|updateUser|changeUserPassword)\\s*\\("); // Also don't want the raw user management commands to show in the shell when run directly // via runCommand. static pcrecpp::RE hiddenCommands( "(run|admin)Command\\s*\\(\\s*{\\s*(createUser|updateUser)\\s*:"); if (!hiddenHelpers.PartialMatch(line) && !hiddenCommands.PartialMatch(line)) { linenoiseHistoryAdd(line); } } void killOps() { if (mongo::shell_utils::_nokillop) return; if (atPrompt.load()) return; sleepmillis(10); // give current op a chance to finish mongo::shell_utils::connectionRegistry.killOperationsOnAllConnections( !shellGlobalParams.autoKillOp); } void quitNicely(int sig) { shutdown(EXIT_CLEAN); } // the returned string is allocated with strdup() or malloc() and must be freed by calling free() char* shellReadline(const char* prompt, int handlesigint = 0) { atPrompt.store(true); char* ret = linenoise(prompt); if (!ret) { gotInterrupted = true; // got ^C, break out of multiline } atPrompt.store(false); return ret; } void setupSignals() { #ifndef _WIN32 signal(SIGHUP, quitNicely); #endif signal(SIGINT, quitNicely); } string getURIFromArgs(const std::string& arg, const std::string& host, const std::string& port) { if (host.empty() && arg.empty() && port.empty()) { // Nothing provided, just play the default. return kDefaultMongoURL.toString(); } if ((str::startsWith(arg, "mongodb://") || str::startsWith(arg, "mongodb+srv://")) && host.empty() && port.empty()) { // mongo mongodb://blah return arg; } if ((str::startsWith(host, "mongodb://") || str::startsWith(arg, "mongodb+srv://")) && arg.empty() && port.empty()) { // mongo --host mongodb://blah return host; } // We expect a positional arg to be a plain dbname or plain hostname at this point // since we have separate host/port args. if ((arg.find('/') != string::npos) && (host.size() || port.size())) { cerr << "If a full URI is provided, you cannot also specify --host or --port" << endl; quickExit(-1); } const auto parseDbHost = [port](const std::string& db, const std::string& host) -> std::string { // Parse --host as a connection string. // e.g. rs0/host0:27000,host1:27001 const auto slashPos = host.find('/'); const auto hasReplSet = (slashPos > 0) && (slashPos != std::string::npos); std::ostringstream ss; ss << "mongodb://"; // Handle each sub-element of the connection string individually. // Comma separated list of host elements. // Each host element may be: // * /unix/domain.sock // * hostname // * hostname:port // If --port is specified and port is included in connection string, // then they must match exactly. auto start = hasReplSet ? slashPos + 1 : 0; while (start < host.size()) { // Encode each host component. auto end = host.find(',', start); if (end == std::string::npos) { end = host.size(); } if ((end - start) == 0) { // Ignore empty components. start = end + 1; continue; } const auto hostElem = host.substr(start, end - start); if ((hostElem.find('/') != std::string::npos) && str::endsWith(hostElem, ".sock")) { // Unix domain socket, ignore --port. ss << uriEncode(hostElem); } else { auto colon = hostElem.find(':'); if ((colon != std::string::npos) && (hostElem.find(':', colon + 1) != std::string::npos)) { // Looks like an IPv6 numeric address. const auto close = hostElem.find(']'); if ((hostElem[0] == '[') && (close != std::string::npos)) { // Encapsulated already. ss << '[' << uriEncode(hostElem.substr(1, close - 1), ":") << ']'; colon = hostElem.find(':', close + 1); } else { // Not encapsulated yet. ss << '[' << uriEncode(hostElem, ":") << ']'; colon = std::string::npos; } } else if (colon != std::string::npos) { // Not IPv6 numeric, but does have a port. ss << uriEncode(hostElem.substr(0, colon)); } else { // Raw hostname/IPv4 without port. ss << uriEncode(hostElem); } if (colon != std::string::npos) { // Have a port in our host element, verify it. const auto myport = hostElem.substr(colon + 1); if (port.size() && (port != myport)) { cerr << "connection string bears different port than provided by --port" << endl; quickExit(-1); } ss << ':' << uriEncode(myport); } else if (port.size()) { ss << ':' << uriEncode(port); } else { ss << ":27017"; } } start = end + 1; if (start < host.size()) { ss << ','; } } ss << '/' << uriEncode(db); if (hasReplSet) { // Remap included replica set name to URI option ss << "?replicaSet=" << uriEncode(host.substr(0, slashPos)); } return ss.str(); }; if (host.size()) { // --host provided, treat it as the connect string and get db from positional arg. return parseDbHost(arg, host); } else if (arg.size()) { // --host missing, but we have a potential host/db positional arg. const auto slashPos = arg.find('/'); if (slashPos != std::string::npos) { // host/db pair. return parseDbHost(arg.substr(slashPos + 1), arg.substr(0, slashPos)); } // Compatability formats. // * Any arg with a dot is assumed to be a hostname or IPv4 numeric address. // * Any arg with a colon followed by a digit assumed to be host or IP followed by port. // * Anything else is assumed to be a db. if (arg.find('.') != std::string::npos) { // Assume IPv4 or hostnameish. return parseDbHost("test", arg); } const auto colonPos = arg.find(':'); if ((colonPos != std::string::npos) && ((colonPos + 1) < arg.size()) && isdigit(arg[colonPos + 1])) { // Assume IPv4 or hostname with port. return parseDbHost("test", arg); } // db, assume localhost. return parseDbHost(arg, "127.0.0.1"); } // --host empty, position arg empty, fallback on localhost without a dbname. return parseDbHost("", "127.0.0.1"); } 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 isUseCmd(const std::string& code) { string cmd = code; if (cmd.find(" ") > 0) cmd = cmd.substr(0, cmd.find(" ")); return cmd == "use"; } /** * Skip over a quoted string, including quotes escaped with backslash * * @param code String * @param start Starting position within string, always > 0 * @param quote Quote character (single or double quote) * @return Position of ending quote, or code.size() if no quote found */ size_t skipOverString(const std::string& code, size_t start, char quote) { size_t pos = start; while (pos < code.size()) { pos = code.find(quote, pos); if (pos == std::string::npos) { return code.size(); } // We want to break if the quote we found is not escaped, but we need to make sure // that the escaping backslash is not itself escaped. Comparisons of start and pos // are to keep us from reading beyond the beginning of the quoted string. // if (start == pos || code[pos - 1] != '\\' || // previous char was backslash start == pos - 1 || code[pos - 2] == '\\' // char before backslash was not another ) { break; // The quote we found was not preceded by an unescaped backslash; it is real } ++pos; // The quote we found was escaped with backslash, so it doesn't count } return pos; } bool isBalanced(const std::string& code) { if (isUseCmd(code)) return true; // don't balance "use " in case dbname contains special chars int curlyBrackets = 0; int squareBrackets = 0; int parens = 0; bool danglingOp = false; for (size_t i = 0; i < code.size(); i++) { switch (code[i]) { case '/': if (i + 1 < code.size() && code[i + 1] == '/') { while (i < code.size() && code[i] != '\n') i++; } continue; case '{': curlyBrackets++; break; case '}': if (curlyBrackets <= 0) return true; curlyBrackets--; break; case '[': squareBrackets++; break; case ']': if (squareBrackets <= 0) return true; squareBrackets--; break; case '(': parens++; break; case ')': if (parens <= 0) return true; parens--; break; case '"': case '\'': i = skipOverString(code, i + 1, code[i]); if (i >= code.size()) { return true; // Do not let unterminated strings enter multi-line mode } 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 (i >= code.size()) { danglingOp = false; break; } if (isOpSymbol(code[i])) danglingOp = true; else if (!std::isspace(static_cast(code[i]))) danglingOp = false; } return curlyBrackets == 0 && squareBrackets == 0 && parens == 0 && !danglingOp; } struct BalancedTest : public mongo::StartupTest { public: void run() { verify(isBalanced("x = 5")); verify(isBalanced("function(){}")); verify(isBalanced("function(){\n}")); verify(!isBalanced("function(){")); verify(isBalanced("x = \"{\";")); verify(isBalanced("// {")); verify(!isBalanced("// \n {")); verify(!isBalanced("\"//\" {")); verify(isBalanced("{x:/x\\//}")); verify(!isBalanced("{ \\/// }")); verify(isBalanced("x = 5 + y ")); verify(!isBalanced("x = ")); verify(!isBalanced("x = // hello")); verify(!isBalanced("x = 5 +")); verify(isBalanced(" x ++")); verify(isBalanced("-- x")); verify(!isBalanced("a.")); verify(!isBalanced("a. ")); verify(isBalanced("a.b")); // SERVER-5809 and related cases -- verify(isBalanced("a = {s:\"\\\"\"}")); // a = {s:"\""} verify(isBalanced("db.test.save({s:\"\\\"\"})")); // db.test.save({s:"\""}) verify(isBalanced("printjson(\" \\\" \")")); // printjson(" \" ") -- SERVER-8554 verify(isBalanced("var a = \"\\\\\";")); // var a = "\\"; verify(isBalanced("var a = (\"\\\\\") //\"")); // var a = ("\\") //" verify(isBalanced("var a = (\"\\\\\") //\\\"")); // var a = ("\\") //\" verify(isBalanced("var a = (\"\\\\\") //")); // var a = ("\\") // verify(isBalanced("var a = (\"\\\\\")")); // var a = ("\\") verify(isBalanced("var a = (\"\\\\\\\"\")")); // var a = ("\\\"") verify(!isBalanced("var a = (\"\\\\\" //\"")); // var a = ("\\" //" verify(!isBalanced("var a = (\"\\\\\" //")); // var a = ("\\" // verify(!isBalanced("var a = (\"\\\\\"")); // var a = ("\\" } } balanced_test; string finishCode(string code) { while (!isBalanced(code)) { inMultiLine = true; code += "\n"; // cancel multiline if two blank lines are entered if (code.find("\n\n\n") != string::npos) return ";"; char* line = shellReadline("... ", 1); if (gotInterrupted) { if (line) free(line); return ""; } if (!line) return ""; char* linePtr = line; while (str::startsWith(linePtr, "... ")) linePtr += 4; code += linePtr; free(line); } return code; } bool execPrompt(mongo::Scope& scope, const char* promptFunction, string& prompt) { string execStatement = string("__promptWrapper__(") + promptFunction + ");"; scope.exec("delete __prompt__;", "", false, false, false, 0); scope.exec(execStatement, "", false, false, false, 0); if (scope.type("__prompt__") == String) { prompt = scope.getString("__prompt__"); return true; } return false; } /** * Edit a variable or input buffer text in an external editor -- EDITOR must be defined * * @param whatToEdit Name of JavaScript variable to be edited, or any text string */ static void edit(const string& whatToEdit) { // EDITOR may be defined in the JavaScript scope or in the environment string editor; if (shellMainScope->type("EDITOR") == String) { editor = shellMainScope->getString("EDITOR"); } else { static const char* editorFromEnv = getenv("EDITOR"); if (editorFromEnv) { editor = editorFromEnv; } } if (editor.empty()) { cout << "please define EDITOR as a JavaScript string or as an environment variable" << endl; return; } // "whatToEdit" might look like a variable/property name bool editingVariable = true; for (const char* p = whatToEdit.c_str(); *p; ++p) { if (!(isalnum(*p) || *p == '_' || *p == '.')) { editingVariable = false; break; } } string js; if (editingVariable) { // If "whatToEdit" is undeclared or uninitialized, declare int varType = shellMainScope->type(whatToEdit.c_str()); if (varType == Undefined) { shellMainScope->exec("var " + whatToEdit, "(shell)", false, true, false); } // Convert "whatToEdit" to JavaScript (JSON) text if (!shellMainScope->exec( "__jsout__ = tojson(" + whatToEdit + ")", "tojs", false, false, false)) return; // Error already printed js = shellMainScope->getString("__jsout__"); if (strstr(js.c_str(), "[native code]")) { cout << "can't edit native functions" << endl; return; } } else { js = whatToEdit; } // Pick a name to use for the temp file string filename; const int maxAttempts = 10; int i; for (i = 0; i < maxAttempts; ++i) { StringBuilder sb; #ifdef _WIN32 char tempFolder[MAX_PATH]; GetTempPathA(sizeof tempFolder, tempFolder); sb << tempFolder << "mongo_edit" << time(0) + i << ".js"; #else sb << "/tmp/mongo_edit" << time(0) + i << ".js"; #endif filename = sb.str(); if (!::mongo::shell_utils::fileExists(filename)) break; } if (i == maxAttempts) { cout << "couldn't create unique temp file after " << maxAttempts << " attempts" << endl; return; } // Create the temp file FILE* tempFileStream; tempFileStream = fopen(filename.c_str(), "wt"); if (!tempFileStream) { cout << "couldn't create temp file (" << filename << "): " << errnoWithDescription() << endl; return; } // Write JSON into the temp file size_t fileSize = js.size(); if (fwrite(js.data(), sizeof(char), fileSize, tempFileStream) != fileSize) { int systemErrno = errno; cout << "failed to write to temp file: " << errnoWithDescription(systemErrno) << endl; fclose(tempFileStream); remove(filename.c_str()); return; } fclose(tempFileStream); // Pass file to editor StringBuilder sb; sb << editor << " " << filename; int ret = ::system(sb.str().c_str()); if (ret) { if (ret == -1) { int systemErrno = errno; cout << "failed to launch $EDITOR (" << editor << "): " << errnoWithDescription(systemErrno) << endl; } else cout << "editor exited with error (" << ret << "), not applying changes" << endl; remove(filename.c_str()); return; } // The editor gave return code zero, so read the file back in tempFileStream = fopen(filename.c_str(), "rt"); if (!tempFileStream) { cout << "couldn't open temp file on return from editor: " << errnoWithDescription() << endl; remove(filename.c_str()); return; } sb.reset(); int bytes; do { char buf[1024]; bytes = fread(buf, sizeof(char), sizeof buf, tempFileStream); if (ferror(tempFileStream)) { cout << "failed to read temp file: " << errnoWithDescription() << endl; fclose(tempFileStream); remove(filename.c_str()); return; } sb.append(StringData(buf, bytes)); } while (bytes); // Done with temp file, close and delete it fclose(tempFileStream); remove(filename.c_str()); if (editingVariable) { // Try to execute assignment to copy edited value back into the variable const string code = whatToEdit + string(" = ") + sb.str(); if (!shellMainScope->exec(code, "tojs", false, true, false)) { cout << "error executing assignment: " << code << endl; } } else { linenoisePreloadBuffer(sb.str().c_str()); } } namespace { bool mechanismRequiresPassword() { using std::begin; using std::end; const std::string passwordlessMechanisms[] = {"GSSAPI", "MONGODB-X509"}; auto isInShellParameters = [](const auto& mech) { return mech == shellGlobalParams.authenticationMechanism; }; return std::none_of( begin(passwordlessMechanisms), end(passwordlessMechanisms), isInShellParameters); } } // namespace int _main(int argc, char* argv[], char** envp) { registerShutdownTask([] { // NOTE: This function may be called at any time. It must not // depend on the prior execution of mongo initializers or the // existence of threads. ::killOps(); ::shellHistoryDone(); }); setupSignalHandlers(); setupSignals(); mongo::shell_utils::RecordMyLocation(argv[0]); mongo::runGlobalInitializersOrDie(argc, argv, envp); setGlobalServiceContext(ServiceContext::make()); // TODO This should use a TransportLayerManager or TransportLayerFactory auto serviceContext = getGlobalServiceContext(); transport::TransportLayerASIO::Options opts; opts.enableIPv6 = shellGlobalParams.enableIPv6; opts.mode = transport::TransportLayerASIO::Options::kEgress; serviceContext->setTransportLayer( std::make_unique(opts, nullptr)); auto tlPtr = serviceContext->getTransportLayer(); uassertStatusOK(tlPtr->setup()); uassertStatusOK(tlPtr->start()); // hide password from ps output redactPasswordOptions(argc, argv); if (!mongo::serverGlobalParams.quiet.load()) cout << mongoShellVersion(VersionInfoInterface::instance()) << endl; mongo::StartupTest::runTests(); logger::globalLogManager() ->getNamedDomain("javascriptOutput") ->attachAppender(std::make_unique>( std::make_unique())); std::string& cmdlineURI = shellGlobalParams.url; MongoURI parsedURI; if (!cmdlineURI.empty()) { parsedURI = uassertStatusOK(MongoURI::parse(stdx::as_const(cmdlineURI))); } // We create an altered URI from the one passed so that we can pass that to replica set // monitors. This is to avoid making potentially breaking changes to the replica set monitor // code. std::string processedURI = cmdlineURI; auto pos = cmdlineURI.find('@'); auto protocolLength = processedURI.find("://"); if (pos != std::string::npos && protocolLength != std::string::npos) { processedURI = processedURI.substr(0, protocolLength) + "://" + processedURI.substr(pos + 1); } if (!shellGlobalParams.nodb) { // connect to db stringstream ss; if (mongo::serverGlobalParams.quiet.load()) ss << "__quiet = true;"; ss << "db = connect( \"" << getURIFromArgs(processedURI, shellGlobalParams.dbhost, shellGlobalParams.port) << "\");"; if (shellGlobalParams.shouldRetryWrites || parsedURI.getRetryWrites()) { // If the --retryWrites cmdline argument or retryWrites URI param was specified, then // replace the global `db` object with a DB object started in a session. The resulting // Mongo connection checks its _retryWrites property. ss << "db = db.getMongo().startSession().getDatabase(db.getName());"; } mongo::shell_utils::_dbConnect = ss.str(); if (cmdlineURI.size()) { const auto mechanismKey = parsedURI.getOptions().find("authMechanism"); if (mechanismKey != end(parsedURI.getOptions()) && shellGlobalParams.authenticationMechanism.empty()) { shellGlobalParams.authenticationMechanism = mechanismKey->second; } if (mechanismRequiresPassword() && (parsedURI.getUser().size() || shellGlobalParams.username.size())) { shellGlobalParams.usingPassword = true; } if (shellGlobalParams.usingPassword && shellGlobalParams.password.empty()) { shellGlobalParams.password = parsedURI.getPassword().size() ? parsedURI.getPassword() : mongo::askPassword(); } if (parsedURI.getUser().size() && shellGlobalParams.username.empty()) { shellGlobalParams.username = parsedURI.getUser(); } auto authParam = parsedURI.getOptions().find(kAuthParam); if (authParam != end(parsedURI.getOptions()) && shellGlobalParams.authenticationDatabase.empty()) { shellGlobalParams.authenticationDatabase = authParam->second; } } else if (shellGlobalParams.usingPassword && shellGlobalParams.password.empty()) { shellGlobalParams.password = mongo::askPassword(); } } // We now substitute the altered URI to permit the replica set monitors to see it without // usernames. This is to avoid making potentially breaking changes to the replica set monitor // code. cmdlineURI = processedURI; // Construct the authentication-related code to execute on shell startup. // // This constructs and immediately executes an anonymous function, to avoid // the shell's default behavior of printing statement results to the console. // // It constructs a statement of the following form: // // (function() { // // Set default authentication mechanism and, maybe, authenticate. // }()) stringstream authStringStream; authStringStream << "(function() { " << endl; if (!shellGlobalParams.authenticationMechanism.empty()) { authStringStream << "DB.prototype._defaultAuthenticationMechanism = \"" << escape(shellGlobalParams.authenticationMechanism) << "\";" << endl; } if (!shellGlobalParams.gssapiServiceName.empty()) { authStringStream << "DB.prototype._defaultGssapiServiceName = \"" << escape(shellGlobalParams.gssapiServiceName) << "\";" << endl; } if (!shellGlobalParams.nodb && (!shellGlobalParams.username.empty() || shellGlobalParams.authenticationMechanism == "MONGODB-X509")) { authStringStream << "var username = \"" << escape(shellGlobalParams.username) << "\";" << endl; if (shellGlobalParams.usingPassword) { authStringStream << "var password = \"" << escape(shellGlobalParams.password) << "\";" << endl; } if (shellGlobalParams.authenticationDatabase.empty()) { authStringStream << "var authDb = db;" << endl; } else { authStringStream << "var authDb = db.getSiblingDB(\"" << escape(shellGlobalParams.authenticationDatabase) << "\");" << endl; } authStringStream << "authDb._authOrThrow({ "; if (!shellGlobalParams.username.empty()) { authStringStream << saslCommandUserFieldName << ": username "; } if (shellGlobalParams.usingPassword) { authStringStream << ", " << saslCommandPasswordFieldName << ": password "; } if (!shellGlobalParams.gssapiHostName.empty()) { authStringStream << ", " << saslCommandServiceHostnameFieldName << ": \"" << escape(shellGlobalParams.gssapiHostName) << '"' << endl; } authStringStream << "});" << endl; } authStringStream << "}())"; mongo::shell_utils::_dbAuth = authStringStream.str(); mongo::ScriptEngine::setConnectCallback(mongo::shell_utils::onConnect); mongo::ScriptEngine::setup(); mongo::getGlobalScriptEngine()->setJSHeapLimitMB(shellGlobalParams.jsHeapLimitMB); mongo::getGlobalScriptEngine()->setScopeInitCallback(mongo::shell_utils::initScope); mongo::getGlobalScriptEngine()->enableJIT(!shellGlobalParams.nojit); mongo::getGlobalScriptEngine()->enableJavaScriptProtection( shellGlobalParams.javascriptProtection); auto poolGuard = MakeGuard([] { ScriptEngine::dropScopeCache(); }); unique_ptr scope(mongo::getGlobalScriptEngine()->newScope()); shellMainScope = scope.get(); if (shellGlobalParams.runShell && !mongo::serverGlobalParams.quiet.load()) cout << "type \"help\" for help" << endl; // Load and execute /etc/mongorc.js before starting shell std::string rcGlobalLocation; #ifndef _WIN32 rcGlobalLocation = "/etc/mongorc.js"; #else wchar_t programDataPath[MAX_PATH]; if (S_OK == SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, 0, programDataPath)) { rcGlobalLocation = str::stream() << toUtf8String(programDataPath) << "\\MongoDB\\mongorc.js"; } #endif if (!rcGlobalLocation.empty() && ::mongo::shell_utils::fileExists(rcGlobalLocation)) { if (!scope->execFile(rcGlobalLocation, false, true)) { cout << "The \"" << rcGlobalLocation << "\" file could not be executed" << endl; } } if (!shellGlobalParams.script.empty()) { mongo::shell_utils::MongoProgramScope s; if (!scope->exec(shellGlobalParams.script, "(shell eval)", false, true, false)) { error() << "exiting with code " << static_cast(kEvalError); return kEvalError; } scope->exec("shellPrintHelper( __lastres__ );", "(shell2 eval)", true, true, false); } for (size_t i = 0; i < shellGlobalParams.files.size(); ++i) { mongo::shell_utils::MongoProgramScope s; if (shellGlobalParams.files.size() > 1) cout << "loading file: " << shellGlobalParams.files[i] << endl; if (!scope->execFile(shellGlobalParams.files[i], false, true)) { severe() << "failed to load: " << shellGlobalParams.files[i]; error() << "exiting with code " << static_cast(kInputFileError); return kInputFileError; } // Check if the process left any running child processes. std::vector pids = mongo::shell_utils::getRunningMongoChildProcessIds(); if (!pids.empty()) { cout << "terminating the following processes started by " << shellGlobalParams.files[i] << ": "; std::copy(pids.begin(), pids.end(), std::ostream_iterator(cout, " ")); cout << endl; if (mongo::shell_utils::KillMongoProgramInstances() != EXIT_SUCCESS) { severe() << "one more more child processes exited with an error during " << shellGlobalParams.files[i]; error() << "exiting with code " << static_cast(kProcessTerminationError); return kProcessTerminationError; } bool failIfUnterminatedProcesses = false; const StringData code = "function() { return typeof TestData === 'object' && TestData !== null && " "TestData.hasOwnProperty('failIfUnterminatedProcesses') && " "TestData.failIfUnterminatedProcesses; }"_sd; shellMainScope->invokeSafe(code.rawData(), 0, 0); failIfUnterminatedProcesses = shellMainScope->getBoolean("__returnValue"); if (failIfUnterminatedProcesses) { severe() << "exiting with a failure due to unterminated processes, " "a call to MongoRunner.stopMongod(), ReplSetTest#stopSet(), or " "ShardingTest#stop() may be missing from the test"; error() << "exiting with code " << static_cast(kUnterminatedProcess); return kUnterminatedProcess; } } } if (shellGlobalParams.files.size() == 0 && shellGlobalParams.script.empty()) shellGlobalParams.runShell = true; bool lastLineSuccessful = true; if (shellGlobalParams.runShell) { mongo::shell_utils::MongoProgramScope s; // If they specify norc, assume it's not their first time bool hasMongoRC = shellGlobalParams.norc; string rcLocation; if (!shellGlobalParams.norc) { #ifndef _WIN32 if (getenv("HOME") != NULL) rcLocation = str::stream() << getenv("HOME") << "/.mongorc.js"; #else if (getenv("HOMEDRIVE") != NULL && getenv("HOMEPATH") != NULL) rcLocation = str::stream() << toUtf8String(_wgetenv(L"HOMEDRIVE")) << toUtf8String(_wgetenv(L"HOMEPATH")) << "\\.mongorc.js"; #endif if (!rcLocation.empty() && ::mongo::shell_utils::fileExists(rcLocation)) { hasMongoRC = true; if (!scope->execFile(rcLocation, false, true)) { severe() << "The \".mongorc.js\" file located in your home folder could not be " "executed"; error() << "exiting with code " << static_cast(kMongorcError); return kMongorcError; } } } if (!hasMongoRC && isatty(fileno(stdin))) { cout << "Welcome to the MongoDB shell.\n" "For interactive help, type \"help\".\n" "For more comprehensive documentation, see\n\thttp://docs.mongodb.org/\n" "Questions? Try the support group\n\thttp://groups.google.com/group/mongodb-user" << endl; File f; f.open(rcLocation.c_str(), false); // Create empty .mongorc.js file } if (!shellGlobalParams.nodb && !mongo::serverGlobalParams.quiet.load() && isatty(fileno(stdin))) { scope->exec( "shellHelper( 'show', 'startupWarnings' )", "(shellwarnings)", false, true, false); scope->exec( "shellHelper( 'show', 'freeMonitoring' )", "(freeMonitoring)", false, true, false); scope->exec("shellHelper( 'show', 'automationNotices' )", "(automationnotices)", false, true, false); } shellHistoryInit(); string prompt; int promptType; while (1) { inMultiLine = false; gotInterrupted = false; promptType = scope->type("prompt"); if (promptType == String) { prompt = scope->getString("prompt"); } else if ((promptType == Code) && execPrompt(*scope, "prompt", prompt)) { } else if (execPrompt(*scope, "defaultPrompt", prompt)) { } else { prompt = "> "; } char* line = shellReadline(prompt.c_str()); char* linePtr = line; // can't clobber 'line', we need to free() it later if (linePtr) { while (linePtr[0] == ' ') ++linePtr; int lineLen = strlen(linePtr); while (lineLen > 0 && linePtr[lineLen - 1] == ' ') linePtr[--lineLen] = 0; } if (!linePtr || (strlen(linePtr) == 4 && strstr(linePtr, "exit"))) { if (!mongo::serverGlobalParams.quiet.load()) cout << "bye" << endl; if (line) free(line); break; } string code = linePtr; if (code == "exit" || code == "exit;") { free(line); break; } if (code == "cls") { free(line); linenoiseClearScreen(); continue; } if (code.size() == 0) { free(line); continue; } if (str::startsWith(linePtr, "edit ")) { shellHistoryAdd(linePtr); const char* s = linePtr + 5; // skip "edit " while (*s && isspace(*s)) s++; edit(s); free(line); continue; } gotInterrupted = false; code = finishCode(code); if (gotInterrupted) { cout << endl; free(line); continue; } if (code.size() == 0) { free(line); break; } bool wascmd = false; { string cmd = linePtr; string::size_type firstSpace; if ((firstSpace = cmd.find(" ")) != string::npos) cmd = cmd.substr(0, firstSpace); if (cmd.find("\"") == string::npos) { try { lastLineSuccessful = scope->exec((string) "__iscmd__ = shellHelper[\"" + cmd + "\"];", "(shellhelp1)", false, true, true); if (scope->getBoolean("__iscmd__")) { lastLineSuccessful = 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; lastLineSuccessful = false; } } } if (!wascmd) { try { lastLineSuccessful = scope->exec(code.c_str(), "(shell)", false, true, false); if (lastLineSuccessful) { scope->exec( "shellPrintHelper( __lastres__ );", "(shell2)", true, true, false); } } catch (std::exception& e) { cout << "error:" << e.what() << endl; lastLineSuccessful = false; } } shellHistoryAdd(code.c_str()); free(line); } shellHistoryDone(); } return (lastLineSuccessful ? 0 : 1); } #ifdef _WIN32 int wmain(int argc, wchar_t* argvW[], wchar_t* envpW[]) { int returnCode; try { WindowsCommandLine wcl(argc, argvW, envpW); returnCode = _main(argc, wcl.argv(), wcl.envp()); } catch (mongo::DBException& e) { severe() << "exception: " << e.what(); error() << "exiting with code " << static_cast(kDBException); returnCode = kDBException; } quickExit(returnCode); } #else // #ifdef _WIN32 int main(int argc, char* argv[], char** envp) { int returnCode; try { returnCode = _main(argc, argv, envp); } catch (mongo::DBException& e) { severe() << "exception: " << e.what(); error() << "exiting with code " << static_cast(kDBException); returnCode = kDBException; } quickExit(returnCode); } #endif // #ifdef _WIN32