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