enhancement(search): add support for testcontainers to run local tests

This commit is contained in:
fschade
2025-08-28 09:32:26 +02:00
parent ad866b8ce3
commit ca0493b286
851 changed files with 111016 additions and 18 deletions
+638
View File
@@ -0,0 +1,638 @@
// SPDX-License-Identifier: BSD-3-Clause
package common
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package binary implements simple translation between numbers and byte
// sequences and encoding and decoding of varints.
//
// Numbers are translated by reading and writing fixed-size values.
// A fixed-size value is either a fixed-size arithmetic
// type (int8, uint8, int16, float32, complex64, ...)
// or an array or struct containing only fixed-size values.
//
// The varint functions encode and decode single integer values using
// a variable-length encoding; smaller values require fewer bytes.
// For a specification, see
// http://code.google.com/apis/protocolbuffers/docs/encoding.html.
//
// This package favors simplicity over efficiency. Clients that require
// high-performance serialization, especially for large data structures,
// should look at more advanced solutions such as the encoding/gob
// package or protocol buffers.
import (
"errors"
"io"
"math"
"reflect"
)
// A ByteOrder specifies how to convert byte sequences into
// 16-, 32-, or 64-bit unsigned integers.
type ByteOrder interface {
Uint16([]byte) uint16
Uint32([]byte) uint32
Uint64([]byte) uint64
PutUint16([]byte, uint16)
PutUint32([]byte, uint32)
PutUint64([]byte, uint64)
String() string
}
// LittleEndian is the little-endian implementation of ByteOrder.
var LittleEndian littleEndian
// BigEndian is the big-endian implementation of ByteOrder.
var BigEndian bigEndian
type littleEndian struct{}
func (littleEndian) Uint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 }
func (littleEndian) PutUint16(b []byte, v uint16) {
b[0] = byte(v)
b[1] = byte(v >> 8)
}
func (littleEndian) Uint32(b []byte) uint32 {
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
func (littleEndian) PutUint32(b []byte, v uint32) {
b[0] = byte(v)
b[1] = byte(v >> 8)
b[2] = byte(v >> 16)
b[3] = byte(v >> 24)
}
func (littleEndian) Uint64(b []byte) uint64 {
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
}
func (littleEndian) PutUint64(b []byte, v uint64) {
b[0] = byte(v)
b[1] = byte(v >> 8)
b[2] = byte(v >> 16)
b[3] = byte(v >> 24)
b[4] = byte(v >> 32)
b[5] = byte(v >> 40)
b[6] = byte(v >> 48)
b[7] = byte(v >> 56)
}
func (littleEndian) String() string { return "LittleEndian" }
func (littleEndian) GoString() string { return "binary.LittleEndian" }
type bigEndian struct{}
func (bigEndian) Uint16(b []byte) uint16 { return uint16(b[1]) | uint16(b[0])<<8 }
func (bigEndian) PutUint16(b []byte, v uint16) {
b[0] = byte(v >> 8)
b[1] = byte(v)
}
func (bigEndian) Uint32(b []byte) uint32 {
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
}
func (bigEndian) PutUint32(b []byte, v uint32) {
b[0] = byte(v >> 24)
b[1] = byte(v >> 16)
b[2] = byte(v >> 8)
b[3] = byte(v)
}
func (bigEndian) Uint64(b []byte) uint64 {
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
}
func (bigEndian) PutUint64(b []byte, v uint64) {
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
}
func (bigEndian) String() string { return "BigEndian" }
func (bigEndian) GoString() string { return "binary.BigEndian" }
// Read reads structured binary data from r into data.
// Data must be a pointer to a fixed-size value or a slice
// of fixed-size values.
// Bytes read from r are decoded using the specified byte order
// and written to successive fields of the data.
// When reading into structs, the field data for fields with
// blank (_) field names is skipped; i.e., blank field names
// may be used for padding.
// When reading into a struct, all non-blank fields must be exported.
func Read(r io.Reader, order ByteOrder, data any) error {
// Fast path for basic types and slices.
if n := intDataSize(data); n != 0 {
var b [8]byte
var bs []byte
if n > len(b) {
bs = make([]byte, n)
} else {
bs = b[:n]
}
if _, err := io.ReadFull(r, bs); err != nil {
return err
}
switch data := data.(type) {
case *int8:
*data = int8(b[0])
case *uint8:
*data = b[0]
case *int16:
*data = int16(order.Uint16(bs))
case *uint16:
*data = order.Uint16(bs)
case *int32:
*data = int32(order.Uint32(bs))
case *uint32:
*data = order.Uint32(bs)
case *int64:
*data = int64(order.Uint64(bs))
case *uint64:
*data = order.Uint64(bs)
case []int8:
for i, x := range bs { // Easier to loop over the input for 8-bit values.
data[i] = int8(x)
}
case []uint8:
copy(data, bs)
case []int16:
for i := range data {
data[i] = int16(order.Uint16(bs[2*i:]))
}
case []uint16:
for i := range data {
data[i] = order.Uint16(bs[2*i:])
}
case []int32:
for i := range data {
data[i] = int32(order.Uint32(bs[4*i:]))
}
case []uint32:
for i := range data {
data[i] = order.Uint32(bs[4*i:])
}
case []int64:
for i := range data {
data[i] = int64(order.Uint64(bs[8*i:]))
}
case []uint64:
for i := range data {
data[i] = order.Uint64(bs[8*i:])
}
}
return nil
}
// Fallback to reflect-based decoding.
v := reflect.ValueOf(data)
size := -1
switch v.Kind() {
case reflect.Ptr:
v = v.Elem()
size = dataSize(v)
case reflect.Slice:
size = dataSize(v)
}
if size < 0 {
return errors.New("binary.Read: invalid type " + reflect.TypeOf(data).String())
}
d := &decoder{order: order, buf: make([]byte, size)}
if _, err := io.ReadFull(r, d.buf); err != nil {
return err
}
d.value(v)
return nil
}
// Write writes the binary representation of data into w.
// Data must be a fixed-size value or a slice of fixed-size
// values, or a pointer to such data.
// Bytes written to w are encoded using the specified byte order
// and read from successive fields of the data.
// When writing structs, zero values are written for fields
// with blank (_) field names.
func Write(w io.Writer, order ByteOrder, data any) error {
// Fast path for basic types and slices.
if n := intDataSize(data); n != 0 {
var b [8]byte
var bs []byte
if n > len(b) {
bs = make([]byte, n)
} else {
bs = b[:n]
}
switch v := data.(type) {
case *int8:
bs = b[:1]
b[0] = byte(*v)
case int8:
bs = b[:1]
b[0] = byte(v)
case []int8:
for i, x := range v {
bs[i] = byte(x)
}
case *uint8:
bs = b[:1]
b[0] = *v
case uint8:
bs = b[:1]
b[0] = byte(v)
case []uint8:
bs = v
case *int16:
bs = b[:2]
order.PutUint16(bs, uint16(*v))
case int16:
bs = b[:2]
order.PutUint16(bs, uint16(v))
case []int16:
for i, x := range v {
order.PutUint16(bs[2*i:], uint16(x))
}
case *uint16:
bs = b[:2]
order.PutUint16(bs, *v)
case uint16:
bs = b[:2]
order.PutUint16(bs, v)
case []uint16:
for i, x := range v {
order.PutUint16(bs[2*i:], x)
}
case *int32:
bs = b[:4]
order.PutUint32(bs, uint32(*v))
case int32:
bs = b[:4]
order.PutUint32(bs, uint32(v))
case []int32:
for i, x := range v {
order.PutUint32(bs[4*i:], uint32(x))
}
case *uint32:
bs = b[:4]
order.PutUint32(bs, *v)
case uint32:
bs = b[:4]
order.PutUint32(bs, v)
case []uint32:
for i, x := range v {
order.PutUint32(bs[4*i:], x)
}
case *int64:
bs = b[:8]
order.PutUint64(bs, uint64(*v))
case int64:
bs = b[:8]
order.PutUint64(bs, uint64(v))
case []int64:
for i, x := range v {
order.PutUint64(bs[8*i:], uint64(x))
}
case *uint64:
bs = b[:8]
order.PutUint64(bs, *v)
case uint64:
bs = b[:8]
order.PutUint64(bs, v)
case []uint64:
for i, x := range v {
order.PutUint64(bs[8*i:], x)
}
}
_, err := w.Write(bs)
return err
}
// Fallback to reflect-based encoding.
v := reflect.Indirect(reflect.ValueOf(data))
size := dataSize(v)
if size < 0 {
return errors.New("binary.Write: invalid type " + reflect.TypeOf(data).String())
}
buf := make([]byte, size)
e := &encoder{order: order, buf: buf}
e.value(v)
_, err := w.Write(buf)
return err
}
// Size returns how many bytes Write would generate to encode the value v, which
// must be a fixed-size value or a slice of fixed-size values, or a pointer to such data.
// If v is neither of these, Size returns -1.
func Size(v any) int {
return dataSize(reflect.Indirect(reflect.ValueOf(v)))
}
// dataSize returns the number of bytes the actual data represented by v occupies in memory.
// For compound structures, it sums the sizes of the elements. Thus, for instance, for a slice
// it returns the length of the slice times the element size and does not count the memory
// occupied by the header. If the type of v is not acceptable, dataSize returns -1.
func dataSize(v reflect.Value) int {
if v.Kind() == reflect.Slice {
if s := sizeof(v.Type().Elem()); s >= 0 {
return s * v.Len()
}
return -1
}
return sizeof(v.Type())
}
// sizeof returns the size >= 0 of variables for the given type or -1 if the type is not acceptable.
func sizeof(t reflect.Type) int {
switch t.Kind() {
case reflect.Array:
if s := sizeof(t.Elem()); s >= 0 {
return s * t.Len()
}
case reflect.Struct:
sum := 0
for i, n := 0, t.NumField(); i < n; i++ {
s := sizeof(t.Field(i).Type)
if s < 0 {
return -1
}
sum += s
}
return sum
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.Ptr:
return int(t.Size())
}
return -1
}
type coder struct {
order ByteOrder
buf []byte
}
type (
decoder coder
encoder coder
)
func (d *decoder) uint8() uint8 {
x := d.buf[0]
d.buf = d.buf[1:]
return x
}
func (e *encoder) uint8(x uint8) {
e.buf[0] = x
e.buf = e.buf[1:]
}
func (d *decoder) uint16() uint16 {
x := d.order.Uint16(d.buf[0:2])
d.buf = d.buf[2:]
return x
}
func (e *encoder) uint16(x uint16) {
e.order.PutUint16(e.buf[0:2], x)
e.buf = e.buf[2:]
}
func (d *decoder) uint32() uint32 {
x := d.order.Uint32(d.buf[0:4])
d.buf = d.buf[4:]
return x
}
func (e *encoder) uint32(x uint32) {
e.order.PutUint32(e.buf[0:4], x)
e.buf = e.buf[4:]
}
func (d *decoder) uint64() uint64 {
x := d.order.Uint64(d.buf[0:8])
d.buf = d.buf[8:]
return x
}
func (e *encoder) uint64(x uint64) {
e.order.PutUint64(e.buf[0:8], x)
e.buf = e.buf[8:]
}
func (d *decoder) int8() int8 { return int8(d.uint8()) }
func (e *encoder) int8(x int8) { e.uint8(uint8(x)) }
func (d *decoder) int16() int16 { return int16(d.uint16()) }
func (e *encoder) int16(x int16) { e.uint16(uint16(x)) }
func (d *decoder) int32() int32 { return int32(d.uint32()) }
func (e *encoder) int32(x int32) { e.uint32(uint32(x)) }
func (d *decoder) int64() int64 { return int64(d.uint64()) }
func (e *encoder) int64(x int64) { e.uint64(uint64(x)) }
func (d *decoder) value(v reflect.Value) {
switch v.Kind() {
case reflect.Array:
l := v.Len()
for i := 0; i < l; i++ {
d.value(v.Index(i))
}
case reflect.Struct:
t := v.Type()
l := v.NumField()
for i := 0; i < l; i++ {
// Note: Calling v.CanSet() below is an optimization.
// It would be sufficient to check the field name,
// but creating the StructField info for each field is
// costly (run "go test -bench=ReadStruct" and compare
// results when making changes to this code).
if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" {
d.value(v)
} else {
d.skip(v)
}
}
case reflect.Slice:
l := v.Len()
for i := 0; i < l; i++ {
d.value(v.Index(i))
}
case reflect.Int8:
v.SetInt(int64(d.int8()))
case reflect.Int16:
v.SetInt(int64(d.int16()))
case reflect.Int32:
v.SetInt(int64(d.int32()))
case reflect.Int64:
v.SetInt(d.int64())
case reflect.Uint8:
v.SetUint(uint64(d.uint8()))
case reflect.Uint16:
v.SetUint(uint64(d.uint16()))
case reflect.Uint32:
v.SetUint(uint64(d.uint32()))
case reflect.Uint64:
v.SetUint(d.uint64())
case reflect.Float32:
v.SetFloat(float64(math.Float32frombits(d.uint32())))
case reflect.Float64:
v.SetFloat(math.Float64frombits(d.uint64()))
case reflect.Complex64:
v.SetComplex(complex(
float64(math.Float32frombits(d.uint32())),
float64(math.Float32frombits(d.uint32())),
))
case reflect.Complex128:
v.SetComplex(complex(
math.Float64frombits(d.uint64()),
math.Float64frombits(d.uint64()),
))
}
}
func (e *encoder) value(v reflect.Value) {
switch v.Kind() {
case reflect.Array:
l := v.Len()
for i := 0; i < l; i++ {
e.value(v.Index(i))
}
case reflect.Struct:
t := v.Type()
l := v.NumField()
for i := 0; i < l; i++ {
// see comment for corresponding code in decoder.value()
if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" {
e.value(v)
} else {
e.skip(v)
}
}
case reflect.Slice:
l := v.Len()
for i := 0; i < l; i++ {
e.value(v.Index(i))
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch v.Type().Kind() {
case reflect.Int8:
e.int8(int8(v.Int()))
case reflect.Int16:
e.int16(int16(v.Int()))
case reflect.Int32:
e.int32(int32(v.Int()))
case reflect.Int64:
e.int64(v.Int())
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
switch v.Type().Kind() {
case reflect.Uint8:
e.uint8(uint8(v.Uint()))
case reflect.Uint16:
e.uint16(uint16(v.Uint()))
case reflect.Uint32:
e.uint32(uint32(v.Uint()))
case reflect.Uint64:
e.uint64(v.Uint())
}
case reflect.Float32, reflect.Float64:
switch v.Type().Kind() {
case reflect.Float32:
e.uint32(math.Float32bits(float32(v.Float())))
case reflect.Float64:
e.uint64(math.Float64bits(v.Float()))
}
case reflect.Complex64, reflect.Complex128:
switch v.Type().Kind() {
case reflect.Complex64:
x := v.Complex()
e.uint32(math.Float32bits(float32(real(x))))
e.uint32(math.Float32bits(float32(imag(x))))
case reflect.Complex128:
x := v.Complex()
e.uint64(math.Float64bits(real(x)))
e.uint64(math.Float64bits(imag(x)))
}
}
}
func (d *decoder) skip(v reflect.Value) {
d.buf = d.buf[dataSize(v):]
}
func (e *encoder) skip(v reflect.Value) {
n := dataSize(v)
for i := range e.buf[0:n] {
e.buf[i] = 0
}
e.buf = e.buf[n:]
}
// intDataSize returns the size of the data required to represent the data when encoded.
// It returns zero if the type cannot be implemented by the fast path in Read or Write.
func intDataSize(data any) int {
switch data := data.(type) {
case int8, *int8, *uint8:
return 1
case []int8:
return len(data)
case []uint8:
return len(data)
case int16, *int16, *uint16:
return 2
case []int16:
return 2 * len(data)
case []uint16:
return 2 * len(data)
case int32, *int32, *uint32:
return 4
case []int32:
return 4 * len(data)
case []uint32:
return 4 * len(data)
case int64, *int64, *uint64:
return 8
case []int64:
return 8 * len(data)
case []uint64:
return 8 * len(data)
}
return 0
}
+473
View File
@@ -0,0 +1,473 @@
// 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)
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"math"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
"runtime"
"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 (i 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 string, 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 && len(line) > 0 {
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 {
for _, t := range target {
if strings.Contains(t, src) {
return true
}
}
return false
}
// IntContains checks the src in any int of the target int slice.
func IntContains(target []int, src int) bool {
for _, t := range target {
if src == t {
return true
}
}
return false
}
// 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 string, 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 string, 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 (see DoSysctrl).
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
}
+400
View File
@@ -0,0 +1,400 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin
package common
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"unsafe"
"github.com/ebitengine/purego"
"golang.org/x/sys/unix"
)
func DoSysctrlWithContext(ctx context.Context, mib string) ([]string, error) {
cmd := exec.CommandContext(ctx, "sysctl", "-n", mib)
cmd.Env = getSysctrlEnv(os.Environ())
out, err := cmd.Output()
if err != nil {
return []string{}, err
}
v := strings.Replace(string(out), "{ ", "", 1)
v = strings.Replace(string(v), " }", "", 1)
values := strings.Fields(string(v))
return values, nil
}
func CallSyscall(mib []int32) ([]byte, uint64, error) {
miblen := uint64(len(mib))
// get required buffer size
length := uint64(0)
_, _, err := unix.Syscall6(
202, // unix.SYS___SYSCTL https://github.com/golang/sys/blob/76b94024e4b621e672466e8db3d7f084e7ddcad2/unix/zsysnum_darwin_amd64.go#L146
uintptr(unsafe.Pointer(&mib[0])),
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(
202, // unix.SYS___SYSCTL https://github.com/golang/sys/blob/76b94024e4b621e672466e8db3d7f084e7ddcad2/unix/zsysnum_darwin_amd64.go#L146
uintptr(unsafe.Pointer(&mib[0])),
uintptr(miblen),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
return buf, length, err
}
return buf, length, nil
}
// Library represents a dynamic library loaded by purego.
type Library struct {
addr uintptr
path string
close func()
}
// library paths
const (
IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit"
CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
System = "/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
}
closeFunc := func() {
purego.Dlclose(lib)
}
return &Library{
addr: lib,
path: path,
close: closeFunc,
}, nil
}
func (lib *Library) Dlsym(symbol string) (uintptr, error) {
return purego.Dlsym(lib.addr, symbol)
}
func GetFunc[T any](lib *Library, symbol string) T {
var fptr T
purego.RegisterLibFunc(&fptr, lib.addr, symbol)
return fptr
}
func (lib *Library) Close() {
lib.close()
}
// status codes
const (
KERN_SUCCESS = 0
)
// IOKit functions and symbols.
type (
IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32
IOServiceGetMatchingServicesFunc func(mainPort uint32, matching uintptr, existing *uint32) int
IOServiceMatchingFunc func(name string) unsafe.Pointer
IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int
IOServiceCloseFunc func(connect uint32) int
IOIteratorNextFunc func(iterator uint32) uint32
IORegistryEntryGetNameFunc func(entry uint32, name CStr) int
IORegistryEntryGetParentEntryFunc func(entry uint32, plane string, parent *uint32) int
IORegistryEntryCreateCFPropertyFunc func(entry uint32, key, allocator uintptr, options uint32) unsafe.Pointer
IORegistryEntryCreateCFPropertiesFunc func(entry uint32, properties unsafe.Pointer, allocator uintptr, options uint32) int
IOObjectConformsToFunc func(object uint32, className string) bool
IOObjectReleaseFunc func(object uint32) int
IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int
IOHIDEventSystemClientCreateFunc func(allocator uintptr) unsafe.Pointer
IOHIDEventSystemClientSetMatchingFunc func(client, match uintptr) int
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 (
IOServiceGetMatchingServiceSym = "IOServiceGetMatchingService"
IOServiceGetMatchingServicesSym = "IOServiceGetMatchingServices"
IOServiceMatchingSym = "IOServiceMatching"
IOServiceOpenSym = "IOServiceOpen"
IOServiceCloseSym = "IOServiceClose"
IOIteratorNextSym = "IOIteratorNext"
IORegistryEntryGetNameSym = "IORegistryEntryGetName"
IORegistryEntryGetParentEntrySym = "IORegistryEntryGetParentEntry"
IORegistryEntryCreateCFPropertySym = "IORegistryEntryCreateCFProperty"
IORegistryEntryCreateCFPropertiesSym = "IORegistryEntryCreateCFProperties"
IOObjectConformsToSym = "IOObjectConformsTo"
IOObjectReleaseSym = "IOObjectRelease"
IOConnectCallStructMethodSym = "IOConnectCallStructMethod"
IOHIDEventSystemClientCreateSym = "IOHIDEventSystemClientCreate"
IOHIDEventSystemClientSetMatchingSym = "IOHIDEventSystemClientSetMatching"
IOHIDServiceClientCopyEventSym = "IOHIDServiceClientCopyEvent"
IOHIDServiceClientCopyPropertySym = "IOHIDServiceClientCopyProperty"
IOHIDEventGetFloatValueSym = "IOHIDEventGetFloatValue"
IOHIDEventSystemClientCopyServicesSym = "IOHIDEventSystemClientCopyServices"
)
const (
KIOMainPortDefault = 0
KIOHIDEventTypeTemperature = 15
KNilOptions = 0
)
const (
KIOMediaWholeKey = "Media"
KIOServicePlane = "IOService"
)
// CoreFoundation functions and symbols.
type (
CFGetTypeIDFunc func(cf uintptr) int32
CFNumberCreateFunc func(allocator uintptr, theType int32, valuePtr uintptr) unsafe.Pointer
CFNumberGetValueFunc func(num uintptr, theType int32, valuePtr uintptr) bool
CFDictionaryCreateFunc func(allocator uintptr, keys, values *unsafe.Pointer, numValues int32,
keyCallBacks, valueCallBacks uintptr) unsafe.Pointer
CFDictionaryAddValueFunc func(theDict, key, value uintptr)
CFDictionaryGetValueFunc func(theDict, key uintptr) unsafe.Pointer
CFArrayGetCountFunc func(theArray uintptr) int32
CFArrayGetValueAtIndexFunc func(theArray uintptr, index int32) unsafe.Pointer
CFStringCreateMutableFunc func(alloc uintptr, maxLength int32) unsafe.Pointer
CFStringGetLengthFunc func(theString uintptr) int32
CFStringGetCStringFunc func(theString uintptr, buffer CStr, bufferSize int32, encoding uint32)
CFStringCreateWithCStringFunc func(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer
CFDataGetLengthFunc func(theData uintptr) int32
CFDataGetBytePtrFunc func(theData uintptr) unsafe.Pointer
CFReleaseFunc func(cf uintptr)
)
const (
CFGetTypeIDSym = "CFGetTypeID"
CFNumberCreateSym = "CFNumberCreate"
CFNumberGetValueSym = "CFNumberGetValue"
CFDictionaryCreateSym = "CFDictionaryCreate"
CFDictionaryAddValueSym = "CFDictionaryAddValue"
CFDictionaryGetValueSym = "CFDictionaryGetValue"
CFArrayGetCountSym = "CFArrayGetCount"
CFArrayGetValueAtIndexSym = "CFArrayGetValueAtIndex"
CFStringCreateMutableSym = "CFStringCreateMutable"
CFStringGetLengthSym = "CFStringGetLength"
CFStringGetCStringSym = "CFStringGetCString"
CFStringCreateWithCStringSym = "CFStringCreateWithCString"
CFDataGetLengthSym = "CFDataGetLength"
CFDataGetBytePtrSym = "CFDataGetBytePtr"
CFReleaseSym = "CFRelease"
)
const (
KCFStringEncodingUTF8 = 0x08000100
KCFNumberSInt64Type = 4
KCFNumberIntType = 9
KCFAllocatorDefault = 0
)
// Kernel functions and symbols.
type MachTimeBaseInfo struct {
Numer uint32
Denom uint32
}
type (
HostProcessorInfoFunc func(host uint32, flavor int32, outProcessorCount *uint32, outProcessorInfo uintptr,
outProcessorInfoCnt *uint32) int
HostStatisticsFunc func(host uint32, flavor int32, hostInfoOut uintptr, hostInfoOutCnt *uint32) int
MachHostSelfFunc func() uint32
MachTaskSelfFunc func() uint32
MachTimeBaseInfoFunc func(info uintptr) int
VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int
)
const (
HostProcessorInfoSym = "host_processor_info"
HostStatisticsSym = "host_statistics"
MachHostSelfSym = "mach_host_self"
MachTaskSelfSym = "mach_task_self"
MachTimeBaseInfoSym = "mach_timebase_info"
VMDeallocateSym = "vm_deallocate"
)
const (
CTL_KERN = 1
KERN_ARGMAX = 8
KERN_PROCARGS2 = 49
HOST_VM_INFO = 2
HOST_CPU_LOAD_INFO = 3
HOST_VM_INFO_COUNT = 0xf
)
// System functions and symbols.
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_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN
PROC_PIDTASKINFO = 4
PROC_PIDVNODEPATHINFO = 9
)
// SMC represents a SMC instance.
type SMC struct {
lib *Library
conn uint32
callStruct IOConnectCallStructMethodFunc
}
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(ioKit *Library) (*SMC, error) {
if ioKit.path != IOKit {
return nil, errors.New("library is not IOKit")
}
ioServiceGetMatchingService := GetFunc[IOServiceGetMatchingServiceFunc](ioKit, IOServiceGetMatchingServiceSym)
ioServiceMatching := GetFunc[IOServiceMatchingFunc](ioKit, IOServiceMatchingSym)
ioServiceOpen := GetFunc[IOServiceOpenFunc](ioKit, IOServiceOpenSym)
ioObjectRelease := GetFunc[IOObjectReleaseFunc](ioKit, IOObjectReleaseSym)
machTaskSelf := GetFunc[MachTaskSelfFunc](ioKit, MachTaskSelfSym)
ioConnectCallStructMethod := GetFunc[IOConnectCallStructMethodFunc](ioKit, IOConnectCallStructMethodSym)
service := ioServiceGetMatchingService(0, uintptr(ioServiceMatching(ioServiceSMC)))
if service == 0 {
return nil, fmt.Errorf("ERROR: %s NOT FOUND", ioServiceSMC)
}
var conn uint32
if result := ioServiceOpen(service, machTaskSelf(), 0, &conn); result != 0 {
return nil, errors.New("ERROR: IOServiceOpen failed")
}
ioObjectRelease(service)
return &SMC{
lib: ioKit,
conn: conn,
callStruct: ioConnectCallStructMethod,
}, nil
}
func (s *SMC) CallStruct(selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int {
return s.callStruct(s.conn, selector, inputStruct, inputStructCnt, outputStruct, outputStructCnt)
}
func (s *SMC) Close() error {
ioServiceClose := GetFunc[IOServiceCloseFunc](s.lib, IOServiceCloseSym)
if result := ioServiceClose(s.conn); result != 0 {
return errors.New("ERROR: IOServiceClose failed")
}
return nil
}
type CStr []byte
func NewCStr(length int32) CStr {
return make(CStr, length)
}
func (s CStr) Length() int32 {
// Include null terminator to make CFStringGetCString properly functions
return int32(len(s)) + 1
}
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))
}
+82
View File
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build freebsd || openbsd
package common
import (
"fmt"
"os"
"os/exec"
"strings"
"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 DoSysctrl(mib string) ([]string, error) {
cmd := exec.Command("sysctl", "-n", mib)
cmd.Env = getSysctrlEnv(os.Environ())
out, err := cmd.Output()
if err != nil {
return []string{}, err
}
v := strings.Replace(string(out), "{ ", "", 1)
v = strings.Replace(string(v), " }", "", 1)
values := strings.Fields(string(v))
return values, nil
}
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
}
+358
View File
@@ -0,0 +1,358 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux
package common
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
)
// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64
func DoSysctrl(mib string) ([]string, error) {
cmd := exec.Command("sysctl", "-n", mib)
cmd.Env = getSysctrlEnv(os.Environ())
out, err := cmd.Output()
if err != nil {
return []string{}, err
}
v := strings.Replace(string(out), "{ ", "", 1)
v = strings.Replace(string(v), " }", "", 1)
values := strings.Fields(string(v))
return values, nil
}
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) {
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
}
return 0, err
}
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 string, version string, err error) {
return GetOSReleaseWithContext(context.Background())
}
func GetOSReleaseWithContext(ctx context.Context) (platform string, 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
}
+66
View File
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build netbsd
package common
import (
"os"
"os/exec"
"strings"
"unsafe"
"golang.org/x/sys/unix"
)
func DoSysctrl(mib string) ([]string, error) {
cmd := exec.Command("sysctl", "-n", mib)
cmd.Env = getSysctrlEnv(os.Environ())
out, err := cmd.Output()
if err != nil {
return []string{}, err
}
v := strings.Replace(string(out), "{ ", "", 1)
v = strings.Replace(string(v), " }", "", 1)
values := strings.Fields(string(v))
return values, nil
}
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
}
+66
View File
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build openbsd
package common
import (
"os"
"os/exec"
"strings"
"unsafe"
"golang.org/x/sys/unix"
)
func DoSysctrl(mib string) ([]string, error) {
cmd := exec.Command("sysctl", "-n", mib)
cmd.Env = getSysctrlEnv(os.Environ())
out, err := cmd.Output()
if err != nil {
return []string{}, err
}
v := strings.Replace(string(out), "{ ", "", 1)
v = strings.Replace(string(v), " }", "", 1)
values := strings.Fields(string(v))
return values, nil
}
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
}
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: BSD-3-Clause
package common
import (
"errors"
"testing"
)
func SkipIfNotImplementedErr(tb testing.TB, err error) {
tb.Helper()
if errors.Is(err, ErrNotImplementedError) {
tb.Skip("not implemented")
}
}
+42
View File
@@ -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 len(l) == 0 {
continue
}
ret = append(ret, l)
}
return ret, nil
}
+304
View File
@@ -0,0 +1,304 @@
// 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")
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
}
+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
}
}
+31
View File
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: BSD-3-Clause
package common
import "fmt"
type Warnings struct {
List []error
Verbose bool
}
func (w *Warnings) Add(err error) {
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 := ""
for i, e := range w.List {
str += fmt.Sprintf("\tError %d: %s\n", i, e.Error())
}
return str
}
return fmt.Sprintf("Number of warnings: %v", len(w.List))
}