diff --git a/example/main.go b/example/main.go index b671f0c..a876dc4 100644 --- a/example/main.go +++ b/example/main.go @@ -1,17 +1,18 @@ package main import ( - "../../service" + "bitbucket.org/kardianos/service" "fmt" "os" ) func main() { - var displayName = "Go Service Test2" + var name = "GoServiceTest" + var displayName = "Go Service Test" var desc = "This is a test Go service. It is designed to run well." - var ws, err = service.NewService("GoServiceTest2", displayName, desc) + var ws, err = service.NewService(name, displayName, desc) - if(err != nil) { + if err != nil { fmt.Printf("%s unable to start: %s", displayName, err) return } diff --git a/serviceCgo_windows.go b/serviceCgo_windows.go deleted file mode 100644 index ebfc827..0000000 --- a/serviceCgo_windows.go +++ /dev/null @@ -1,248 +0,0 @@ -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) { - ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); - 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) { - ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); - 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 -// stop and return that error. -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. - // Will signal to stop 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 index cc2c010..935bca6 100644 --- a/service_windows.go +++ b/service_windows.go @@ -5,6 +5,9 @@ import ( "unicode/utf16" "fmt" "unsafe" + "code.google.com/p/winsvc/svc" + "code.google.com/p/winsvc/mgr" + "code.google.com/p/winsvc/eventlog" ) func newService(name, displayName, description string) (*windowsService, error) { @@ -17,91 +20,127 @@ func newService(name, displayName, description string) (*windowsService, error) type windowsService struct { name, displayName, description string + onStart, onStop func() error + logger *eventlog.Log +} + +func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { + const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown + changes <- svc.Status{State: svc.StartPending} + + if err := ws.onStart(); err != nil { + ws.LogError(err.Error()) + return true, 1 + } + + changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} +loop: + for { + c := <-r + switch c.Cmd { + case svc.Interrogate: + changes <- c.CurrentStatus + case svc.Stop, svc.Shutdown: + changes <- svc.Status{State: svc.StopPending} + if err := ws.onStop(); err != nil { + ws.LogError(err.Error()) + changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + continue loop + } + break loop + default: + continue loop + } + } + + return } func (ws *windowsService) Install() error { - pathToBinary, err := getModuleFileName() + exepath, err := getExePath() if err != nil { return err } - - scmManagerHandle, err := openSCManager() + m, err := mgr.Connect() if err != nil { return err } - defer closeServiceHandle(scmManagerHandle) - - serviceHandle, err := createService(scmManagerHandle, ws.name, ws.displayName, pathToBinary) + defer m.Disconnect() + s, err := m.OpenService(ws.name) + if err == nil { + s.Close() + return fmt.Errorf("service %s already exists", ws.name) + } + s, err = m.CreateService(ws.name, exepath, mgr.Config{ + DisplayName: ws.displayName, + Description: ws.description, + StartType: mgr.StartAutomatic, + }) if err != nil { return err } - defer closeServiceHandle(serviceHandle) - - err = changeServiceDescription(serviceHandle, ws.description) + defer s.Close() + err = eventlog.InstallAsEventCreate(ws.name, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil { - return err + s.Delete() + return fmt.Errorf("InstallAsEventCreate() failed: %s", err) } - - regKey, err := regCreateKey(`SYSTEM\CurrentControlSet\Services\Eventlog\Application\` + ws.name) - if err != nil { - return err - } - defer regCloseKey(regKey) - - regSetKeyValue(regKey, "EventMessageFile", `%SystemRoot%\System32\EventCreate.exe`) - if err != nil { - return err - } - regSetKeyValue(regKey, "CustomSource", uint32(1)) - if err != nil { - return err - } - regSetKeyValue(regKey, "TypesSupported", uint32(7)) - if err != nil { - return err - } - return nil } func (ws *windowsService) Remove() error { - scmManagerHandle, err := openSCManager() + m, err := mgr.Connect() if err != nil { return err } - defer closeServiceHandle(scmManagerHandle) - - serviceHandle, err := openService(scmManagerHandle, ws.name) + defer m.Disconnect() + s, err := m.OpenService(ws.name) + if err != nil { + return fmt.Errorf("service %s is not installed", ws.name) + } + defer s.Close() + err = s.Delete() if err != nil { return err } - defer closeServiceHandle(serviceHandle) - - err = deleteService(serviceHandle) + err = eventlog.Remove(ws.name) if err != nil { - return err + return fmt.Errorf("RemoveEventLogSource() failed: %s", err) } - - err = regDeleteKey(`SYSTEM\CurrentControlSet\Services\Eventlog\Application\` + ws.name) - if err != nil { - return err - } - return nil } func (ws *windowsService) Run(onStart, onStop func() error) error { - return runService(ws.name, onStart, onStop) + elog, err := eventlog.Open(ws.name) + if err != nil { + return err + } + defer elog.Close() + + ws.logger = elog + + ws.onStart = onStart + ws.onStop = onStop + return svc.Run(ws.name, ws) } func (ws *windowsService) LogError(format string, a ...interface{}) error { - return writeToEventLog(ws.name, fmt.Sprintf(format, a ...), levelError) + if ws.logger == nil { + return nil + } + return ws.logger.Error(3, fmt.Sprintf(format, a...)) } func (ws *windowsService) LogWarning(format string, a ...interface{}) error { - return writeToEventLog(ws.name, fmt.Sprintf(format, a ...), levelWarning) + if ws.logger == nil { + return nil + } + return ws.logger.Warning(2, fmt.Sprintf(format, a...)) } func (ws *windowsService) LogInfo(format string, a ...interface{}) error { - return writeToEventLog(ws.name, fmt.Sprintf(format, a ...), levelInfo) + if ws.logger == nil { + return nil + } + return ws.logger.Info(1, fmt.Sprintf(format, a...)) } func getExePath() (exePath string, err error) { @@ -109,185 +148,12 @@ func getExePath() (exePath string, err error) { } var ( - advapi = syscall.MustLoadDLL("advapi32.dll") kernel = syscall.MustLoadDLL("kernel32.dll") - //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") - changeServiceConfig2Proc = advapi.MustFindProc("ChangeServiceConfig2W") - - // Registry - regCloseKeyProc = advapi.MustFindProc("RegCloseKey") - regSetKeyValueExProc = advapi.MustFindProc("RegSetValueExW") - regCreateKeyExProc = advapi.MustFindProc("RegCreateKeyExW") - regDeleteKeyProc = advapi.MustFindProc("RegDeleteKeyW") - // 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(text)))} - r0, _, e1 := reportEventProc.Call( - uintptr(eventSource), - uintptr(level), //type - uintptr(0), //category - uintptr(1), //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) @@ -300,147 +166,3 @@ func getModuleFileName() (string, error) { } 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 -} - -const ( - _SERVICE_CONFIG_DESCRIPTION = 1 -) - -func changeServiceDescription(h syscall.Handle, desc string) error { - msg := &struct { - desc uintptr - }{ - desc: uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(desc))), - } - r0, _, e1 := changeServiceConfig2Proc.Call( - uintptr(h), - uintptr(_SERVICE_CONFIG_DESCRIPTION), - uintptr(unsafe.Pointer(msg)), - ) - if r0 == 0 { - return e1 - } - return nil -} - -// registry functions - -/* -LONG WINAPI RegCloseKey( - __in HKEY hKey -); -LONG WINAPI RegSetValueExW( - __in HKEY hKey, - __in_opt LPCTSTR lpValueName, - __reserved DWORD Reserved, - __in DWORD dwType, - __in const BYTE *lpData, - __in DWORD cbData -); -LONG WINAPI RegCreateKeyExW( - __in HKEY hKey, - __in LPCTSTR lpSubKey, - __reserved DWORD Reserved, - __in_opt LPTSTR lpClass, - __in DWORD dwOptions, - __in REGSAM samDesired, - __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, - __out PHKEY phkResult, - __out_opt LPDWORD lpdwDisposition -); -LONG WINAPI RegDeleteKeyExW( - __in HKEY hKey, - __in LPCTSTR lpSubKey, - __in REGSAM samDesired, - __reserved DWORD Reserved -); -*/ - -func StringToUTF16PtrLen(s string) (ptr uintptr, l uintptr) { - u := syscall.StringToUTF16(s) - l = uintptr(len(u) * 2) //size in uint8, length of uint16 - ptr = uintptr(unsafe.Pointer(&u[0])) - return -} - -const ( - _HKEY_LOCAL_MACHINE = 0x80000002 - - _REG_SZ = 1 - _REG_DWORD = 4 - - _KEY_ALL_ACCESS = 0xF003F -) - -func regCloseKey(h syscall.Handle) error { - r0, _, e1 := regCloseKeyProc.Call( - uintptr(h), - ) - if r0 != 0 { - return e1 - } - return nil -} - -func regSetKeyValue(h syscall.Handle, keyName string, data interface{}) error { - var dataPtr, dataLen, dataType uintptr - switch v := data.(type) { - case uint32: - dataPtr, dataLen = uintptr(unsafe.Pointer(&v)), 4 - dataType = _REG_DWORD - case string: - // The comment on MSDN regarding escaping back-slashes, are c-lang specific. - // The API just takes a normal NUL terminated string, no special escaping required. - dataPtr, dataLen = StringToUTF16PtrLen(v) - dataType = _REG_SZ - } - r0, _, e1 := regSetKeyValueExProc.Call( - uintptr(h), - uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(keyName))), - 0, - dataType, - dataPtr, - dataLen, - ) - if r0 != 0 { - return e1 - } - return nil -} - -func regCreateKey(keyName string) (h syscall.Handle, err error) { - r0, _, e1 := regCreateKeyExProc.Call( - uintptr(_HKEY_LOCAL_MACHINE), - uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(keyName))), - 0, - 0, //class - 0, //no options - uintptr(_KEY_ALL_ACCESS), - 0, //no security - uintptr(unsafe.Pointer(&h)), - 0, //can return if created or opened - ) - if r0 != 0 { - err = e1 - return - } - return -} - -func regDeleteKey(keyName string) error { - r0, _, e1 := regDeleteKeyProc.Call( - uintptr(_HKEY_LOCAL_MACHINE), - uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(keyName))), - ) - if r0 != 0 { - return e1 - } - return nil -}