mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
8.2 KiB
316 lines
8.2 KiB
// 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 |
|
}
|
|
|