github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 "io/ioutil" 9 "reflect" 10 "strings" 11 "syscall" 12 "unsafe" 13 14 "github.com/gabriel-samfira/sys/windows" 15 "github.com/gabriel-samfira/sys/windows/svc" 16 "github.com/gabriel-samfira/sys/windows/svc/mgr" 17 "github.com/juju/errors" 18 19 "github.com/juju/juju/service/common" 20 "github.com/juju/juju/service/windows/securestring" 21 ) 22 23 //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 24 25 type enumService struct { 26 name *uint16 27 displayName *uint16 28 Status windows.SERVICE_STATUS 29 } 30 31 // Name returns the name of the service stored in enumService. 32 func (s *enumService) Name() string { 33 if s.name != nil { 34 return syscall.UTF16ToString((*[1 << 16]uint16)(unsafe.Pointer(s.name))[:]) 35 } 36 return "" 37 } 38 39 // mgrInterface exposes Mgr methods needed by the windows service package. 40 type mgrInterface interface { 41 CreateService(name, exepath string, c mgr.Config, args ...string) (svcInterface, error) 42 OpenService(name string) (svcInterface, error) 43 } 44 45 // svcInterface exposes mgr.Service methods needed by the windows service package. 46 type svcInterface interface { 47 Config() (mgr.Config, error) 48 Control(c svc.Cmd) (svc.Status, error) 49 Delete() error 50 Query() (svc.Status, error) 51 Start(...string) error 52 } 53 54 // manager is meant to help stub out winsvc for testing 55 type manager struct { 56 m *mgr.Mgr 57 } 58 59 // CreateService wraps Mgr.CreateService method. 60 func (m *manager) CreateService(name, exepath string, c mgr.Config, args ...string) (svcInterface, error) { 61 return m.m.CreateService(name, exepath, c, args...) 62 } 63 64 // CreateService wraps Mgr.OpenService method. It returns a svcInterface object. 65 // This allows us to stub out this module for testing. 66 func (m *manager) OpenService(name string) (svcInterface, error) { 67 return m.m.OpenService(name) 68 } 69 70 func newManagerConn() (mgrInterface, error) { 71 s, err := mgr.Connect() 72 if err != nil { 73 return nil, err 74 } 75 return &manager{m: s}, nil 76 } 77 78 var newConn = newManagerConn 79 80 // enumServices casts the bytes returned by enumServicesStatus into an array of 81 // enumService with all the services on the current system 82 func enumServices(h windows.Handle) ([]enumService, error) { 83 var needed uint32 84 var returned uint32 85 var resume uint32 86 var all []enumService 87 88 for { 89 var buf [256]enumService 90 err := enumServicesStatus(h, windows.SERVICE_WIN32, 91 windows.SERVICE_STATE_ALL, uintptr(unsafe.Pointer(&buf[0])), uint32(unsafe.Sizeof(buf)), &needed, &returned, &resume) 92 if err != nil { 93 if err == windows.ERROR_MORE_DATA { 94 all = append(all, buf[:returned]...) 95 continue 96 } 97 return []enumService{}, err 98 } 99 all = append(all, buf[:returned]...) 100 return all, nil 101 } 102 } 103 104 // getPassword attempts to read the password for the jujud user. We define it as 105 // a variable to allow us to mock it out for testing 106 var getPassword = func() (string, error) { 107 f, err := ioutil.ReadFile(jujuPasswdFile) 108 if err != nil { 109 return "", errors.Annotate(err, "Failed to read password file") 110 } 111 encryptedPasswd := strings.TrimSpace(string(f)) 112 passwd, err := securestring.Decrypt(encryptedPasswd) 113 if err != nil { 114 return "", errors.Annotate(err, "Failed to decrypt password") 115 } 116 return passwd, nil 117 } 118 119 // listServices returns an array of strings containing all the services on 120 // the current system. It is defined as a variable to allow us to mock it out 121 // for testing 122 var listServices = func() ([]string, error) { 123 services := []string{} 124 host := syscall.StringToUTF16Ptr(".") 125 126 sc, err := windows.OpenSCManager(host, nil, windows.SC_MANAGER_ALL_ACCESS) 127 if err != nil { 128 return services, err 129 } 130 enum, err := enumServices(sc) 131 if err != nil { 132 return services, err 133 } 134 for _, v := range enum { 135 services = append(services, v.Name()) 136 } 137 return services, nil 138 } 139 140 // SvcManager implements ServiceManager interface 141 type SvcManager struct { 142 svc svcInterface 143 mgr mgrInterface 144 serviceConf common.Conf 145 } 146 147 func (s *SvcManager) query(name string) (svc.State, error) { 148 var err error 149 s.svc, err = s.mgr.OpenService(name) 150 if err != nil { 151 return svc.Stopped, errors.Trace(err) 152 } 153 status, err := s.svc.Query() 154 if err != nil { 155 return svc.Stopped, errors.Trace(err) 156 } 157 return status.State, nil 158 } 159 160 func (s *SvcManager) status(name string) (svc.State, error) { 161 status, err := s.query(name) 162 if err != nil { 163 return svc.Stopped, errors.Trace(err) 164 } 165 return status, nil 166 } 167 168 func (s *SvcManager) exists(name string) (bool, error) { 169 _, err := s.query(name) 170 if err == c_ERROR_SERVICE_DOES_NOT_EXIST { 171 return false, nil 172 } else if err != nil { 173 return false, err 174 } 175 return true, nil 176 } 177 178 // Start starts a service. 179 func (s *SvcManager) Start(name string) error { 180 running, err := s.Running(name) 181 if err != nil { 182 return errors.Trace(err) 183 } 184 if running { 185 return nil 186 } 187 err = s.svc.Start() 188 if err != nil { 189 return err 190 } 191 return nil 192 } 193 194 func (s *SvcManager) escapeExecPath(exePath string, args []string) string { 195 ret := syscall.EscapeArg(exePath) 196 for _, v := range args { 197 ret += " " + syscall.EscapeArg(v) 198 } 199 return ret 200 } 201 202 // Exists checks whether the config of the installed service matches the 203 // config supplied to this function 204 func (s *SvcManager) Exists(name string, conf common.Conf) (bool, error) { 205 passwd, err := getPassword() 206 if err != nil { 207 return false, err 208 } 209 210 // We escape and compose BinaryPathName the same way mgr.CreateService does. 211 execStart := s.escapeExecPath(conf.ServiceBinary, conf.ServiceArgs) 212 cfg := mgr.Config{ 213 // make this service dependent on WMI service. WMI is needed for almost 214 // all installers to work properly, and is needed for all of the advanced windows 215 // instrumentation bits (powershell included). Juju agents must start after this 216 // service to ensure hooks run properly. 217 Dependencies: []string{"Winmgmt"}, 218 StartType: mgr.StartAutomatic, 219 DisplayName: conf.Desc, 220 ServiceStartName: jujudUser, 221 Password: passwd, 222 BinaryPathName: execStart, 223 } 224 currentConfig, err := s.Config(name) 225 if err != nil { 226 return false, err 227 } 228 229 if reflect.DeepEqual(cfg, currentConfig) { 230 return true, nil 231 } 232 return false, nil 233 } 234 235 // Stop stops a service. 236 func (s *SvcManager) Stop(name string) error { 237 running, err := s.Running(name) 238 if err != nil { 239 return errors.Trace(err) 240 } 241 if !running { 242 return nil 243 } 244 _, err = s.svc.Control(svc.Stop) 245 if err != nil { 246 return errors.Trace(err) 247 } 248 return nil 249 } 250 251 // Delete deletes a service. 252 func (s *SvcManager) Delete(name string) error { 253 exists, err := s.exists(name) 254 if err != nil { 255 return err 256 } 257 if !exists { 258 return nil 259 } 260 err = s.svc.Delete() 261 if err == c_ERROR_SERVICE_DOES_NOT_EXIST { 262 return nil 263 } else if err != nil { 264 return errors.Trace(err) 265 } 266 return nil 267 } 268 269 // Create creates a service with the given config. 270 func (s *SvcManager) Create(name string, conf common.Conf) error { 271 passwd, err := getPassword() 272 if err != nil { 273 return errors.Trace(err) 274 } 275 cfg := mgr.Config{ 276 Dependencies: []string{"Winmgmt"}, 277 StartType: mgr.StartAutomatic, 278 DisplayName: conf.Desc, 279 ServiceStartName: jujudUser, 280 Password: passwd, 281 } 282 // mgr.CreateService actually does correct argument escaping itself. There is no 283 // need for quoted strings of any kind passed to this function. It takes in 284 // a binary name, and an array or arguments. 285 _, err = s.mgr.CreateService(name, conf.ServiceBinary, cfg, conf.ServiceArgs...) 286 if err != nil { 287 return errors.Trace(err) 288 } 289 return nil 290 } 291 292 // Running returns the status of a service. 293 func (s *SvcManager) Running(name string) (bool, error) { 294 status, err := s.status(name) 295 if err != nil { 296 return false, errors.Trace(err) 297 } 298 logger.Infof("Service %q Status %v", name, status) 299 if status == svc.Running { 300 return true, nil 301 } 302 return false, nil 303 } 304 305 // Config returns the mgr.Config of the service. This config reflects the actual 306 // service configuration in Windows. 307 func (s *SvcManager) Config(name string) (mgr.Config, error) { 308 exists, err := s.exists(name) 309 if err != nil { 310 return mgr.Config{}, err 311 } 312 if !exists { 313 return mgr.Config{}, c_ERROR_SERVICE_DOES_NOT_EXIST 314 } 315 return s.svc.Config() 316 } 317 318 var newServiceManager = func() (ServiceManager, error) { 319 m, err := newConn() 320 if err != nil { 321 return nil, errors.Trace(err) 322 } 323 return &SvcManager{ 324 mgr: m, 325 }, nil 326 }