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