190 lines
4.4 KiB
Go
190 lines
4.4 KiB
Go
|
// Copyright 2016 The Periph Authors. All rights reserved.
|
||
|
// Use of this source code is governed under the Apache License, Version 2.0
|
||
|
// that can be found in the LICENSE file.
|
||
|
|
||
|
// Package distro implements common functionality to auto-detect features on
|
||
|
// the host; generally about linux distributions.
|
||
|
//
|
||
|
// Most of the functions exported as in the form IsFoo() where Foo is a linux
|
||
|
// distribution.
|
||
|
package distro
|
||
|
|
||
|
import (
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"unicode"
|
||
|
)
|
||
|
|
||
|
// IsArmbian returns true if running on a Armbian distribution.
|
||
|
//
|
||
|
// http://www.armbian.com/
|
||
|
func IsArmbian() bool {
|
||
|
if isArm && isLinux {
|
||
|
// Armbian presents itself as debian in /etc/os-release so OSRelease()
|
||
|
// cannot be used..
|
||
|
_, err := os.Stat("/etc/armbian.txt")
|
||
|
return err == nil
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// IsDebian returns true if running on an Debian derived distribution.
|
||
|
//
|
||
|
// This function returns true on both Armbian, Raspbian and Ubuntu.
|
||
|
//
|
||
|
// https://debian.org/
|
||
|
func IsDebian() bool {
|
||
|
if isLinux {
|
||
|
// http://0pointer.de/public/systemd-man/os-release.html#ID_LIKE=
|
||
|
if OSRelease()["ID"] == "debian" {
|
||
|
return true
|
||
|
}
|
||
|
for _, part := range strings.Split(OSRelease()["ID_LIKE"], " ") {
|
||
|
if part == "debian" {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// IsRaspbian returns true if running on a Raspbian distribution.
|
||
|
//
|
||
|
// https://raspbian.org/
|
||
|
func IsRaspbian() bool {
|
||
|
if isArm && isLinux {
|
||
|
return OSRelease()["ID"] == "raspbian"
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// IsUbuntu returns true if running on an Ubuntu derived distribution.
|
||
|
//
|
||
|
// https://ubuntu.com/
|
||
|
func IsUbuntu() bool {
|
||
|
if isLinux {
|
||
|
return OSRelease()["ID"] == "ubuntu"
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// OSRelease returns parsed data from /etc/os-release.
|
||
|
//
|
||
|
// For more information, see
|
||
|
// http://0pointer.de/public/systemd-man/os-release.html
|
||
|
func OSRelease() map[string]string {
|
||
|
if isLinux {
|
||
|
return makeOSReleaseLinux()
|
||
|
}
|
||
|
return osRelease
|
||
|
}
|
||
|
|
||
|
// CPU
|
||
|
|
||
|
// CPUInfo returns parsed data from /proc/cpuinfo.
|
||
|
func CPUInfo() map[string]string {
|
||
|
if isLinux {
|
||
|
return makeCPUInfoLinux()
|
||
|
}
|
||
|
return cpuInfo
|
||
|
}
|
||
|
|
||
|
//
|
||
|
|
||
|
var (
|
||
|
mu sync.Mutex
|
||
|
cpuInfo map[string]string
|
||
|
osRelease map[string]string
|
||
|
readFile = ioutil.ReadFile
|
||
|
)
|
||
|
|
||
|
func splitSemiColon(content string) map[string]string {
|
||
|
// Strictly speaking this format isn't ok, there can be multiple group.
|
||
|
out := map[string]string{}
|
||
|
for _, line := range strings.Split(content, "\n") {
|
||
|
parts := strings.SplitN(line, ":", 2)
|
||
|
if len(parts) != 2 {
|
||
|
continue
|
||
|
}
|
||
|
// This format may have space around the ':'.
|
||
|
key := strings.TrimRightFunc(parts[0], unicode.IsSpace)
|
||
|
if len(key) == 0 || key[0] == '#' {
|
||
|
continue
|
||
|
}
|
||
|
// Ignore duplicate keys.
|
||
|
// TODO(maruel): Keep them all.
|
||
|
if _, ok := out[key]; !ok {
|
||
|
// Trim on both side, trailing space was observed on "Features" value.
|
||
|
out[key] = strings.TrimFunc(parts[1], unicode.IsSpace)
|
||
|
}
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
func splitStrict(content string) map[string]string {
|
||
|
out := map[string]string{}
|
||
|
for _, line := range strings.Split(content, "\n") {
|
||
|
parts := strings.SplitN(line, "=", 2)
|
||
|
if len(parts) != 2 {
|
||
|
continue
|
||
|
}
|
||
|
key := parts[0]
|
||
|
if len(key) == 0 || key[0] == '#' {
|
||
|
continue
|
||
|
}
|
||
|
// Overwrite previous key.
|
||
|
value := parts[1]
|
||
|
if len(value) > 2 && value[0] == '"' && value[len(value)-1] == '"' {
|
||
|
// Not exactly 100% right but #closeenough. See for more details
|
||
|
// https://www.freedesktop.org/software/systemd/man/os-release.html
|
||
|
var err error
|
||
|
value, err = strconv.Unquote(value)
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
out[key] = value
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// splitNull returns the null-terminated strings in the data
|
||
|
func splitNull(data []byte) []string {
|
||
|
ss := strings.Split(string(data), "\x00")
|
||
|
// The last string is typically null-terminated, so remove empty string
|
||
|
// from end of array.
|
||
|
if len(ss) > 0 && len(ss[len(ss)-1]) == 0 {
|
||
|
ss = ss[:len(ss)-1]
|
||
|
}
|
||
|
return ss
|
||
|
}
|
||
|
|
||
|
func makeCPUInfoLinux() map[string]string {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
if cpuInfo == nil {
|
||
|
cpuInfo = map[string]string{}
|
||
|
if bytes, err := readFile("/proc/cpuinfo"); err == nil {
|
||
|
cpuInfo = splitSemiColon(string(bytes))
|
||
|
}
|
||
|
}
|
||
|
return cpuInfo
|
||
|
}
|
||
|
|
||
|
func makeOSReleaseLinux() map[string]string {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
if osRelease == nil {
|
||
|
// This file may not exist on older distros. Send a PR if you want to have
|
||
|
// a specific fallback.
|
||
|
osRelease = map[string]string{}
|
||
|
if bytes, err := readFile("/etc/os-release"); err == nil {
|
||
|
osRelease = splitStrict(string(bytes))
|
||
|
}
|
||
|
}
|
||
|
return osRelease
|
||
|
}
|