main: Add Windows Service support
I've added PIPE_EVENT_SHUTDOWN because calling g_main_loop_quit() do not work when called from another thread. Main thread was sleeping in g_poll() so I needed some way to wake it up. By some strange reason call close(event_pipe[0]) in event_pipe_deinit() hangs. In current implementation that code never reached so that was not a problem :-) I've added a conditional to leave event_pipe[0] open on Win32.
This commit is contained in:
		 Denis Krjuchkov
					Denis Krjuchkov
				
			
				
					committed by
					
						 Max Kellermann
						Max Kellermann
					
				
			
			
				
	
			
			
			 Max Kellermann
						Max Kellermann
					
				
			
						parent
						
							9fa3d7c4fa
						
					
				
				
					commit
					e8ebb1af91
				
			| @@ -278,6 +278,7 @@ src_mpd_SOURCES = \ | ||||
| 	src/log.c \ | ||||
| 	src/ls.c \ | ||||
| 	src/main.c \ | ||||
| 	src/main_win32.c \ | ||||
| 	src/event_pipe.c \ | ||||
| 	src/daemon.c \ | ||||
| 	src/AudioCompress/compress.c \ | ||||
|   | ||||
| @@ -116,7 +116,10 @@ void event_pipe_deinit(void) | ||||
| 	g_source_remove(event_pipe_source_id); | ||||
| 	g_io_channel_unref(event_channel); | ||||
|  | ||||
| #ifndef WIN32 | ||||
| 	/* By some strange reason this call hangs on Win32 */ | ||||
| 	close(event_pipe[0]); | ||||
| #endif | ||||
| 	close(event_pipe[1]); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -44,6 +44,9 @@ enum pipe_event { | ||||
| 	/** a hardware mixer plugin has detected a change */ | ||||
| 	PIPE_EVENT_MIXER, | ||||
|  | ||||
| 	/** shutdown requested */ | ||||
| 	PIPE_EVENT_SHUTDOWN, | ||||
|  | ||||
| 	PIPE_EVENT_MAX | ||||
| }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										28
									
								
								src/main.c
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/main.c
									
									
									
									
									
								
							| @@ -269,7 +269,25 @@ idle_event_emitted(void) | ||||
| 		client_manager_idle_add(flags); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * event_pipe callback function for PIPE_EVENT_SHUTDOWN | ||||
|  */ | ||||
| static void | ||||
| shutdown_event_emitted(void) | ||||
| { | ||||
| 	g_main_loop_quit(main_loop); | ||||
| } | ||||
|  | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
| #ifdef WIN32 | ||||
| 	return win32_main(argc, argv); | ||||
| #else | ||||
| 	return mpd_main(argc, argv); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| int mpd_main(int argc, char *argv[]) | ||||
| { | ||||
| 	struct options options; | ||||
| 	clock_t start; | ||||
| @@ -324,6 +342,7 @@ int main(int argc, char *argv[]) | ||||
|  | ||||
| 	event_pipe_init(); | ||||
| 	event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted); | ||||
| 	event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted); | ||||
|  | ||||
| 	path_global_init(); | ||||
| 	glue_mapper_init(); | ||||
| @@ -392,10 +411,17 @@ int main(int argc, char *argv[]) | ||||
| 	   playlist_state_restore() */ | ||||
| 	pc_update_audio(); | ||||
|  | ||||
| 	/* run the main loop */ | ||||
| #ifdef WIN32 | ||||
| 	win32_app_started(); | ||||
| #endif | ||||
|  | ||||
| 	/* run the main loop */ | ||||
| 	g_main_loop_run(main_loop); | ||||
|  | ||||
| #ifdef WIN32 | ||||
| 	win32_app_stopping(); | ||||
| #endif | ||||
|  | ||||
| 	/* cleanup */ | ||||
|  | ||||
| 	g_main_loop_unref(main_loop); | ||||
|   | ||||
							
								
								
									
										41
									
								
								src/main.h
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								src/main.h
									
									
									
									
									
								
							| @@ -28,4 +28,45 @@ extern GMainLoop *main_loop; | ||||
|  | ||||
| extern GCond *main_cond; | ||||
|  | ||||
| /** | ||||
|  * A entry point for application. | ||||
|  * On non-Windows platforms this is called directly from main() | ||||
|  * On Windows platform this is called from win32_main() | ||||
|  * after doing some initialization. | ||||
|  */ | ||||
| int mpd_main(int argc, char *argv[]); | ||||
|  | ||||
| #ifdef WIN32 | ||||
|  | ||||
| /** | ||||
|  * If program is run as windows service performs nessesary initialization | ||||
|  * and then calls mpd_main() with specified arguments. | ||||
|  * If program is run as a regular application calls mpd_main() immediately. | ||||
|  */ | ||||
| int | ||||
| win32_main(int argc, char *argv[]); | ||||
|  | ||||
| /** | ||||
|  * When running as a service reports to service control manager | ||||
|  * that our service is started. | ||||
|  * When running as a console application enables console handler that will | ||||
|  * trigger PIPE_EVENT_SHUTDOWN when user closes console window | ||||
|  * or presses Ctrl+C. | ||||
|  * This function should be called just before entering main loop. | ||||
|  */ | ||||
| void | ||||
| win32_app_started(void); | ||||
|  | ||||
| /** | ||||
|  * When running as a service reports to service control manager | ||||
|  * that our service is about to stop. | ||||
|  * When running as a console application enables console handler that will | ||||
|  * catch all shutdown requests and ignore them. | ||||
|  * This function should be called just after leaving main loop. | ||||
|  */ | ||||
| void | ||||
| win32_app_stopping(void); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										154
									
								
								src/main_win32.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								src/main_win32.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| /* | ||||
|  * Copyright (C) 2003-2010 The Music Player Daemon Project | ||||
|  * http://www.musicpd.org | ||||
|  * | ||||
|  * 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; either version 2 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * 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-1301 USA. | ||||
|  */ | ||||
|  | ||||
| #include "config.h" | ||||
| #include "main.h" | ||||
|  | ||||
| #ifdef WIN32 | ||||
|  | ||||
| #include "event_pipe.h" | ||||
|  | ||||
| #include <glib.h> | ||||
|  | ||||
| #define WINVER 0x0501 | ||||
| #include <windows.h> | ||||
|  | ||||
| static int service_argc; | ||||
| static char **service_argv; | ||||
| static char service_name[] = ""; | ||||
| static BOOL ignore_console_events; | ||||
| static SERVICE_STATUS_HANDLE service_handle; | ||||
|  | ||||
| static void WINAPI | ||||
| service_main(DWORD argc, CHAR *argv[]); | ||||
|  | ||||
| static SERVICE_TABLE_ENTRY service_registry[] = { | ||||
| 	{service_name, service_main}, | ||||
| 	{NULL, NULL} | ||||
| }; | ||||
|  | ||||
| static void | ||||
| service_notify_status(DWORD status_code) | ||||
| { | ||||
| 	SERVICE_STATUS current_status; | ||||
|  | ||||
| 	current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; | ||||
| 	current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING | ||||
| 		? 0 | ||||
| 		: SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; | ||||
|  | ||||
| 	current_status.dwCurrentState = status_code; | ||||
| 	current_status.dwWin32ExitCode = NO_ERROR; | ||||
| 	current_status.dwCheckPoint = 0; | ||||
| 	current_status.dwWaitHint = 1000; | ||||
|  | ||||
| 	SetServiceStatus(service_handle, ¤t_status); | ||||
| } | ||||
|  | ||||
| static DWORD WINAPI | ||||
| service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type, | ||||
| 		   G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context) | ||||
| { | ||||
| 	switch (control) { | ||||
| 	case SERVICE_CONTROL_SHUTDOWN: | ||||
| 	case SERVICE_CONTROL_STOP: | ||||
| 		event_pipe_emit(PIPE_EVENT_SHUTDOWN); | ||||
| 		return NO_ERROR; | ||||
| 	default: | ||||
| 		return NO_ERROR; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void WINAPI | ||||
| service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[]) | ||||
| { | ||||
| 	DWORD error_code; | ||||
| 	gchar* error_message; | ||||
|  | ||||
| 	service_handle = | ||||
| 		RegisterServiceCtrlHandlerEx(service_name, | ||||
| 					     service_dispatcher, NULL); | ||||
|  | ||||
| 	if (service_handle == 0) { | ||||
| 		error_code = GetLastError(); | ||||
| 		error_message = g_win32_error_message(error_code); | ||||
| 		g_error("RegisterServiceCtrlHandlerEx() failed: %s", | ||||
| 			error_message); | ||||
| 	} | ||||
|  | ||||
| 	service_notify_status(SERVICE_START_PENDING); | ||||
| 	mpd_main(service_argc, service_argv); | ||||
| 	service_notify_status(SERVICE_STOPPED); | ||||
| } | ||||
|  | ||||
| static BOOL WINAPI | ||||
| console_handler(DWORD event) | ||||
| { | ||||
| 	switch (event) { | ||||
| 	case CTRL_C_EVENT: | ||||
| 	case CTRL_CLOSE_EVENT: | ||||
| 		if (!ignore_console_events) | ||||
| 			event_pipe_emit(PIPE_EVENT_SHUTDOWN); | ||||
| 		return TRUE; | ||||
| 	default: | ||||
| 		return FALSE; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int win32_main(int argc, char *argv[]) | ||||
| { | ||||
| 	DWORD error_code; | ||||
| 	gchar* error_message; | ||||
|  | ||||
| 	service_argc = argc; | ||||
| 	service_argv = argv; | ||||
|  | ||||
| 	if (StartServiceCtrlDispatcher(service_registry)) | ||||
| 		return 0; /* run as service successefully */ | ||||
|  | ||||
| 	error_code = GetLastError(); | ||||
| 	if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { | ||||
| 		/* running as console app */ | ||||
| 		SetConsoleTitle("Music Player Daemon"); | ||||
| 		ignore_console_events = TRUE; | ||||
| 		SetConsoleCtrlHandler(console_handler, TRUE); | ||||
| 		return mpd_main(argc, argv); | ||||
| 	} | ||||
|  | ||||
| 	error_message = g_win32_error_message(error_code); | ||||
| 	g_error("StartServiceCtrlDispatcher() failed: %s", error_message); | ||||
| } | ||||
|  | ||||
| void win32_app_started() | ||||
| { | ||||
| 	if (service_handle != 0) | ||||
| 		service_notify_status(SERVICE_RUNNING); | ||||
| 	else | ||||
| 		ignore_console_events = FALSE; | ||||
| } | ||||
|  | ||||
| void win32_app_stopping() | ||||
| { | ||||
| 	if (service_handle != 0) | ||||
| 		service_notify_status(SERVICE_STOP_PENDING); | ||||
| 	else | ||||
| 		ignore_console_events = TRUE; | ||||
| } | ||||
|  | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user