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  }