safing-portmaster/cmds/portmaster-start/service_windows.go
2022-10-17 11:55:48 -07:00

134 lines
3.3 KiB
Go

package main
// Based on the official Go examples from
// https://github.com/golang/sys/blob/master/windows/svc/example
// by The Go Authors.
// Original LICENSE (sha256sum: 2d36597f7117c38b006835ae7f537487207d8ec407aa9d9980794b2030cbc067) can be found in vendor/pkg cache directory.
import (
"fmt"
"log"
"sync"
"time"
"github.com/spf13/cobra"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/debug"
)
var (
runCoreService = &cobra.Command{
Use: "core-service",
Short: "Run the Portmaster Core as a Windows Service",
RunE: runAndLogControlError(func(cmd *cobra.Command, args []string) error {
return runService(cmd, &Options{
Name: "Portmaster Core Service",
Identifier: "core/portmaster-core",
ShortIdentifier: "core",
AllowDownload: true,
AllowHidingWindow: false,
NoOutput: true,
RestartOnFail: true,
}, args)
}),
FParseErrWhitelist: cobra.FParseErrWhitelist{
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
UnknownFlags: true,
},
}
// wait groups
runWg sync.WaitGroup
finishWg sync.WaitGroup
)
func init() {
rootCmd.AddCommand(runCoreService)
}
const serviceName = "PortmasterCore"
type windowsService struct{}
func (ws *windowsService) Execute(args []string, changeRequests <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
changes <- svc.Status{State: svc.StartPending}
service:
for {
select {
case <-startupComplete:
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
case <-shuttingDown:
changes <- svc.Status{State: svc.StopPending}
break service
case c := <-changeRequests:
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
initiateShutdown(nil)
default:
log.Printf("unexpected control request: #%d\n", c)
}
}
}
// define return values
if getShutdownError() != nil {
ssec = true // this error is specific to this service (ie. custom)
errno = 1 // generic error, check logs / windows events
}
// wait until everything else is finished
finishWg.Wait()
// send stopped status
changes <- svc.Status{State: svc.Stopped}
// wait a little for the status to reach Windows
time.Sleep(100 * time.Millisecond)
return ssec, errno
}
func runService(_ *cobra.Command, opts *Options, cmdArgs []string) error {
// check if we are running interactively
isDebug, err := svc.IsAnInteractiveSession()
if err != nil {
return fmt.Errorf("could not determine if running interactively: %s", err)
}
// select service run type
svcRun := svc.Run
if isDebug {
log.Printf("WARNING: running interactively, switching to debug execution (no real service).\n")
svcRun = debug.Run
}
runWg.Add(2)
finishWg.Add(1)
// run service client
go func() {
sErr := svcRun(serviceName, &windowsService{})
initiateShutdown(sErr)
runWg.Done()
}()
// run service
go func() {
// run slightly delayed
time.Sleep(250 * time.Millisecond)
err := run(opts, getExecArgs(opts, cmdArgs))
initiateShutdown(err)
finishWg.Done()
runWg.Done()
}()
runWg.Wait()
err = getShutdownError()
if err != nil {
log.Printf("%s service experienced an error: %s\n", serviceName, err)
}
return err
}