mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	* Dropped unused codekit config * Integrated dynamic and static bindata for public * Ignore public bindata * Add a general generate make task * Integrated flexible public assets into web command * Updated vendoring, added all missiong govendor deps * Made the linter happy with the bindata and dynamic code * Moved public bindata definition to modules directory * Ignoring the new bindata path now * Updated to the new public modules import path * Updated public bindata command and drop the new prefix
		
			
				
	
	
		
			317 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			317 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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
 | 
						|
}
 |