summaryrefslogtreecommitdiff
path: root/src/mongo/util/ntservice.cpp
diff options
context:
space:
mode:
authorAntonin Kral <a.kral@bobek.cz>2012-08-29 20:54:51 +0200
committerAntonin Kral <a.kral@bobek.cz>2012-08-29 20:54:51 +0200
commit83957b73f9177f6e38bd5375bd93ca1f6a47188c (patch)
treef20b7d6ac9a9c64ff5bb6b5910a24abbb356b1d5 /src/mongo/util/ntservice.cpp
parent5071d203970edd4c995493d810abe20987e76fe9 (diff)
Imported Upstream version 2.2.0upstream/2.2.0
Diffstat (limited to 'src/mongo/util/ntservice.cpp')
-rw-r--r--src/mongo/util/ntservice.cpp497
1 files changed, 497 insertions, 0 deletions
diff --git a/src/mongo/util/ntservice.cpp b/src/mongo/util/ntservice.cpp
new file mode 100644
index 00000000000..92dfa7d8990
--- /dev/null
+++ b/src/mongo/util/ntservice.cpp
@@ -0,0 +1,497 @@
+// ntservice.cpp
+
+/* Copyright 2009 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 "ntservice.h"
+#include "../db/client.h"
+#include "../db/instance.h"
+#include "winutil.h"
+#include "text.h"
+
+#if defined(_WIN32)
+
+using std::wstring;
+
+namespace mongo {
+
+ SERVICE_STATUS_HANDLE ServiceController::_statusHandle = NULL;
+ wstring ServiceController::_serviceName;
+ ServiceCallback ServiceController::_serviceCallback = NULL;
+
+ ServiceController::ServiceController() {}
+
+ // defined in db/db.cpp for mongod.exe and in s/server.cpp for mongos.exe
+ extern bool initService();
+
+ // returns true if the service is started.
+ bool serviceParamsCheck(
+ boost::program_options::variables_map& params,
+ const std::string dbpath,
+ const ntServiceDefaultStrings& defaultStrings,
+ const vector<string>& disallowedOptions,
+ int argc,
+ char* argv[]
+ ) {
+ bool installService = false;
+ bool removeService = false;
+ bool reinstallService = false;
+ bool startService = false;
+
+ int badOption = -1;
+ for ( int i = 0, disallowedListLength = disallowedOptions.size(); i < disallowedListLength; ++i ) {
+ if ( params.count( disallowedOptions[i] ) > 0 ) {
+ badOption = i;
+ break;
+ }
+ }
+
+ wstring windowsServiceName( defaultStrings.serviceName );
+ wstring windowsServiceDisplayName( defaultStrings.displayName );
+ wstring windowsServiceDescription( defaultStrings.serviceDescription );
+ wstring windowsServiceUser;
+ wstring windowsServicePassword;
+
+ if (params.count("install")) {
+ if ( badOption != -1 ) {
+ log() << "--install cannot be used with --" << disallowedOptions[badOption] << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ if ( ! params.count( "logpath" ) ) {
+ log() << "--install has to be used with --logpath" << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ installService = true;
+ }
+ if (params.count("reinstall")) {
+ if ( badOption != -1 ) {
+ log() << "--reinstall cannot be used with --" << disallowedOptions[badOption] << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ if ( ! params.count( "logpath" ) ) {
+ log() << "--reinstall has to be used with --logpath" << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ reinstallService = true;
+ }
+ if (params.count("remove")) {
+ if ( badOption != -1 ) {
+ log() << "--remove cannot be used with --" << disallowedOptions[badOption] << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ removeService = true;
+ }
+ if (params.count("service")) {
+ if ( badOption != -1 ) {
+ log() << "--service cannot be used with --" << disallowedOptions[badOption] << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ startService = true;
+ }
+
+ if (params.count("serviceName")) {
+ if ( badOption != -1 ) {
+ log() << "--serviceName cannot be used with --" << disallowedOptions[badOption] << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ windowsServiceName = toWideString( params[ "serviceName" ].as<string>().c_str() );
+ }
+ if (params.count("serviceDisplayName")) {
+ if ( badOption != -1 ) {
+ log() << "--serviceDisplayName cannot be used with --" << disallowedOptions[badOption] << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ windowsServiceDisplayName = toWideString( params[ "serviceDisplayName" ].as<string>().c_str() );
+ }
+ if (params.count("serviceDescription")) {
+ if ( badOption != -1 ) {
+ log() << "--serviceDescription cannot be used with --" << disallowedOptions[badOption] << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ windowsServiceDescription = toWideString( params[ "serviceDescription" ].as<string>().c_str() );
+ }
+ if (params.count("serviceUser")) {
+ if ( badOption != -1 ) {
+ log() << "--serviceUser cannot be used with --" << disallowedOptions[badOption] << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ windowsServiceUser = toWideString( params[ "serviceUser" ].as<string>().c_str() );
+ }
+ if (params.count("servicePassword")) {
+ if ( badOption != -1 ) {
+ log() << "--servicePassword cannot be used with --" << disallowedOptions[badOption] << endl;
+ ::_exit( EXIT_BADOPTIONS );
+ }
+ windowsServicePassword = toWideString( params[ "servicePassword" ].as<string>().c_str() );
+ }
+
+ if ( reinstallService ) {
+ ServiceController::removeService( windowsServiceName );
+ }
+ if ( installService || reinstallService ) {
+ if ( !ServiceController::installService(
+ windowsServiceName,
+ windowsServiceDisplayName,
+ windowsServiceDescription,
+ windowsServiceUser,
+ windowsServicePassword,
+ dbpath,
+ argc,
+ argv )
+ ) {
+ ::_exit( EXIT_NTSERVICE_ERROR );
+ }
+ ::_exit( EXIT_CLEAN );
+ }
+ else if ( removeService ) {
+ if ( !ServiceController::removeService( windowsServiceName ) ) {
+ ::_exit( EXIT_NTSERVICE_ERROR );
+ }
+ ::_exit( EXIT_CLEAN );
+ }
+ else if ( startService ) {
+ if ( !ServiceController::startService( windowsServiceName , mongo::initService ) ) {
+ ::_exit( EXIT_NTSERVICE_ERROR );
+ }
+ return true;
+ }
+ return false;
+ }
+
+ bool ServiceController::installService(
+ const wstring& serviceName,
+ const wstring& displayName,
+ const wstring& serviceDesc,
+ const wstring& serviceUser,
+ const wstring& servicePassword,
+ const std::string dbpath,
+ int argc,
+ char* argv[]
+ ) {
+ log() << "Trying to install Windows service '" << toUtf8String(serviceName) << "'" << endl;
+
+ stringstream commandLine;
+
+ 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.
+
+ for ( int 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 << " \"" << dbpath << "\" ";
+ 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 << " ";
+ }
+
+ SC_HANDLE schSCManager = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
+ if ( schSCManager == NULL ) {
+ DWORD err = ::GetLastError();
+ log() << "Error connecting to the Service Control Manager: " << GetWinErrMsg(err) << endl;
+ return false;
+ }
+
+ // Make sure service doesn't already exist.
+ // TODO: Check to see if service is in "Deleting" status, suggest the user close down Services MMC snap-ins.
+ SC_HANDLE schService = ::OpenService( schSCManager, serviceName.c_str(), SERVICE_ALL_ACCESS );
+ if ( schService != NULL ) {
+ log() << "There is already a service named '" << toUtf8String(serviceName) << "', aborting" << endl;
+ ::CloseServiceHandle( schService );
+ ::CloseServiceHandle( schSCManager );
+ return false;
+ }
+ std::basic_ostringstream< TCHAR > commandLineWide;
+ commandLineWide << commandLine.str().c_str();
+
+ // create new service
+ schService = ::CreateService(
+ schSCManager, // Service Control Manager handle
+ serviceName.c_str(), // service name
+ displayName.c_str(), // service display name
+ SERVICE_ALL_ACCESS, // desired access
+ SERVICE_WIN32_OWN_PROCESS, // service type
+ SERVICE_AUTO_START, // start type
+ SERVICE_ERROR_NORMAL, // error control
+ commandLineWide.str().c_str(), // command line
+ NULL, // load order group
+ NULL, // tag id
+ L"\0\0", // dependencies
+ NULL, // user account
+ NULL ); // user account password
+ if ( schService == NULL ) {
+ DWORD err = ::GetLastError();
+ log() << "Error creating service: " << GetWinErrMsg(err) << endl;
+ ::CloseServiceHandle( schSCManager );
+ return false;
+ }
+
+ log() << "Service '" << toUtf8String(serviceName) << "' (" << toUtf8String(displayName) <<
+ ") installed with command line '" << commandLine.str() << "'" << endl;
+ string typeableName( ( serviceName.find(L' ') != wstring::npos ) ?
+ "\"" + toUtf8String(serviceName) + "\"" :
+ toUtf8String(serviceName) );
+ log() << "Service can be started from the command line with 'net start " << typeableName << "'" << endl;
+
+ bool serviceInstalled;
+
+ // TODO: If neccessary grant user "Login as a Service" permission.
+ if ( !serviceUser.empty() ) {
+ wstring actualServiceUser;
+ if ( serviceUser.find(L"\\") == string::npos ) {
+ actualServiceUser = L".\\" + serviceUser;
+ }
+ else {
+ actualServiceUser = serviceUser;
+ }
+
+ log() << "Setting service login credentials for user: " << toUtf8String(actualServiceUser) << endl;
+ serviceInstalled = ::ChangeServiceConfig(
+ schService, // service handle
+ SERVICE_NO_CHANGE, // service type
+ SERVICE_NO_CHANGE, // start type
+ SERVICE_NO_CHANGE, // error control
+ NULL, // path
+ NULL, // load order group
+ NULL, // tag id
+ NULL, // dependencies
+ actualServiceUser.c_str(), // user account
+ servicePassword.c_str(), // user account password
+ NULL ); // service display name
+ if ( !serviceInstalled ) {
+ log() << "Setting service login failed, service has 'LocalService' permissions" << endl;
+ }
+ }
+
+ // set the service description
+ SERVICE_DESCRIPTION serviceDescription;
+ serviceDescription.lpDescription = (LPTSTR)serviceDesc.c_str();
+ serviceInstalled = ::ChangeServiceConfig2( schService, SERVICE_CONFIG_DESCRIPTION, &serviceDescription );
+
+#if 1
+ if ( ! serviceInstalled ) {
+#else
+ // This code sets the mongod service to auto-restart, forever.
+ // This might be a fine thing to do except that when mongod or Windows has a crash, the mongo.lock
+ // file is still around, so any attempt at a restart will immediately fail. With auto-restart, we
+ // go into a loop, crashing and restarting, crashing and restarting, until someone comes in and
+ // disables the service or deletes the mongod.lock file.
+ //
+ // I'm leaving the old code here for now in case we solve this and are able to turn SC_ACTION_RESTART
+ // back on.
+ //
+ if ( serviceInstalled ) {
+ SC_ACTION aActions[ 3 ] = { { SC_ACTION_RESTART, 0 }, { SC_ACTION_RESTART, 0 }, { SC_ACTION_RESTART, 0 } };
+
+ SERVICE_FAILURE_ACTIONS serviceFailure;
+ ZeroMemory( &serviceFailure, sizeof( SERVICE_FAILURE_ACTIONS ) );
+ serviceFailure.cActions = 3;
+ serviceFailure.lpsaActions = aActions;
+
+ // set service recovery options
+ serviceInstalled = ::ChangeServiceConfig2( schService, SERVICE_CONFIG_FAILURE_ACTIONS, &serviceFailure );
+
+ }
+ else {
+#endif
+ log() << "Could not set service description. Check the Windows Event Log for more details." << endl;
+ }
+
+ ::CloseServiceHandle( schService );
+ ::CloseServiceHandle( schSCManager );
+
+ return serviceInstalled;
+ }
+
+ bool ServiceController::removeService( const wstring& serviceName ) {
+ log() << "Trying to remove Windows service '" << toUtf8String(serviceName) << "'" << endl;
+
+ SC_HANDLE schSCManager = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
+ if ( schSCManager == NULL ) {
+ DWORD err = ::GetLastError();
+ log() << "Error connecting to the Service Control Manager: " << GetWinErrMsg(err) << endl;
+ return false;
+ }
+
+ SC_HANDLE schService = ::OpenService( schSCManager, serviceName.c_str(), SERVICE_ALL_ACCESS );
+ if ( schService == NULL ) {
+ log() << "Could not find a service named '" << toUtf8String(serviceName) << "' to remove" << endl;
+ ::CloseServiceHandle( schSCManager );
+ return false;
+ }
+
+ SERVICE_STATUS serviceStatus;
+
+ // stop service if its running
+ if ( ::ControlService( schService, SERVICE_CONTROL_STOP, &serviceStatus ) ) {
+ log() << "Service " << toUtf8String(serviceName) << " is currently running, stopping service" << endl;
+ while ( ::QueryServiceStatus( schService, &serviceStatus ) ) {
+ if ( serviceStatus.dwCurrentState == SERVICE_STOP_PENDING ) {
+ Sleep( 1000 );
+ }
+ else { break; }
+ }
+ log() << "Service '" << toUtf8String(serviceName) << "' stopped" << endl;
+ }
+
+ bool serviceRemoved = ::DeleteService( schService );
+
+ ::CloseServiceHandle( schService );
+ ::CloseServiceHandle( schSCManager );
+
+ if (serviceRemoved) {
+ log() << "Service '" << toUtf8String(serviceName) << "' removed" << endl;
+ }
+ else {
+ log() << "Failed to remove service '" << toUtf8String(serviceName) << "'" << endl;
+ }
+
+ return serviceRemoved;
+ }
+
+ bool ServiceController::startService( const wstring& serviceName, ServiceCallback startService ) {
+ _serviceName = serviceName;
+ _serviceCallback = startService;
+
+ SERVICE_TABLE_ENTRY dispTable[] = {
+ { (LPTSTR)serviceName.c_str(), (LPSERVICE_MAIN_FUNCTION)ServiceController::initService },
+ { NULL, NULL }
+ };
+
+ log() << "Trying to start Windows service '" << toUtf8String(serviceName) << "'" << endl;
+ return StartServiceCtrlDispatcher( dispTable );
+ }
+
+ bool ServiceController::reportStatus( DWORD reportState, DWORD waitHint ) {
+ if ( _statusHandle == NULL )
+ return false;
+
+ static DWORD checkPoint = 1;
+
+ SERVICE_STATUS ssStatus;
+
+ DWORD dwControlsAccepted;
+ switch ( reportState ) {
+ case SERVICE_START_PENDING:
+ case SERVICE_STOP_PENDING:
+ case SERVICE_STOPPED:
+ dwControlsAccepted = 0;
+ break;
+ default:
+ dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+ break;
+ }
+
+ ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ ssStatus.dwServiceSpecificExitCode = 0;
+ ssStatus.dwControlsAccepted = dwControlsAccepted;
+ ssStatus.dwCurrentState = reportState;
+ ssStatus.dwWin32ExitCode = NO_ERROR;
+ ssStatus.dwWaitHint = waitHint;
+ ssStatus.dwCheckPoint = ( reportState == SERVICE_RUNNING || reportState == SERVICE_STOPPED ) ? 0 : checkPoint++;
+
+ return SetServiceStatus( _statusHandle, &ssStatus );
+ }
+
+ void WINAPI ServiceController::initService( DWORD argc, LPTSTR *argv ) {
+ _statusHandle = RegisterServiceCtrlHandler( _serviceName.c_str(), serviceCtrl );
+ if ( !_statusHandle )
+ return;
+
+ reportStatus( SERVICE_START_PENDING, 1000 );
+
+ _serviceCallback();
+ reportStatus( SERVICE_STOPPED );
+ ::_exit( EXIT_CLEAN );
+ }
+
+ static void serviceShutdown( const char* controlCodeName ) {
+ Client::initThread( "serviceShutdown" );
+ log() << "got " << controlCodeName << " request from Windows Service Control Manager, " <<
+ ( inShutdown() ? "already in shutdown" : "will terminate after current cmd ends" ) << endl;
+ ServiceController::reportStatus( SERVICE_STOP_PENDING );
+ if ( ! inShutdown() ) {
+ // TODO: SERVER-5703, separate the "cleanup for shutdown" functionality from
+ // the "terminate process" functionality in exitCleanly.
+ exitCleanly( EXIT_WINDOWS_SERVICE_STOP );
+ ServiceController::reportStatus( SERVICE_STOPPED );
+ }
+ }
+
+ void WINAPI ServiceController::serviceCtrl( DWORD ctrlCode ) {
+ switch ( ctrlCode ) {
+ case SERVICE_CONTROL_STOP:
+ serviceShutdown( "SERVICE_CONTROL_STOP" );
+ break;
+ case SERVICE_CONTROL_SHUTDOWN:
+ serviceShutdown( "SERVICE_CONTROL_SHUTDOWN" );
+ break;
+ }
+ }
+
+} // namespace mongo
+
+#endif