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