123 lines
2.9 KiB
Go
123 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"runtime"
|
|
"time"
|
|
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
F "github.com/sagernet/sing/common/format"
|
|
|
|
"golang.org/x/sys/windows/svc"
|
|
"golang.org/x/sys/windows/svc/eventlog"
|
|
)
|
|
|
|
var commandRunFlagAllowUnsafeInstallation bool
|
|
|
|
func init() {
|
|
commandRun.Flags().BoolVar(
|
|
&commandRunFlagAllowUnsafeInstallation,
|
|
"allow-unsafe-installation-directory-permissions",
|
|
false,
|
|
"allow unsafe daemon working directory ancestor permissions",
|
|
)
|
|
}
|
|
|
|
func runService() (bool, error) {
|
|
isWindowsService, err := svc.IsWindowsService()
|
|
if err != nil {
|
|
return true, E.Cause(err, "check windows service")
|
|
}
|
|
if !isWindowsService {
|
|
return false, nil
|
|
}
|
|
return true, svc.Run(serviceName, &windowsService{})
|
|
}
|
|
|
|
func preparePlatformWorkingDirectory() error {
|
|
if listenAddress != "" {
|
|
return os.MkdirAll(workingDirectory, 0o700)
|
|
}
|
|
serviceWorkingDirectory, err := resolveWindowsServiceWorkingDirectory(
|
|
workingDirectory,
|
|
commandRunFlagAllowUnsafeInstallation,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
workingDirectory = serviceWorkingDirectory
|
|
return ensureWindowsWorkingDirectory(serviceWorkingDirectory)
|
|
}
|
|
|
|
type windowsService struct{}
|
|
|
|
func (s *windowsService) Execute(arguments []string, requests <-chan svc.ChangeRequest, statuses chan<- svc.Status) (serviceSpecific bool, exitCode uint32) {
|
|
statuses <- svc.Status{State: svc.StartPending}
|
|
if listenAddress != "" {
|
|
exitCode = 1
|
|
serviceLogError(E.New("--listen is not allowed in service mode"))
|
|
return
|
|
}
|
|
err := allowAuthenticatedUsersToQueryCurrentProcess()
|
|
if err != nil {
|
|
exitCode = 1
|
|
serviceLogError(E.Cause(err, "secure daemon process"))
|
|
return
|
|
}
|
|
err = prepareWorkingDirectory()
|
|
if err != nil {
|
|
exitCode = 1
|
|
serviceLogError(err)
|
|
return
|
|
}
|
|
d, err := newDaemon()
|
|
if err != nil {
|
|
exitCode = 1
|
|
serviceLogError(err)
|
|
return
|
|
}
|
|
err = d.Start()
|
|
if err != nil {
|
|
exitCode = 1
|
|
serviceLogError(err)
|
|
return
|
|
}
|
|
statuses <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.AcceptSessionChange}
|
|
runtime.GC()
|
|
for request := range requests {
|
|
if request.Cmd == svc.Interrogate {
|
|
statuses <- request.CurrentStatus
|
|
continue
|
|
}
|
|
if request.Cmd == svc.SessionChange {
|
|
err = d.handlePlatformSessionChange(request.EventType, uint32(request.EventData))
|
|
if err != nil {
|
|
serviceLogError(E.Cause(err, "handle session change"))
|
|
}
|
|
continue
|
|
}
|
|
if request.Cmd == svc.Stop || request.Cmd == svc.Shutdown {
|
|
break
|
|
}
|
|
serviceLogError(E.New("unexpected service command: ", uint32(request.Cmd)))
|
|
}
|
|
statuses <- svc.Status{State: svc.StopPending}
|
|
watchdog := time.AfterFunc(3*time.Second, func() {
|
|
serviceLogError(E.New("daemon did not close"))
|
|
os.Exit(1)
|
|
})
|
|
d.Close()
|
|
watchdog.Stop()
|
|
statuses <- svc.Status{State: svc.Stopped}
|
|
return
|
|
}
|
|
|
|
func serviceLogError(err error) {
|
|
eventLog, openError := eventlog.Open(serviceName)
|
|
if openError != nil {
|
|
return
|
|
}
|
|
eventLog.Error(1, F.ToString(err))
|
|
eventLog.Close()
|
|
}
|