| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 | 
							- // Copyright 2012 The Go Authors. All rights reserved.
 
- // Use of this source code is governed by a BSD-style
 
- // license that can be found in the LICENSE file.
 
- // +build windows
 
- // Package svc provides everything required to build Windows service.
 
- //
 
- package svc
 
- import (
 
- 	"errors"
 
- 	"runtime"
 
- 	"syscall"
 
- 	"unsafe"
 
- 	"golang.org/x/sys/windows"
 
- )
 
- // State describes service execution state (Stopped, Running and so on).
 
- type State uint32
 
- const (
 
- 	Stopped         = State(windows.SERVICE_STOPPED)
 
- 	StartPending    = State(windows.SERVICE_START_PENDING)
 
- 	StopPending     = State(windows.SERVICE_STOP_PENDING)
 
- 	Running         = State(windows.SERVICE_RUNNING)
 
- 	ContinuePending = State(windows.SERVICE_CONTINUE_PENDING)
 
- 	PausePending    = State(windows.SERVICE_PAUSE_PENDING)
 
- 	Paused          = State(windows.SERVICE_PAUSED)
 
- )
 
- // Cmd represents service state change request. It is sent to a service
 
- // by the service manager, and should be actioned upon by the service.
 
- type Cmd uint32
 
- const (
 
- 	Stop        = Cmd(windows.SERVICE_CONTROL_STOP)
 
- 	Pause       = Cmd(windows.SERVICE_CONTROL_PAUSE)
 
- 	Continue    = Cmd(windows.SERVICE_CONTROL_CONTINUE)
 
- 	Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
 
- 	Shutdown    = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
 
- )
 
- // Accepted is used to describe commands accepted by the service.
 
- // Note that Interrogate is always accepted.
 
- type Accepted uint32
 
- const (
 
- 	AcceptStop             = Accepted(windows.SERVICE_ACCEPT_STOP)
 
- 	AcceptShutdown         = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
 
- 	AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
 
- )
 
- // Status combines State and Accepted commands to fully describe running service.
 
- type Status struct {
 
- 	State      State
 
- 	Accepts    Accepted
 
- 	CheckPoint uint32 // used to report progress during a lengthy operation
 
- 	WaitHint   uint32 // estimated time required for a pending operation, in milliseconds
 
- }
 
- // ChangeRequest is sent to the service Handler to request service status change.
 
- type ChangeRequest struct {
 
- 	Cmd           Cmd
 
- 	CurrentStatus Status
 
- }
 
- // Handler is the interface that must be implemented to build Windows service.
 
- type Handler interface {
 
- 	// Execute will be called by the package code at the start of
 
- 	// the service, and the service will exit once Execute completes.
 
- 	// Inside Execute you must read service change requests from r and
 
- 	// act accordingly. You must keep service control manager up to date
 
- 	// about state of your service by writing into s as required.
 
- 	// args contains service name followed by argument strings passed
 
- 	// to the service.
 
- 	// You can provide service exit code in exitCode return parameter,
 
- 	// with 0 being "no error". You can also indicate if exit code,
 
- 	// if any, is service specific or not by using svcSpecificEC
 
- 	// parameter.
 
- 	Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
 
- }
 
- var (
 
- 	// These are used by asm code.
 
- 	goWaitsH                     uintptr
 
- 	cWaitsH                      uintptr
 
- 	ssHandle                     uintptr
 
- 	sName                        *uint16
 
- 	sArgc                        uintptr
 
- 	sArgv                        **uint16
 
- 	ctlHandlerProc               uintptr
 
- 	cSetEvent                    uintptr
 
- 	cWaitForSingleObject         uintptr
 
- 	cRegisterServiceCtrlHandlerW uintptr
 
- )
 
- func init() {
 
- 	k := syscall.MustLoadDLL("kernel32.dll")
 
- 	cSetEvent = k.MustFindProc("SetEvent").Addr()
 
- 	cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr()
 
- 	a := syscall.MustLoadDLL("advapi32.dll")
 
- 	cRegisterServiceCtrlHandlerW = a.MustFindProc("RegisterServiceCtrlHandlerW").Addr()
 
- }
 
- type ctlEvent struct {
 
- 	cmd   Cmd
 
- 	errno uint32
 
- }
 
- // service provides access to windows service api.
 
- type service struct {
 
- 	name    string
 
- 	h       windows.Handle
 
- 	cWaits  *event
 
- 	goWaits *event
 
- 	c       chan ctlEvent
 
- 	handler Handler
 
- }
 
- func newService(name string, handler Handler) (*service, error) {
 
- 	var s service
 
- 	var err error
 
- 	s.name = name
 
- 	s.c = make(chan ctlEvent)
 
- 	s.handler = handler
 
- 	s.cWaits, err = newEvent()
 
- 	if err != nil {
 
- 		return nil, err
 
- 	}
 
- 	s.goWaits, err = newEvent()
 
- 	if err != nil {
 
- 		s.cWaits.Close()
 
- 		return nil, err
 
- 	}
 
- 	return &s, nil
 
- }
 
- func (s *service) close() error {
 
- 	s.cWaits.Close()
 
- 	s.goWaits.Close()
 
- 	return nil
 
- }
 
- type exitCode struct {
 
- 	isSvcSpecific bool
 
- 	errno         uint32
 
- }
 
- func (s *service) updateStatus(status *Status, ec *exitCode) error {
 
- 	if s.h == 0 {
 
- 		return errors.New("updateStatus with no service status handle")
 
- 	}
 
- 	var t windows.SERVICE_STATUS
 
- 	t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
 
- 	t.CurrentState = uint32(status.State)
 
- 	if status.Accepts&AcceptStop != 0 {
 
- 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
 
- 	}
 
- 	if status.Accepts&AcceptShutdown != 0 {
 
- 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
 
- 	}
 
- 	if status.Accepts&AcceptPauseAndContinue != 0 {
 
- 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
 
- 	}
 
- 	if ec.errno == 0 {
 
- 		t.Win32ExitCode = windows.NO_ERROR
 
- 		t.ServiceSpecificExitCode = windows.NO_ERROR
 
- 	} else if ec.isSvcSpecific {
 
- 		t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
 
- 		t.ServiceSpecificExitCode = ec.errno
 
- 	} else {
 
- 		t.Win32ExitCode = ec.errno
 
- 		t.ServiceSpecificExitCode = windows.NO_ERROR
 
- 	}
 
- 	t.CheckPoint = status.CheckPoint
 
- 	t.WaitHint = status.WaitHint
 
- 	return windows.SetServiceStatus(s.h, &t)
 
- }
 
- const (
 
- 	sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
 
- 	sysErrNewThreadInCallback
 
- )
 
- func (s *service) run() {
 
- 	s.goWaits.Wait()
 
- 	s.h = windows.Handle(ssHandle)
 
- 	argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc]
 
- 	args := make([]string, len(argv))
 
- 	for i, a := range argv {
 
- 		args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:])
 
- 	}
 
- 	cmdsToHandler := make(chan ChangeRequest)
 
- 	changesFromHandler := make(chan Status)
 
- 	exitFromHandler := make(chan exitCode)
 
- 	go func() {
 
- 		ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
 
- 		exitFromHandler <- exitCode{ss, errno}
 
- 	}()
 
- 	status := Status{State: Stopped}
 
- 	ec := exitCode{isSvcSpecific: true, errno: 0}
 
- 	var outch chan ChangeRequest
 
- 	inch := s.c
 
- 	var cmd Cmd
 
- loop:
 
- 	for {
 
- 		select {
 
- 		case r := <-inch:
 
- 			if r.errno != 0 {
 
- 				ec.errno = r.errno
 
- 				break loop
 
- 			}
 
- 			inch = nil
 
- 			outch = cmdsToHandler
 
- 			cmd = r.cmd
 
- 		case outch <- ChangeRequest{cmd, status}:
 
- 			inch = s.c
 
- 			outch = nil
 
- 		case c := <-changesFromHandler:
 
- 			err := s.updateStatus(&c, &ec)
 
- 			if err != nil {
 
- 				// best suitable error number
 
- 				ec.errno = sysErrSetServiceStatusFailed
 
- 				if err2, ok := err.(syscall.Errno); ok {
 
- 					ec.errno = uint32(err2)
 
- 				}
 
- 				break loop
 
- 			}
 
- 			status = c
 
- 		case ec = <-exitFromHandler:
 
- 			break loop
 
- 		}
 
- 	}
 
- 	s.updateStatus(&Status{State: Stopped}, &ec)
 
- 	s.cWaits.Set()
 
- }
 
- func newCallback(fn interface{}) (cb uintptr, err error) {
 
- 	defer func() {
 
- 		r := recover()
 
- 		if r == nil {
 
- 			return
 
- 		}
 
- 		cb = 0
 
- 		switch v := r.(type) {
 
- 		case string:
 
- 			err = errors.New(v)
 
- 		case error:
 
- 			err = v
 
- 		default:
 
- 			err = errors.New("unexpected panic in syscall.NewCallback")
 
- 		}
 
- 	}()
 
- 	return syscall.NewCallback(fn), nil
 
- }
 
- // BUG(brainman): There is no mechanism to run multiple services
 
- // inside one single executable. Perhaps, it can be overcome by
 
- // using RegisterServiceCtrlHandlerEx Windows api.
 
- // Run executes service name by calling appropriate handler function.
 
- func Run(name string, handler Handler) error {
 
- 	runtime.LockOSThread()
 
- 	tid := windows.GetCurrentThreadId()
 
- 	s, err := newService(name, handler)
 
- 	if err != nil {
 
- 		return err
 
- 	}
 
- 	ctlHandler := func(ctl uint32) uintptr {
 
- 		e := ctlEvent{cmd: Cmd(ctl)}
 
- 		// We assume that this callback function is running on
 
- 		// the same thread as Run. Nowhere in MS documentation
 
- 		// I could find statement to guarantee that. So putting
 
- 		// check here to verify, otherwise things will go bad
 
- 		// quickly, if ignored.
 
- 		i := windows.GetCurrentThreadId()
 
- 		if i != tid {
 
- 			e.errno = sysErrNewThreadInCallback
 
- 		}
 
- 		s.c <- e
 
- 		return 0
 
- 	}
 
- 	var svcmain uintptr
 
- 	getServiceMain(&svcmain)
 
- 	t := []windows.SERVICE_TABLE_ENTRY{
 
- 		{syscall.StringToUTF16Ptr(s.name), svcmain},
 
- 		{nil, 0},
 
- 	}
 
- 	goWaitsH = uintptr(s.goWaits.h)
 
- 	cWaitsH = uintptr(s.cWaits.h)
 
- 	sName = t[0].ServiceName
 
- 	ctlHandlerProc, err = newCallback(ctlHandler)
 
- 	if err != nil {
 
- 		return err
 
- 	}
 
- 	go s.run()
 
- 	err = windows.StartServiceCtrlDispatcher(&t[0])
 
- 	if err != nil {
 
- 		return err
 
- 	}
 
- 	return nil
 
- }
 
 
  |