TUN-1451: Make non-interactive, non-service execution possible on Windows
This commit is contained in:
parent
abdbc76a46
commit
fea3569956
|
@ -8,6 +8,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
@ -30,6 +31,10 @@ const (
|
||||||
// not defined in golang.org/x/sys/windows package
|
// not defined in golang.org/x/sys/windows package
|
||||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681988(v=vs.85).aspx
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681988(v=vs.85).aspx
|
||||||
serviceConfigFailureActionsFlag = 4
|
serviceConfigFailureActionsFlag = 4
|
||||||
|
|
||||||
|
// ERROR_FAILED_SERVICE_CONTROLLER_CONNECT
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--1000-1299-
|
||||||
|
serviceControllerConnectionFailure = 1063
|
||||||
)
|
)
|
||||||
|
|
||||||
func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
|
func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
|
||||||
|
@ -50,16 +55,48 @@ func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// `IsAnInteractiveSession()` isn't exactly equivalent to "should the
|
||||||
|
// process run as a normal EXE?" There are legitimate non-service cases,
|
||||||
|
// like running cloudflared in a GCP startup script, for which
|
||||||
|
// `IsAnInteractiveSession()` returns false. For more context, see:
|
||||||
|
// https://github.com/judwhite/go-svc/issues/6
|
||||||
|
// It seems that the "correct way" to check "is this a normal EXE?" is:
|
||||||
|
// 1. attempt to connect to the Service Control Manager
|
||||||
|
// 2. get ERROR_FAILED_SERVICE_CONTROLLER_CONNECT
|
||||||
|
// This involves actually trying to start the service.
|
||||||
|
|
||||||
isIntSess, err := svc.IsAnInteractiveSession()
|
isIntSess, err := svc.IsAnInteractiveSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("failed to determine if we are running in an interactive session: %v", err)
|
logger.Fatalf("failed to determine if we are running in an interactive session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isIntSess {
|
if isIntSess {
|
||||||
app.Run(os.Args)
|
app.Run(os.Args)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run executes service name by calling windowsService which is a Handler
|
||||||
|
// interface that implements Execute method.
|
||||||
|
// It will set service status to stop after Execute returns
|
||||||
|
err = svc.Run(windowsServiceName, &windowsService{app: app, shutdownC: shutdownC, graceShutdownC: graceShutdownC})
|
||||||
|
if err != nil {
|
||||||
|
if errno, ok := err.(syscall.Errno); ok && int(errno) == serviceControllerConnectionFailure {
|
||||||
|
// Hack: assume this is a false negative from the IsAnInteractiveSession() check above.
|
||||||
|
// Run the app in "interactive" mode anyway.
|
||||||
|
app.Run(os.Args)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Fatalf("%s service failed: %v", windowsServiceName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type windowsService struct {
|
||||||
|
app *cli.App
|
||||||
|
shutdownC chan struct{}
|
||||||
|
graceShutdownC chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called by the package code at the start of the service
|
||||||
|
func (s *windowsService) Execute(serviceArgs []string, r <-chan svc.ChangeRequest, statusChan chan<- svc.Status) (ssec bool, errno uint32) {
|
||||||
elog, err := eventlog.Open(windowsServiceName)
|
elog, err := eventlog.Open(windowsServiceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Errorf("Cannot open event log for %s", windowsServiceName)
|
logger.WithError(err).Errorf("Cannot open event log for %s", windowsServiceName)
|
||||||
|
@ -68,26 +105,10 @@ func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
|
||||||
defer elog.Close()
|
defer elog.Close()
|
||||||
|
|
||||||
elog.Info(1, fmt.Sprintf("%s service starting", windowsServiceName))
|
elog.Info(1, fmt.Sprintf("%s service starting", windowsServiceName))
|
||||||
// Run executes service name by calling windowsService which is a Handler
|
defer func() {
|
||||||
// interface that implements Execute method.
|
elog.Info(1, fmt.Sprintf("%s service stopped", windowsServiceName))
|
||||||
// It will set service status to stop after Execute returns
|
}()
|
||||||
err = svc.Run(windowsServiceName, &windowsService{app: app, elog: elog, shutdownC: shutdownC, graceShutdownC: graceShutdownC})
|
|
||||||
if err != nil {
|
|
||||||
elog.Error(1, fmt.Sprintf("%s service failed: %v", windowsServiceName, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
elog.Info(1, fmt.Sprintf("%s service stopped", windowsServiceName))
|
|
||||||
}
|
|
||||||
|
|
||||||
type windowsService struct {
|
|
||||||
app *cli.App
|
|
||||||
elog *eventlog.Log
|
|
||||||
shutdownC chan struct{}
|
|
||||||
graceShutdownC chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// called by the package code at the start of the service
|
|
||||||
func (s *windowsService) Execute(serviceArgs []string, r <-chan svc.ChangeRequest, statusChan chan<- svc.Status) (ssec bool, errno uint32) {
|
|
||||||
// the arguments passed here are only meaningful if they were manually
|
// the arguments passed here are only meaningful if they were manually
|
||||||
// specified by the user, e.g. using the Services console or `sc start`.
|
// specified by the user, e.g. using the Services console or `sc start`.
|
||||||
// https://docs.microsoft.com/en-us/windows/desktop/services/service-entry-point
|
// https://docs.microsoft.com/en-us/windows/desktop/services/service-entry-point
|
||||||
|
@ -99,7 +120,7 @@ func (s *windowsService) Execute(serviceArgs []string, r <-chan svc.ChangeReques
|
||||||
// fall back to the arguments from ImagePath (or, as sc calls it, binPath)
|
// fall back to the arguments from ImagePath (or, as sc calls it, binPath)
|
||||||
args = os.Args
|
args = os.Args
|
||||||
}
|
}
|
||||||
s.elog.Info(1, fmt.Sprintf("%s service arguments: %v", windowsServiceName, args))
|
elog.Info(1, fmt.Sprintf("%s service arguments: %v", windowsServiceName, args))
|
||||||
|
|
||||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
||||||
statusChan <- svc.Status{State: svc.StartPending}
|
statusChan <- svc.Status{State: svc.StartPending}
|
||||||
|
@ -114,26 +135,26 @@ func (s *windowsService) Execute(serviceArgs []string, r <-chan svc.ChangeReques
|
||||||
case c := <-r:
|
case c := <-r:
|
||||||
switch c.Cmd {
|
switch c.Cmd {
|
||||||
case svc.Interrogate:
|
case svc.Interrogate:
|
||||||
s.elog.Info(1, fmt.Sprintf("control request 1 #%d", c))
|
elog.Info(1, fmt.Sprintf("control request 1 #%d", c))
|
||||||
statusChan <- c.CurrentStatus
|
statusChan <- c.CurrentStatus
|
||||||
case svc.Stop:
|
case svc.Stop:
|
||||||
s.elog.Info(1, "received stop control request")
|
elog.Info(1, "received stop control request")
|
||||||
close(s.graceShutdownC)
|
close(s.graceShutdownC)
|
||||||
statusChan <- svc.Status{State: svc.StopPending}
|
statusChan <- svc.Status{State: svc.StopPending}
|
||||||
case svc.Shutdown:
|
case svc.Shutdown:
|
||||||
s.elog.Info(1, "received shutdown control request")
|
elog.Info(1, "received shutdown control request")
|
||||||
close(s.shutdownC)
|
close(s.shutdownC)
|
||||||
statusChan <- svc.Status{State: svc.StopPending}
|
statusChan <- svc.Status{State: svc.StopPending}
|
||||||
default:
|
default:
|
||||||
s.elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
|
elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
|
||||||
}
|
}
|
||||||
case err := <-errC:
|
case err := <-errC:
|
||||||
ssec = true
|
ssec = true
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.elog.Error(1, fmt.Sprintf("cloudflared terminated with error %v", err))
|
elog.Error(1, fmt.Sprintf("cloudflared terminated with error %v", err))
|
||||||
errno = 1
|
errno = 1
|
||||||
} else {
|
} else {
|
||||||
s.elog.Info(1, "cloudflared terminated without error")
|
elog.Info(1, "cloudflared terminated without error")
|
||||||
errno = 0
|
errno = 0
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue