
full diff: https://github.com/containerd/consolve/compare/v1.0.0...v1.0.1
vendor golang.org/x/sys 2334cc1a136f0a5b4384f1a27e3634457be08553
full diff: ed371f2e16...2334cc1a13
- unix: add Darwin support for clonefile syscalls
- Adds openat2 for linux
openat2 is a new syscall added to Linux 5.6. It provides a superset of
openat(2) functionality, extending it with flags telling the kernel how
to resolve the paths.
For more info, see https://lwn.net/Articles/803237/
NOTE that this is a second attempt to add the call; the previous one
(https://golang.org/cl/227280) was reverted
(https://golang.org/cl/227846) due to the test case failure on ARM
(https://golang.org/issue/38357).
This CL has the test case reworked to be less assumptive to the testing
environment. In particular, it first tries if the most simplistic
openat2() call succeeds, and skips the test otherwise. It is done that
way because CI can be under under different kernels and in various
envrionments -- in particular, Docker+seccomp can result in EPERM from a
system call (which is not expected otherwise).
For previous discussions about the test case, see
https://golang.org/cl/227865.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
377 lines
11 KiB
Go
377 lines
11 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/internal/unsafeheader"
|
|
"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)
|
|
ParamChange = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE)
|
|
NetBindAdd = Cmd(windows.SERVICE_CONTROL_NETBINDADD)
|
|
NetBindRemove = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE)
|
|
NetBindEnable = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE)
|
|
NetBindDisable = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE)
|
|
DeviceEvent = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT)
|
|
HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE)
|
|
PowerEvent = Cmd(windows.SERVICE_CONTROL_POWEREVENT)
|
|
SessionChange = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE)
|
|
PreShutdown = Cmd(windows.SERVICE_CONTROL_PRESHUTDOWN)
|
|
)
|
|
|
|
// 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)
|
|
AcceptParamChange = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
|
|
AcceptNetBindChange = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE)
|
|
AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE)
|
|
AcceptPowerEvent = Accepted(windows.SERVICE_ACCEPT_POWEREVENT)
|
|
AcceptSessionChange = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE)
|
|
AcceptPreShutdown = Accepted(windows.SERVICE_ACCEPT_PRESHUTDOWN)
|
|
)
|
|
|
|
// 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
|
|
ProcessId uint32 // if the service is running, the process identifier of it, and otherwise zero
|
|
}
|
|
|
|
// ChangeRequest is sent to the service Handler to request service status change.
|
|
type ChangeRequest struct {
|
|
Cmd Cmd
|
|
EventType uint32
|
|
EventData uintptr
|
|
CurrentStatus Status
|
|
Context uintptr
|
|
}
|
|
|
|
// 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
|
|
ctlHandlerExProc uintptr
|
|
cSetEvent uintptr
|
|
cWaitForSingleObject uintptr
|
|
cRegisterServiceCtrlHandlerExW uintptr
|
|
)
|
|
|
|
func init() {
|
|
k := windows.NewLazySystemDLL("kernel32.dll")
|
|
cSetEvent = k.NewProc("SetEvent").Addr()
|
|
cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr()
|
|
a := windows.NewLazySystemDLL("advapi32.dll")
|
|
cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr()
|
|
}
|
|
|
|
type ctlEvent struct {
|
|
cmd Cmd
|
|
eventType uint32
|
|
eventData uintptr
|
|
context uintptr
|
|
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 status.Accepts&AcceptParamChange != 0 {
|
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
|
|
}
|
|
if status.Accepts&AcceptNetBindChange != 0 {
|
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
|
|
}
|
|
if status.Accepts&AcceptHardwareProfileChange != 0 {
|
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
|
|
}
|
|
if status.Accepts&AcceptPowerEvent != 0 {
|
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
|
|
}
|
|
if status.Accepts&AcceptSessionChange != 0 {
|
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
|
|
}
|
|
if status.Accepts&AcceptPreShutdown != 0 {
|
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_PRESHUTDOWN
|
|
}
|
|
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)
|
|
|
|
var argv []*uint16
|
|
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&argv))
|
|
hdr.Data = unsafe.Pointer(sArgv)
|
|
hdr.Len = int(sArgc)
|
|
hdr.Cap = int(sArgc)
|
|
|
|
args := make([]string, len(argv))
|
|
for i, a := range argv {
|
|
args[i] = windows.UTF16PtrToString(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}
|
|
}()
|
|
|
|
ec := exitCode{isSvcSpecific: true, errno: 0}
|
|
outcr := ChangeRequest{
|
|
CurrentStatus: Status{State: Stopped},
|
|
}
|
|
var outch chan ChangeRequest
|
|
inch := s.c
|
|
loop:
|
|
for {
|
|
select {
|
|
case r := <-inch:
|
|
if r.errno != 0 {
|
|
ec.errno = r.errno
|
|
break loop
|
|
}
|
|
inch = nil
|
|
outch = cmdsToHandler
|
|
outcr.Cmd = r.cmd
|
|
outcr.EventType = r.eventType
|
|
outcr.EventData = r.eventData
|
|
outcr.Context = r.context
|
|
case outch <- outcr:
|
|
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
|
|
}
|
|
outcr.CurrentStatus = 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, evtype, evdata, context uintptr) uintptr {
|
|
e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context}
|
|
// 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
|
|
// Always return NO_ERROR (0) for now.
|
|
return windows.NO_ERROR
|
|
}
|
|
|
|
var svcmain uintptr
|
|
getServiceMain(&svcmain)
|
|
t := []windows.SERVICE_TABLE_ENTRY{
|
|
{ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain},
|
|
{ServiceName: nil, ServiceProc: 0},
|
|
}
|
|
|
|
goWaitsH = uintptr(s.goWaits.h)
|
|
cWaitsH = uintptr(s.cWaits.h)
|
|
sName = t[0].ServiceName
|
|
ctlHandlerExProc, err = newCallback(ctlHandler)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go s.run()
|
|
|
|
err = windows.StartServiceCtrlDispatcher(&t[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StatusHandle returns service status handle. It is safe to call this function
|
|
// from inside the Handler.Execute because then it is guaranteed to be set.
|
|
// This code will have to change once multiple services are possible per process.
|
|
func StatusHandle() windows.Handle {
|
|
return windows.Handle(ssHandle)
|
|
}
|