mirror of
https://github.com/safing/portbase
synced 2025-09-01 10:09:50 +00:00
Add osdetail package for special os integration needs
This commit is contained in:
parent
682264acc5
commit
e97504dfba
5 changed files with 242 additions and 0 deletions
50
utils/osdetail/colors_windows.go
Normal file
50
utils/osdetail/colors_windows.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package osdetail
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
colorSupport bool
|
||||
|
||||
colorSupportChecked bool
|
||||
checkingColorSupport sync.Mutex
|
||||
)
|
||||
|
||||
// EnableColorSupport tries to enable color support for cmd on windows and returns whether it is enabled.
|
||||
func EnableColorSupport() bool {
|
||||
checkingColorSupport.Lock()
|
||||
defer checkingColorSupport.Unlock()
|
||||
|
||||
if !colorSupportChecked {
|
||||
colorSupport = enableColorSupport()
|
||||
}
|
||||
return colorSupport
|
||||
}
|
||||
|
||||
func enableColorSupport() bool {
|
||||
if IsWindowsVersion("10.") {
|
||||
|
||||
// check if windows.Stdout is file
|
||||
if windows.GetFileInformationByHandle(windows.Stdout, &windows.ByHandleFileInformation{}) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var mode uint32
|
||||
err := windows.GetConsoleMode(windows.Stdout, &mode)
|
||||
if err == nil {
|
||||
if mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
|
||||
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
err = windows.SetConsoleMode(windows.Stdout, mode)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
17
utils/osdetail/dnscache_windows.go
Normal file
17
utils/osdetail/dnscache_windows.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package osdetail
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// EnableDNSCache enables the Windows Service "DNS Client" by setting the registry value "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Dnscache" to 2 (Automatic).
|
||||
// A reboot is required for this setting to take effect.
|
||||
func EnableDNSCache() error {
|
||||
return exec.Command("reg", "add", "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\Dnscache", "/v", "Start", "/t", "REG_DWORD", "/d", "2", "/f").Run()
|
||||
}
|
||||
|
||||
// DisableDNSCache disables the Windows Service "DNS Client" by setting the registry value "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Dnscache" to 4 (Disabled).
|
||||
// A reboot is required for this setting to take effect.
|
||||
func DisableDNSCache() error {
|
||||
return exec.Command("reg", "add", "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\Dnscache", "/v", "Start", "/t", "REG_DWORD", "/d", "4", "/f").Run()
|
||||
}
|
112
utils/osdetail/service_windows.go
Normal file
112
utils/osdetail/service_windows.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package osdetail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Service Status
|
||||
const (
|
||||
StatusUnknown uint8 = iota
|
||||
StatusRunningStoppable
|
||||
StatusRunningNotStoppable
|
||||
StatusStartPending
|
||||
StatusStopPending
|
||||
StatusStopped
|
||||
)
|
||||
|
||||
// Exported errors
|
||||
var (
|
||||
ErrServiceNotStoppable = errors.New("the service is not stoppable")
|
||||
)
|
||||
|
||||
// GetServiceStatus returns the current status of a Windows Service (limited implementation).
|
||||
func GetServiceStatus(name string) (status uint8, err error) {
|
||||
|
||||
output, err := exec.Command("sc", "query", name).Output()
|
||||
if err != nil {
|
||||
return StatusUnknown, fmt.Errorf("failed to query service: %s", err)
|
||||
}
|
||||
outputString := string(output)
|
||||
|
||||
switch {
|
||||
case strings.Contains(outputString, "RUNNING"):
|
||||
if strings.Contains(outputString, "NOT_STOPPABLE") {
|
||||
return StatusRunningNotStoppable, nil
|
||||
}
|
||||
return StatusRunningStoppable, nil
|
||||
case strings.Contains(outputString, "STOP_PENDING"):
|
||||
return StatusStopPending, nil
|
||||
case strings.Contains(outputString, "STOPPED"):
|
||||
return StatusStopped, nil
|
||||
case strings.Contains(outputString, "START_PENDING"):
|
||||
return StatusStopPending, nil
|
||||
}
|
||||
|
||||
return StatusUnknown, errors.New("unknown service status")
|
||||
}
|
||||
|
||||
// StopService stops a Windows Service.
|
||||
func StopService(name string) (err error) {
|
||||
pendingCnt := 0
|
||||
for {
|
||||
|
||||
// get status
|
||||
status, err := GetServiceStatus(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch status {
|
||||
case StatusRunningStoppable:
|
||||
err := exec.Command("sc", "stop", name).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop service: %s", err)
|
||||
}
|
||||
case StatusRunningNotStoppable:
|
||||
return ErrServiceNotStoppable
|
||||
case StatusStartPending, StatusStopPending:
|
||||
pendingCnt++
|
||||
if pendingCnt > 50 {
|
||||
return errors.New("service stuck in pending status (5s)")
|
||||
}
|
||||
case StatusStopped:
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// SartService starts a Windows Service.
|
||||
func SartService(name string) (err error) {
|
||||
pendingCnt := 0
|
||||
for {
|
||||
|
||||
// get status
|
||||
status, err := GetServiceStatus(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch status {
|
||||
case StatusRunningStoppable, StatusRunningNotStoppable:
|
||||
return nil
|
||||
case StatusStartPending, StatusStopPending:
|
||||
pendingCnt++
|
||||
if pendingCnt > 50 {
|
||||
return errors.New("service stuck in pending status (5s)")
|
||||
}
|
||||
case StatusStopped:
|
||||
err := exec.Command("sc", "start", name).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop service: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
54
utils/osdetail/version_windows.go
Normal file
54
utils/osdetail/version_windows.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package osdetail
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
versionRe = regexp.MustCompile(`[0-9\.]+`)
|
||||
|
||||
windowsVersion string
|
||||
|
||||
fetching sync.Mutex
|
||||
fetched bool
|
||||
)
|
||||
|
||||
func fetchVersion() {
|
||||
if !fetched {
|
||||
fetched = true
|
||||
|
||||
output, err := exec.Command("cmd", "ver").Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
match := versionRe.Find(output)
|
||||
if match == nil {
|
||||
return
|
||||
}
|
||||
|
||||
windowsVersion = string(match)
|
||||
}
|
||||
}
|
||||
|
||||
// WindowsVersion returns the current Windows version.
|
||||
func WindowsVersion() string {
|
||||
fetching.Lock()
|
||||
defer fetching.Unlock()
|
||||
fetchVersion()
|
||||
|
||||
return windowsVersion
|
||||
}
|
||||
|
||||
// IsWindowsVersion returns whether the given version matches (HasPrefix) the current Windows version.
|
||||
func IsWindowsVersion(version string) bool {
|
||||
fetching.Lock()
|
||||
defer fetching.Unlock()
|
||||
fetchVersion()
|
||||
|
||||
// TODO: we can do better.
|
||||
return strings.HasPrefix(windowsVersion, version)
|
||||
}
|
9
utils/osdetail/version_windows_test.go
Normal file
9
utils/osdetail/version_windows_test.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package osdetail
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestWindowsVersion(t *testing.T) {
|
||||
if WindowsVersion() == "" {
|
||||
t.Fatal("could not get windows version")
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue