Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+475
View File
@@ -0,0 +1,475 @@
// SPDX-License-Identifier: BSD-3-Clause
package common
//
// gopsutil is a port of psutil(http://pythonhosted.org/psutil/).
// This covers these architectures.
// - linux (amd64, arm)
// - freebsd (amd64)
// - windows (amd64)
// - aix (ppc64)
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"math"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
"runtime"
"slices"
"strconv"
"strings"
"time"
"github.com/shirou/gopsutil/v4/common"
)
var (
Timeout = 3 * time.Second
ErrNotImplementedError = errors.New("not implemented yet")
ErrTimeout = errors.New("command timed out")
)
type Invoker interface {
Command(string, ...string) ([]byte, error)
CommandWithContext(context.Context, string, ...string) ([]byte, error)
}
type Invoke struct{}
func (i Invoke) Command(name string, arg ...string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), Timeout)
defer cancel()
return i.CommandWithContext(ctx, name, arg...)
}
func (Invoke) CommandWithContext(ctx context.Context, name string, arg ...string) ([]byte, error) {
cmd := exec.CommandContext(ctx, name, arg...)
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
if err := cmd.Start(); err != nil {
return buf.Bytes(), err
}
if err := cmd.Wait(); err != nil {
return buf.Bytes(), err
}
return buf.Bytes(), nil
}
type FakeInvoke struct {
Suffix string // Suffix species expected file name suffix such as "fail"
Error error // If Error specified, return the error.
}
// Command in FakeInvoke returns from expected file if exists.
func (i FakeInvoke) Command(name string, arg ...string) ([]byte, error) {
if i.Error != nil {
return []byte{}, i.Error
}
arch := runtime.GOOS
commandName := filepath.Base(name)
fname := strings.Join(append([]string{commandName}, arg...), "")
fname = url.QueryEscape(fname)
fpath := path.Join("testdata", arch, fname)
if i.Suffix != "" {
fpath += "_" + i.Suffix
}
if PathExists(fpath) {
return os.ReadFile(fpath)
}
return []byte{}, fmt.Errorf("could not find testdata: %s", fpath)
}
func (i FakeInvoke) CommandWithContext(_ context.Context, name string, arg ...string) ([]byte, error) {
return i.Command(name, arg...)
}
// ReadFile reads contents from a file
func ReadFile(filename string) (string, error) {
content, err := os.ReadFile(filename)
if err != nil {
return "", err
}
return string(content), nil
}
// ReadLines reads contents from a file and splits them by new lines.
// A convenience wrapper to ReadLinesOffsetN(filename, 0, -1).
func ReadLines(filename string) ([]string, error) {
return ReadLinesOffsetN(filename, 0, -1)
}
// ReadLine reads a file and returns the first occurrence of a line that is prefixed with prefix.
func ReadLine(filename, prefix string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close()
r := bufio.NewReader(f)
for {
line, err := r.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return "", err
}
if strings.HasPrefix(line, prefix) {
return line, nil
}
}
return "", nil
}
// ReadLinesOffsetN reads contents from file and splits them by new line.
// The offset tells at which line number to start.
// The count determines the number of lines to read (starting from offset):
// n >= 0: at most n lines
// n < 0: whole file
func ReadLinesOffsetN(filename string, offset uint, n int) ([]string, error) {
f, err := os.Open(filename)
if err != nil {
return []string{""}, err
}
defer f.Close()
var ret []string
r := bufio.NewReader(f)
for i := uint(0); i < uint(n)+offset || n < 0; i++ {
line, err := r.ReadString('\n')
if err != nil {
if err == io.EOF && line != "" {
ret = append(ret, strings.Trim(line, "\n"))
}
break
}
if i < offset {
continue
}
ret = append(ret, strings.Trim(line, "\n"))
}
return ret, nil
}
func IntToString(orig []int8) string {
ret := make([]byte, len(orig))
size := -1
for i, o := range orig {
if o == 0 {
size = i
break
}
ret[i] = byte(o)
}
if size == -1 {
size = len(orig)
}
return string(ret[0:size])
}
func UintToString(orig []uint8) string {
ret := make([]byte, len(orig))
size := -1
for i, o := range orig {
if o == 0 {
size = i
break
}
ret[i] = byte(o)
}
if size == -1 {
size = len(orig)
}
return string(ret[0:size])
}
func ByteToString(orig []byte) string {
n := -1
l := -1
for i, b := range orig {
// skip left side null
if l == -1 && b == 0 {
continue
}
if l == -1 {
l = i
}
if b == 0 {
break
}
n = i + 1
}
if n == -1 {
return string(orig)
}
return string(orig[l:n])
}
// ReadInts reads contents from single line file and returns them as []int32.
func ReadInts(filename string) ([]int64, error) {
f, err := os.Open(filename)
if err != nil {
return []int64{}, err
}
defer f.Close()
var ret []int64
r := bufio.NewReader(f)
// The int files that this is concerned with should only be one liners.
line, err := r.ReadString('\n')
if err != nil {
return []int64{}, err
}
i, err := strconv.ParseInt(strings.Trim(line, "\n"), 10, 32)
if err != nil {
return []int64{}, err
}
ret = append(ret, i)
return ret, nil
}
// Parse Hex to uint32 without error
func HexToUint32(hex string) uint32 {
vv, _ := strconv.ParseUint(hex, 16, 32)
return uint32(vv)
}
// Parse to int32 without error
func mustParseInt32(val string) int32 {
vv, _ := strconv.ParseInt(val, 10, 32)
return int32(vv)
}
// Parse to uint64 without error
func mustParseUint64(val string) uint64 {
vv, _ := strconv.ParseInt(val, 10, 64)
return uint64(vv)
}
// Parse to Float64 without error
func mustParseFloat64(val string) float64 {
vv, _ := strconv.ParseFloat(val, 64)
return vv
}
// StringsHas checks the target string slice contains src or not
func StringsHas(target []string, src string) bool {
for _, t := range target {
if strings.TrimSpace(t) == src {
return true
}
}
return false
}
// StringsContains checks the src in any string of the target string slice
func StringsContains(target []string, src string) bool {
return slices.ContainsFunc(target, func(s string) bool {
return strings.Contains(s, src)
})
}
// IntContains checks the src in any int of the target int slice.
func IntContains(target []int, src int) bool {
return slices.Contains(target, src)
}
// get struct attributes.
// This method is used only for debugging platform dependent code.
func attributes(m any) map[string]reflect.Type {
typ := reflect.TypeOf(m)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
attrs := make(map[string]reflect.Type)
if typ.Kind() != reflect.Struct {
return nil
}
for i := 0; i < typ.NumField(); i++ {
p := typ.Field(i)
if !p.Anonymous {
attrs[p.Name] = p.Type
}
}
return attrs
}
func PathExists(filename string) bool {
if _, err := os.Stat(filename); err == nil {
return true
}
return false
}
// PathExistsWithContents returns the filename exists and it is not empty
func PathExistsWithContents(filename string) bool {
info, err := os.Stat(filename)
if err != nil {
return false
}
return info.Size() > 4 && !info.IsDir() // at least 4 bytes
}
// GetEnvWithContext retrieves the environment variable key. If it does not exist it returns the default.
// The context may optionally contain a map superseding os.EnvKey.
func GetEnvWithContext(ctx context.Context, key, dfault string, combineWith ...string) string {
var value string
if env, ok := ctx.Value(common.EnvKey).(common.EnvMap); ok {
value = env[common.EnvKeyType(key)]
}
if value == "" {
value = os.Getenv(key)
}
if value == "" {
value = dfault
}
return combine(value, combineWith)
}
// GetEnv retrieves the environment variable key. If it does not exist it returns the default.
func GetEnv(key, dfault string, combineWith ...string) string {
value := os.Getenv(key)
if value == "" {
value = dfault
}
return combine(value, combineWith)
}
func combine(value string, combineWith []string) string {
switch len(combineWith) {
case 0:
return value
case 1:
return filepath.Join(value, combineWith[0])
default:
all := make([]string, len(combineWith)+1)
all[0] = value
copy(all[1:], combineWith)
return filepath.Join(all...)
}
}
func HostProc(combineWith ...string) string {
return GetEnv("HOST_PROC", "/proc", combineWith...)
}
func HostSys(combineWith ...string) string {
return GetEnv("HOST_SYS", "/sys", combineWith...)
}
func HostEtc(combineWith ...string) string {
return GetEnv("HOST_ETC", "/etc", combineWith...)
}
func HostVar(combineWith ...string) string {
return GetEnv("HOST_VAR", "/var", combineWith...)
}
func HostRun(combineWith ...string) string {
return GetEnv("HOST_RUN", "/run", combineWith...)
}
func HostDev(combineWith ...string) string {
return GetEnv("HOST_DEV", "/dev", combineWith...)
}
func HostRoot(combineWith ...string) string {
return GetEnv("HOST_ROOT", "/", combineWith...)
}
func HostProcWithContext(ctx context.Context, combineWith ...string) string {
return GetEnvWithContext(ctx, "HOST_PROC", "/proc", combineWith...)
}
func HostProcMountInfoWithContext(ctx context.Context, combineWith ...string) string {
return GetEnvWithContext(ctx, "HOST_PROC_MOUNTINFO", "", combineWith...)
}
func HostSysWithContext(ctx context.Context, combineWith ...string) string {
return GetEnvWithContext(ctx, "HOST_SYS", "/sys", combineWith...)
}
func HostEtcWithContext(ctx context.Context, combineWith ...string) string {
return GetEnvWithContext(ctx, "HOST_ETC", "/etc", combineWith...)
}
func HostVarWithContext(ctx context.Context, combineWith ...string) string {
return GetEnvWithContext(ctx, "HOST_VAR", "/var", combineWith...)
}
func HostRunWithContext(ctx context.Context, combineWith ...string) string {
return GetEnvWithContext(ctx, "HOST_RUN", "/run", combineWith...)
}
func HostDevWithContext(ctx context.Context, combineWith ...string) string {
return GetEnvWithContext(ctx, "HOST_DEV", "/dev", combineWith...)
}
func HostRootWithContext(ctx context.Context, combineWith ...string) string {
return GetEnvWithContext(ctx, "HOST_ROOT", "/", combineWith...)
}
// getSysctrlEnv sets LC_ALL=C in a list of env vars for use when running
// sysctl commands.
func getSysctrlEnv(env []string) []string {
foundLC := false
for i, line := range env {
if strings.HasPrefix(line, "LC_ALL") {
env[i] = "LC_ALL=C"
foundLC = true
}
}
if !foundLC {
env = append(env, "LC_ALL=C")
}
return env
}
// Round places rounds the number 'val' to 'n' decimal places
func Round(val float64, n int) float64 {
// Calculate the power of 10 to the n
pow10 := math.Pow(10, float64(n))
// Multiply the value by pow10, round it, then divide it by pow10
return math.Round(val*pow10) / pow10
}
func TimeSince(ts uint64) uint64 {
return uint64(time.Now().Unix()) - ts
}
func TimeSinceMillis(ts uint64) uint64 {
return uint64(time.Now().UnixMilli()) - ts
}
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build aix
package common
import (
"context"
"errors"
"strconv"
"strings"
)
func BootTimeWithContext(ctx context.Context, invoke Invoker) (btime uint64, err error) {
ut, err := UptimeWithContext(ctx, invoke)
if err != nil {
return 0, err
}
if ut <= 0 {
return 0, errors.New("uptime was not set, so cannot calculate boot time from it")
}
return TimeSince(ut), nil
}
// Uses ps to get the elapsed time for PID 1 in DAYS-HOURS:MINUTES:SECONDS format.
// Examples of ps -o etimes -p 1 output:
// 124-01:40:39 (with days)
// 15:03:02 (without days, hours only)
// 01:02 (just-rebooted systems, minutes and seconds)
func UptimeWithContext(ctx context.Context, invoke Invoker) (uint64, error) {
out, err := invoke.CommandWithContext(ctx, "ps", "-o", "etimes", "-p", "1")
if err != nil {
return 0, err
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
if len(lines) < 2 {
return 0, errors.New("ps output has fewer than 2 rows")
}
// Extract the etimes value from the second row, trimming whitespace
etimes := strings.TrimSpace(lines[1])
return ParseUptime(etimes), nil
}
// Parses etimes output from ps command into total seconds.
// Handles formats like:
// - "124-01:40:39" (DAYS-HOURS:MINUTES:SECONDS)
// - "15:03:02" (HOURS:MINUTES:SECONDS)
// - "01:02" (MINUTES:SECONDS, from just-rebooted systems)
func ParseUptime(etimes string) uint64 {
var days, hours, mins, secs uint64
// Check if days component is present (contains a dash)
if strings.Contains(etimes, "-") {
parts := strings.Split(etimes, "-")
if len(parts) != 2 {
return 0
}
var err error
days, err = strconv.ParseUint(parts[0], 10, 64)
if err != nil {
return 0
}
// Parse the HH:MM:SS portion (after days, must have 3 parts)
etimes = parts[1]
timeParts := strings.Split(etimes, ":")
if len(timeParts) != 3 {
return 0
}
var err2 error
hours, err2 = strconv.ParseUint(timeParts[0], 10, 64)
if err2 != nil {
return 0
}
mins, err2 = strconv.ParseUint(timeParts[1], 10, 64)
if err2 != nil {
return 0
}
secs, err2 = strconv.ParseUint(timeParts[2], 10, 64)
if err2 != nil {
return 0
}
} else {
// Parse time portions (either HH:MM:SS or MM:SS) when no days present
timeParts := strings.Split(etimes, ":")
switch len(timeParts) {
case 3:
// HH:MM:SS format
var err error
hours, err = strconv.ParseUint(timeParts[0], 10, 64)
if err != nil {
return 0
}
mins, err = strconv.ParseUint(timeParts[1], 10, 64)
if err != nil {
return 0
}
secs, err = strconv.ParseUint(timeParts[2], 10, 64)
if err != nil {
return 0
}
case 2:
// MM:SS format (just-rebooted systems)
var err error
mins, err = strconv.ParseUint(timeParts[0], 10, 64)
if err != nil {
return 0
}
secs, err = strconv.ParseUint(timeParts[1], 10, 64)
if err != nil {
return 0
}
default:
return 0
}
}
// Convert to total seconds
totalSeconds := (days * 24 * 60 * 60) + (hours * 60 * 60) + (mins * 60) + secs
return totalSeconds
}
@@ -0,0 +1,577 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin
package common
import (
"errors"
"fmt"
"math"
"sync"
"unsafe"
"github.com/ebitengine/purego"
)
// Library represents a dynamic library loaded by purego.
type library struct {
handle uintptr
fnMap map[string]any
mu sync.RWMutex
}
// library paths
const (
IOKitLibPath = "/System/Library/Frameworks/IOKit.framework/IOKit"
CoreFoundationLibPath = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
SystemLibPath = "/usr/lib/libSystem.B.dylib"
)
func newLibrary(path string) (*library, error) {
lib, err := purego.Dlopen(path, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
return nil, err
}
return &library{
handle: lib,
fnMap: make(map[string]any),
}, nil
}
func (lib *library) Dlsym(symbol string) (uintptr, error) {
return purego.Dlsym(lib.handle, symbol)
}
// getFunc resolves a function pointer from the library, caching it in fnMap.
// Thread-safe via double-checked locking to support shared library handles.
func getFunc[T any](lib *library, symbol string) T {
// Fast path: read lock only
lib.mu.RLock()
if f, ok := lib.fnMap[symbol].(*dlFunc[T]); ok {
lib.mu.RUnlock()
return f.fn
}
lib.mu.RUnlock()
// Slow path: write lock for first-time resolution
lib.mu.Lock()
defer lib.mu.Unlock()
// Double-check after acquiring write lock
if f, ok := lib.fnMap[symbol].(*dlFunc[T]); ok {
return f.fn
}
dlfun := newDlfunc[T](symbol)
dlfun.init(lib.handle)
lib.fnMap[symbol] = dlfun
return dlfun.fn
}
func (lib *library) Close() {
purego.Dlclose(lib.handle)
}
type dlFunc[T any] struct {
sym string
fn T
}
func (d *dlFunc[T]) init(handle uintptr) {
purego.RegisterLibFunc(&d.fn, handle, d.sym)
}
func newDlfunc[T any](sym string) *dlFunc[T] {
return &dlFunc[T]{sym: sym}
}
type CoreFoundationLib struct {
*library
}
func NewCoreFoundationLib() (*CoreFoundationLib, error) {
library, err := newLibrary(CoreFoundationLibPath)
if err != nil {
return nil, err
}
return &CoreFoundationLib{library}, nil
}
func (c *CoreFoundationLib) CFGetTypeID(cf uintptr) int64 {
fn := getFunc[CFGetTypeIDFunc](c.library, "CFGetTypeID")
return fn(cf)
}
func (c *CoreFoundationLib) CFNumberCreate(allocator uintptr, theType int64, valuePtr uintptr) unsafe.Pointer {
fn := getFunc[CFNumberCreateFunc](c.library, "CFNumberCreate")
return fn(allocator, theType, valuePtr)
}
func (c *CoreFoundationLib) CFNumberGetValue(num uintptr, theType int64, valuePtr uintptr) bool {
fn := getFunc[CFNumberGetValueFunc](c.library, "CFNumberGetValue")
return fn(num, theType, valuePtr)
}
func (c *CoreFoundationLib) CFDictionaryCreate(allocator uintptr, keys, values *unsafe.Pointer, numValues int64,
keyCallBacks, valueCallBacks uintptr,
) unsafe.Pointer {
fn := getFunc[CFDictionaryCreateFunc](c.library, "CFDictionaryCreate")
return fn(allocator, keys, values, numValues, keyCallBacks, valueCallBacks)
}
func (c *CoreFoundationLib) CFDictionaryAddValue(theDict, key, value uintptr) {
fn := getFunc[CFDictionaryAddValueFunc](c.library, "CFDictionaryAddValue")
fn(theDict, key, value)
}
func (c *CoreFoundationLib) CFDictionaryGetValue(theDict, key uintptr) unsafe.Pointer {
fn := getFunc[CFDictionaryGetValueFunc](c.library, "CFDictionaryGetValue")
return fn(theDict, key)
}
func (c *CoreFoundationLib) CFArrayGetCount(theArray uintptr) int64 {
fn := getFunc[CFArrayGetCountFunc](c.library, "CFArrayGetCount")
return fn(theArray)
}
func (c *CoreFoundationLib) CFArrayGetValueAtIndex(theArray uintptr, index int64) unsafe.Pointer {
fn := getFunc[CFArrayGetValueAtIndexFunc](c.library, "CFArrayGetValueAtIndex")
return fn(theArray, index)
}
func (c *CoreFoundationLib) CFStringCreateMutable(alloc uintptr, maxLength int64) unsafe.Pointer {
fn := getFunc[CFStringCreateMutableFunc](c.library, "CFStringCreateMutable")
return fn(alloc, maxLength)
}
func (c *CoreFoundationLib) CFStringGetLength(theString uintptr) int64 {
fn := getFunc[CFStringGetLengthFunc](c.library, "CFStringGetLength")
return fn(theString)
}
func (c *CoreFoundationLib) CFStringGetCString(theString uintptr, buffer CStr, bufferSize int64, encoding uint32) {
fn := getFunc[CFStringGetCStringFunc](c.library, "CFStringGetCString")
fn(theString, buffer, bufferSize, encoding)
}
func (c *CoreFoundationLib) CFStringCreateWithCString(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer {
fn := getFunc[CFStringCreateWithCStringFunc](c.library, "CFStringCreateWithCString")
return fn(alloc, cStr, encoding)
}
func (c *CoreFoundationLib) CFDataGetLength(theData uintptr) int64 {
fn := getFunc[CFDataGetLengthFunc](c.library, "CFDataGetLength")
return fn(theData)
}
func (c *CoreFoundationLib) CFDataGetBytePtr(theData uintptr) unsafe.Pointer {
fn := getFunc[CFDataGetBytePtrFunc](c.library, "CFDataGetBytePtr")
return fn(theData)
}
func (c *CoreFoundationLib) CFRelease(cf uintptr) {
fn := getFunc[CFReleaseFunc](c.library, "CFRelease")
fn(cf)
}
type IOKitLib struct {
*library
}
func NewIOKitLib() (*IOKitLib, error) {
library, err := newLibrary(IOKitLibPath)
if err != nil {
return nil, err
}
return &IOKitLib{library}, nil
}
func (l *IOKitLib) IOServiceGetMatchingService(mainPort uint32, matching uintptr) uint32 {
fn := getFunc[IOServiceGetMatchingServiceFunc](l.library, "IOServiceGetMatchingService")
return fn(mainPort, matching)
}
func (l *IOKitLib) IOServiceGetMatchingServices(mainPort uint32, matching uintptr, existing *uint32) int32 {
fn := getFunc[IOServiceGetMatchingServicesFunc](l.library, "IOServiceGetMatchingServices")
return fn(mainPort, matching, existing)
}
func (l *IOKitLib) IOServiceMatching(name string) unsafe.Pointer {
fn := getFunc[IOServiceMatchingFunc](l.library, "IOServiceMatching")
return fn(name)
}
func (l *IOKitLib) IOServiceOpen(service, owningTask, connType uint32, connect *uint32) int32 {
fn := getFunc[IOServiceOpenFunc](l.library, "IOServiceOpen")
return fn(service, owningTask, connType, connect)
}
func (l *IOKitLib) IOServiceClose(connect uint32) int32 {
fn := getFunc[IOServiceCloseFunc](l.library, "IOServiceClose")
return fn(connect)
}
func (l *IOKitLib) IOIteratorNext(iterator uint32) uint32 {
fn := getFunc[IOIteratorNextFunc](l.library, "IOIteratorNext")
return fn(iterator)
}
func (l *IOKitLib) IORegistryEntryGetName(entry uint32, name CStr) int32 {
fn := getFunc[IORegistryEntryGetNameFunc](l.library, "IORegistryEntryGetName")
return fn(entry, name)
}
func (l *IOKitLib) IORegistryEntryGetParentEntry(entry uint32, plane string, parent *uint32) int32 {
fn := getFunc[IORegistryEntryGetParentEntryFunc](l.library, "IORegistryEntryGetParentEntry")
return fn(entry, plane, parent)
}
func (l *IOKitLib) IORegistryEntryCreateCFProperty(entry uint32, key, allocator uintptr, options uint32) unsafe.Pointer {
fn := getFunc[IORegistryEntryCreateCFPropertyFunc](l.library, "IORegistryEntryCreateCFProperty")
return fn(entry, key, allocator, options)
}
func (l *IOKitLib) IORegistryEntryCreateCFProperties(entry uint32, properties unsafe.Pointer, allocator uintptr, options uint32) int32 {
fn := getFunc[IORegistryEntryCreateCFPropertiesFunc](l.library, "IORegistryEntryCreateCFProperties")
return fn(entry, properties, allocator, options)
}
func (l *IOKitLib) IOObjectConformsTo(object uint32, className string) bool {
fn := getFunc[IOObjectConformsToFunc](l.library, "IOObjectConformsTo")
return fn(object, className)
}
func (l *IOKitLib) IOObjectRelease(object uint32) int32 {
fn := getFunc[IOObjectReleaseFunc](l.library, "IOObjectRelease")
return fn(object)
}
func (l *IOKitLib) IOConnectCallStructMethod(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int32 {
fn := getFunc[IOConnectCallStructMethodFunc](l.library, "IOConnectCallStructMethod")
return fn(connection, selector, inputStruct, inputStructCnt, outputStruct, outputStructCnt)
}
func (l *IOKitLib) IOHIDEventSystemClientCreate(allocator uintptr) unsafe.Pointer {
fn := getFunc[IOHIDEventSystemClientCreateFunc](l.library, "IOHIDEventSystemClientCreate")
return fn(allocator)
}
func (l *IOKitLib) IOHIDEventSystemClientSetMatching(client, match uintptr) int32 {
fn := getFunc[IOHIDEventSystemClientSetMatchingFunc](l.library, "IOHIDEventSystemClientSetMatching")
return fn(client, match)
}
func (l *IOKitLib) IOHIDServiceClientCopyEvent(service uintptr, eventType int64, options int32, timeout int64) unsafe.Pointer {
fn := getFunc[IOHIDServiceClientCopyEventFunc](l.library, "IOHIDServiceClientCopyEvent")
return fn(service, eventType, options, timeout)
}
func (l *IOKitLib) IOHIDServiceClientCopyProperty(service, property uintptr) unsafe.Pointer {
fn := getFunc[IOHIDServiceClientCopyPropertyFunc](l.library, "IOHIDServiceClientCopyProperty")
return fn(service, property)
}
func (l *IOKitLib) IOHIDEventGetFloatValue(event uintptr, field int32) float64 {
fn := getFunc[IOHIDEventGetFloatValueFunc](l.library, "IOHIDEventGetFloatValue")
return fn(event, field)
}
func (l *IOKitLib) IOHIDEventSystemClientCopyServices(client uintptr) unsafe.Pointer {
fn := getFunc[IOHIDEventSystemClientCopyServicesFunc](l.library, "IOHIDEventSystemClientCopyServices")
return fn(client)
}
type SystemLib struct {
*library
}
func NewSystemLib() (*SystemLib, error) {
library, err := newLibrary(SystemLibPath)
if err != nil {
return nil, err
}
return &SystemLib{library}, nil
}
func (s *SystemLib) HostProcessorInfo(host uint32, flavor int32, outProcessorCount *uint32, outProcessorInfo uintptr,
outProcessorInfoCnt *uint32,
) int32 {
fn := getFunc[HostProcessorInfoFunc](s.library, "host_processor_info")
return fn(host, flavor, outProcessorCount, outProcessorInfo, outProcessorInfoCnt)
}
func (s *SystemLib) HostStatistics(host uint32, flavor int32, hostInfoOut uintptr, hostInfoOutCnt *uint32) int32 {
fn := getFunc[HostStatisticsFunc](s.library, "host_statistics")
return fn(host, flavor, hostInfoOut, hostInfoOutCnt)
}
func (s *SystemLib) MachHostSelf() uint32 {
fn := getFunc[MachHostSelfFunc](s.library, "mach_host_self")
return fn()
}
func (s *SystemLib) MachTaskSelf() uint32 {
fn := getFunc[MachTaskSelfFunc](s.library, "mach_task_self")
return fn()
}
func (s *SystemLib) MachTimeBaseInfo(info uintptr) int32 {
fn := getFunc[MachTimeBaseInfoFunc](s.library, "mach_timebase_info")
return fn(info)
}
func (s *SystemLib) VMDeallocate(targetTask uint32, vmAddress, vmSize uintptr) int32 {
fn := getFunc[VMDeallocateFunc](s.library, "vm_deallocate")
return fn(targetTask, vmAddress, vmSize)
}
func (s *SystemLib) ProcPidPath(pid int32, buffer uintptr, bufferSize uint32) int32 {
fn := getFunc[ProcPidPathFunc](s.library, "proc_pidpath")
return fn(pid, buffer, bufferSize)
}
func (s *SystemLib) ProcPidInfo(pid, flavor int32, arg uint64, buffer uintptr, bufferSize int32) int32 {
fn := getFunc[ProcPidInfoFunc](s.library, "proc_pidinfo")
return fn(pid, flavor, arg, buffer, bufferSize)
}
// status codes
const (
KERN_SUCCESS = 0
)
// IOKit types and constants.
type (
IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32
IOServiceGetMatchingServicesFunc func(mainPort uint32, matching uintptr, existing *uint32) int32
IOServiceMatchingFunc func(name string) unsafe.Pointer
IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int32
IOServiceCloseFunc func(connect uint32) int32
IOIteratorNextFunc func(iterator uint32) uint32
IORegistryEntryGetNameFunc func(entry uint32, name CStr) int32
IORegistryEntryGetParentEntryFunc func(entry uint32, plane string, parent *uint32) int32
IORegistryEntryCreateCFPropertyFunc func(entry uint32, key, allocator uintptr, options uint32) unsafe.Pointer
IORegistryEntryCreateCFPropertiesFunc func(entry uint32, properties unsafe.Pointer, allocator uintptr, options uint32) int32
IOObjectConformsToFunc func(object uint32, className string) bool
IOObjectReleaseFunc func(object uint32) int32
IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int32
IOHIDEventSystemClientCreateFunc func(allocator uintptr) unsafe.Pointer
IOHIDEventSystemClientSetMatchingFunc func(client, match uintptr) int32
IOHIDServiceClientCopyEventFunc func(service uintptr, eventType int64,
options int32, timeout int64) unsafe.Pointer
IOHIDServiceClientCopyPropertyFunc func(service, property uintptr) unsafe.Pointer
IOHIDEventGetFloatValueFunc func(event uintptr, field int32) float64
IOHIDEventSystemClientCopyServicesFunc func(client uintptr) unsafe.Pointer
)
const (
KIOMainPortDefault = 0
KIOHIDEventTypeTemperature = 15
KNilOptions = 0
)
const (
KIOMediaWholeKey = "Media"
KIOServicePlane = "IOService"
)
// CoreFoundation types and constants.
type (
CFGetTypeIDFunc func(cf uintptr) int64
CFNumberCreateFunc func(allocator uintptr, theType int64, valuePtr uintptr) unsafe.Pointer
CFNumberGetValueFunc func(num uintptr, theType int64, valuePtr uintptr) bool
CFDictionaryCreateFunc func(allocator uintptr, keys, values *unsafe.Pointer, numValues int64,
keyCallBacks, valueCallBacks uintptr) unsafe.Pointer
CFDictionaryAddValueFunc func(theDict, key, value uintptr)
CFDictionaryGetValueFunc func(theDict, key uintptr) unsafe.Pointer
CFArrayGetCountFunc func(theArray uintptr) int64
CFArrayGetValueAtIndexFunc func(theArray uintptr, index int64) unsafe.Pointer
CFStringCreateMutableFunc func(alloc uintptr, maxLength int64) unsafe.Pointer
CFStringGetLengthFunc func(theString uintptr) int64
CFStringGetCStringFunc func(theString uintptr, buffer CStr, bufferSize int64, encoding uint32)
CFStringCreateWithCStringFunc func(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer
CFDataGetLengthFunc func(theData uintptr) int64
CFDataGetBytePtrFunc func(theData uintptr) unsafe.Pointer
CFReleaseFunc func(cf uintptr)
)
const (
KCFStringEncodingUTF8 = 0x08000100
KCFNumberSInt64Type = 4
KCFNumberIntType = 9
KCFAllocatorDefault = 0
KCFNotFound = -1
)
// libSystem types and constants.
type MachTimeBaseInfo struct {
Numer uint32
Denom uint32
}
type (
HostProcessorInfoFunc func(host uint32, flavor int32, outProcessorCount *uint32, outProcessorInfo uintptr,
outProcessorInfoCnt *uint32) int32
HostStatisticsFunc func(host uint32, flavor int32, hostInfoOut uintptr, hostInfoOutCnt *uint32) int32
MachHostSelfFunc func() uint32
MachTaskSelfFunc func() uint32
MachTimeBaseInfoFunc func(info uintptr) int32
VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int32
)
const (
HostProcessorInfoSym = "host_processor_info"
HostStatisticsSym = "host_statistics"
MachHostSelfSym = "mach_host_self"
MachTaskSelfSym = "mach_task_self"
MachTimeBaseInfoSym = "mach_timebase_info"
VMDeallocateSym = "vm_deallocate"
)
const (
HOST_VM_INFO = 2
HOST_CPU_LOAD_INFO = 3
HOST_VM_INFO_COUNT = 0xf
)
type (
ProcPidPathFunc func(pid int32, buffer uintptr, bufferSize uint32) int32
ProcPidInfoFunc func(pid, flavor int32, arg uint64, buffer uintptr, bufferSize int32) int32
)
const (
SysctlSym = "sysctl"
ProcPidPathSym = "proc_pidpath"
ProcPidInfoSym = "proc_pidinfo"
)
const (
MAXPATHLEN = 1024
PROC_PIDLISTFDS = 1
PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN
PROC_PIDTASKINFO = 4
PROC_PIDVNODEPATHINFO = 9
)
// SMC represents a SMC instance.
type SMC struct {
lib *IOKitLib
conn uint32
}
const ioServiceSMC = "AppleSMC"
const (
KSMCUserClientOpen = 0
KSMCUserClientClose = 1
KSMCHandleYPCEvent = 2
KSMCReadKey = 5
KSMCWriteKey = 6
KSMCGetKeyCount = 7
KSMCGetKeyFromIndex = 8
KSMCGetKeyInfo = 9
)
const (
KSMCSuccess = 0
KSMCError = 1
KSMCKeyNotFound = 132
)
func NewSMC() (*SMC, error) {
iokit, err := NewIOKitLib()
if err != nil {
return nil, err
}
service := iokit.IOServiceGetMatchingService(0, uintptr(iokit.IOServiceMatching(ioServiceSMC)))
if service == 0 {
return nil, fmt.Errorf("ERROR: %s NOT FOUND", ioServiceSMC)
}
var conn uint32
machTaskSelf := getFunc[MachTaskSelfFunc](iokit.library, "mach_task_self")
if result := iokit.IOServiceOpen(service, machTaskSelf(), 0, &conn); result != 0 {
return nil, errors.New("ERROR: IOServiceOpen failed")
}
iokit.IOObjectRelease(service)
return &SMC{
lib: iokit,
conn: conn,
}, nil
}
func (s *SMC) CallStruct(selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int32 {
return s.lib.IOConnectCallStructMethod(s.conn, selector, inputStruct, inputStructCnt, outputStruct, outputStructCnt)
}
func (s *SMC) Close() error {
if result := s.lib.IOServiceClose(s.conn); result != 0 {
return errors.New("ERROR: IOServiceClose failed")
}
s.lib.Close()
return nil
}
type CStr []byte
func NewCStr(length int64) CStr {
return make(CStr, length)
}
func (s CStr) Length() int64 {
return int64(len(s))
}
func (s CStr) Ptr() *byte {
if len(s) < 1 {
return nil
}
return &s[0]
}
func (s CStr) Addr() uintptr {
return uintptr(unsafe.Pointer(s.Ptr()))
}
func (s CStr) GoString() string {
if s == nil {
return ""
}
var length int
for _, char := range s {
if char == '\x00' {
break
}
length++
}
return string(s[:length])
}
// https://github.com/ebitengine/purego/blob/main/internal/strings/strings.go#L26
func GoString(cStr *byte) string {
if cStr == nil {
return ""
}
var length int
for *(*byte)(unsafe.Add(unsafe.Pointer(cStr), uintptr(length))) != '\x00' {
length++
}
return string(unsafe.Slice(cStr, length))
}
// https://github.com/apple-oss-distributions/CF/blob/dc54c6bb1c1e5e0b9486c1d26dd5bef110b20bf3/CFString.c#L463
func GetCFStringBufLengthForUTF8(length int64) int64 {
if length > (math.MaxInt64 / 3) {
return KCFNotFound
}
return length*3 + 1 // includes null terminator
}
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build freebsd || openbsd
package common
import (
"fmt"
"unsafe"
"golang.org/x/sys/unix"
)
func SysctlUint(mib string) (uint64, error) {
buf, err := unix.SysctlRaw(mib)
if err != nil {
return 0, err
}
if len(buf) == 8 { // 64 bit
return *(*uint64)(unsafe.Pointer(&buf[0])), nil
}
if len(buf) == 4 { // 32bit
t := *(*uint32)(unsafe.Pointer(&buf[0]))
return uint64(t), nil
}
return 0, fmt.Errorf("unexpected size: %s, %d", mib, len(buf))
}
func CallSyscall(mib []int32) ([]byte, uint64, error) {
mibptr := unsafe.Pointer(&mib[0])
miblen := uint64(len(mib))
// get required buffer size
length := uint64(0)
_, _, err := unix.Syscall6(
unix.SYS___SYSCTL,
uintptr(mibptr),
uintptr(miblen),
0,
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
var b []byte
return b, length, err
}
if length == 0 {
var b []byte
return b, length, err
}
// get proc info itself
buf := make([]byte, length)
_, _, err = unix.Syscall6(
unix.SYS___SYSCTL,
uintptr(mibptr),
uintptr(miblen),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
return buf, length, err
}
return buf, length, nil
}
@@ -0,0 +1,343 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux
package common
import (
"context"
"errors"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
)
// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64
func NumProcs() (uint64, error) {
return NumProcsWithContext(context.Background())
}
func NumProcsWithContext(ctx context.Context) (uint64, error) {
f, err := os.Open(HostProcWithContext(ctx))
if err != nil {
return 0, err
}
defer f.Close()
list, err := f.Readdirnames(-1)
if err != nil {
return 0, err
}
var cnt uint64
for _, v := range list {
if _, err = strconv.ParseUint(v, 10, 64); err == nil {
cnt++
}
}
return cnt, nil
}
func BootTimeWithContext(ctx context.Context, enableCache bool) (uint64, error) {
if enableCache {
t := atomic.LoadUint64(&cachedBootTime)
if t != 0 {
return t, nil
}
}
system, role, err := VirtualizationWithContext(ctx)
if err != nil {
return 0, err
}
useStatFile := true
if system == "lxc" && role == "guest" {
// if lxc, /proc/uptime is used.
useStatFile = false
} else if system == "docker" && role == "guest" {
// also docker, guest
useStatFile = false
}
if useStatFile {
t, err := readBootTimeStat(ctx)
if err != nil {
return 0, err
}
if enableCache {
atomic.StoreUint64(&cachedBootTime, t)
}
return t, nil
}
filename := HostProcWithContext(ctx, "uptime")
lines, err := ReadLines(filename)
if err != nil {
return handleBootTimeFileReadErr(err)
}
currentTime := float64(time.Now().UnixNano()) / float64(time.Second)
if len(lines) != 1 {
return 0, errors.New("wrong uptime format")
}
f := strings.Fields(lines[0])
b, err := strconv.ParseFloat(f[0], 64)
if err != nil {
return 0, err
}
t := currentTime - b
if enableCache {
atomic.StoreUint64(&cachedBootTime, uint64(t))
}
return uint64(t), nil
}
func handleBootTimeFileReadErr(err error) (uint64, error) {
if !os.IsPermission(err) {
return 0, err
}
var info syscall.Sysinfo_t
err = syscall.Sysinfo(&info)
if err != nil {
return 0, err
}
currentTime := time.Now().UnixNano() / int64(time.Second)
t := currentTime - int64(info.Uptime)
return uint64(t), nil
}
func readBootTimeStat(ctx context.Context) (uint64, error) {
filename := HostProcWithContext(ctx, "stat")
line, err := ReadLine(filename, "btime")
if err != nil {
return handleBootTimeFileReadErr(err)
}
if strings.HasPrefix(line, "btime") {
f := strings.Fields(line)
if len(f) != 2 {
return 0, errors.New("wrong btime format")
}
b, err := strconv.ParseInt(f[1], 10, 64)
if err != nil {
return 0, err
}
t := uint64(b)
return t, nil
}
return 0, errors.New("could not find btime")
}
func Virtualization() (string, string, error) {
return VirtualizationWithContext(context.Background())
}
// required variables for concurrency safe virtualization caching
var (
cachedVirtMap map[string]string
cachedVirtMutex sync.RWMutex
cachedVirtOnce sync.Once
)
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
var system, role string
// if cached already, return from cache
cachedVirtMutex.RLock() // unlock won't be deferred so concurrent reads don't wait for long
if cachedVirtMap != nil {
cachedSystem, cachedRole := cachedVirtMap["system"], cachedVirtMap["role"]
cachedVirtMutex.RUnlock()
return cachedSystem, cachedRole, nil
}
cachedVirtMutex.RUnlock()
filename := HostProcWithContext(ctx, "xen")
if PathExists(filename) {
system = "xen"
role = "guest" // assume guest
if PathExists(filepath.Join(filename, "capabilities")) {
contents, err := ReadLines(filepath.Join(filename, "capabilities"))
if err == nil {
if StringsContains(contents, "control_d") {
role = "host"
}
}
}
}
filename = HostProcWithContext(ctx, "modules")
if PathExists(filename) {
contents, err := ReadLines(filename)
if err == nil {
switch {
case StringsContains(contents, "kvm"):
system = "kvm"
role = "host"
case StringsContains(contents, "hv_util"):
system = "hyperv"
role = "guest"
case StringsContains(contents, "vboxdrv"):
system = "vbox"
role = "host"
case StringsContains(contents, "vboxguest"):
system = "vbox"
role = "guest"
case StringsContains(contents, "vmware"):
system = "vmware"
role = "guest"
}
}
}
filename = HostProcWithContext(ctx, "cpuinfo")
if PathExists(filename) {
contents, err := ReadLines(filename)
if err == nil {
if StringsContains(contents, "QEMU Virtual CPU") ||
StringsContains(contents, "Common KVM processor") ||
StringsContains(contents, "Common 32-bit KVM processor") {
system = "kvm"
role = "guest"
}
}
}
filename = HostProcWithContext(ctx, "bus/pci/devices")
if PathExists(filename) {
contents, err := ReadLines(filename)
if err == nil {
if StringsContains(contents, "virtio-pci") {
role = "guest"
}
}
}
filename = HostProcWithContext(ctx)
if PathExists(filepath.Join(filename, "bc", "0")) {
system = "openvz"
role = "host"
} else if PathExists(filepath.Join(filename, "vz")) {
system = "openvz"
role = "guest"
}
// not use dmidecode because it requires root
if PathExists(filepath.Join(filename, "self", "status")) {
contents, err := ReadLines(filepath.Join(filename, "self", "status"))
if err == nil {
if StringsContains(contents, "s_context:") ||
StringsContains(contents, "VxID:") {
system = "linux-vserver"
}
// TODO: guest or host
}
}
if PathExists(filepath.Join(filename, "1", "environ")) {
contents, err := ReadFile(filepath.Join(filename, "1", "environ"))
if err == nil {
if strings.Contains(contents, "container=lxc") {
system = "lxc"
role = "guest"
}
}
}
if PathExists(filepath.Join(filename, "self", "cgroup")) {
contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
if err == nil {
switch {
case StringsContains(contents, "lxc"):
system = "lxc"
role = "guest"
case StringsContains(contents, "docker"):
system = "docker"
role = "guest"
case StringsContains(contents, "machine-rkt"):
system = "rkt"
role = "guest"
case PathExists("/usr/bin/lxc-version"):
system = "lxc"
role = "host"
}
}
}
if PathExists(HostEtcWithContext(ctx, "os-release")) {
p, _, err := GetOSReleaseWithContext(ctx)
if err == nil && p == "coreos" {
system = "rkt" // Is it true?
role = "host"
}
}
if PathExists(HostRootWithContext(ctx, ".dockerenv")) {
system = "docker"
role = "guest"
}
// before returning for the first time, cache the system and role
cachedVirtOnce.Do(func() {
cachedVirtMutex.Lock()
defer cachedVirtMutex.Unlock()
cachedVirtMap = map[string]string{
"system": system,
"role": role,
}
})
return system, role, nil
}
func GetOSRelease() (platform, version string, err error) {
return GetOSReleaseWithContext(context.Background())
}
func GetOSReleaseWithContext(ctx context.Context) (platform, version string, err error) {
contents, err := ReadLines(HostEtcWithContext(ctx, "os-release"))
if err != nil {
return "", "", nil // return empty
}
for _, line := range contents {
field := strings.Split(line, "=")
if len(field) < 2 {
continue
}
switch field[0] {
case "ID": // use ID for lowercase
platform = trimQuotes(field[1])
case "VERSION_ID":
version = trimQuotes(field[1])
}
}
// cleanup amazon ID
if platform == "amzn" {
platform = "amazon"
}
return platform, version, nil
}
// Remove quotes of the source string
func trimQuotes(s string) string {
if len(s) >= 2 {
if s[0] == '"' && s[len(s)-1] == '"' {
return s[1 : len(s)-1]
}
}
return s
}
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build netbsd
package common
import (
"unsafe"
"golang.org/x/sys/unix"
)
func CallSyscall(mib []int32) ([]byte, uint64, error) {
mibptr := unsafe.Pointer(&mib[0])
miblen := uint64(len(mib))
// get required buffer size
length := uint64(0)
_, _, err := unix.Syscall6(
unix.SYS___SYSCTL,
uintptr(mibptr),
uintptr(miblen),
0,
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
var b []byte
return b, length, err
}
if length == 0 {
var b []byte
return b, length, err
}
// get proc info itself
buf := make([]byte, length)
_, _, err = unix.Syscall6(
unix.SYS___SYSCTL,
uintptr(mibptr),
uintptr(miblen),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
return buf, length, err
}
return buf, length, nil
}
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build openbsd
package common
import (
"unsafe"
"golang.org/x/sys/unix"
)
func CallSyscall(mib []int32) ([]byte, uint64, error) {
mibptr := unsafe.Pointer(&mib[0])
miblen := uint64(len(mib))
// get required buffer size
length := uint64(0)
_, _, err := unix.Syscall6(
unix.SYS___SYSCTL,
uintptr(mibptr),
uintptr(miblen),
0,
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
var b []byte
return b, length, err
}
if length == 0 {
var b []byte
return b, length, err
}
// get proc info itself
buf := make([]byte, length)
_, _, err = unix.Syscall6(
unix.SYS___SYSCTL,
uintptr(mibptr),
uintptr(miblen),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
return buf, length, err
}
return buf, length, nil
}
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux || freebsd || darwin || openbsd
package common
import (
"context"
"errors"
"os/exec"
"strconv"
"strings"
)
func CallLsofWithContext(ctx context.Context, invoke Invoker, pid int32, args ...string) ([]string, error) {
var cmd []string
if pid == 0 { // will get from all processes.
cmd = []string{"-a", "-n", "-P"}
} else {
cmd = []string{"-a", "-n", "-P", "-p", strconv.Itoa(int(pid))}
}
cmd = append(cmd, args...)
out, err := invoke.CommandWithContext(ctx, "lsof", cmd...)
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
return []string{}, err
}
// if no pid found, lsof returns code 1.
if err.Error() == "exit status 1" && len(out) == 0 {
return []string{}, nil
}
}
lines := strings.Split(string(out), "\n")
var ret []string
for _, l := range lines[1:] {
if l == "" {
continue
}
ret = append(ret, l)
}
return ret, nil
}
@@ -0,0 +1,305 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build windows
package common
import (
"context"
"fmt"
"path/filepath"
"reflect"
"strings"
"syscall"
"unsafe"
"github.com/yusufpapurcu/wmi"
"golang.org/x/sys/windows"
)
// for double values
type PDH_FMT_COUNTERVALUE_DOUBLE struct { //nolint:revive //FIXME
CStatus uint32
DoubleValue float64
}
// for 64 bit integer values
type PDH_FMT_COUNTERVALUE_LARGE struct { //nolint:revive //FIXME
CStatus uint32
LargeValue int64
}
// for long values
type PDH_FMT_COUNTERVALUE_LONG struct { //nolint:revive //FIXME
CStatus uint32
LongValue int32
padding [4]byte
}
// windows system const
const (
ERROR_SUCCESS = 0
ERROR_FILE_NOT_FOUND = 2
DRIVE_REMOVABLE = 2
DRIVE_FIXED = 3
HKEY_LOCAL_MACHINE = 0x80000002
RRF_RT_REG_SZ = 0x00000002
RRF_RT_REG_DWORD = 0x00000010
PDH_FMT_LONG = 0x00000100
PDH_FMT_DOUBLE = 0x00000200
PDH_FMT_LARGE = 0x00000400
PDH_INVALID_DATA = 0xc0000bc6
PDH_INVALID_HANDLE = 0xC0000bbc
PDH_NO_DATA = 0x800007d5
STATUS_BUFFER_OVERFLOW = 0x80000005
STATUS_BUFFER_TOO_SMALL = 0xC0000023
STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
)
const (
ProcessBasicInformation = 0
ProcessWow64Information = 26
ProcessQueryInformation = windows.PROCESS_DUP_HANDLE | windows.PROCESS_QUERY_INFORMATION
SystemExtendedHandleInformationClass = 64
)
var (
Modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
ModNt = windows.NewLazySystemDLL("ntdll.dll")
ModPdh = windows.NewLazySystemDLL("pdh.dll")
ModPsapi = windows.NewLazySystemDLL("psapi.dll")
ModPowrProf = windows.NewLazySystemDLL("powrprof.dll")
ProcGetSystemTimes = Modkernel32.NewProc("GetSystemTimes")
ProcNtQuerySystemInformation = ModNt.NewProc("NtQuerySystemInformation")
ProcRtlGetNativeSystemInformation = ModNt.NewProc("RtlGetNativeSystemInformation")
ProcRtlNtStatusToDosError = ModNt.NewProc("RtlNtStatusToDosError")
ProcNtQueryInformationProcess = ModNt.NewProc("NtQueryInformationProcess")
ProcNtReadVirtualMemory = ModNt.NewProc("NtReadVirtualMemory")
ProcNtWow64QueryInformationProcess64 = ModNt.NewProc("NtWow64QueryInformationProcess64")
ProcNtWow64ReadVirtualMemory64 = ModNt.NewProc("NtWow64ReadVirtualMemory64")
PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery")
PdhAddEnglishCounterW = ModPdh.NewProc("PdhAddEnglishCounterW")
PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData")
PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue")
PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery")
procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW")
)
type FILETIME struct {
DwLowDateTime uint32
DwHighDateTime uint32
}
// borrowed from net/interface_windows.go
func BytePtrToString(p *uint8) string {
a := (*[10000]uint8)(unsafe.Pointer(p))
i := 0
for a[i] != 0 {
i++
}
return string(a[:i])
}
// CounterInfo struct is used to track a windows performance counter
// copied from https://github.com/mackerelio/mackerel-agent/
type CounterInfo struct {
PostName string
CounterName string
Counter windows.Handle
}
// CreateQuery with a PdhOpenQuery call
// copied from https://github.com/mackerelio/mackerel-agent/
func CreateQuery() (windows.Handle, error) {
var query windows.Handle
r, _, err := PdhOpenQuery.Call(0, 0, uintptr(unsafe.Pointer(&query)))
if r != 0 {
return 0, err
}
return query, nil
}
// CreateCounter with a PdhAddEnglishCounterW call
func CreateCounter(query windows.Handle, pname, cname string) (*CounterInfo, error) {
var counter windows.Handle
r, _, err := PdhAddEnglishCounterW.Call(
uintptr(query),
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(cname))),
0,
uintptr(unsafe.Pointer(&counter)))
if r != 0 {
return nil, err
}
return &CounterInfo{
PostName: pname,
CounterName: cname,
Counter: counter,
}, nil
}
// GetCounterValue get counter value from handle
// adapted from https://github.com/mackerelio/mackerel-agent/
func GetCounterValue(counter windows.Handle) (float64, error) {
var value PDH_FMT_COUNTERVALUE_DOUBLE
r, _, err := PdhGetFormattedCounterValue.Call(uintptr(counter), PDH_FMT_DOUBLE, uintptr(0), uintptr(unsafe.Pointer(&value)))
if r != 0 && r != PDH_INVALID_DATA {
return 0.0, err
}
return value.DoubleValue, nil
}
type Win32PerformanceCounter struct {
PostName string
CounterName string
Query windows.Handle
Counter windows.Handle
}
func NewWin32PerformanceCounter(postName, counterName string) (*Win32PerformanceCounter, error) {
query, err := CreateQuery()
if err != nil {
return nil, err
}
counter := Win32PerformanceCounter{
Query: query,
PostName: postName,
CounterName: counterName,
}
r, _, err := PdhAddEnglishCounterW.Call(
uintptr(counter.Query),
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(counter.CounterName))),
0,
uintptr(unsafe.Pointer(&counter.Counter)),
)
if r != 0 {
return nil, err
}
return &counter, nil
}
func (w *Win32PerformanceCounter) GetValue() (float64, error) {
r, _, err := PdhCollectQueryData.Call(uintptr(w.Query))
if r != 0 && err != nil {
if r == PDH_NO_DATA {
return 0.0, fmt.Errorf("%w: this counter has not data", err)
}
return 0.0, err
}
return GetCounterValue(w.Counter)
}
func ProcessorQueueLengthCounter() (*Win32PerformanceCounter, error) {
return NewWin32PerformanceCounter("processor_queue_length", `\System\Processor Queue Length`)
}
// WMIQueryWithContext - wraps wmi.Query with a timed-out context to avoid hanging
func WMIQueryWithContext(ctx context.Context, query string, dst any, connectServerArgs ...any) error {
if _, ok := ctx.Deadline(); !ok {
ctxTimeout, cancel := context.WithTimeout(ctx, Timeout)
defer cancel()
ctx = ctxTimeout
}
errChan := make(chan error, 1)
go func() {
errChan <- wmi.Query(query, dst, connectServerArgs...)
}()
select {
case <-ctx.Done():
return ctx.Err()
case err := <-errChan:
return err
}
}
// Convert paths using native DOS format like:
//
// "\Device\HarddiskVolume1\Windows\systemew\file.txt"
//
// into:
//
// "C:\Windows\systemew\file.txt"
func ConvertDOSPath(p string) string {
rawDrive := strings.Join(strings.Split(p, `\`)[:3], `\`)
for d := 'A'; d <= 'Z'; d++ {
szDeviceName := string(d) + ":"
szTarget := make([]uint16, 512)
ret, _, _ := procQueryDosDeviceW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(szDeviceName))),
uintptr(unsafe.Pointer(&szTarget[0])),
uintptr(len(szTarget)))
if ret != 0 && windows.UTF16ToString(szTarget) == rawDrive {
return filepath.Join(szDeviceName, p[len(rawDrive):])
}
}
return p
}
type NtStatus uint32
func (s NtStatus) Error() error {
if s == 0 {
return nil
}
return fmt.Errorf("NtStatus 0x%08x", uint32(s))
}
func (s NtStatus) IsError() bool {
return s>>30 == 3
}
type SystemExtendedHandleTableEntryInformation struct {
Object uintptr
UniqueProcessId uintptr
HandleValue uintptr
GrantedAccess uint32
CreatorBackTraceIndex uint16
ObjectTypeIndex uint16
HandleAttributes uint32
Reserved uint32
}
type SystemExtendedHandleInformation struct {
NumberOfHandles uintptr
Reserved uintptr
Handles [1]SystemExtendedHandleTableEntryInformation
}
// CallWithExpandingBuffer https://github.com/hillu/go-ntdll
func CallWithExpandingBuffer(fn func() NtStatus, buf *[]byte, resultLength *uint32) NtStatus {
for {
st := fn()
if st == STATUS_BUFFER_OVERFLOW || st == STATUS_BUFFER_TOO_SMALL || st == STATUS_INFO_LENGTH_MISMATCH {
if int(*resultLength) <= cap(*buf) {
(*reflect.SliceHeader)(unsafe.Pointer(buf)).Len = int(*resultLength)
} else {
*buf = make([]byte, int(*resultLength))
}
continue
}
if !st.IsError() {
*buf = (*buf)[:int(*resultLength)]
}
return st
}
}
func NtQuerySystemInformation(
SystemInformationClass uint32,
SystemInformation *byte,
SystemInformationLength uint32,
ReturnLength *uint32,
) NtStatus {
r0, _, _ := ProcNtQuerySystemInformation.Call(
uintptr(SystemInformationClass),
uintptr(unsafe.Pointer(SystemInformation)),
uintptr(SystemInformationLength),
uintptr(unsafe.Pointer(ReturnLength)))
return NtStatus(r0)
}
+11
View File
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: BSD-3-Clause
package common
import "unsafe"
// IsLittleEndian checks if the current platform uses little-endian.
// copied from https://github.com/ntrrg/ntgo/blob/v0.8.0/runtime/infrastructure.go#L16 (MIT License)
func IsLittleEndian() bool {
var x int16 = 0x0011
return *(*byte)(unsafe.Pointer(&x)) == 0x11
}
@@ -0,0 +1,53 @@
package common
import (
"errors"
"os"
"sync"
"syscall"
)
var bufferPool = sync.Pool{
New: func() any {
b := make([]byte, syscall.PathMax)
return &b
},
}
// The following three functions are copied from stdlib.
// ignoringEINTR2 is ignoringEINTR, but returning an additional value.
func ignoringEINTR2[T any](fn func() (T, error)) (T, error) {
for {
v, err := fn()
if !errors.Is(err, syscall.EINTR) {
return v, err
}
}
}
// Many functions in package syscall return a count of -1 instead of 0.
// Using fixCount(call()) instead of call() corrects the count.
func fixCount(n int, err error) (int, error) {
if n < 0 {
n = 0
}
return n, err
}
// Readlink behaves like os.Readlink but caches the buffer passed to syscall.Readlink.
func Readlink(name string) (string, error) {
b := bufferPool.Get().(*[]byte)
n, err := ignoringEINTR2(func() (int, error) {
return fixCount(syscall.Readlink(name, *b))
})
if err != nil {
bufferPool.Put(b)
return "", &os.PathError{Op: "readlink", Path: name, Err: err}
}
result := string((*b)[:n])
bufferPool.Put(b)
return result, nil
}
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: BSD-3-Clause
package common
import (
"context"
"time"
)
// Sleep awaits for provided interval.
// Can be interrupted by context cancellation.
func Sleep(ctx context.Context, interval time.Duration) error {
timer := time.NewTimer(interval)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return ctx.Err()
case <-timer.C:
return nil
}
}
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: BSD-3-Clause
package common
import (
"fmt"
"strings"
)
const (
maxWarnings = 100 // An arbitrary limit to avoid excessive memory usage, it has no sense to store hundreds of errors
tooManyErrorsMessage = "too many errors reported, next errors were discarded"
numberOfWarningsMessage = "Number of warnings:"
)
type Warnings struct {
List []error
tooManyErrors bool
Verbose bool
}
func (w *Warnings) Add(err error) {
if len(w.List) >= maxWarnings {
w.tooManyErrors = true
return
}
w.List = append(w.List, err)
}
func (w *Warnings) Reference() error {
if len(w.List) > 0 {
return w
}
return nil
}
func (w *Warnings) Error() string {
if w.Verbose {
str := ""
var sb strings.Builder
for i, e := range w.List {
fmt.Fprintf(&sb, "\tError %d: %s\n", i, e.Error())
}
str += sb.String()
if w.tooManyErrors {
str += fmt.Sprintf("\t%s\n", tooManyErrorsMessage)
}
return str
}
if w.tooManyErrors {
return fmt.Sprintf("%s > %v - %s", numberOfWarningsMessage, maxWarnings, tooManyErrorsMessage)
}
return fmt.Sprintf("%s %v", numberOfWarningsMessage, len(w.List))
}