/*
*
* Copyright (C) 1997 Magnus Hagander <mha@solnet.sollentuna.se>
*
* You can find the latest version of the program at:
* ftp://ftp.des.sollentuna.se/pub/utils/dessvc.exe
* or the source at
* ftp://ftp.des.sollentuna.se/pub/utils/source/dessvc.cpp
* and finally the usage instructions, which may very well be out of date on
* http://www.des.sollentuna.se/dessvc.html
*
* Permission to compile and distribute yourself.
* If you make any modifications, chose another name and clearly state
* that it is not the official version. Also state that it is based on
* this code (C) 1997 Magnus Hagander.
*
* Note that this was a fast hack, to answer a huge demand for a simple
* program.
* Therefor, it does not follow all the neat specifications on how to
* write nice code. It also does not do range-checking and validity-checking
* at all the places it should.
* However, and I see this as a big point, it works.
*
* If you want to add functions/features to this program, I ask you to
* please submit a patch to me instead of releasing your own version of
* it. Naturally, credit will be awarded to those who help out.
*
*
* The piping in here would not have been done unless I had the help from
* "MarkG's Win32 Programming Page", http://markg.pptnet.com
*
* The service framework is also originating from a webpage, but I no longer
* have the address for that page available.
*
* -----------------------------------------------------------------------
* The above text is the original one, only minor textual changes are made
* by me.. darth@vader.dk, 19980401
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <direct.h> //Required to do _chdir()
static SERVICE_STATUS_HANDLE serviceStatusHandle;
static HANDLE hThreadEvent;
static HANDLE killServiceEvent;
static int serviceCurrentStatus;
static char workdir[1024];
static char commandline[1024];
static char logfile[1024];
/*
* Stop the current service
*/
void KillService(void) {
SetEvent(hThreadEvent);
Sleep(2000);
SetEvent(killServiceEvent);
}
/*
* Run a program <cmd> and pipe the output to the file <fn>
*/
bool PipeRun(char *cmd, char *fn) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
HANDLE hInputRead, hInputWrite;
HANDLE hOutputRead, hOutputWrite;
HANDLE hDupInputWrite;
HANDLE hDupOutputRead;
BOOL bStatus;
DWORD dwRead, dwAvail, dwMsg;
char szInput[ 1024 ];
/*
* Check if the file can be opened. If not, we bail out with error
*/
FILE *f = fopen(fn,"a+");
if (!f)
return false;
fclose(f);
memset( &si, 0, sizeof(STARTUPINFO) ); // Initialize structures
memset( &pi, 0, sizeof(PROCESS_INFORMATION) );
// Create pipe for console -> file data
bStatus = CreatePipe( &hInputRead, &hInputWrite, NULL, 1024 );
if( ! bStatus )
return( FALSE );
// Create pipe for file -> console data
bStatus = CreatePipe( &hOutputRead, &hOutputWrite, NULL, 1024 );
if( ! bStatus )
{
CloseHandle( hInputRead ); // Close first pipe if second fails
CloseHandle( hInputWrite );
return( FALSE );
}
// Make an inheritable pipe endpoint handle
DuplicateHandle( GetCurrentProcess(), hInputWrite,
GetCurrentProcess(), &hDupInputWrite, 0L, TRUE,
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS );
// and a second for GUI -> console
DuplicateHandle( GetCurrentProcess(), hOutputRead,
GetCurrentProcess(), &hDupOutputRead, 0L, TRUE,
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS );
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE; // Don't show the console window (DOS box)
si.hStdOutput = hDupInputWrite; // Redirect command line to GUI
si.hStdError = hDupInputWrite;
si.hStdInput = hDupOutputRead; // and send stuff from here to command line
bStatus = CreateProcess(NULL,cmd,NULL,NULL,TRUE,0,NULL,NULL,&si,&pi);
CloseHandle( hDupInputWrite ); // Close duplicates so GUI end
CloseHandle( hDupOutputRead ); // can use the pipes
if( ! bStatus )
{
CloseHandle( hInputRead );
CloseHandle( hInputWrite );
CloseHandle( hOutputRead );
CloseHandle( hOutputWrite );
return( FALSE );
}
// Loop until either the window closure sets the event or
int result;
do {
HANDLE hArray[3] = {hThreadEvent,hInputRead,pi.hProcess};
result = WaitForMultipleObjects(3,hArray,false,INFINITE);
if (result == WAIT_OBJECT_0)
break; //This event is set if the service should stop, so quit looping
if (result == WAIT_OBJECT_0+2) {
pi.hProcess = NULL;
f = fopen(fn,"a+");
fprintf(f,"Child program terminated!\n");
fclose(f);
break;
}
if (result == WAIT_OBJECT_0+1) {
//Yep, something coming in on the pipe
//However, this seems to happen when things are not coming in on the pipe too :-(
// If there's anything to read
if( PeekNamedPipe( hInputRead, szInput, 1024, &dwRead,
&dwAvail, &dwMsg ) && dwAvail > 0)
{ // Read it into a buffer
ReadFile( hInputRead, szInput, dwAvail, &dwRead, NULL );
szInput[dwRead] = 0x00;
f = fopen(fn,"a+");
fprintf(f,"%s",szInput);
fclose(f);
}
Sleep(500); //If not, we get a runaway using 100% CPU :-(
}
} while (result != WAIT_OBJECT_0);
/*
* Write shutdown data to the log
*/
f = fopen(fn,"a+");
fprintf(f,"Service shutting down!\n");
fclose(f);
if( pi.hProcess ) // If the dos box is running, kill it
TerminateProcess( pi.hProcess, 0 );
CloseHandle( hInputWrite );
CloseHandle( hOutputRead );
return( TRUE ); // Back to thread wrapper
}
/*
* Simple wrapper for the working thread
*/
DWORD WINAPI WorkerThread(LPDWORD param) {
_chdir(workdir);
PipeRun(commandline,logfile);
KillService();
return 0;
}
/*
* An even simplier wrapper that just starts the working thread
*/
void StartServiceThread(void) {
DWORD dwd;
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WorkerThread,NULL,0,&dwd);
}
/*
* Update the service status (in the SCM)
*/
BOOL UpdateSCMStatus (DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwServiceSpecificExitCode,
DWORD dwCheckPoint,
DWORD dwWaitHint)
{
BOOL success;
SERVICE_STATUS serviceStatus;
// Fill in all of the SERVICE_STATUS fields
serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; //We don't want to mess up the system
serviceStatus.dwCurrentState = dwCurrentState;
// If in the process of something, then accept
// no control events, else accept anything
if (dwCurrentState == SERVICE_START_PENDING)
{
serviceStatus.dwControlsAccepted = 0;
}
else
{
serviceStatus.dwControlsAccepted =
SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_SHUTDOWN;
}
// if a specific exit code is defines, set up
// the Win32 exit code properly
if (dwServiceSpecificExitCode == 0)
{
serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
}
else
{
serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
}
serviceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
serviceStatus.dwCheckPoint = dwCheckPoint;
serviceStatus.dwWaitHint = dwWaitHint;
// Pass the status record to the SCM
success = SetServiceStatus (serviceStatusHandle, &serviceStatus);
if (!success)
{
/*
* Ouch. We couldn't update the status. Well. Better exit, then
*/
KillService();
}
return success;
}
/*
* Signal we are down
*/
void terminateService (int code, int wincode) {
UpdateSCMStatus(SERVICE_STOPPED,wincode?wincode:ERROR_SERVICE_SPECIFIC_ERROR,(wincode)?0:code,0,0);
return;
}
// Handles the events dispatched by the Service Control Manager.
VOID ServiceCtrlHandler (DWORD controlCode)
{
switch(controlCode)
{
// There is no START option because
// ServiceMain gets called on a start
// Update the current status for the SCM.
case SERVICE_CONTROL_INTERROGATE:
// This does nothing, here we will just fall through to the end
// and send our current status.
break;
// For a shutdown, we can do cleanup but it must take place quickly
// because the system will go down out from under us.
// For this app we have time to stop here, which I do by just falling
// through to the stop message.
case SERVICE_CONTROL_SHUTDOWN:
// Stop the service
case SERVICE_CONTROL_STOP:
// Tell the SCM we're about to Stop.
serviceCurrentStatus = SERVICE_STOP_PENDING;
UpdateSCMStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
KillService();
UpdateSCMStatus(SERVICE_STOPPED,NO_ERROR,0,0,0);
return;
default:
break;
}
UpdateSCMStatus(serviceCurrentStatus, NO_ERROR, 0, 0, 0);
}
VOID ServiceMain(DWORD argc, LPTSTR *argv)
{
BOOL success;
HKEY MyKey;
DWORD type=0,size=0;
// First we must call the Registration function
serviceStatusHandle = RegisterServiceCtrlHandler("DancerSvc",
(LPHANDLER_FUNCTION) ServiceCtrlHandler);
if (!serviceStatusHandle)
{
terminateService(1,GetLastError());
return;
}
// Next Notify the Service Control Manager of progress
success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 1, 1000);
if (!success)
{
terminateService(2,GetLastError());
return;
}
killServiceEvent = CreateEvent(NULL,true,false,NULL);
hThreadEvent = CreateEvent(NULL,true,false,NULL);
if (!killServiceEvent || !hThreadEvent) {
terminateService(99,GetLastError());
return;
}
//Now open the registry key, query values and close it
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Services\\DancerSvc",NULL,KEY_QUERY_VALUE,&MyKey) != ERROR_SUCCESS) {
//Umm. No good!
terminateService(3,GetLastError());
return;
}
size = sizeof(workdir);
if (RegQueryValueEx(MyKey,"WorkDir",NULL,&type,(unsigned char *)workdir,&size) != ERROR_SUCCESS) {
//Umm. No good!
terminateService(4,GetLastError());
return;
}
size = sizeof(commandline);
if (RegQueryValueEx(MyKey,"Command",NULL,&type,(unsigned char *)commandline,&size) != ERROR_SUCCESS) {
//Umm. No good!
terminateService(5,GetLastError());
return;
}
size = sizeof(logfile);
if (RegQueryValueEx(MyKey,"Logfile",NULL,&type,(unsigned char *)logfile,&size) != ERROR_SUCCESS) {
//F**
terminateService(6,GetLastError());
return;
}
RegCloseKey(MyKey);
success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000);
if (!success)
{
terminateService(2,GetLastError());
return;
}
// Start the service execution thread by calling our StartServiceThread function...
StartServiceThread();
// The service is now running. Notify the SCM of this fact.
serviceCurrentStatus = SERVICE_RUNNING;
success = UpdateSCMStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
if (!success)
{
terminateService(6,GetLastError());
return;
}
// Now just wait for our killed service signal, and then exit, which
// terminates the service!
WaitForSingleObject (killServiceEvent, INFINITE);
// success = UpdateSCMStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0);
}
/*
* Misc functions - not actual part of the service!
*/
/*
* Install the service in the SCM
*/
void InstallService(void) {
SC_HANDLE myService, scm;
char modname[256];
GetModuleFileName(NULL,modname,sizeof(modname));
printf("Installing service...\n");
scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
if (!scm) {
printf("Failed to open Service Control Manager! (Error code %i)\n",GetLastError());
return;
}
myService = CreateService(scm,
"DancerSvc", //Internal service name
"Dancer IRC Bot service", //Show name
SERVICE_ALL_ACCESS, //We want full control
SERVICE_WIN32_OWN_PROCESS, //Let's not mess it up for somebody else..
SERVICE_DEMAND_START, //The service requires manual start
SERVICE_ERROR_NORMAL, //Normal handling when error in startup
modname, //Binary file
0,0,0,0,0); //Misc :)
if (!myService) {
printf("Failed to create the service! (Error code %i)\n",GetLastError());
CloseServiceHandle(scm);
return;
}
printf("Service successfully installed.\nYou may now start it manually, or set it for automatic startup.\n");
CloseServiceHandle(myService);
CloseServiceHandle(scm);
}
/*
* Remove the service from the SCM
*/
void RemoveService(void) {
SC_HANDLE myService, scm;
printf("Removing service...\n");
scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
if (!scm) {
printf("Failed to open Service Control Manager! (Error code %i)\n",GetLastError());
return;
}
myService = OpenService(scm,"DancerSvc",SERVICE_ALL_ACCESS);
if (!myService) {
printf("Failed to open service! (Error code %i)\n",GetLastError());
CloseServiceHandle(scm);
return;
}
if (!DeleteService(myService)) {
printf("Failed to delete service! (Error code %i)\n",GetLastError());
CloseServiceHandle(myService);
CloseServiceHandle(scm);
return;
}
CloseServiceHandle(myService);
CloseServiceHandle(scm);
printf("Service successfully removed.\n");
}
/*
* Update the configuration entries in the registry
*/
void ConfigureService(void) {
char wd[1024];memset(wd,0,sizeof(wd));
char cmd[1024];memset(cmd,0,sizeof(cmd));
char lf[1024];memset(lf,0,sizeof(lf));
HKEY MyKey;
DWORD Disposition = 0;
printf("Configuring service.\n\n");
printf("Enter working directory:");
gets(wd);
printf("Enter command line, including program name:");
gets(cmd);
printf("Enter name of log file:");
gets(lf);
if (
RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services\\DancerSvc",
NULL, //Reserved
NULL, //Class
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
NULL, //Security attributes :)
&MyKey,
&Disposition)
!= ERROR_SUCCESS) {
printf("Failed to open registry key!\n");
return;
}
if (
RegSetValueEx(MyKey,"WorkDir",NULL,REG_SZ,(unsigned char *)wd,strlen(wd)+1)
!= ERROR_SUCCESS) {
printf("Failed to write working directory to registry!\n");
RegCloseKey(MyKey);
return;
}
if (
RegSetValueEx(MyKey,"Command",NULL,REG_SZ,(unsigned char *)cmd,strlen(cmd)+1)
!= ERROR_SUCCESS) {
printf("Failed to write command line to registry!\n");
RegCloseKey(MyKey);
return;
}
if (
RegSetValueEx(MyKey,"LogFile",NULL,REG_SZ,(unsigned char *)lf,strlen(lf)+1)
!= ERROR_SUCCESS) {
printf("Failed to write logfile name to registry!\n");
RegCloseKey(MyKey);
return;
}
RegCloseKey(MyKey);
printf("Configuration successfully saved.\n");
}
/*
* Guess...
*/
void Usage(void) {
printf("Usage:\n\n"
" DancerSvc -install To install service\n"
" DancerSvc -remove To remove service\n"
" DancerSvc -conf To configure service\n");
}
/*
* main()
*
* As suspected, this is the entrypoint in the program :-)
*
* If no parameters are given, assume that we are started by the SCM,
* and there for start the Service Control Dispatcher.
* If one parameter is given, it is checked against the allowed ones.
* If one of them, the appropriate actionn is taken.
* If wrong number of parameters or an incorrect parameter is given,
* the usage instructions are shown.
*
* May be a dirty way to find out if we are a service or not, but who cares
*/
int main(int argc, char *argv[]) {
switch(argc) {
case 1: {
SERVICE_TABLE_ENTRY serviceTable[]= { {"DancerSvc", (LPSERVICE_MAIN_FUNCTION)ServiceMain},{NULL,NULL}};
StartServiceCtrlDispatcher(serviceTable);
}
break;
case 2:
if (!stricmp(argv[1],"-install"))
InstallService();
else if (!stricmp(argv[1],"-remove"))
RemoveService();
else if (!strnicmp(argv[1],"-conf",5))
ConfigureService();
else
Usage();
break;
default:
Usage();
}
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1