github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/service/windows/service_windows.go (about) 1 // Copyright 2015 Cloudbase Solutions 2 // Copyright 2015 Canonical Ltd. 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package windows 6 7 import ( 8 "reflect" 9 "syscall" 10 "unsafe" 11 12 // https://bugs.launchpad.net/juju-core/+bug/1470820 13 "github.com/gabriel-samfira/sys/windows" 14 "github.com/gabriel-samfira/sys/windows/svc" 15 "github.com/gabriel-samfira/sys/windows/svc/mgr" 16 "github.com/juju/errors" 17 "github.com/juju/utils/series" 18 19 "github.com/juju/juju/service/common" 20 ) 21 22 type SC_ENUM_TYPE int 23 24 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681988(v=vs.85).aspx 25 const ( 26 SC_ENUM_PROCESS_INFO SC_ENUM_TYPE = 0 27 SERVICE_CONFIG_FAILURE_ACTIONS = 2 28 SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4 29 ) 30 31 //sys enumServicesStatus(h windows.Handle, InfoLevel SC_ENUM_TYPE, dwServiceType uint32, dwServiceState uint32, lpServices uintptr, cbBufSize uint32, pcbBytesNeeded *uint32, lpServicesReturned *uint32, lpResumeHandle *uint32, pszGroupName *uint32) (err error) [failretval==0] = advapi32.EnumServicesStatusExW 32 33 const ( 34 SC_ACTION_NONE = iota 35 SC_ACTION_RESTART 36 SC_ACTION_REBOOT 37 SC_ACTION_RUN_COMMAND 38 ) 39 40 type serviceAction struct { 41 actionType uint16 42 delay uint32 43 } 44 45 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685939(v=vs.85).aspx 46 type serviceFailureActions struct { 47 dwResetPeriod uint32 48 lpRebootMsg *uint16 49 lpCommand *uint16 50 cActions uint32 51 scAction *serviceAction 52 } 53 54 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685937(v=vs.85).aspx 55 type serviceFailureActionsFlag struct { 56 failureActionsOnNonCrashFailures int32 57 } 58 59 // This is done so we can mock this function out 60 var WinChangeServiceConfig2 = windows.ChangeServiceConfig2 61 62 // serviceStatusProcess is used by EnumServicesStatusEx 63 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685992%28v=vs.85%29.aspx 64 type serviceStatusProcess struct { 65 ServiceType uint32 66 CurrentState uint32 67 ControlsAccepted uint32 68 Win32ExitCode uint32 69 ServiceSpecificExitCode uint32 70 CheckPoint uint32 71 WaitHint uint32 72 ProcessId uint32 73 ServiceFlags uint32 74 } 75 76 type enumService struct { 77 name *uint16 78 displayName *uint16 79 Status serviceStatusProcess 80 } 81 82 // Name returns the name of the service stored in enumService. 83 func (s *enumService) Name() string { 84 if s.name != nil { 85 return syscall.UTF16ToString((*[1 << 16]uint16)(unsafe.Pointer(s.name))[:]) 86 } 87 return "" 88 } 89 90 // windowsManager exposes Mgr methods needed by the windows service package. 91 type windowsManager interface { 92 CreateService(name, exepath string, c mgr.Config, args ...string) (windowsService, error) 93 OpenService(name string) (windowsService, error) 94 GetHandle(name string) (windows.Handle, error) 95 CloseHandle(handle windows.Handle) error 96 } 97 98 // windowsService exposes mgr.Service methods needed by the windows service package. 99 type windowsService interface { 100 Close() error 101 Config() (mgr.Config, error) 102 Control(c svc.Cmd) (svc.Status, error) 103 Delete() error 104 Query() (svc.Status, error) 105 Start(...string) error 106 UpdateConfig(mgr.Config) error 107 } 108 109 // manager is meant to help stub out winsvc for testing 110 type manager struct { 111 m *mgr.Mgr 112 } 113 114 // CreateService wraps Mgr.CreateService method. 115 func (m *manager) CreateService(name, exepath string, c mgr.Config, args ...string) (windowsService, error) { 116 // The Create function relies on the fact that this calls Connect(which connects to localhost) and not 117 // ConnectRemote. If we get to the point where we need to call ConnectRemote we need to stop using 118 // series.HostSeries inside Create. 119 s, err := mgr.Connect() 120 if err != nil { 121 return nil, err 122 } 123 defer s.Disconnect() 124 return s.CreateService(name, exepath, c, args...) 125 } 126 127 // CreateService wraps Mgr.OpenService method. It returns a windowsService object. 128 // This allows us to stub out this module for testing. 129 func (m *manager) OpenService(name string) (windowsService, error) { 130 s, err := mgr.Connect() 131 if err != nil { 132 return nil, err 133 } 134 defer s.Disconnect() 135 return s.OpenService(name) 136 } 137 138 // CreateService wraps Mgr.OpenService method but returns a windows.Handle object. 139 // This is used to access a lower level function not directly exposed by 140 // the sys/windows package. 141 func (m *manager) GetHandle(name string) (handle windows.Handle, err error) { 142 s, err := mgr.Connect() 143 if err != nil { 144 return handle, err 145 } 146 defer s.Disconnect() 147 service, err := s.OpenService(name) 148 if err != nil { 149 return handle, err 150 } 151 return service.Handle, nil 152 } 153 154 // CloseHandle wraps the windows.CloseServiceHandle method. 155 // This allows us to stub out this module for testing. 156 func (m *manager) CloseHandle(handle windows.Handle) error { 157 return windows.CloseServiceHandle(handle) 158 } 159 160 var newManager = func() (windowsManager, error) { 161 return &manager{}, nil 162 } 163 164 // getPassword attempts to read the password for the jujud user. We define it as 165 // a variable to allow us to mock it out for testing 166 var getPassword = func() (string, error) { 167 passwd, err := resetJujudPassword() 168 if err != nil { 169 return "", errors.Annotate(err, "cannot reset jujud password") 170 } 171 return passwd, nil 172 } 173 174 // listServices returns an array of strings containing all the services on 175 // the current system. It is defined as a variable to allow us to mock it out 176 // for testing 177 var listServices = func() (services []string, err error) { 178 host := syscall.StringToUTF16Ptr(".") 179 180 sc, err := windows.OpenSCManager(host, nil, windows.SC_MANAGER_ALL_ACCESS) 181 defer func() { 182 // The close service handle error is less important than others 183 if err == nil { 184 err = windows.CloseServiceHandle(sc) 185 } 186 }() 187 if err != nil { 188 return nil, err 189 } 190 191 var needed uint32 192 var returned uint32 193 var resume uint32 = 0 194 var enum []enumService 195 196 for { 197 var buf [512]enumService 198 err := enumServicesStatus(sc, SC_ENUM_PROCESS_INFO, windows.SERVICE_WIN32, 199 windows.SERVICE_STATE_ALL, uintptr(unsafe.Pointer(&buf[0])), uint32(unsafe.Sizeof(buf)), &needed, &returned, &resume, nil) 200 if err != nil { 201 if err == windows.ERROR_MORE_DATA { 202 enum = append(enum, buf[:returned]...) 203 continue 204 } 205 return nil, err 206 } 207 enum = append(enum, buf[:returned]...) 208 break 209 } 210 211 services = make([]string, len(enum)) 212 for i, v := range enum { 213 services[i] = v.Name() 214 } 215 return services, nil 216 } 217 218 // SvcManager implements ServiceManager interface 219 type SvcManager struct { 220 svc windowsService 221 mgr windowsManager 222 serviceConf common.Conf 223 } 224 225 func (s *SvcManager) getService(name string) (windowsService, error) { 226 service, err := s.mgr.OpenService(name) 227 if err != nil { 228 return nil, errors.Trace(err) 229 } 230 return service, nil 231 } 232 233 func (s *SvcManager) status(name string) (svc.State, error) { 234 service, err := s.getService(name) 235 if err != nil { 236 return svc.Stopped, errors.Trace(err) 237 } 238 defer service.Close() 239 status, err := service.Query() 240 if err != nil { 241 return svc.Stopped, errors.Trace(err) 242 } 243 return status.State, nil 244 } 245 246 func (s *SvcManager) exists(name string) (bool, error) { 247 service, err := s.getService(name) 248 if err == c_ERROR_SERVICE_DOES_NOT_EXIST { 249 return false, nil 250 } else if err != nil { 251 return false, err 252 } 253 defer service.Close() 254 return true, nil 255 } 256 257 // Start starts a service. 258 func (s *SvcManager) Start(name string) error { 259 running, err := s.Running(name) 260 if err != nil { 261 return errors.Trace(err) 262 } 263 if running { 264 return nil 265 } 266 service, err := s.getService(name) 267 if err != nil { 268 return errors.Trace(err) 269 } 270 defer service.Close() 271 err = service.Start() 272 if err != nil { 273 return err 274 } 275 return nil 276 } 277 278 func (s *SvcManager) escapeExecPath(exePath string, args []string) string { 279 ret := syscall.EscapeArg(exePath) 280 for _, v := range args { 281 ret += " " + syscall.EscapeArg(v) 282 } 283 return ret 284 } 285 286 // Exists checks whether the config of the installed service matches the 287 // config supplied to this function 288 func (s *SvcManager) Exists(name string, conf common.Conf) (bool, error) { 289 // We escape and compose BinaryPathName the same way mgr.CreateService does. 290 execStart := s.escapeExecPath(conf.ServiceBinary, conf.ServiceArgs) 291 cfg := mgr.Config{ 292 // make this service dependent on WMI service. WMI is needed for almost 293 // all installers to work properly, and is needed for all of the advanced windows 294 // instrumentation bits (powershell included). Juju agents must start after this 295 // service to ensure hooks run properly. 296 Dependencies: []string{"Winmgmt"}, 297 StartType: mgr.StartAutomatic, 298 DisplayName: conf.Desc, 299 ServiceStartName: jujudUser, 300 BinaryPathName: execStart, 301 } 302 currentConfig, err := s.Config(name) 303 if err != nil { 304 return false, err 305 } 306 307 if reflect.DeepEqual(cfg, currentConfig) { 308 return true, nil 309 } 310 return false, nil 311 } 312 313 // Stop stops a service. 314 func (s *SvcManager) Stop(name string) error { 315 running, err := s.Running(name) 316 if err != nil { 317 return errors.Trace(err) 318 } 319 if !running { 320 return nil 321 } 322 service, err := s.getService(name) 323 if err != nil { 324 return errors.Trace(err) 325 } 326 defer service.Close() 327 _, err = service.Control(svc.Stop) 328 if err != nil { 329 return errors.Trace(err) 330 } 331 return nil 332 } 333 334 // Delete deletes a service. 335 func (s *SvcManager) Delete(name string) error { 336 exists, err := s.exists(name) 337 if err != nil { 338 return err 339 } 340 if !exists { 341 return nil 342 } 343 service, err := s.getService(name) 344 if err != nil { 345 return errors.Trace(err) 346 } 347 defer service.Close() 348 err = service.Delete() 349 if err == c_ERROR_SERVICE_DOES_NOT_EXIST { 350 return nil 351 } else if err != nil { 352 return errors.Trace(err) 353 } 354 return nil 355 } 356 357 // Create creates a service with the given config. 358 func (s *SvcManager) Create(name string, conf common.Conf) error { 359 serviceStartName := "LocalSystem" 360 var passwd string 361 if !series.IsWindowsNano(series.HostSeries()) { 362 password, err := getPassword() 363 if err != nil { 364 return errors.Trace(err) 365 } 366 passwd = password 367 serviceStartName = jujudUser 368 } 369 cfg := mgr.Config{ 370 Dependencies: []string{"Winmgmt"}, 371 ErrorControl: mgr.ErrorSevere, 372 StartType: mgr.StartAutomatic, 373 DisplayName: conf.Desc, 374 ServiceStartName: serviceStartName, 375 Password: passwd, 376 } 377 // mgr.CreateService actually does correct argument escaping itself. There is no 378 // need for quoted strings of any kind passed to this function. It takes in 379 // a binary name, and an array or arguments. 380 service, err := s.mgr.CreateService(name, conf.ServiceBinary, cfg, conf.ServiceArgs...) 381 if err != nil { 382 return errors.Trace(err) 383 } 384 defer service.Close() 385 err = s.ensureRestartOnFailure(name) 386 if err != nil { 387 return errors.Trace(err) 388 } 389 return nil 390 } 391 392 // Running returns the status of a service. 393 func (s *SvcManager) Running(name string) (bool, error) { 394 status, err := s.status(name) 395 if err != nil { 396 return false, errors.Trace(err) 397 } 398 logger.Infof("Service %q Status %v", name, status) 399 if status == svc.Running { 400 return true, nil 401 } 402 return false, nil 403 } 404 405 // Config returns the mgr.Config of the service. This config reflects the actual 406 // service configuration in Windows. 407 func (s *SvcManager) Config(name string) (mgr.Config, error) { 408 exists, err := s.exists(name) 409 if err != nil { 410 return mgr.Config{}, err 411 } 412 if !exists { 413 return mgr.Config{}, c_ERROR_SERVICE_DOES_NOT_EXIST 414 } 415 service, err := s.getService(name) 416 if err != nil { 417 return mgr.Config{}, errors.Trace(err) 418 } 419 defer service.Close() 420 return service.Config() 421 } 422 423 func (s *SvcManager) ensureRestartOnFailure(name string) (err error) { 424 handle, err := s.mgr.GetHandle(name) 425 if err != nil { 426 return errors.Trace(err) 427 } 428 defer func() { 429 // The CloseHandle error is less important than another error 430 closeErr := s.mgr.CloseHandle(handle) 431 if closeErr != nil { 432 if err == nil { 433 err = errors.Annotatef(closeErr, "close %q handle failed", name) 434 } else { 435 err = errors.Annotatef(err, "(also close %q handle failed: %s)", name, closeErr) 436 } 437 } 438 }() 439 action := serviceAction{ 440 actionType: SC_ACTION_RESTART, 441 delay: 5000, 442 } 443 failActions := serviceFailureActions{ 444 dwResetPeriod: 5, 445 lpRebootMsg: nil, 446 lpCommand: nil, 447 cActions: 1, 448 scAction: &action, 449 } 450 err = WinChangeServiceConfig2(handle, SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&failActions))) 451 if err != nil { 452 return errors.Trace(err) 453 } 454 flag := serviceFailureActionsFlag{ 455 failureActionsOnNonCrashFailures: 1, 456 } 457 err = WinChangeServiceConfig2(handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, (*byte)(unsafe.Pointer(&flag))) 458 if err != nil { 459 return errors.Trace(err) 460 } 461 return nil 462 } 463 464 // ChangeServicePassword can change the password of a service 465 // as long as it belongs to the user defined in this package 466 func (s *SvcManager) ChangeServicePassword(svcName, newPassword string) error { 467 currentConfig, err := s.Config(svcName) 468 if err != nil { 469 // If access is denied when accessing the service it means 470 // we can't own it, so there's no reason to return an error 471 // since we only want to change the password on services started 472 // by us. 473 if errors.Cause(err) == syscall.ERROR_ACCESS_DENIED { 474 return nil 475 } 476 return errors.Trace(err) 477 } 478 if currentConfig.ServiceStartName == jujudUser { 479 currentConfig.Password = newPassword 480 service, err := s.getService(svcName) 481 if err != nil { 482 return errors.Trace(err) 483 } 484 defer service.Close() 485 err = service.UpdateConfig(currentConfig) 486 if err != nil { 487 return errors.Trace(err) 488 } 489 } 490 if err != nil { 491 return errors.Trace(err) 492 } 493 return nil 494 } 495 496 var NewServiceManager = func() (ServiceManager, error) { 497 m, err := newManager() 498 if err != nil { 499 return nil, errors.Trace(err) 500 } 501 return &SvcManager{ 502 mgr: m, 503 }, nil 504 }