mirror of
https://github.com/golang/sys.git
synced 2026-02-09 04:06:04 +03:00
windows/svc: add IsWindowsService function
CL 244958 includes isWindowsService function that determines if a process is running as a service. The code of the function is based on public .Net implementation. IsAnInteractiveSession function implements similar functionality, but is based on an old Stackoverflow post., which is not as authoritative as code written by Microsoft for their official product. This change copies CL 244958 isWindowsService function into svc package and makes it public. The intention is that future users will prefer IsWindowsService to IsAnInteractiveSession. Also this change adds "Deprecated" comment to IsAnInteractiveSession to point future users to IsWindowsService. Call to IsAnInteractiveSession is also replaced with IsWindowsService in golang.org/x/sys/windows/svc/example package. Change-Id: I4a33b7f590ee8161d1134d8e83668e9da4e6b434 Reviewed-on: https://go-review.googlesource.com/c/sys/+/259397 Run-TryBot: Alex Brainman <alex.brainman@gmail.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Trust: Brad Fitzpatrick <bradfitz@golang.org> Trust: Alex Brainman <alex.brainman@gmail.com>
This commit is contained in:
@@ -36,11 +36,11 @@ func usage(errmsg string) {
|
||||
func main() {
|
||||
const svcName = "myservice"
|
||||
|
||||
isIntSess, err := svc.IsAnInteractiveSession()
|
||||
inService, err := svc.IsWindowsService()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to determine if we are running in an interactive session: %v", err)
|
||||
log.Fatalf("failed to determine if we are running in service: %v", err)
|
||||
}
|
||||
if !isIntSess {
|
||||
if inService {
|
||||
runService(svcName, false)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
@@ -23,6 +27,8 @@ func allocSid(subAuth0 uint32) (*windows.SID, error) {
|
||||
// IsAnInteractiveSession determines if calling process is running interactively.
|
||||
// It queries the process token for membership in the Interactive group.
|
||||
// http://stackoverflow.com/questions/2668851/how-do-i-detect-that-my-application-is-running-as-service-or-in-an-interactive-s
|
||||
//
|
||||
// Deprecated: Use IsWindowsService instead.
|
||||
func IsAnInteractiveSession() (bool, error) {
|
||||
interSid, err := allocSid(windows.SECURITY_INTERACTIVE_RID)
|
||||
if err != nil {
|
||||
@@ -57,3 +63,95 @@ func IsAnInteractiveSession() (bool, error) {
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ntdll = windows.NewLazySystemDLL("ntdll.dll")
|
||||
_NtQueryInformationProcess = ntdll.NewProc("NtQueryInformationProcess")
|
||||
|
||||
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
_QueryFullProcessImageNameA = kernel32.NewProc("QueryFullProcessImageNameA")
|
||||
)
|
||||
|
||||
// IsWindowsService reports whether the process is currently executing
|
||||
// as a Windows service.
|
||||
func IsWindowsService() (bool, error) {
|
||||
// This code was copied from runtime.isWindowsService function.
|
||||
|
||||
// The below technique looks a bit hairy, but it's actually
|
||||
// exactly what the .NET framework does for the similarly named function:
|
||||
// https://github.com/dotnet/extensions/blob/f4066026ca06984b07e90e61a6390ac38152ba93/src/Hosting/WindowsServices/src/WindowsServiceHelpers.cs#L26-L31
|
||||
// Specifically, it looks up whether the parent process has session ID zero
|
||||
// and is called "services".
|
||||
const _CURRENT_PROCESS = ^uintptr(0)
|
||||
// pbi is a PROCESS_BASIC_INFORMATION struct, where we just care about
|
||||
// the 6th pointer inside of it, which contains the pid of the process
|
||||
// parent:
|
||||
// https://github.com/wine-mirror/wine/blob/42cb7d2ad1caba08de235e6319b9967296b5d554/include/winternl.h#L1294
|
||||
var pbi [6]uintptr
|
||||
var pbiLen uint32
|
||||
r0, _, _ := syscall.Syscall6(_NtQueryInformationProcess.Addr(), 5, _CURRENT_PROCESS, 0, uintptr(unsafe.Pointer(&pbi[0])), uintptr(unsafe.Sizeof(pbi)), uintptr(unsafe.Pointer(&pbiLen)), 0)
|
||||
if r0 != 0 {
|
||||
return false, errors.New("NtQueryInformationProcess failed: error=" + itoa(int(r0)))
|
||||
}
|
||||
var psid uint32
|
||||
err := windows.ProcessIdToSessionId(uint32(pbi[5]), &psid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if psid != 0 {
|
||||
// parent session id should be 0 for service process
|
||||
return false, nil
|
||||
}
|
||||
|
||||
pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi[5]))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer windows.CloseHandle(pproc)
|
||||
|
||||
// exeName gets the path to the executable image of the parent process
|
||||
var exeName [261]byte
|
||||
exeNameLen := uint32(len(exeName) - 1)
|
||||
r0, _, e0 := syscall.Syscall6(_QueryFullProcessImageNameA.Addr(), 4, uintptr(pproc), 0, uintptr(unsafe.Pointer(&exeName[0])), uintptr(unsafe.Pointer(&exeNameLen)), 0, 0)
|
||||
if r0 == 0 {
|
||||
if e0 != 0 {
|
||||
return false, e0
|
||||
} else {
|
||||
return false, syscall.EINVAL
|
||||
}
|
||||
}
|
||||
const (
|
||||
servicesLower = "services.exe"
|
||||
servicesUpper = "SERVICES.EXE"
|
||||
)
|
||||
i := int(exeNameLen) - 1
|
||||
j := len(servicesLower) - 1
|
||||
if i < j {
|
||||
return false, nil
|
||||
}
|
||||
for {
|
||||
if j == -1 {
|
||||
return i == -1 || exeName[i] == '\\', nil
|
||||
}
|
||||
if exeName[i] != servicesLower[j] && exeName[i] != servicesUpper[j] {
|
||||
return false, nil
|
||||
}
|
||||
i--
|
||||
j--
|
||||
}
|
||||
}
|
||||
|
||||
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
|
||||
if val < 0 {
|
||||
return "-" + itoa(-val)
|
||||
}
|
||||
var buf [32]byte // big enough for int64
|
||||
i := len(buf) - 1
|
||||
for val >= 10 {
|
||||
buf[i] = byte(val%10 + '0')
|
||||
i--
|
||||
val /= 10
|
||||
}
|
||||
buf[i] = byte(val + '0')
|
||||
return string(buf[i:])
|
||||
}
|
||||
|
||||
@@ -132,3 +132,23 @@ func TestExample(t *testing.T) {
|
||||
t.Errorf("%q string does not contain %q", string(out), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnInteractiveSession(t *testing.T) {
|
||||
isInteractive, err := svc.IsAnInteractiveSession()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !isInteractive {
|
||||
t.Error("IsAnInteractiveSession retuns false when running interactively.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsWindowsService(t *testing.T) {
|
||||
isSvc, err := svc.IsWindowsService()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if isSvc {
|
||||
t.Error("IsWindowsService retuns true when not running in a service.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +270,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
|
||||
//sys RegEnumKeyEx(key Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, classLen *uint32, lastWriteTime *Filetime) (regerrno error) = advapi32.RegEnumKeyExW
|
||||
//sys RegQueryValueEx(key Handle, name *uint16, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegQueryValueExW
|
||||
//sys GetCurrentProcessId() (pid uint32) = kernel32.GetCurrentProcessId
|
||||
//sys ProcessIdToSessionId(pid uint32, sessionid *uint32) (err error) = kernel32.ProcessIdToSessionId
|
||||
//sys GetConsoleMode(console Handle, mode *uint32) (err error) = kernel32.GetConsoleMode
|
||||
//sys SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode
|
||||
//sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo
|
||||
|
||||
@@ -180,6 +180,7 @@ var (
|
||||
procRegEnumKeyExW = modadvapi32.NewProc("RegEnumKeyExW")
|
||||
procRegQueryValueExW = modadvapi32.NewProc("RegQueryValueExW")
|
||||
procGetCurrentProcessId = modkernel32.NewProc("GetCurrentProcessId")
|
||||
procProcessIdToSessionId = modkernel32.NewProc("ProcessIdToSessionId")
|
||||
procGetConsoleMode = modkernel32.NewProc("GetConsoleMode")
|
||||
procSetConsoleMode = modkernel32.NewProc("SetConsoleMode")
|
||||
procGetConsoleScreenBufferInfo = modkernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
@@ -1943,6 +1944,18 @@ func GetCurrentProcessId() (pid uint32) {
|
||||
return
|
||||
}
|
||||
|
||||
func ProcessIdToSessionId(pid uint32, sessionid *uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procProcessIdToSessionId.Addr(), 2, uintptr(pid), uintptr(unsafe.Pointer(sessionid)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetConsoleMode(console Handle, mode *uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(mode)), 0)
|
||||
if r1 == 0 {
|
||||
|
||||
Reference in New Issue
Block a user