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  }