293 lines
8.5 KiB
C
293 lines
8.5 KiB
C
/*
|
|
* $Id$
|
|
*
|
|
* simple service management functions
|
|
*
|
|
* Copyright (C) 2005 Ron Pedde (ron.pedde@firstmarkcu.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <windows.h>
|
|
|
|
#include "daapd.h"
|
|
#include "err.h"
|
|
|
|
/* Forwards */
|
|
BOOL service_update_status (DWORD,DWORD,DWORD,DWORD,DWORD);
|
|
void service_mainfunc(DWORD, LPTSTR *);
|
|
void service_init(void);
|
|
void service_handler(DWORD);
|
|
|
|
/* Globals */
|
|
SERVICE_STATUS_HANDLE service_status_handle = NULL;
|
|
HANDLE service_kill_event = NULL;
|
|
DWORD service_current_status;
|
|
|
|
/**
|
|
* handle the stop, start, pause requests, etc.
|
|
*
|
|
* @param code action the SCM wants us to take
|
|
*/
|
|
void service_handler(DWORD code) {
|
|
switch(code) {
|
|
case SERVICE_CONTROL_INTERROGATE:
|
|
break;
|
|
|
|
case SERVICE_CONTROL_SHUTDOWN:
|
|
// fallthrough
|
|
|
|
case SERVICE_CONTROL_STOP:
|
|
service_update_status(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
|
|
SetEvent(service_kill_event);
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
service_update_status(service_current_status, NO_ERROR, 0, 0, 0);
|
|
}
|
|
|
|
/**
|
|
* exit the service as gracefully as possible, returning the error
|
|
* level specified. I think there are some state machine problems
|
|
* here, but probably nothing fatal -- an unnecessary call to
|
|
* xfer_shutdown, maybe. That's about it.
|
|
*
|
|
* @param errorlevel errorlevel to return
|
|
*/
|
|
void service_shutdown(int errorlevel) {
|
|
DPRINTF(E_INF,L_MISC,"Service about to terminate with error %d\n",errorlevel);
|
|
|
|
if(service_current_status != SERVICE_STOPPED) {
|
|
service_update_status(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
|
|
|
|
config.stop = 1;
|
|
service_update_status(SERVICE_STOPPED,errorlevel,0,0,3000);
|
|
}
|
|
|
|
if(service_kill_event) {
|
|
SetEvent(service_kill_event);
|
|
CloseHandle(service_kill_event);
|
|
}
|
|
|
|
// exit(errorlevel);
|
|
/* we'll let the main process exit (or hang!) */
|
|
Sleep(5000);
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
* The main for the service -- starts up the service and
|
|
* makes it run. This sits in a tight loop until the service
|
|
* is shutdown. If this function exits, it's because the
|
|
* service is stopping.
|
|
*
|
|
* @param argc argc as passed from SCM
|
|
* @param argv argv as passed from SCM
|
|
*/
|
|
void service_mainfunc(DWORD argc, LPTSTR *argv) {
|
|
BOOL success;
|
|
|
|
service_current_status = SERVICE_STOPPED;
|
|
service_status_handle = RegisterServiceCtrlHandler(PACKAGE,
|
|
(LPHANDLER_FUNCTION) service_handler);
|
|
|
|
if(!service_status_handle) {
|
|
service_shutdown(GetLastError());
|
|
return;
|
|
}
|
|
|
|
// Next Notify the Service Control Manager of progress
|
|
success = service_update_status(SERVICE_START_PENDING, NO_ERROR, 0, 1, 5000);
|
|
if (!success) {
|
|
service_shutdown(GetLastError());
|
|
return;
|
|
}
|
|
|
|
service_kill_event = CreateEvent (0, TRUE, FALSE, 0);
|
|
if(!service_kill_event) {
|
|
service_shutdown(GetLastError());
|
|
return;
|
|
}
|
|
|
|
// FIXME
|
|
// startup errors generate an ERR_FATAL, which exit()s in the
|
|
// logging code -- fine for console apps, not so good here.
|
|
// Generates ugly error messages from the service tool.
|
|
// (terminated unexpectedly).
|
|
// xfer_startup();
|
|
|
|
// The service is now running. Notify the SCM of this fact.
|
|
service_current_status = SERVICE_RUNNING;
|
|
success = service_update_status(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
|
|
if (!success) {
|
|
service_shutdown(GetLastError());
|
|
return;
|
|
}
|
|
|
|
// Now just wait for our killed service signal, and then exit, which
|
|
// terminates the service!
|
|
WaitForSingleObject (service_kill_event, INFINITE);
|
|
service_shutdown(0);
|
|
}
|
|
|
|
|
|
/**
|
|
* update the scm service status, so it can make pretty
|
|
* gui stupidness. light wrapper around SetServiceStatus
|
|
*
|
|
* If the visual studio editor wasn't so horribly useless
|
|
* and munge up the indents, I'd split these parameters
|
|
* into several lines, but Microsoft apparently can't build
|
|
* an editor that is as good as that made by volunteers.
|
|
*
|
|
* Those who do not understand Unix are condemned to reinvent
|
|
* it, poorly. -- Henry Spencer
|
|
*
|
|
* @param dwCurrentState new service state
|
|
* @param dwWin32ExitCode exit code if state is SERVICE_STOPPED
|
|
* @param dwServiceSpecificExitCode specified service exit code
|
|
* @param dwCheckPoint incremented value for long startups
|
|
* @param dwWaitHint how to before giving up on the service
|
|
*
|
|
* @returns result of SetServiceStatus - true on success
|
|
*/
|
|
BOOL service_update_status (DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint) {
|
|
BOOL success;
|
|
SERVICE_STATUS serviceStatus;
|
|
|
|
service_current_status = dwCurrentState;
|
|
|
|
serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
|
serviceStatus.dwCurrentState = dwCurrentState;
|
|
if (dwCurrentState == SERVICE_START_PENDING) {
|
|
serviceStatus.dwControlsAccepted = 0;
|
|
} else {
|
|
serviceStatus.dwControlsAccepted =
|
|
SERVICE_ACCEPT_STOP |
|
|
SERVICE_ACCEPT_SHUTDOWN;
|
|
}
|
|
|
|
if (dwServiceSpecificExitCode == 0) {
|
|
serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
|
|
} else {
|
|
serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
|
|
}
|
|
|
|
serviceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
|
|
serviceStatus.dwCheckPoint = dwCheckPoint;
|
|
serviceStatus.dwWaitHint = dwWaitHint;
|
|
|
|
success = SetServiceStatus (service_status_handle, &serviceStatus);
|
|
if (!success) {
|
|
SetEvent(service_kill_event);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* kick off the application when in service mdoe - suitable for threadprocing
|
|
*/
|
|
void *service_startup(void *arg) {
|
|
SERVICE_TABLE_ENTRY serviceTable[] = {
|
|
{ PACKAGE, (LPSERVICE_MAIN_FUNCTION) service_mainfunc },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
BOOL success;
|
|
|
|
// Register the service with the Service Control Manager
|
|
// If it were to fail, what would we do, anyway?
|
|
success = StartServiceCtrlDispatcher(serviceTable);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* install the service
|
|
*/
|
|
void service_register(void) {
|
|
SC_HANDLE scm;
|
|
SC_HANDLE svc;
|
|
char path[MAX_PATH];
|
|
|
|
GetModuleFileName(NULL, path, MAX_PATH );
|
|
|
|
if(!(scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE))) {
|
|
DPRINTF(E_FATAL,L_MISC,"Cannot open service control manager\n");
|
|
}
|
|
|
|
svc = CreateService(scm,PACKAGE,SERVICENAME,SERVICE_ALL_ACCESS,
|
|
SERVICE_WIN32_OWN_PROCESS,SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,
|
|
path,0,0,0,0,0);
|
|
if(!svc) {
|
|
DPRINTF(E_FATAL,L_MISC,"Cannot create service: %d\n",GetLastError());
|
|
}
|
|
|
|
CloseServiceHandle(svc);
|
|
CloseServiceHandle(scm);
|
|
|
|
DPRINTF(E_LOG,L_MISC,"Registered service successfully\n");
|
|
}
|
|
|
|
|
|
/**
|
|
* uninstall the service
|
|
*/
|
|
void service_unregister(void) {
|
|
SC_HANDLE scm;
|
|
SC_HANDLE svc;
|
|
BOOL res;
|
|
SERVICE_STATUS status;
|
|
|
|
if(!(scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE))) {
|
|
DPRINTF(E_FATAL,L_MISC,"Cannot open service control manager: %d\n",GetLastError());
|
|
}
|
|
|
|
svc = OpenService(scm,PACKAGE,SERVICE_ALL_ACCESS | DELETE);
|
|
if(!svc) {
|
|
DPRINTF(E_FATAL,L_MISC,"Cannot open service: %d (is it installed??)\n",GetLastError());
|
|
}
|
|
|
|
res=QueryServiceStatus(svc,&status);
|
|
if(!res) {
|
|
DPRINTF(E_FATAL,L_MISC,"Cannot query service status: %d\n",GetLastError());
|
|
}
|
|
|
|
if(status.dwCurrentState != SERVICE_STOPPED) {
|
|
DPRINTF(E_LOG,L_MISC,"Stopping service...\n");
|
|
ControlService(svc,SERVICE_CONTROL_STOP,&status);
|
|
// we should probably poll for service stoppage...
|
|
Sleep(2000);
|
|
}
|
|
|
|
DPRINTF(E_LOG,L_MISC,"Deleting service...\n");
|
|
res = DeleteService(svc);
|
|
|
|
if(res) {
|
|
DPRINTF(E_LOG,L_MISC,"Deleted successfully\n");
|
|
} else {
|
|
DPRINTF(E_FATAL,L_MISC,"Error deleting service: %d\n",GetLastError());
|
|
}
|
|
|
|
CloseServiceHandle(svc);
|
|
CloseServiceHandle(scm);
|
|
}
|
|
|