diff --git a/hostinfo/hostinfo_linux.go b/hostinfo/hostinfo_linux.go new file mode 100644 index 000000000..45b368780 --- /dev/null +++ b/hostinfo/hostinfo_linux.go @@ -0,0 +1,93 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && !android +// +build linux,!android + +package hostinfo + +import ( + "bytes" + "fmt" + "io/ioutil" + "strings" + "syscall" + + "tailscale.com/util/lineread" + "tailscale.com/version/distro" +) + +func init() { + osVersion = osVersionLinux +} + +func osVersionLinux() string { + dist := distro.Get() + propFile := "/etc/os-release" + switch dist { + case distro.Synology: + propFile = "/etc.defaults/VERSION" + case distro.OpenWrt: + propFile = "/etc/openwrt_release" + } + + m := map[string]string{} + lineread.File(propFile, func(line []byte) error { + eq := bytes.IndexByte(line, '=') + if eq == -1 { + return nil + } + k, v := string(line[:eq]), strings.Trim(string(line[eq+1:]), `"'`) + m[k] = v + return nil + }) + + var un syscall.Utsname + syscall.Uname(&un) + + var attrBuf strings.Builder + attrBuf.WriteString("; kernel=") + for _, b := range un.Release { + if b == 0 { + break + } + attrBuf.WriteByte(byte(b)) + } + if inContainer() { + attrBuf.WriteString("; container") + } + if env := GetEnvType(); env != "" { + fmt.Fprintf(&attrBuf, "; env=%s", env) + } + attr := attrBuf.String() + + id := m["ID"] + + switch id { + case "debian": + slurp, _ := ioutil.ReadFile("/etc/debian_version") + return fmt.Sprintf("Debian %s (%s)%s", bytes.TrimSpace(slurp), m["VERSION_CODENAME"], attr) + case "ubuntu": + return fmt.Sprintf("Ubuntu %s%s", m["VERSION"], attr) + case "", "centos": // CentOS 6 has no /etc/os-release, so its id is "" + if cr, _ := ioutil.ReadFile("/etc/centos-release"); len(cr) > 0 { // "CentOS release 6.10 (Final) + return fmt.Sprintf("%s%s", bytes.TrimSpace(cr), attr) + } + fallthrough + case "fedora", "rhel", "alpine", "nixos": + // Their PRETTY_NAME is fine as-is for all versions I tested. + fallthrough + default: + if v := m["PRETTY_NAME"]; v != "" { + return fmt.Sprintf("%s%s", v, attr) + } + } + switch dist { + case distro.Synology: + return fmt.Sprintf("Synology %s%s", m["productversion"], attr) + case distro.OpenWrt: + return fmt.Sprintf("OpenWrt %s%s", m["DISTRIB_RELEASE"], attr) + } + return fmt.Sprintf("Other%s", attr) +} diff --git a/hostinfo/hostinfo_test.go b/hostinfo/hostinfo_test.go new file mode 100644 index 000000000..7346b78fe --- /dev/null +++ b/hostinfo/hostinfo_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2020 Tailscale Inc & 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 hostinfo + +import ( + "encoding/json" + "testing" +) + +func TestNew(t *testing.T) { + hi := New() + if hi == nil { + t.Fatal("no Hostinfo") + } + j, err := json.MarshalIndent(hi, " ", "") + if err != nil { + t.Fatal(err) + } + t.Logf("Got: %s", j) +} + +func TestOSVersion(t *testing.T) { + if osVersion == nil { + t.Skip("not available for OS") + } + t.Logf("Got: %#q", osVersion()) +} diff --git a/hostinfo/hostinfo_windows.go b/hostinfo/hostinfo_windows.go new file mode 100644 index 000000000..618b79e69 --- /dev/null +++ b/hostinfo/hostinfo_windows.go @@ -0,0 +1,39 @@ +// Copyright (c) 2020 Tailscale Inc & 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 hostinfo + +import ( + "os/exec" + "strings" + "sync/atomic" + "syscall" +) + +func init() { + osVersion = osVersionWindows +} + +var winVerCache atomic.Value // of string + +func osVersionWindows() string { + if s, ok := winVerCache.Load().(string); ok { + return s + } + cmd := exec.Command("cmd", "/c", "ver") + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + out, _ := cmd.Output() // "\nMicrosoft Windows [Version 10.0.19041.388]\n\n" + s := strings.TrimSpace(string(out)) + s = strings.TrimPrefix(s, "Microsoft Windows [") + s = strings.TrimSuffix(s, "]") + + // "Version 10.x.y.z", with "Version" localized. Keep only stuff after the space. + if sp := strings.Index(s, " "); sp != -1 { + s = s[sp+1:] + } + if s != "" { + winVerCache.Store(s) + } + return s // "10.0.19041.388", ideally +}