commit b86ed93d8682e1d58b821bee0e5a20ad7ffe786a Author: Daniel Theophanes Date: Thu Mar 8 09:25:13 2012 -0800 First commit with working windows service. diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..69f2275 --- /dev/null +++ b/.hgignore @@ -0,0 +1 @@ +old/ \ No newline at end of file diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..e1d6428 --- /dev/null +++ b/example/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "os" + "../../service" +) + +func main() { + var displayName = "Go Service Test2" + var ws = service.NewService("GoServiceTest2", displayName) + + if len(os.Args) > 1 { + var err error + verb := os.Args[1] + switch verb { + case "install": + err = ws.Install() + if err != nil { + fmt.Printf("Failed to install: %s\n", err) + return + } + fmt.Printf("Service \"%s\" installed.\n", displayName) + case "remove": + err = ws.Remove() + if err != nil { + fmt.Printf("Failed to remove: %s\n", err) + return + } + fmt.Printf("Service \"%s\" removed.", displayName) + } + return + } + err := ws.Run(func() error { + // start + go doWork() + ws.LogInfo("I'm Running!") + return nil + }, func() error { + // stop + stopWork() + ws.LogInfo("I'm Stopping!") + return nil + }) + if err != nil { + ws.LogError(err.Error()) + } +} + +func doWork() { + +} +func stopWork() { + +} diff --git a/service.go b/service.go new file mode 100644 index 0000000..82c59f4 --- /dev/null +++ b/service.go @@ -0,0 +1,11 @@ +package service + +type Service interface { + Install() error + Remove() error + Run(onStart, onStop func() error) error + + LogError(text string) error + LogWarning(text string) error + LogInfo(text string) error +} diff --git a/serviceCgo_windows.go b/serviceCgo_windows.go new file mode 100644 index 0000000..4e18543 --- /dev/null +++ b/serviceCgo_windows.go @@ -0,0 +1,246 @@ +package service + +/* +#include + +SERVICE_STATUS gSvcStatus; +SERVICE_STATUS_HANDLE gSvcStatusHandle; +HANDLE ghSvcStopEvent = NULL; + +void SvcInstall(void); +void WINAPI SvcCtrlHandler( DWORD ); +void WINAPI SvcMain( DWORD, LPTSTR * ); + +void ReportSvcStatus( DWORD, DWORD, DWORD ); + +static char *goServiceName; + +int goSigStart = 0; +int goAckStart = 0; +HANDLE goWaitStart = NULL; + +int goSigStop = 0; +int goAckStop = 0; +HANDLE goWaitStop = NULL; + +int goSigError = 0; +char *errorText; + +void +continueStart(int response) { + goSigStart = 0; + goAckStart = response; + SetEvent(goWaitStart); +} + +void +continueStop(int response) { + goSigStop = 0; + goAckStop = response; + SetEvent(goWaitStop); +} + +void +signalError(char *text) { + errorText = text; + goSigError = 1; +} + +void +initService(char *serviceName) { + if(!goServiceName) { + free(goServiceName); + } + goServiceName = serviceName; + SERVICE_TABLE_ENTRY DispatchTable[] = { + { serviceName, (LPSERVICE_MAIN_FUNCTION) SvcMain }, + { NULL, NULL } + }; + + // This call returns when the service has stopped. + // The process should simply terminate when the call returns. + if (!StartServiceCtrlDispatcher( DispatchTable )) { + signalError("StartServiceCtrlDispatcher"); + } +} + +// Entry point for the service +// dwArgc - Number of arguments in the lpszArgv array +// lpszArgv - Array of strings. The first string is the name of +// the service and subsequent strings are passed by the process +// that called the StartService function to start the service. +VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv ) +{ + // Register the handler function for the service + + gSvcStatusHandle = RegisterServiceCtrlHandler( + goServiceName, + SvcCtrlHandler); + + if( !gSvcStatusHandle ) { + signalError("RegisterServiceCtrlHandler"); + return; + } + + // These SERVICE_STATUS members remain as set here + gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + gSvcStatus.dwServiceSpecificExitCode = 0; + + // Report initial status to the SCM + ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 ); + + goWaitStart = CreateEvent(NULL, TRUE, FALSE, NULL); + goWaitStop = CreateEvent(NULL, TRUE, FALSE, NULL); + + // signal go to start + // wait for go to confirm + goSigStart = 1; + WaitForSingleObject(goWaitStart, INFINITE); + if(goAckStart != 1) { + return; + } + + // TODO: Declare and set any required variables. + // Be sure to periodically call ReportSvcStatus() with + // SERVICE_START_PENDING. If initialization fails, call + // ReportSvcStatus with SERVICE_STOPPED. + + // Create an event. The control handler function, SvcCtrlHandler, + // signals this event when it receives the stop control code. + ghSvcStopEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual reset event + FALSE, // not signaled + NULL); // no name + + if ( ghSvcStopEvent == NULL) + { + ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); + return; + } + + // Report running status when initialization is complete. + ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 ); + + while(1) { + + WaitForSingleObject(ghSvcStopEvent, INFINITE); + + // Signal service Stopped + ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); + return; + } +} + +// Sets the current service status and reports it to the SCM. +// dwCurrentState - The current state (see SERVICE_STATUS) +// dwWin32ExitCode - The system error code +// dwWaitHint - Estimated time for pending operation, in milliseconds +VOID ReportSvcStatus( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { + static DWORD dwCheckPoint = 1; + + // Fill in the SERVICE_STATUS structure. + gSvcStatus.dwCurrentState = dwCurrentState; + gSvcStatus.dwWin32ExitCode = dwWin32ExitCode; + gSvcStatus.dwWaitHint = dwWaitHint; + + if (dwCurrentState == SERVICE_START_PENDING) { + gSvcStatus.dwControlsAccepted = 0; + } else { + gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + } + + if ( (dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED) ) { + gSvcStatus.dwCheckPoint = 0; + } else { + gSvcStatus.dwCheckPoint = dwCheckPoint++; + } + + // Report the status of the service to the SCM. + SetServiceStatus( gSvcStatusHandle, &gSvcStatus ); +} + +// Called by SCM whenever a control code is sent to the service +// using the ControlService function. +VOID WINAPI SvcCtrlHandler( DWORD dwCtrl ) { + // Handle the requested control code. + switch(dwCtrl) { + case SERVICE_CONTROL_STOP: + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); + + goSigStop = 1; + WaitForSingleObject(goWaitStop, INFINITE); + if(goAckStop != 1) { + return; + } + + // Now signal on the initial thread to stop blocking. + SetEvent(ghSvcStopEvent); + return; + case SERVICE_CONTROL_INTERROGATE: + ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0); + break; + + default: + break; + } +} + +*/ +import "C" +import ( + "errors" + "time" +) + +// Starts a windows service routine. Service must be registered first. +// Call blocks until an error occurs or the service stops. If onStart returns +// an error, service will not start. If onStop returns an error, the service will +// not stop. +func runService(serviceName string, onStart, onStop func() error) error { + // We alloc a c string here, but do not free it here + cname := C.CString(serviceName) + + retErr := make(chan error, 1) + + go func() { + // Check C vars on timer. + ticker := time.NewTicker(time.Second * 1) + defer ticker.Stop() + for _ = range ticker.C { + if C.goSigStart == 1 { + err := onStart() + if err != nil { + // An error was returned. + // Signal to NOT start the service. + C.continueStart(-1) + retErr <- err + return + } + C.continueStart(1) + } else if C.goSigStop == 1 { + err := onStop() + if err != nil { + // An error was returned. + // Signal to NOT stop the service. + C.continueStop(-1) + retErr <- err + return + } + C.continueStop(1) + retErr <- nil + } else if C.goSigError == 1 { + // Check for service errors. + errText := "SERVICE ERROR: " + if C.errorText != nil { + errText += C.GoString(C.errorText) + } + retErr <- errors.New(errText) + return + } + } + }() + C.initService(cname) + + return <-retErr +} diff --git a/service_windows.go b/service_windows.go new file mode 100644 index 0000000..d886b7d --- /dev/null +++ b/service_windows.go @@ -0,0 +1,268 @@ +package service + +import ( + "syscall" + "unicode/utf16" + "unsafe" +) + +func NewService(name, displayName string) Service { + return &windowsService { + name: name, + displayName: displayName, + } +} + +type windowsService struct { + name, displayName string +} + +func (ws *windowsService) Install() error { + pathToBinary, err := GetModuleFileName() + if err != nil { + return err + } + + scmManagerHandle, err := OpenSCManager() + if err != nil { + return err + } + defer CloseServiceHandle(scmManagerHandle) + + serviceHandle, err := CreateService(scmManagerHandle, ws.name, ws.displayName, pathToBinary) + if err != nil { + return err + } + defer CloseServiceHandle(serviceHandle) + return nil +} + +func (ws *windowsService) Remove() error { + scmManagerHandle, err := OpenSCManager() + if err != nil { + return err + } + defer CloseServiceHandle(scmManagerHandle) + + serviceHandle, err := OpenService(scmManagerHandle, ws.name) + if err != nil { + return err + } + defer CloseServiceHandle(serviceHandle) + + err = DeleteService(serviceHandle) + if err != nil { + return err + } + + return nil +} + +func (ws *windowsService) Run(onStart, onStop func() error) error { + return runService(ws.name, onStart, onStop) +} + +func (ws *windowsService) LogError(text string) error { + return writeToEventLog(ws.name, text, levelError) +} +func (ws *windowsService) LogWarning(text string) error { + return writeToEventLog(ws.name, text, levelWarning) +} +func (ws *windowsService) LogInfo(text string) error { + return writeToEventLog(ws.name, text, levelInfo) +} + +var ( + advapi = syscall.MustLoadDLL("advapi32.dll") + + createServiceProc = advapi.MustFindProc("CreateServiceW") + openServiceProc = advapi.MustFindProc("OpenServiceW") + deleteServiceProc = advapi.MustFindProc("DeleteService") + closeServiceHandleProc = advapi.MustFindProc("CloseServiceHandle") + + openEventLogProc = advapi.MustFindProc("OpenEventLogW") + registerEventSourceProc = advapi.MustFindProc("RegisterEventSourceW") + deregisterEventSourceProc = advapi.MustFindProc("DeregisterEventSource") + reportEventProc = advapi.MustFindProc("ReportEventW") + + openSCManagerProc = advapi.MustFindProc("OpenSCManagerW") + + kernel = syscall.MustLoadDLL("kernel32.dll") + + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +const ( + _STANDARD_RIGHTS_REQUIRED = 0x000F0000 + _SERVICE_WIN32_OWN_PROCESS = 0x00000010 + _SERVICE_DEMAND_START = 0x00000003 + _SERVICE_ERROR_NORMAL = 0x00000001 +) + +const ( + _SC_MANAGER_CONNECT = 0x0001 + _SC_MANAGER_CREATE_SERVICE = 0x0002 + _SC_MANAGER_ENUMERATE_SERVICE = 0x0004 + _SC_MANAGER_LOCK = 0x0008 + _SC_MANAGER_QUERY_LOCK_STATUS = 0x0010 + _SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020 + + _SC_MANAGER_ALL_ACCESS = ( + _STANDARD_RIGHTS_REQUIRED | + _SC_MANAGER_CONNECT | + _SC_MANAGER_CREATE_SERVICE | + _SC_MANAGER_ENUMERATE_SERVICE | + _SC_MANAGER_LOCK | + _SC_MANAGER_QUERY_LOCK_STATUS | + _SC_MANAGER_MODIFY_BOOT_CONFIG) +) + +const ( + _SERVICE_QUERY_CONFIG = 0x0001 + _SERVICE_CHANGE_CONFIG = 0x0002 + _SERVICE_QUERY_STATUS = 0x0004 + _SERVICE_ENUMERATE_DEPENDENTS = 0x0008 + _SERVICE_START = 0x0010 + _SERVICE_STOP = 0x0020 + _SERVICE_PAUSE_CONTINUE = 0x0040 + _SERVICE_INTERROGATE = 0x0080 + _SERVICE_USER_DEFINED_CONTROL = 0x0100 + + _SERVICE_ALL_ACCESS = ( + _STANDARD_RIGHTS_REQUIRED | + _SERVICE_QUERY_CONFIG | + _SERVICE_CHANGE_CONFIG | + _SERVICE_QUERY_STATUS | + _SERVICE_ENUMERATE_DEPENDENTS | + _SERVICE_START | + _SERVICE_STOP | + _SERVICE_PAUSE_CONTINUE | + _SERVICE_INTERROGATE | + _SERVICE_USER_DEFINED_CONTROL) +) + +type eventLevel uint32 +const ( + levelError eventLevel = 0x0001 + levelWarning eventLevel = 0x0002 + levelInfo eventLevel = 0x0004 +) + +func writeToEventLog(title, text string, level eventLevel) error { + eventSource, err := registerEventSource(title) + if err != nil { + return err + } + err = reportEvent(eventSource, title, text, level) + if err != nil { + return err + } + err = deregisterEventSource(eventSource) + if err != nil { + return err + } + return nil +} + +func registerEventSource(title string) (syscall.Handle, error) { + r0, _, e1 := registerEventSourceProc.Call( + 0, + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title)))) + if r0 == 0 { + return syscall.Handle(0), e1 + } + return syscall.Handle(r0), nil +} +func deregisterEventSource(eventSrouce syscall.Handle) error { + r0, _, e1 := deregisterEventSourceProc.Call(uintptr(eventSrouce)) + if r0 == 0 { + return e1 + } + return nil +} +/* +const ( + _EVENTLOG_ERROR_TYPE = 0x0001 + _EVENTLOG_WARNING_TYPE = 0x0002 + _EVENTLOG_INFORMATION_TYPE = 0x0004 +) +*/ + +func reportEvent(eventSource syscall.Handle, title, text string, level eventLevel) error { + msg := [...]uintptr{ + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))} + r0, _, e1 := reportEventProc.Call( + uintptr(eventSource), + uintptr(level), //type + uintptr(0), //category + //uintptr(0xC0020001), //eventID + uintptr(3), //eventID + uintptr(0), + uintptr(2), + uintptr(0), + uintptr(unsafe.Pointer(&msg[0])), + 0) + if r0 == 0 { + return e1 + } + return nil +} + +func CloseServiceHandle(service syscall.Handle) error { + r0, _, e1 := closeServiceHandleProc.Call(uintptr(service)) + if r0 == 0 { + return e1 + } + return nil +} +func CreateService(scManager syscall.Handle, serviceName, serviceDisplayName, pathToBinary string) (syscall.Handle, error) { + r0, _, e1 := createServiceProc.Call( + uintptr(scManager), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(serviceName))), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(serviceDisplayName))), + uintptr(uint32(_SERVICE_ALL_ACCESS)), + uintptr(uint32(_SERVICE_WIN32_OWN_PROCESS)), + uintptr(uint32(_SERVICE_DEMAND_START)), + uintptr(uint32(_SERVICE_ERROR_NORMAL)), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(pathToBinary))), + 0, 0, 0, 0, 0) //+4 optional params not here + if r0 == 0 { + return syscall.Handle(0), e1 + } + return syscall.Handle(r0), nil +} +func DeleteService(serviceHandle syscall.Handle) error { + r0, _, e1 := deleteServiceProc.Call(uintptr(serviceHandle)) + if r0 == 0 { + return e1 + } + return nil +} +func OpenService(scManager syscall.Handle, serviceName string) (syscall.Handle, error) { + r0, _, e1 := openServiceProc.Call(uintptr(scManager), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(serviceName))), uintptr(uint32(_SC_MANAGER_ALL_ACCESS))) + if r0 == 0 { + return syscall.Handle(0), e1 + } + return syscall.Handle(r0), nil +} +func GetModuleFileName() (string, error) { + var n uint32 + b := make([]uint16, syscall.MAX_PATH) + size := uint32(len(b)) + + r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +} + +func OpenSCManager() (syscall.Handle, error) { + r0, _, e1 := openSCManagerProc.Call(uintptr(0), uintptr(0), uintptr(uint32(_SC_MANAGER_ALL_ACCESS))) + if r0 == 0 { + return syscall.Handle(0), e1 + } + return syscall.Handle(r0), nil +}