Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

SERVER-7252 Produce correct commandline for Windows service for --ins…

…tall, --reinstall or --service.

The service command line is derived from the mongod/mongos command line when Mongo is started with
--install, --reinstall and --service.  It does this by stripping out options that describe how to
install the service, and correctly quoting the arguments.  This patch makes this procedure more explicit,
and handles the equals sign and arguments with spaces and backslashes in the process.
  • Loading branch information...
commit c24aa0214b18d875236ff8f31e7bfe54fb537f61 1 parent 1e7a83f
@andy10gen andy10gen authored
View
6 src/mongo/SConscript
@@ -264,7 +264,11 @@ coreServerFiles = [ "db/common.cpp",
"db/stats/service_stats.cpp",
]
-env.StaticLibrary('ntservice', ['util/ntservice.cpp'])
+env.StaticLibrary('ntservice', ['util/ntservice.cpp'], LIBDEPS=['foundation'])
+if windows:
+ env.CppUnitTest('ntservice_test', 'util/ntservice_test.cpp',
+ LIBDEPS=['ntservice'],
+ LIBS=['shell32', env['LIBS']])
scripting_common_files = [ "scripting/engine.cpp",
"scripting/utils.cpp",
View
140 src/mongo/util/ntservice.cpp
@@ -174,6 +174,71 @@ namespace {
}
}
+ // This implementation assumes that inputArgv was a valid argv to mongod. That is, it assumes
+ // that options that take arguments received them, and options that do not take arguments did
+ // not.
+ std::vector<std::string> constructServiceArgv(const std::vector<std::string>& inputArgv) {
+
+ static const char*const optionsWithoutArgumentsToStrip[] = {
+ "-install", "--install",
+ "-reinstall", "--reinstall",
+ "-service", "--service"
+ };
+
+ // Pointer to just past the end of optionsWithoutArgumentsToStrip, for use as an "end"
+ // iterator.
+ static const char*const *const optionsWithoutArgumentsToStripEnd =
+ optionsWithoutArgumentsToStrip + boost::size(optionsWithoutArgumentsToStrip);
+
+ static const char*const optionsWithArgumentsToStrip[] = {
+ "-serviceName", "--serviceName",
+ "-serviceUser", "--serviceUser",
+ "-servicePassword", "--servicePassword",
+ "-serviceDescription", "--serviceDescription",
+ "-serviceDisplayName", "--serviceDisplayName"
+ };
+
+ // Pointer to just past the end of optionsWithArgumentsToStrip, for use as an "end"
+ // iterator.
+ static const char*const *const optionsWithArgumentsToStripEnd =
+ optionsWithArgumentsToStrip + boost::size(optionsWithArgumentsToStrip);
+
+ std::vector<std::string> result;
+ for (std::vector<std::string>::const_iterator iter = inputArgv.begin(),
+ end = inputArgv.end(); iter != end; ++iter) {
+
+ if (optionsWithoutArgumentsToStripEnd != std::find(optionsWithoutArgumentsToStrip,
+ optionsWithoutArgumentsToStripEnd,
+ *iter)) {
+ // The current element of inputArgv is an option that we wish to strip, that takes
+ // no arguments. Skip adding it to "result".
+ continue;
+ }
+
+ std::string name;
+ std::string value;
+ bool foundEqualSign = mongoutils::str::splitOn(*iter, '=', name, value);
+ if (!foundEqualSign)
+ name = *iter;
+ if (optionsWithArgumentsToStripEnd != std::find(optionsWithArgumentsToStrip,
+ optionsWithArgumentsToStripEnd,
+ name)) {
+ // The current element, and maybe the next one, form an option and its argument.
+ // Skip adding them to "result".
+ if (!foundEqualSign) {
+ // The next argv value must be the argument to the parameter, so strip it.
+ ++iter;
+ }
+ continue;
+ }
+
+ result.push_back(*iter);
+ }
+
+ result.push_back("--service"); // Service command lines all contain "--service".
+ return result;
+ }
+
void installServiceOrDie(
const wstring& serviceName,
const wstring& displayName,
@@ -184,71 +249,13 @@ namespace {
) {
log() << "Trying to install Windows service '" << toUtf8String(serviceName) << "'" << endl;
- stringstream commandLine;
+ std::vector<std::string> serviceArgv = constructServiceArgv(argv);
char exePath[1024];
GetModuleFileNameA( NULL, exePath, sizeof exePath );
- commandLine << '"' << exePath << "\" ";
-
- // because we use allow_long_disguise in our style for boost::program_options
- // parsing (to make -vvvvv work) we will accept "-install" and "--install" and
- // likewise for all options. this means that when parsing option-by-option as
- // we do here, we need to handle both "-" and "--" prefixes.
-
- const size_t argc = argv.size();
- for ( size_t i = 1; i < argc; i++ ) {
- std::string arg(argv[i]);
- // replace install command to indicate process is being started as a service
- if ( arg == "-install" || arg == "--install" || arg == "-reinstall" || arg == "--reinstall" ) {
- arg = "--service";
- }
- else if ( (arg == "-dbpath" || arg == "--dbpath") && i + 1 < argc ) {
- commandLine << arg << " \"" << argv[i+1] << "\" ";
- i++;
- continue;
- }
- else if ( (arg == "-logpath" || arg == "--logpath") && i + 1 < argc ) {
- commandLine << arg << " \"" << argv[i+1] << "\" ";
- i++;
- continue;
- }
- else if ( arg == "-f" && i + 1 < argc ) {
- commandLine << arg << " \"" << argv[i+1] << "\" ";
- i++;
- continue;
- }
- else if ( (arg == "-config" || arg == "--config") && i + 1 < argc ) {
- commandLine << arg << " \"" << argv[i+1] << "\" ";
- i++;
- continue;
- }
- else if ( (arg == "-pidfilepath" || arg == "--pidfilepath") && i + 1 < argc ) {
- commandLine << arg << " \"" << argv[i+1] << "\" ";
- i++;
- continue;
- }
- else if ( (arg == "-repairpath" || arg == "--repairpath") && i + 1 < argc ) {
- commandLine << arg << " \"" << argv[i+1] << "\" ";
- i++;
- continue;
- }
- else if ( (arg == "-keyfile" || arg == "--keyfile") && i + 1 < argc ) {
- commandLine << arg << " \"" << argv[i+1] << "\" ";
- i++;
- continue;
- }
- else if ( arg.length() > 8 && arg.substr(0, 8) == "-service" ) {
- // Strip off --service(Name|User|Password) arguments
- i++;
- continue;
- }
- else if ( arg.length() > 9 && arg.substr(0, 9) == "--service" ) {
- // Strip off --service(Name|User|Password) arguments
- i++;
- continue;
- }
- commandLine << arg << " ";
- }
+ serviceArgv.at(0) = exePath;
+
+ std::string commandLine = constructUtf8WindowsCommandLine(serviceArgv);
SC_HANDLE schSCManager = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
if ( schSCManager == NULL ) {
@@ -266,11 +273,10 @@ namespace {
::CloseServiceHandle( schSCManager );
::_exit(EXIT_NTSERVICE_ERROR);
}
- std::basic_ostringstream< TCHAR > commandLineWide;
- commandLineWide << commandLine.str().c_str();
+ std::wstring commandLineWide = toWideString(commandLine.c_str());
// create new service
- schService = ::CreateService(
+ schService = ::CreateServiceW(
schSCManager, // Service Control Manager handle
serviceName.c_str(), // service name
displayName.c_str(), // service display name
@@ -278,7 +284,7 @@ namespace {
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // start type
SERVICE_ERROR_NORMAL, // error control
- commandLineWide.str().c_str(), // command line
+ commandLineWide.c_str(), // command line
NULL, // load order group
NULL, // tag id
L"\0\0", // dependencies
@@ -292,7 +298,7 @@ namespace {
}
log() << "Service '" << toUtf8String(serviceName) << "' (" << toUtf8String(displayName) <<
- ") installed with command line '" << commandLine.str() << "'" << endl;
+ ") installed with command line '" << commandLine << "'" << endl;
string typeableName( ( serviceName.find(L' ') != wstring::npos ) ?
"\"" + toUtf8String(serviceName) + "\"" :
toUtf8String(serviceName) );
View
10 src/mongo/util/ntservice.h
@@ -63,6 +63,16 @@ namespace ntservice {
bool shouldStartService();
/**
+ * Construct an argv array that Windows should use to start mongod/mongos as a service
+ * if mongo was started with "inputArgv", which is assumed to be an argument vector that
+ * dictates that Windows should install mongo as a service.
+ *
+ * The result is suitable for passing to mongo::constructUtf8WindowsCommandLine() to construct
+ * a properly quoted command line string.
+ */
+ std::vector<std::string> constructServiceArgv(const std::vector<std::string>& inputArgv);
+
+ /**
* Start the service. Never returns.
*/
MONGO_COMPILER_NORETURN void startService();
View
110 src/mongo/util/ntservice_test.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2012 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 "mongo/platform/basic.h"
+
+#include <cstdarg>
+#include <cstdlib>
+#include <shellapi.h>
+#include <string>
+#include <vector>
+
+
+#include "mongo/db/client.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/ntservice.h"
+#include "mongo/util/text.h"
+
+using namespace mongo;
+
+static std::vector<std::string> svec(const char* first, ...) {
+ std::vector<std::string> result;
+ if (first) {
+ result.push_back(first);
+ va_list ap;
+ va_start(ap, first);
+ const char* curr;
+ while (NULL != (curr = va_arg(ap, const char*))) {
+ result.push_back(curr);
+ }
+ va_end(ap);
+ }
+ return result;
+}
+
+TEST(NtService, ConstructServiceCommandLine) {
+ ASSERT_TRUE(svec("--dbpath=C:\\Data\\",
+ "-logpath",
+ "C:\\Program Files (x86)\\MongoDB\\Logs\\MongoDB.log",
+ "--service",
+ NULL) ==
+ ntservice::constructServiceArgv(
+ svec("-service", "--service",
+ "--dbpath=C:\\Data\\",
+ "--install", "-install",
+ "--reinstall", "-reinstall",
+ "--servicePassword==a\\b\\",
+ "--servicePassword", "=a\\b\\",
+ "--serviceUser", "andy",
+ "--serviceName", "MongoDB",
+ "-servicePassword==a\\b\\",
+ "-servicePassword", "=a\\b\\",
+ "-serviceUser", "andy",
+ "-serviceName", "MongoDB",
+ "-logpath",
+ "C:\\Program Files (x86)\\MongoDB\\Logs\\MongoDB.log",
+ NULL)));
+}
+
+TEST(NtService, RegressionSERVER_7252) {
+ // Test that we generate a correct service command line from the literal command line supplied
+ // in ticket SERVER-7252.
+
+ const wchar_t inputCommandLine[] =
+ L"mongod --install --serviceName=\"My Service\" --serviceDescription \"My Service\" "
+ L"--serviceDisplayName \"My Service\" --dbpath C:\\mongo\\data\\config --port 20001 "
+ L"--logpath C:\\mongo\\logs\\mongo_config.log.txt --configsvr";
+
+ const char expectedServiceCommandLine[] =
+ "mongod --dbpath C:\\mongo\\data\\config --port 20001 "
+ "--logpath C:\\mongo\\logs\\mongo_config.log.txt --configsvr --service";
+
+ // Convert the input wide-character command line into a UTF-8 vector of std::string.
+ int inputArgc;
+ LPWSTR* inputArgvWide = CommandLineToArgvW(inputCommandLine, &inputArgc);
+ ASSERT_TRUE(NULL != inputArgvWide);
+ ASSERT_GREATER_THAN_OR_EQUALS(inputArgc, 0);
+ std::vector<std::string> inputArgvUtf8(inputArgc);
+ ASSERT_TRUE(inputArgvUtf8.end() == std::transform(inputArgvWide,
+ inputArgvWide + inputArgc,
+ inputArgvUtf8.begin(),
+ toUtf8String));
+ LocalFree(inputArgvWide);
+
+ // Finally, confirm that we properly transform the argument vector and from it construct a legit
+ // service command line.
+ ASSERT_EQUALS(expectedServiceCommandLine,
+ constructUtf8WindowsCommandLine(ntservice::constructServiceArgv(inputArgvUtf8)));
+}
+
+// CRUTCHES!
+namespace mongo {
+ enum ExitCode;
+ void exitCleanly(ExitCode ignored) { std::abort(); }
+ Client& Client::initThread(const char* desc, AbstractMessagingPort* mp) {
+ std::abort(); return *reinterpret_cast<Client*>(NULL);
+ }
+} // namespace mongo
Please sign in to comment.
Something went wrong with that request. Please try again.