mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 10:56:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			393 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			393 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (C) 2020 MariaDB Corporation
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    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 General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
 | |
| */
 | |
| 
 | |
| /*
 | |
|   main() function for the server on Windows is implemented here.
 | |
|   The core functionality is implemented elsewhere, in mysqld_main(), and running as
 | |
|   service is done here.
 | |
| 
 | |
|   Main tasks of the service are
 | |
| 
 | |
|   1. Report current status back to service control manager. Here we're
 | |
|   providing callbacks so code outside of winmain.cc can call it
 | |
|   (via mysqld_set_service_status_callback())
 | |
| 
 | |
|   2. React to notification, the only one we care about is the "stop"
 | |
|   notification. we initiate shutdown, when instructed.
 | |
| 
 | |
|   Note that our service might not be too Windows-friendly, as it might take
 | |
|   a while to startup (recovery), and a while to shut down(innodb cleanups).
 | |
| 
 | |
|   Most of the code more of less standard service stuff, taken from Microsoft
 | |
|   docs examples.
 | |
| 
 | |
|   Notable oddity in running services, is that we do not know for sure,
 | |
|   whether we should run as a service or not (there is no --service parameter that
 | |
|   would tell).Heuristics are used, and if the last command line argument is
 | |
|   valid service name, we try to run as service, but fallback to usual process
 | |
|   if this fails.
 | |
| 
 | |
|   As an example, even if mysqld.exe is started  with command line like "mysqld.exe --help",
 | |
|   it is entirely possible that mysqld.exe run as service "--help".
 | |
| 
 | |
|   Apart from that, now deprecated and obsolete service registration/removal functionality is
 | |
|   still provided (mysqld.exe --install/--remove)
 | |
| */
 | |
| 
 | |
| #include <my_global.h>
 | |
| #include <mysqld.h>
 | |
| #include <log.h>
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <windows.h>
 | |
| #include <string>
 | |
| #include <cassert>
 | |
| #include <winservice.h>
 | |
| 
 | |
| static SERVICE_STATUS svc_status{SERVICE_WIN32_OWN_PROCESS};
 | |
| static SERVICE_STATUS_HANDLE svc_status_handle;
 | |
| static char *svc_name;
 | |
| 
 | |
| static char **save_argv;
 | |
| static int save_argc;
 | |
| 
 | |
| static int install_service(int argc, char **argv, const char *name);
 | |
| static int remove_service(const char *name);
 | |
| 
 | |
| /*
 | |
|   Report service status to SCM. This function is indirectly invoked
 | |
|   by the server to report state transitions.
 | |
| 
 | |
|   1. from START_PENDING to SERVICE_RUNNING, when we start accepting user connections
 | |
|   2. from SERVICE_RUNNING to STOP_PENDING, when we start shutdown
 | |
|   3. from STOP_PENDING to SERVICE_STOPPED, in mysqld_exit()
 | |
|      sometimes also START_PENDING to SERVICE_STOPPED, on startup errors
 | |
| */
 | |
| static void report_svc_status(DWORD current_state, DWORD exit_code, DWORD wait_hint)
 | |
| {
 | |
|   if (!svc_status_handle)
 | |
|     return;
 | |
| 
 | |
|   static DWORD check_point= 1;
 | |
|   if (current_state != (DWORD)-1)
 | |
|     svc_status.dwCurrentState= current_state;
 | |
|   svc_status.dwWaitHint= wait_hint;
 | |
| 
 | |
|   if (exit_code)
 | |
|   {
 | |
|     svc_status.dwWin32ExitCode= ERROR_SERVICE_SPECIFIC_ERROR;
 | |
|     svc_status.dwServiceSpecificExitCode= exit_code;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     svc_status.dwWin32ExitCode= 0;
 | |
|   }
 | |
| 
 | |
|   if (current_state == SERVICE_START_PENDING)
 | |
|     svc_status.dwControlsAccepted= 0;
 | |
|   else
 | |
|     svc_status.dwControlsAccepted= SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN;
 | |
| 
 | |
|   if ((current_state == SERVICE_RUNNING) || (current_state == SERVICE_STOPPED))
 | |
|     svc_status.dwCheckPoint= 0;
 | |
|   else
 | |
|     svc_status.dwCheckPoint= check_point++;
 | |
| 
 | |
|   SetServiceStatus(svc_status_handle, &svc_status);
 | |
| }
 | |
| 
 | |
| /* Report unexpected errors. */
 | |
| static void svc_report_event(const char *svc_name, const char *command)
 | |
| {
 | |
|   char buffer[80];
 | |
|   sprintf_s(buffer, "mariadb service %s, %s failed with %d",
 | |
|       svc_name, command, GetLastError());
 | |
|   OutputDebugString(buffer);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Service control function.
 | |
|   Reacts to service stop, initiates shutdown.
 | |
| */
 | |
| static void WINAPI svc_ctrl_handle(DWORD cntrl)
 | |
| {
 | |
|   switch (cntrl)
 | |
|   {
 | |
|   case SERVICE_CONTROL_SHUTDOWN:
 | |
|   case SERVICE_CONTROL_STOP:
 | |
|     sql_print_information(
 | |
|       "Windows service \"%s\":  received %s",
 | |
|       svc_name,
 | |
|       cntrl == SERVICE_CONTROL_STOP? "SERVICE_CONTROL_STOP": "SERVICE_CONTROL_SHUTDOWN");
 | |
| 
 | |
|     /* The below will also set the status to STOP_PENDING. */
 | |
|     mysqld_win_initiate_shutdown();
 | |
|     break;
 | |
| 
 | |
|   case SERVICE_CONTROL_INTERROGATE:
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* Service main routine, mainly runs mysqld_main() */
 | |
| static void WINAPI svc_main(DWORD svc_argc, char **svc_argv)
 | |
| {
 | |
|   /* Register the handler function for the service */
 | |
|   char *name= svc_argv[0];
 | |
| 
 | |
|   svc_status_handle= RegisterServiceCtrlHandler(name, svc_ctrl_handle);
 | |
|   if (!svc_status_handle)
 | |
|   {
 | |
|     svc_report_event(name, "RegisterServiceCtrlHandler");
 | |
|     return;
 | |
|   }
 | |
|   report_svc_status(SERVICE_START_PENDING, NO_ERROR, 0);
 | |
| 
 | |
|   /* Make server report service status via our callback.*/
 | |
|   mysqld_set_service_status_callback(report_svc_status);
 | |
| 
 | |
|   /* This would add service name entry to load_defaults.*/
 | |
|   mysqld_win_set_service_name(name);
 | |
| 
 | |
|   /*
 | |
|    Do not pass the service name parameter (last on the command line)
 | |
|    to mysqld_main(), it is unaware of it.
 | |
|   */
 | |
|   save_argv[save_argc - 1]= 0;
 | |
|   mysqld_main(save_argc - 1, save_argv);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   This start the service. Sometimes it will fail, because
 | |
|   currently we do not know for sure whether we run as service or not.
 | |
|   If this fails, the fallback is to run as normal process.
 | |
| */
 | |
| static int run_as_service(char *name)
 | |
| {
 | |
|   SERVICE_TABLE_ENTRY stb[]= {{name, svc_main}, {0, 0}};
 | |
|   if (!StartServiceCtrlDispatcher(stb))
 | |
|   {
 | |
|     assert(GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT);
 | |
|     return -1;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Check for valid existing service name.
 | |
|   Part of our guesswork, whether we run as service or not.
 | |
| */
 | |
| static bool is_existing_service(const char *name)
 | |
| {
 | |
|   if (strchr(name, '\\') || strchr(name, '/'))
 | |
|   {
 | |
|     /* Invalid characters in service name */
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   SC_HANDLE sc_service= 0, scm= 0;
 | |
|   bool ret= ((scm= OpenSCManager(0, 0, SC_MANAGER_ENUMERATE_SERVICE)) != 0) &&
 | |
|        ((sc_service= OpenService(scm, name, SERVICE_QUERY_STATUS)) != 0);
 | |
| 
 | |
|   if (sc_service)
 | |
|     CloseServiceHandle(sc_service);
 | |
|   if (scm)
 | |
|     CloseServiceHandle(scm);
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   If service name is not given to --install/--remove
 | |
|   it is assumed to be "MySQL" (traditional handling)
 | |
| */
 | |
| static const char *get_svc_name(const char *arg)
 | |
| {
 | |
|   return arg ? arg : "MySQL";
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Disable CPU throttling for the process.
 | |
| 
 | |
|   Windows 11 heuristics misdetects server as a background process and runs it
 | |
|   on "efficiency" cores, in hybrid  architectures such as Alder Lake (12th
 | |
|   generation Intel Core).This results in serious performance degradation.
 | |
| */
 | |
| void disable_cpu_throttling()
 | |
| {
 | |
| #ifdef PROCESS_POWER_THROTTLING_EXECUTION_SPEED
 | |
|   PROCESS_POWER_THROTTLING_STATE power_throttling{};
 | |
|   power_throttling.Version= PROCESS_POWER_THROTTLING_CURRENT_VERSION;
 | |
|   power_throttling.ControlMask= PROCESS_POWER_THROTTLING_EXECUTION_SPEED;
 | |
|   power_throttling.StateMask= 0;
 | |
|   SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling,
 | |
|                         &power_throttling, sizeof(power_throttling));
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Main function on Windows.
 | |
|   Runs mysqld as normal process, or as a service.
 | |
| 
 | |
|   Plus, the obsolete functionality to register/remove services.
 | |
| */
 | |
| __declspec(dllexport) int mysqld_win_main(int argc, char **argv)
 | |
| {
 | |
|   save_argv= argv;
 | |
|   save_argc= argc;
 | |
| 
 | |
|   disable_cpu_throttling();
 | |
|    /*
 | |
|      If no special arguments are given, service name is nor present
 | |
|      run as normal program.
 | |
|    */
 | |
|   if (argc == 1)
 | |
|     return mysqld_main(argc, argv);
 | |
| 
 | |
|   auto cmd= argv[1];
 | |
| 
 | |
|   /* Handle install/remove */
 | |
|   if (!strcmp(cmd, "--install") || !strcmp(cmd, "--install-manual"))
 | |
|     return install_service(argc, argv, get_svc_name(argv[2]));
 | |
| 
 | |
|   if (!strcmp(cmd, "--remove"))
 | |
|     return remove_service(get_svc_name(argv[2]));
 | |
| 
 | |
|   /* Try to run as service, and fallback to mysqld_main(), if this fails */
 | |
|   svc_name= argv[argc - 1];
 | |
|   if (is_existing_service(svc_name) && !run_as_service(svc_name))
 | |
|     return 0;
 | |
|   svc_name= 0;
 | |
| 
 | |
|   /* Run as normal program.*/
 | |
|   return mysqld_main(argc, argv);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Register/remove services functionality.
 | |
|   This is kept for backward compatibility only, and is
 | |
|   superseeded by much more versatile mysql_install_db.exe
 | |
| 
 | |
|   "mysqld --remove=svc" has no advantage over
 | |
|   OS own "sc delete svc"
 | |
| */
 | |
| static void ATTRIBUTE_NORETURN die(const char *func, const char *name)
 | |
| {
 | |
|   DWORD err= GetLastError();
 | |
|   fprintf(stderr, "FATAL ERROR : %s failed (%lu)\n", func, err);
 | |
|   switch (err)
 | |
|   {
 | |
|   case ERROR_SERVICE_EXISTS:
 | |
|     fprintf(stderr, "Service %s already exists.\n", name);
 | |
|     break;
 | |
|   case ERROR_SERVICE_DOES_NOT_EXIST:
 | |
|     fprintf(stderr, "Service %s does not exist.\n", name);
 | |
|     break;
 | |
|   case ERROR_ACCESS_DENIED:
 | |
|     fprintf(stderr, "Access is denied. "
 | |
|         "Make sure to run as elevated admin user.\n");
 | |
|     break;
 | |
|   case ERROR_INVALID_NAME:
 | |
|     fprintf(stderr, "Invalid service name '%s'\n", name);
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
|   exit(1);
 | |
| }
 | |
| 
 | |
| static inline std::string quoted(const char *src)
 | |
| {
 | |
|   std::string s;
 | |
|   s.append("\"").append(src).append("\"");
 | |
|   return s;
 | |
| }
 | |
| 
 | |
| static int install_service(int argc, char **argv, const char *name)
 | |
| {
 | |
|   std::string cmdline;
 | |
| 
 | |
|   char path[MAX_PATH];
 | |
|   auto nSize = GetModuleFileName(0, path, sizeof(path));
 | |
| 
 | |
|   if (nSize == (DWORD) sizeof(path) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
 | |
|     die("GetModuleName", name);
 | |
| 
 | |
|   cmdline.append(quoted(path));
 | |
| 
 | |
|   const char *user= 0;
 | |
|   // mysqld --install[-manual] name ...[--local-service]
 | |
|   if (argc > 2)
 | |
|   {
 | |
|     for (int i= 3; argv[i]; i++)
 | |
|     {
 | |
|       if (!strcmp(argv[i], "--local-service"))
 | |
|         user= "NT AUTHORITY\\LocalService";
 | |
|       else
 | |
|       {
 | |
|         cmdline.append(" ").append(quoted(argv[i]));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   cmdline.append(" ").append(quoted(name));
 | |
| 
 | |
|   DWORD start_type;
 | |
|   if (!strcmp(argv[1], "--install-manual"))
 | |
|     start_type= SERVICE_DEMAND_START;
 | |
|   else
 | |
|     start_type= SERVICE_AUTO_START;
 | |
| 
 | |
|   SC_HANDLE scm, sc_service;
 | |
|   if (!(scm= OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE)))
 | |
|     die("OpenSCManager", name);
 | |
| 
 | |
|   if (!(sc_service= CreateService(
 | |
|       scm, name, name, SERVICE_ALL_ACCESS,
 | |
|       SERVICE_WIN32_OWN_PROCESS, start_type, SERVICE_ERROR_NORMAL,
 | |
|       cmdline.c_str(), 0, 0, 0, user, 0)))
 | |
|     die("CreateService", name);
 | |
| 
 | |
|   char description[]= "MariaDB database server";
 | |
|   SERVICE_DESCRIPTION sd= {description};
 | |
|   ChangeServiceConfig2(sc_service, SERVICE_CONFIG_DESCRIPTION, &sd);
 | |
| 
 | |
|   CloseServiceHandle(sc_service);
 | |
|   CloseServiceHandle(scm);
 | |
| 
 | |
|   printf("Service '%s' successfully installed.\n", name);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static int remove_service(const char *name)
 | |
| {
 | |
|   SC_HANDLE scm, sc_service;
 | |
| 
 | |
|   if (!(scm= OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE)))
 | |
|     die("OpenSCManager", name);
 | |
| 
 | |
|   if (!(sc_service= OpenService(scm, name, DELETE)))
 | |
|     die("OpenService", name);
 | |
| 
 | |
|   if (!DeleteService(sc_service))
 | |
|     die("DeleteService", name);
 | |
| 
 | |
|   CloseServiceHandle(sc_service);
 | |
|   CloseServiceHandle(scm);
 | |
| 
 | |
|   printf("Service '%s' successfully deleted.\n", name);
 | |
|   return 0;
 | |
| }
 | 
