1// +build darwin
2
3package host
4
5import (
6	"bytes"
7	"context"
8	"encoding/binary"
9	"io/ioutil"
10	"os"
11	"os/exec"
12	"runtime"
13	"strings"
14	"sync/atomic"
15	"time"
16	"unsafe"
17
18	"github.com/shirou/gopsutil/internal/common"
19	"github.com/shirou/gopsutil/process"
20	"golang.org/x/sys/unix"
21)
22
23// from utmpx.h
24const USER_PROCESS = 7
25
26func Info() (*InfoStat, error) {
27	return InfoWithContext(context.Background())
28}
29
30func InfoWithContext(ctx context.Context) (*InfoStat, error) {
31	ret := &InfoStat{
32		OS:             runtime.GOOS,
33		PlatformFamily: "darwin",
34	}
35
36	hostname, err := os.Hostname()
37	if err == nil {
38		ret.Hostname = hostname
39	}
40
41	kernelVersion, err := KernelVersionWithContext(ctx)
42	if err == nil {
43		ret.KernelVersion = kernelVersion
44	}
45
46	kernelArch, err := kernelArch()
47	if err == nil {
48		ret.KernelArch = kernelArch
49	}
50
51	platform, family, pver, err := PlatformInformation()
52	if err == nil {
53		ret.Platform = platform
54		ret.PlatformFamily = family
55		ret.PlatformVersion = pver
56	}
57
58	system, role, err := Virtualization()
59	if err == nil {
60		ret.VirtualizationSystem = system
61		ret.VirtualizationRole = role
62	}
63
64	boot, err := BootTime()
65	if err == nil {
66		ret.BootTime = boot
67		ret.Uptime = uptime(boot)
68	}
69
70	procs, err := process.Pids()
71	if err == nil {
72		ret.Procs = uint64(len(procs))
73	}
74
75	uuid, err := unix.Sysctl("kern.uuid")
76	if err == nil && uuid != "" {
77		ret.HostID = strings.ToLower(uuid)
78	}
79
80	return ret, nil
81}
82
83// cachedBootTime must be accessed via atomic.Load/StoreUint64
84var cachedBootTime uint64
85
86func BootTime() (uint64, error) {
87	return BootTimeWithContext(context.Background())
88}
89
90func BootTimeWithContext(ctx context.Context) (uint64, error) {
91	// https://github.com/AaronO/dashd/blob/222e32ef9f7a1f9bea4a8da2c3627c4cb992f860/probe/probe_darwin.go
92	t := atomic.LoadUint64(&cachedBootTime)
93	if t != 0 {
94		return t, nil
95	}
96	value, err := unix.Sysctl("kern.boottime")
97	if err != nil {
98		return 0, err
99	}
100	bytes := []byte(value[:])
101	var boottime uint64
102	boottime = uint64(bytes[0]) + uint64(bytes[1])*256 + uint64(bytes[2])*256*256 + uint64(bytes[3])*256*256*256
103
104	atomic.StoreUint64(&cachedBootTime, boottime)
105
106	return boottime, nil
107}
108
109func uptime(boot uint64) uint64 {
110	return uint64(time.Now().Unix()) - boot
111}
112
113func Uptime() (uint64, error) {
114	return UptimeWithContext(context.Background())
115}
116
117func UptimeWithContext(ctx context.Context) (uint64, error) {
118	boot, err := BootTimeWithContext(ctx)
119	if err != nil {
120		return 0, err
121	}
122	return uptime(boot), nil
123}
124
125func Users() ([]UserStat, error) {
126	return UsersWithContext(context.Background())
127}
128
129func UsersWithContext(ctx context.Context) ([]UserStat, error) {
130	utmpfile := "/var/run/utmpx"
131	var ret []UserStat
132
133	file, err := os.Open(utmpfile)
134	if err != nil {
135		return ret, err
136	}
137	defer file.Close()
138
139	buf, err := ioutil.ReadAll(file)
140	if err != nil {
141		return ret, err
142	}
143
144	u := Utmpx{}
145	entrySize := int(unsafe.Sizeof(u))
146	count := len(buf) / entrySize
147
148	for i := 0; i < count; i++ {
149		b := buf[i*entrySize : i*entrySize+entrySize]
150
151		var u Utmpx
152		br := bytes.NewReader(b)
153		err := binary.Read(br, binary.LittleEndian, &u)
154		if err != nil {
155			continue
156		}
157		if u.Type != USER_PROCESS {
158			continue
159		}
160		user := UserStat{
161			User:     common.IntToString(u.User[:]),
162			Terminal: common.IntToString(u.Line[:]),
163			Host:     common.IntToString(u.Host[:]),
164			Started:  int(u.Tv.Sec),
165		}
166		ret = append(ret, user)
167	}
168
169	return ret, nil
170
171}
172
173func PlatformInformation() (string, string, string, error) {
174	return PlatformInformationWithContext(context.Background())
175}
176
177func PlatformInformationWithContext(ctx context.Context) (string, string, string, error) {
178	platform := ""
179	family := ""
180	pver := ""
181
182	sw_vers, err := exec.LookPath("sw_vers")
183	if err != nil {
184		return "", "", "", err
185	}
186
187	p, err := unix.Sysctl("kern.ostype")
188	if err == nil {
189		platform = strings.ToLower(p)
190	}
191
192	out, err := invoke.CommandWithContext(ctx, sw_vers, "-productVersion")
193	if err == nil {
194		pver = strings.ToLower(strings.TrimSpace(string(out)))
195	}
196
197	// check if the macos server version file exists
198	_, err = os.Stat("/System/Library/CoreServices/ServerVersion.plist")
199
200	// server file doesn't exist
201	if os.IsNotExist(err) {
202		family = "Standalone Workstation"
203	} else {
204		family = "Server"
205	}
206
207	return platform, family, pver, nil
208}
209
210func Virtualization() (string, string, error) {
211	return VirtualizationWithContext(context.Background())
212}
213
214func VirtualizationWithContext(ctx context.Context) (string, string, error) {
215	return "", "", common.ErrNotImplementedError
216}
217
218func KernelVersion() (string, error) {
219	return KernelVersionWithContext(context.Background())
220}
221
222func KernelVersionWithContext(ctx context.Context) (string, error) {
223	version, err := unix.Sysctl("kern.osrelease")
224	return strings.ToLower(version), err
225}
226