github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/osext/winsvc/svc/service.go (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package svc provides everything required to build Windows service. 6 // 7 package svc 8 9 import ( 10 "chai2010.gopkg/osext/winsvc/winapi" 11 "errors" 12 "runtime" 13 "syscall" 14 "unsafe" 15 ) 16 17 // State describes service execution state (Stopped, Running and so on). 18 type State uint32 19 20 const ( 21 Stopped = State(winapi.SERVICE_STOPPED) 22 StartPending = State(winapi.SERVICE_START_PENDING) 23 StopPending = State(winapi.SERVICE_STOP_PENDING) 24 Running = State(winapi.SERVICE_RUNNING) 25 ContinuePending = State(winapi.SERVICE_CONTINUE_PENDING) 26 PausePending = State(winapi.SERVICE_PAUSE_PENDING) 27 Paused = State(winapi.SERVICE_PAUSED) 28 ) 29 30 // Cmd represents service state change request. It is sent to a service 31 // by the service manager, and should be actioned upon by the service. 32 type Cmd uint32 33 34 const ( 35 Stop = Cmd(winapi.SERVICE_CONTROL_STOP) 36 Pause = Cmd(winapi.SERVICE_CONTROL_PAUSE) 37 Continue = Cmd(winapi.SERVICE_CONTROL_CONTINUE) 38 Interrogate = Cmd(winapi.SERVICE_CONTROL_INTERROGATE) 39 Shutdown = Cmd(winapi.SERVICE_CONTROL_SHUTDOWN) 40 ) 41 42 // Accepted is used to describe commands accepted by the service. 43 // Note, that Interrogate is always accepted. 44 type Accepted uint32 45 46 const ( 47 AcceptStop = Accepted(winapi.SERVICE_ACCEPT_STOP) 48 AcceptShutdown = Accepted(winapi.SERVICE_ACCEPT_SHUTDOWN) 49 AcceptPauseAndContinue = Accepted(winapi.SERVICE_ACCEPT_PAUSE_CONTINUE) 50 ) 51 52 // Status combines State and Accepted commands to fully describe running service. 53 type Status struct { 54 State State 55 Accepts Accepted 56 CheckPoint uint32 // used to report progress during a lengthy operation 57 WaitHint uint32 // estimated time required for a pending operation, in milliseconds 58 } 59 60 // ChangeRequest is sent to service Handler to request service status change. 61 type ChangeRequest struct { 62 Cmd Cmd 63 CurrentStatus Status 64 } 65 66 // Handler is the interface that must be implemented to build Windows service. 67 type Handler interface { 68 69 // Execute will be called by the package code at the start of 70 // the service, and the service will exit once Execute completes. 71 // Inside Execute you must read service change requests from r and 72 // act accordingly. You must keep service control manager up to date 73 // about state of your service by writing into s as required. 74 // args contains argument strings passed to the service. 75 // You can provide service exit code in exitCode return parameter, 76 // with 0 being "no error". You can also indicate if exit code, 77 // if any, is service specific or not by using svcSpecificEC 78 // parameter. 79 Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) 80 } 81 82 var ( 83 // These are used by asm code. 84 goWaitsH uintptr 85 cWaitsH uintptr 86 ssHandle uintptr 87 sName *uint16 88 sArgc uintptr 89 sArgv **uint16 90 ctlHandlerProc uintptr 91 cSetEvent uintptr 92 cWaitForSingleObject uintptr 93 cRegisterServiceCtrlHandlerW uintptr 94 ) 95 96 func init() { 97 k := syscall.MustLoadDLL("kernel32.dll") 98 cSetEvent = k.MustFindProc("SetEvent").Addr() 99 cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr() 100 a := syscall.MustLoadDLL("advapi32.dll") 101 cRegisterServiceCtrlHandlerW = a.MustFindProc("RegisterServiceCtrlHandlerW").Addr() 102 } 103 104 type ctlEvent struct { 105 cmd Cmd 106 errno uint32 107 } 108 109 // service provides access to windows service api. 110 type service struct { 111 name string 112 h syscall.Handle 113 cWaits *event 114 goWaits *event 115 c chan ctlEvent 116 handler Handler 117 } 118 119 func newService(name string, handler Handler) (*service, error) { 120 var s service 121 var err error 122 s.name = name 123 s.c = make(chan ctlEvent) 124 s.handler = handler 125 s.cWaits, err = newEvent() 126 if err != nil { 127 return nil, err 128 } 129 s.goWaits, err = newEvent() 130 if err != nil { 131 s.cWaits.Close() 132 return nil, err 133 } 134 return &s, nil 135 } 136 137 func (s *service) close() error { 138 s.cWaits.Close() 139 s.goWaits.Close() 140 return nil 141 } 142 143 type exitCode struct { 144 isSvcSpecific bool 145 errno uint32 146 } 147 148 func (s *service) updateStatus(status *Status, ec *exitCode) error { 149 if s.h == 0 { 150 return errors.New("updateStatus with no service status handle") 151 } 152 var t winapi.SERVICE_STATUS 153 t.ServiceType = winapi.SERVICE_WIN32_OWN_PROCESS 154 t.CurrentState = uint32(status.State) 155 if status.Accepts&AcceptStop != 0 { 156 t.ControlsAccepted |= winapi.SERVICE_ACCEPT_STOP 157 } 158 if status.Accepts&AcceptShutdown != 0 { 159 t.ControlsAccepted |= winapi.SERVICE_ACCEPT_SHUTDOWN 160 } 161 if status.Accepts&AcceptPauseAndContinue != 0 { 162 t.ControlsAccepted |= winapi.SERVICE_ACCEPT_PAUSE_CONTINUE 163 } 164 if ec.errno == 0 { 165 t.Win32ExitCode = winapi.NO_ERROR 166 t.ServiceSpecificExitCode = winapi.NO_ERROR 167 } else if ec.isSvcSpecific { 168 t.Win32ExitCode = uint32(winapi.ERROR_SERVICE_SPECIFIC_ERROR) 169 t.ServiceSpecificExitCode = ec.errno 170 } else { 171 t.Win32ExitCode = ec.errno 172 t.ServiceSpecificExitCode = winapi.NO_ERROR 173 } 174 t.CheckPoint = status.CheckPoint 175 t.WaitHint = status.WaitHint 176 return winapi.SetServiceStatus(s.h, &t) 177 } 178 179 const ( 180 sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota 181 sysErrNewThreadInCallback 182 ) 183 184 func (s *service) run() { 185 s.goWaits.Wait() 186 s.h = syscall.Handle(ssHandle) 187 argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc] 188 args := make([]string, len(argv)) 189 for i, a := range argv { 190 args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:]) 191 } 192 193 cmdsToHandler := make(chan ChangeRequest) 194 changesFromHandler := make(chan Status) 195 exitFromHandler := make(chan exitCode) 196 197 go func() { 198 ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler) 199 exitFromHandler <- exitCode{ss, errno} 200 }() 201 202 status := Status{State: Stopped} 203 ec := exitCode{isSvcSpecific: true, errno: 0} 204 loop: 205 for { 206 select { 207 case r := <-s.c: 208 if r.errno != 0 { 209 ec.errno = r.errno 210 break loop 211 } 212 cmdsToHandler <- ChangeRequest{r.cmd, status} 213 case c := <-changesFromHandler: 214 err := s.updateStatus(&c, &ec) 215 if err != nil { 216 // best suitable error number 217 ec.errno = sysErrSetServiceStatusFailed 218 if err2, ok := err.(syscall.Errno); ok { 219 ec.errno = uint32(err2) 220 } 221 break loop 222 } 223 status = c 224 case ec = <-exitFromHandler: 225 break loop 226 } 227 } 228 229 s.updateStatus(&Status{State: Stopped}, &ec) 230 s.cWaits.Set() 231 } 232 233 // from sys.c 234 func getServiceMain(r *uintptr) 235 236 // BUG(brainman): There is no mechanism to run multiple services 237 // inside one single executable. Perhaps, it can be overcome by 238 // using RegisterServiceCtrlHandlerEx Windows api. 239 240 // Run executes service named name by calling appropriate handler function. 241 func Run(name string, handler Handler) error { 242 runtime.LockOSThread() 243 244 tid := winapi.GetCurrentThreadId() 245 246 s, err := newService(name, handler) 247 if err != nil { 248 return err 249 } 250 251 ctlHandler := func(ctl uint32) uintptr { 252 e := ctlEvent{cmd: Cmd(ctl)} 253 // We assume that this callback function is running on 254 // the same thread as Run. Nowhere in MS documentation 255 // I could find statement to guarantee that. So putting 256 // check here to verify, otherwise things will go bad 257 // quickly, if ignored. 258 i := winapi.GetCurrentThreadId() 259 if i != tid { 260 e.errno = sysErrNewThreadInCallback 261 } 262 s.c <- e 263 return 0 264 } 265 266 var svcmain uintptr 267 getServiceMain(&svcmain) 268 t := []winapi.SERVICE_TABLE_ENTRY{ 269 {syscall.StringToUTF16Ptr(s.name), svcmain}, 270 {nil, 0}, 271 } 272 273 goWaitsH = uintptr(s.goWaits.h) 274 cWaitsH = uintptr(s.cWaits.h) 275 sName = t[0].ServiceName 276 ctlHandlerProc = syscall.NewCallback(ctlHandler) 277 278 go s.run() 279 280 err = winapi.StartServiceCtrlDispatcher(&t[0]) 281 if err != nil { 282 return err 283 } 284 return nil 285 }