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.
gopsutil/host/host_linux.go

393 lines
11 KiB
Go

// SPDX-License-Identifier: BSD-3-Clause
3 years ago
//go:build linux
11 years ago
package host
11 years ago
import (
"bytes"
"context"
11 years ago
"encoding/binary"
"fmt"
"io"
11 years ago
"os"
"regexp"
11 years ago
"strings"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/v4/internal/common"
11 years ago
)
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)
11 years ago
}
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
}
8 years ago
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]
11 years ago
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], `"`, ``)
}
}
10 years ago
} 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")) {
11 years ago
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
11 years ago
} else {
if common.PathExistsWithContents("/usr/bin/raspi-config") {
11 years ago
platform = "raspbian"
} else {
platform = "debian"
}
contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "debian_version"))
if err == nil && len(contents) > 0 && contents[0] != "" {
11 years ago
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
}
11 years ago
} else if lsb.ID == "RedHat" {
platform = "redhat"
version = lsb.Release
11 years ago
} else if lsb.ID == "Amazon" {
platform = "amazon"
11 years ago
version = lsb.Release
} else if lsb.ID == "ScientificSL" {
platform = "scientific"
11 years ago
version = lsb.Release
} else if lsb.ID == "XenServer" {
platform = "xenserver"
11 years ago
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]
}
11 years ago
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)
}