Add osdetail package for special os integration needs

This commit is contained in:
Daniel 2019-04-26 11:38:50 +02:00
parent 682264acc5
commit e97504dfba
5 changed files with 242 additions and 0 deletions

View 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
}

View 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()
}

View 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)
}
}

View 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)
}

View file

@ -0,0 +1,9 @@
package osdetail
import "testing"
func TestWindowsVersion(t *testing.T) {
if WindowsVersion() == "" {
t.Fatal("could not get windows version")
}
}