1package host
2
3import (
4	"bufio"
5	"bytes"
6	"context"
7	"fmt"
8	"io/ioutil"
9	"os"
10	"os/exec"
11	"regexp"
12	"runtime"
13	"strconv"
14	"strings"
15	"time"
16
17	"github.com/shirou/gopsutil/internal/common"
18)
19
20func Info() (*InfoStat, error) {
21	return InfoWithContext(context.Background())
22}
23
24func InfoWithContext(ctx context.Context) (*InfoStat, error) {
25	result := &InfoStat{
26		OS: runtime.GOOS,
27	}
28
29	hostname, err := os.Hostname()
30	if err != nil {
31		return nil, err
32	}
33	result.Hostname = hostname
34
35	// Parse versions from output of `uname(1)`
36	uname, err := exec.LookPath("uname")
37	if err != nil {
38		return nil, err
39	}
40
41	out, err := invoke.CommandWithContext(ctx, uname, "-srv")
42	if err != nil {
43		return nil, err
44	}
45
46	fields := strings.Fields(string(out))
47	if len(fields) >= 1 {
48		result.PlatformFamily = fields[0]
49	}
50	if len(fields) >= 2 {
51		result.KernelVersion = fields[1]
52	}
53	if len(fields) == 3 {
54		result.PlatformVersion = fields[2]
55	}
56
57	kernelArch, err := kernelArch()
58	if err == nil {
59		result.KernelArch = kernelArch
60	}
61
62	// Find distribution name from /etc/release
63	fh, err := os.Open("/etc/release")
64	if err != nil {
65		return nil, err
66	}
67	defer fh.Close()
68
69	sc := bufio.NewScanner(fh)
70	if sc.Scan() {
71		line := strings.TrimSpace(sc.Text())
72		switch {
73		case strings.HasPrefix(line, "SmartOS"):
74			result.Platform = "SmartOS"
75		case strings.HasPrefix(line, "OpenIndiana"):
76			result.Platform = "OpenIndiana"
77		case strings.HasPrefix(line, "OmniOS"):
78			result.Platform = "OmniOS"
79		case strings.HasPrefix(line, "Open Storage"):
80			result.Platform = "NexentaStor"
81		case strings.HasPrefix(line, "Solaris"):
82			result.Platform = "Solaris"
83		case strings.HasPrefix(line, "Oracle Solaris"):
84			result.Platform = "Solaris"
85		default:
86			result.Platform = strings.Fields(line)[0]
87		}
88	}
89
90	switch result.Platform {
91	case "SmartOS":
92		// If everything works, use the current zone ID as the HostID if present.
93		zonename, err := exec.LookPath("zonename")
94		if err == nil {
95			out, err := invoke.CommandWithContext(ctx, zonename)
96			if err == nil {
97				sc := bufio.NewScanner(bytes.NewReader(out))
98				for sc.Scan() {
99					line := sc.Text()
100
101					// If we're in the global zone, rely on the hostname.
102					if line == "global" {
103						hostname, err := os.Hostname()
104						if err == nil {
105							result.HostID = hostname
106						}
107					} else {
108						result.HostID = strings.TrimSpace(line)
109						break
110					}
111				}
112			}
113		}
114	}
115
116	// If HostID is still empty, use hostid(1), which can lie to callers but at
117	// this point there are no hardware facilities available.  This behavior
118	// matches that of other supported OSes.
119	if result.HostID == "" {
120		hostID, err := exec.LookPath("hostid")
121		if err == nil {
122			out, err := invoke.CommandWithContext(ctx, hostID)
123			if err == nil {
124				sc := bufio.NewScanner(bytes.NewReader(out))
125				for sc.Scan() {
126					line := sc.Text()
127					result.HostID = strings.TrimSpace(line)
128					break
129				}
130			}
131		}
132	}
133
134	// Find the boot time and calculate uptime relative to it
135	bootTime, err := BootTime()
136	if err != nil {
137		return nil, err
138	}
139	result.BootTime = bootTime
140	result.Uptime = uptimeSince(bootTime)
141
142	// Count number of processes based on the number of entries in /proc
143	dirs, err := ioutil.ReadDir("/proc")
144	if err != nil {
145		return nil, err
146	}
147	result.Procs = uint64(len(dirs))
148
149	return result, nil
150}
151
152var kstatMatch = regexp.MustCompile(`([^\s]+)[\s]+([^\s]*)`)
153
154func BootTime() (uint64, error) {
155	return BootTimeWithContext(context.Background())
156}
157
158func BootTimeWithContext(ctx context.Context) (uint64, error) {
159	kstat, err := exec.LookPath("kstat")
160	if err != nil {
161		return 0, err
162	}
163
164	out, err := invoke.CommandWithContext(ctx, kstat, "-p", "unix:0:system_misc:boot_time")
165	if err != nil {
166		return 0, err
167	}
168
169	kstats := kstatMatch.FindAllStringSubmatch(string(out), -1)
170	if len(kstats) != 1 {
171		return 0, fmt.Errorf("expected 1 kstat, found %d", len(kstats))
172	}
173
174	return strconv.ParseUint(kstats[0][2], 10, 64)
175}
176
177func Uptime() (uint64, error) {
178	return UptimeWithContext(context.Background())
179}
180
181func UptimeWithContext(ctx context.Context) (uint64, error) {
182	bootTime, err := BootTime()
183	if err != nil {
184		return 0, err
185	}
186	return uptimeSince(bootTime), nil
187}
188
189func uptimeSince(since uint64) uint64 {
190	return uint64(time.Now().Unix()) - since
191}
192
193func Users() ([]UserStat, error) {
194	return UsersWithContext(context.Background())
195}
196
197func UsersWithContext(ctx context.Context) ([]UserStat, error) {
198	return []UserStat{}, common.ErrNotImplementedError
199}
200
201func SensorsTemperatures() ([]TemperatureStat, error) {
202	return SensorsTemperaturesWithContext(context.Background())
203}
204
205func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
206	return []TemperatureStat{}, common.ErrNotImplementedError
207}
208
209func Virtualization() (string, string, error) {
210	return VirtualizationWithContext(context.Background())
211}
212
213func VirtualizationWithContext(ctx context.Context) (string, string, error) {
214	return "", "", common.ErrNotImplementedError
215}
216
217func KernelVersion() (string, error) {
218	return KernelVersionWithContext(context.Background())
219}
220
221func KernelVersionWithContext(ctx context.Context) (string, error) {
222	// Parse versions from output of `uname(1)`
223	uname, err := exec.LookPath("uname")
224	if err != nil {
225		return "", err
226	}
227
228	out, err := invoke.CommandWithContext(ctx, uname, "-srv")
229	if err != nil {
230		return "", err
231	}
232
233	fields := strings.Fields(string(out))
234	if len(fields) >= 2 {
235		return fields[1], nil
236	}
237	return "", fmt.Errorf("could not get kernel version")
238}
239
240func PlatformInformation() (platform string, family string, version string, err error) {
241	return PlatformInformationWithContext(context.Background())
242}
243
244func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) {
245	/* This is not finished yet at all. Please contribute! */
246
247	version, err = KernelVersion()
248	if err != nil {
249		return "", "", "", err
250	}
251
252	return "solaris", "solaris", version, nil
253}
254