mirror of https://github.com/shirou/gopsutil
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
11 KiB
Go
393 lines
11 KiB
Go
// SPDX-License-Identifier: BSD-3-Clause
|
|
//go:build linux
|
|
|
|
package host
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/shirou/gopsutil/v4/internal/common"
|
|
)
|
|
|
|
type lsbStruct struct {
|
|
ID string
|
|
Release string
|
|
Codename string
|
|
Description string
|
|
}
|
|
|
|
// from utmp.h
|
|
const (
|
|
user_PROCESS = 7
|
|
)
|
|
|
|
func HostIDWithContext(ctx context.Context) (string, error) {
|
|
sysProductUUID := common.HostSysWithContext(ctx, "class/dmi/id/product_uuid")
|
|
machineID := common.HostEtcWithContext(ctx, "machine-id")
|
|
procSysKernelRandomBootID := common.HostProcWithContext(ctx, "sys/kernel/random/boot_id")
|
|
switch {
|
|
// In order to read this file, needs to be supported by kernel/arch and run as root
|
|
// so having fallback is important
|
|
case common.PathExists(sysProductUUID):
|
|
lines, err := common.ReadLines(sysProductUUID)
|
|
if err == nil && len(lines) > 0 && lines[0] != "" {
|
|
return strings.ToLower(lines[0]), nil
|
|
}
|
|
fallthrough
|
|
// Fallback on GNU Linux systems with systemd, readable by everyone
|
|
case common.PathExists(machineID):
|
|
lines, err := common.ReadLines(machineID)
|
|
if err == nil && len(lines) > 0 && len(lines[0]) == 32 {
|
|
st := lines[0]
|
|
return fmt.Sprintf("%s-%s-%s-%s-%s", st[0:8], st[8:12], st[12:16], st[16:20], st[20:32]), nil
|
|
}
|
|
fallthrough
|
|
// Not stable between reboot, but better than nothing
|
|
default:
|
|
lines, err := common.ReadLines(procSysKernelRandomBootID)
|
|
if err == nil && len(lines) > 0 && lines[0] != "" {
|
|
return strings.ToLower(lines[0]), nil
|
|
}
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func numProcs(ctx context.Context) (uint64, error) {
|
|
return common.NumProcsWithContext(ctx)
|
|
}
|
|
|
|
func BootTimeWithContext(ctx context.Context) (uint64, error) {
|
|
return common.BootTimeWithContext(ctx, enableBootTimeCache)
|
|
}
|
|
|
|
func UptimeWithContext(ctx context.Context) (uint64, error) {
|
|
sysinfo := &unix.Sysinfo_t{}
|
|
if err := unix.Sysinfo(sysinfo); err != nil {
|
|
return 0, err
|
|
}
|
|
return uint64(sysinfo.Uptime), nil
|
|
}
|
|
|
|
func UsersWithContext(ctx context.Context) ([]UserStat, error) {
|
|
utmpfile := common.HostVarWithContext(ctx, "run/utmp")
|
|
|
|
file, err := os.Open(utmpfile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
buf, err := io.ReadAll(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
count := len(buf) / sizeOfUtmp
|
|
|
|
ret := make([]UserStat, 0, count)
|
|
|
|
for i := 0; i < count; i++ {
|
|
b := buf[i*sizeOfUtmp : (i+1)*sizeOfUtmp]
|
|
|
|
var u utmp
|
|
br := bytes.NewReader(b)
|
|
err := binary.Read(br, binary.LittleEndian, &u)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if u.Type != user_PROCESS {
|
|
continue
|
|
}
|
|
user := UserStat{
|
|
User: common.IntToString(u.User[:]),
|
|
Terminal: common.IntToString(u.Line[:]),
|
|
Host: common.IntToString(u.Host[:]),
|
|
Started: int(u.Tv.Sec),
|
|
}
|
|
ret = append(ret, user)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func getlsbStruct(ctx context.Context) (*lsbStruct, error) {
|
|
ret := &lsbStruct{}
|
|
if common.PathExists(common.HostEtcWithContext(ctx, "lsb-release")) {
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "lsb-release"))
|
|
if err != nil {
|
|
return ret, err // return empty
|
|
}
|
|
for _, line := range contents {
|
|
field := strings.Split(line, "=")
|
|
if len(field) < 2 {
|
|
continue
|
|
}
|
|
switch field[0] {
|
|
case "DISTRIB_ID":
|
|
ret.ID = strings.ReplaceAll(field[1], `"`, ``)
|
|
case "DISTRIB_RELEASE":
|
|
ret.Release = strings.ReplaceAll(field[1], `"`, ``)
|
|
case "DISTRIB_CODENAME":
|
|
ret.Codename = strings.ReplaceAll(field[1], `"`, ``)
|
|
case "DISTRIB_DESCRIPTION":
|
|
ret.Description = strings.ReplaceAll(field[1], `"`, ``)
|
|
}
|
|
}
|
|
} else if common.PathExists("/usr/bin/lsb_release") {
|
|
out, err := invoke.Command("/usr/bin/lsb_release")
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
field := strings.Split(line, ":")
|
|
if len(field) < 2 {
|
|
continue
|
|
}
|
|
switch field[0] {
|
|
case "Distributor ID":
|
|
ret.ID = strings.ReplaceAll(field[1], `"`, ``)
|
|
case "Release":
|
|
ret.Release = strings.ReplaceAll(field[1], `"`, ``)
|
|
case "Codename":
|
|
ret.Codename = strings.ReplaceAll(field[1], `"`, ``)
|
|
case "Description":
|
|
ret.Description = strings.ReplaceAll(field[1], `"`, ``)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) {
|
|
lsb, err := getlsbStruct(ctx)
|
|
if err != nil {
|
|
lsb = &lsbStruct{}
|
|
}
|
|
|
|
if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "oracle-release")) {
|
|
platform = "oracle"
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "oracle-release"))
|
|
if err == nil {
|
|
version = getRedhatishVersion(contents)
|
|
}
|
|
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "enterprise-release")) {
|
|
platform = "oracle"
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "enterprise-release"))
|
|
if err == nil {
|
|
version = getRedhatishVersion(contents)
|
|
}
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "slackware-version")) {
|
|
platform = "slackware"
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "slackware-version"))
|
|
if err == nil {
|
|
version = getSlackwareVersion(contents)
|
|
}
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "debian_version")) {
|
|
if lsb.ID == "Ubuntu" {
|
|
platform = "ubuntu"
|
|
version = lsb.Release
|
|
} else if lsb.ID == "LinuxMint" {
|
|
platform = "linuxmint"
|
|
version = lsb.Release
|
|
} else if lsb.ID == "Kylin" {
|
|
platform = "Kylin"
|
|
version = lsb.Release
|
|
} else if lsb.ID == `"Cumulus Linux"` {
|
|
platform = "cumuluslinux"
|
|
version = lsb.Release
|
|
} else if lsb.ID == "uos" {
|
|
platform = "uos"
|
|
version = lsb.Release
|
|
} else if lsb.ID == "Deepin" {
|
|
platform = "Deepin"
|
|
version = lsb.Release
|
|
} else {
|
|
if common.PathExistsWithContents("/usr/bin/raspi-config") {
|
|
platform = "raspbian"
|
|
} else {
|
|
platform = "debian"
|
|
}
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "debian_version"))
|
|
if err == nil && len(contents) > 0 && contents[0] != "" {
|
|
version = contents[0]
|
|
}
|
|
}
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "neokylin-release")) {
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "neokylin-release"))
|
|
if err == nil {
|
|
version = getRedhatishVersion(contents)
|
|
platform = getRedhatishPlatform(contents)
|
|
}
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "redhat-release")) {
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "redhat-release"))
|
|
if err == nil {
|
|
version = getRedhatishVersion(contents)
|
|
platform = getRedhatishPlatform(contents)
|
|
}
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "system-release")) {
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "system-release"))
|
|
if err == nil {
|
|
version = getRedhatishVersion(contents)
|
|
platform = getRedhatishPlatform(contents)
|
|
}
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "gentoo-release")) {
|
|
platform = "gentoo"
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "gentoo-release"))
|
|
if err == nil {
|
|
version = getRedhatishVersion(contents)
|
|
}
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "SuSE-release")) {
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "SuSE-release"))
|
|
if err == nil {
|
|
version = getSuseVersion(contents)
|
|
platform = getSusePlatform(contents)
|
|
}
|
|
// TODO: slackware detecion
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "arch-release")) {
|
|
platform = "arch"
|
|
version = lsb.Release
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "alpine-release")) {
|
|
platform = "alpine"
|
|
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "alpine-release"))
|
|
if err == nil && len(contents) > 0 && contents[0] != "" {
|
|
version = contents[0]
|
|
}
|
|
} else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "os-release")) {
|
|
p, v, err := common.GetOSReleaseWithContext(ctx)
|
|
if err == nil {
|
|
platform = p
|
|
version = v
|
|
}
|
|
} else if lsb.ID == "RedHat" {
|
|
platform = "redhat"
|
|
version = lsb.Release
|
|
} else if lsb.ID == "Amazon" {
|
|
platform = "amazon"
|
|
version = lsb.Release
|
|
} else if lsb.ID == "ScientificSL" {
|
|
platform = "scientific"
|
|
version = lsb.Release
|
|
} else if lsb.ID == "XenServer" {
|
|
platform = "xenserver"
|
|
version = lsb.Release
|
|
} else if lsb.ID != "" {
|
|
platform = strings.ToLower(lsb.ID)
|
|
version = lsb.Release
|
|
}
|
|
|
|
platform = strings.Trim(platform, `"`)
|
|
|
|
switch platform {
|
|
case "debian", "ubuntu", "linuxmint", "raspbian", "Kylin", "cumuluslinux", "uos", "Deepin":
|
|
family = "debian"
|
|
case "fedora":
|
|
family = "fedora"
|
|
case "oracle", "centos", "redhat", "scientific", "enterpriseenterprise", "amazon", "xenserver", "cloudlinux", "ibm_powerkvm", "rocky", "almalinux":
|
|
family = "rhel"
|
|
case "suse", "opensuse", "opensuse-leap", "opensuse-tumbleweed", "opensuse-tumbleweed-kubic", "sles", "sled", "caasp":
|
|
family = "suse"
|
|
case "gentoo":
|
|
family = "gentoo"
|
|
case "slackware":
|
|
family = "slackware"
|
|
case "arch":
|
|
family = "arch"
|
|
case "exherbo":
|
|
family = "exherbo"
|
|
case "alpine":
|
|
family = "alpine"
|
|
case "coreos":
|
|
family = "coreos"
|
|
case "solus":
|
|
family = "solus"
|
|
case "neokylin":
|
|
family = "neokylin"
|
|
case "anolis":
|
|
family = "anolis"
|
|
}
|
|
|
|
return platform, family, version, nil
|
|
}
|
|
|
|
func KernelVersionWithContext(ctx context.Context) (version string, err error) {
|
|
var utsname unix.Utsname
|
|
err = unix.Uname(&utsname)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return unix.ByteSliceToString(utsname.Release[:]), nil
|
|
}
|
|
|
|
func getSlackwareVersion(contents []string) string {
|
|
c := strings.ToLower(strings.Join(contents, ""))
|
|
c = strings.Replace(c, "slackware ", "", 1)
|
|
return c
|
|
}
|
|
|
|
var redhatishReleaseMatch = regexp.MustCompile(`release (\w[\d.]*)`)
|
|
|
|
func getRedhatishVersion(contents []string) string {
|
|
c := strings.ToLower(strings.Join(contents, ""))
|
|
|
|
if strings.Contains(c, "rawhide") {
|
|
return "rawhide"
|
|
}
|
|
if matches := redhatishReleaseMatch.FindStringSubmatch(c); matches != nil {
|
|
return matches[1]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getRedhatishPlatform(contents []string) string {
|
|
c := strings.ToLower(strings.Join(contents, ""))
|
|
|
|
if strings.Contains(c, "red hat") {
|
|
return "redhat"
|
|
}
|
|
f := strings.Split(c, " ")
|
|
|
|
return f[0]
|
|
}
|
|
|
|
var (
|
|
suseVersionMatch = regexp.MustCompile(`VERSION = ([\d.]+)`)
|
|
susePatchLevelMatch = regexp.MustCompile(`PATCHLEVEL = (\d+)`)
|
|
)
|
|
|
|
func getSuseVersion(contents []string) string {
|
|
version := ""
|
|
for _, line := range contents {
|
|
if matches := suseVersionMatch.FindStringSubmatch(line); matches != nil {
|
|
version = matches[1]
|
|
} else if matches = susePatchLevelMatch.FindStringSubmatch(line); matches != nil {
|
|
version = version + "." + matches[1]
|
|
}
|
|
}
|
|
return version
|
|
}
|
|
|
|
func getSusePlatform(contents []string) string {
|
|
c := strings.ToLower(strings.Join(contents, ""))
|
|
if strings.Contains(c, "opensuse") {
|
|
return "opensuse"
|
|
}
|
|
return "suse"
|
|
}
|
|
|
|
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
|
|
return common.VirtualizationWithContext(ctx)
|
|
}
|