Initial QSfera import
This commit is contained in:
+475
@@ -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
|
||||
}
|
||||
+131
@@ -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
|
||||
}
|
||||
+577
@@ -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
|
||||
}
|
||||
+65
@@ -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
|
||||
}
|
||||
+343
@@ -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
|
||||
}
|
||||
+49
@@ -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
|
||||
}
|
||||
+49
@@ -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
|
||||
}
|
||||
+42
@@ -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
|
||||
}
|
||||
+305
@@ -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
@@ -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
|
||||
}
|
||||
+53
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
+53
@@ -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))
|
||||
}
|
||||
Reference in New Issue
Block a user