host_linux.go 8.8 KB
Newer Older
1
// +build linux
W
WAKAYAMA Shirou 已提交
2

S
Shirou WAKAYAMA 已提交
3
package host
W
WAKAYAMA Shirou 已提交
4 5

import (
S
Shirou WAKAYAMA 已提交
6
	"bytes"
W
go fmt  
WAKAYAMA shirou 已提交
7
	"encoding/binary"
S
Shirou WAKAYAMA 已提交
8
	"io/ioutil"
W
WAKAYAMA Shirou 已提交
9
	"os"
10
	"os/exec"
11
	"regexp"
12
	"runtime"
S
go fmt  
Shirou WAKAYAMA 已提交
13
	"strings"
W
WAKAYAMA Shirou 已提交
14
	"syscall"
S
Shirou WAKAYAMA 已提交
15
	"unsafe"
16 17

	common "github.com/shirou/gopsutil/common"
W
WAKAYAMA Shirou 已提交
18 19
)

20 21 22 23 24 25 26
type LSB struct {
	ID          string
	Release     string
	Codename    string
	Description string
}

27
func HostInfo() (*HostInfoStat, error) {
W
WAKAYAMA Shirou 已提交
28
	hostname, err := os.Hostname()
S
Shirou WAKAYAMA 已提交
29
	if err != nil {
30
		return nil, err
S
Shirou WAKAYAMA 已提交
31 32
	}

33 34
	ret := &HostInfoStat{
		Hostname: hostname,
35
		OS:       runtime.GOOS,
36
	}
37

38
	platform, family, version, err := GetPlatformInformation()
S
go fmt  
Shirou WAKAYAMA 已提交
39
	if err == nil {
40 41 42 43
		ret.Platform = platform
		ret.PlatformFamily = family
		ret.PlatformVersion = version
	}
44 45 46 47 48
	system, role, err := GetVirtualization()
	if err == nil {
		ret.VirtualizationSystem = system
		ret.VirtualizationRole = role
	}
W
WAKAYAMA Shirou 已提交
49 50 51 52
	uptime, err := BootTime()
	if err == nil {
		ret.Uptime = uptime
	}
53

54 55 56
	return ret, nil
}

W
WAKAYAMA Shirou 已提交
57
func BootTime() (uint64, error) {
S
Shirou WAKAYAMA 已提交
58 59
	sysinfo := &syscall.Sysinfo_t{}
	if err := syscall.Sysinfo(sysinfo); err != nil {
60
		return 0, err
S
Shirou WAKAYAMA 已提交
61
	}
W
WAKAYAMA shirou 已提交
62
	return uint64(sysinfo.Uptime), nil
W
WAKAYAMA Shirou 已提交
63
}
S
Shirou WAKAYAMA 已提交
64 65 66 67 68 69

func Users() ([]UserStat, error) {
	utmpfile := "/var/run/utmp"

	file, err := os.Open(utmpfile)
	if err != nil {
S
Shirou WAKAYAMA 已提交
70
		return nil, err
S
Shirou WAKAYAMA 已提交
71 72 73 74
	}

	buf, err := ioutil.ReadAll(file)
	if err != nil {
S
Shirou WAKAYAMA 已提交
75
		return nil, err
S
Shirou WAKAYAMA 已提交
76 77 78 79 80 81
	}

	u := utmp{}
	entrySize := int(unsafe.Sizeof(u))
	count := len(buf) / entrySize

S
Shirou WAKAYAMA 已提交
82 83
	ret := make([]UserStat, 0, count)

S
Shirou WAKAYAMA 已提交
84 85 86
	for i := 0; i < count; i++ {
		b := buf[i*entrySize : i*entrySize+entrySize]

W
go fmt  
WAKAYAMA shirou 已提交
87 88 89
		var u utmp
		br := bytes.NewReader(b)
		err := binary.Read(br, binary.LittleEndian, &u)
S
Shirou WAKAYAMA 已提交
90 91 92 93
		if err != nil {
			continue
		}
		user := UserStat{
S
Shirou WAKAYAMA 已提交
94 95 96 97
			User:     common.IntToString(u.User[:]),
			Terminal: common.IntToString(u.Line[:]),
			Host:     common.IntToString(u.Host[:]),
			Started:  int(u.Tv.TvSec),
S
Shirou WAKAYAMA 已提交
98 99 100 101 102 103 104
		}
		ret = append(ret, user)
	}

	return ret, nil

}
105 106 107

func getLSB() (*LSB, error) {
	ret := &LSB{}
S
Shirou WAKAYAMA 已提交
108
	if common.PathExists("/etc/lsb-release") {
W
WAKAYAMA shirou 已提交
109
		contents, err := common.ReadLines("/etc/lsb-release")
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
		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 = field[1]
			case "DISTRIB_RELEASE":
				ret.Release = field[1]
			case "DISTRIB_CODENAME":
				ret.Codename = field[1]
			case "DISTRIB_DESCRIPTION":
				ret.Description = field[1]
			}
		}
S
Shirou WAKAYAMA 已提交
129
	} else if common.PathExists("/usr/bin/lsb_release") {
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
		out, err := exec.Command("/usr/bin/lsb_release").Output()
		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 = field[1]
			case "Release":
				ret.Release = field[1]
			case "Codename":
				ret.Codename = field[1]
			case "Description":
				ret.Description = field[1]
			}
		}

	}

	return ret, nil
}

L
Lukas Lueg 已提交
156
func GetPlatformInformation() (platform string, family string, version string, err error) {
157

W
WAKAYAMA Shirou 已提交
158
	lsb, err := getLSB()
159 160
	if err != nil {
		lsb = &LSB{}
W
WAKAYAMA Shirou 已提交
161
	}
162

S
Shirou WAKAYAMA 已提交
163
	if common.PathExists("/etc/oracle-release") {
164
		platform = "oracle"
W
WAKAYAMA shirou 已提交
165
		contents, err := common.ReadLines("/etc/oracle-release")
166
		if err == nil {
167
			version = getRedhatishVersion(contents)
168
		}
S
Shirou WAKAYAMA 已提交
169
	} else if common.PathExists("/etc/enterprise-release") {
170
		platform = "oracle"
W
WAKAYAMA shirou 已提交
171
		contents, err := common.ReadLines("/etc/enterprise-release")
172
		if err == nil {
173
			version = getRedhatishVersion(contents)
174
		}
S
Shirou WAKAYAMA 已提交
175
	} else if common.PathExists("/etc/debian_version") {
S
go fmt  
Shirou WAKAYAMA 已提交
176 177 178 179 180 181 182
		if lsb.ID == "Ubuntu" {
			platform = "ubuntu"
			version = lsb.Release
		} else if lsb.ID == "LinuxMint" {
			platform = "linuxmint"
			version = lsb.Release
		} else {
S
Shirou WAKAYAMA 已提交
183
			if common.PathExists("/usr/bin/raspi-config") {
S
go fmt  
Shirou WAKAYAMA 已提交
184 185 186 187
				platform = "raspbian"
			} else {
				platform = "debian"
			}
W
WAKAYAMA shirou 已提交
188
			contents, err := common.ReadLines("/etc/debian_version")
S
go fmt  
Shirou WAKAYAMA 已提交
189 190 191
			if err == nil {
				version = contents[0]
			}
192
		}
S
Shirou WAKAYAMA 已提交
193
	} else if common.PathExists("/etc/redhat-release") {
W
WAKAYAMA shirou 已提交
194
		contents, err := common.ReadLines("/etc/redhat-release")
195
		if err == nil {
196 197
			version = getRedhatishVersion(contents)
			platform = getRedhatishPlatform(contents)
198
		}
S
Shirou WAKAYAMA 已提交
199
	} else if common.PathExists("/etc/system-release") {
W
WAKAYAMA shirou 已提交
200
		contents, err := common.ReadLines("/etc/system-release")
201
		if err == nil {
202 203
			version = getRedhatishVersion(contents)
			platform = getRedhatishPlatform(contents)
204
		}
S
Shirou WAKAYAMA 已提交
205
	} else if common.PathExists("/etc/gentoo-release") {
206
		platform = "gentoo"
W
WAKAYAMA shirou 已提交
207
		contents, err := common.ReadLines("/etc/gentoo-release")
208
		if err == nil {
209
			version = getRedhatishVersion(contents)
210
		}
F
Fabian Ruff 已提交
211 212 213 214 215 216
	} else if common.PathExists("/etc/SuSE-release") {
		contents, err := common.ReadLines("/etc/SuSE-release")
		if err == nil {
			version = getSuseVersion(contents)
			platform = getSusePlatform(contents)
		}
217
		// TODO: slackware detecion
S
Shirou WAKAYAMA 已提交
218
	} else if common.PathExists("/etc/arch-release") {
219 220
		platform = "arch"
		// TODO: exherbo detection
S
go fmt  
Shirou WAKAYAMA 已提交
221
	} else if lsb.ID == "RedHat" {
222 223
		platform = "redhat"
		version = lsb.Release
S
go fmt  
Shirou WAKAYAMA 已提交
224
	} else if lsb.ID == "Amazon" {
225
		platform = "amazon"
S
go fmt  
Shirou WAKAYAMA 已提交
226 227
		version = lsb.Release
	} else if lsb.ID == "ScientificSL" {
228
		platform = "scientific"
S
go fmt  
Shirou WAKAYAMA 已提交
229 230
		version = lsb.Release
	} else if lsb.ID == "XenServer" {
231
		platform = "xenserver"
S
go fmt  
Shirou WAKAYAMA 已提交
232 233
		version = lsb.Release
	} else if lsb.ID != "" {
234 235 236 237 238 239 240 241 242 243 244
		platform = strings.ToLower(lsb.ID)
		version = lsb.Release
	}

	switch platform {
	case "debian", "ubuntu", "linuxmint", "raspbian":
		family = "debian"
	case "fedora":
		family = "fedora"
	case "oracle", "centos", "redhat", "scientific", "enterpriseenterprise", "amazon", "xenserver", "cloudlinux", "ibm_powerkvm":
		family = "rhel"
F
Fabian Ruff 已提交
245
	case "suse", "opensuse":
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
		family = "suse"
	case "gentoo":
		family = "gentoo"
	case "slackware":
		family = "slackware"
	case "arch":
		family = "arch"
	case "exherbo":
		family = "exherbo"
	}

	return platform, family, version, nil

}

261 262 263 264 265 266 267 268 269
func getRedhatishVersion(contents []string) string {
	c := strings.ToLower(strings.Join(contents, ""))

	if strings.Contains(c, "rawhide") {
		return "rawhide"
	}
	if matches := regexp.MustCompile(`release (\d[\d.]*)`).FindStringSubmatch(c); matches != nil {
		return matches[1]
	}
L
Lukas Lueg 已提交
270
	return ""
271 272
}

273 274 275 276 277 278 279 280 281
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]
282
}
283

F
Fabian Ruff 已提交
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
func getSuseVersion(contents []string) string {
	version := ""
	for _, line := range contents {
		if matches := regexp.MustCompile(`VERSION = ([\d.]+)`).FindStringSubmatch(line); matches != nil {
			version = matches[1]
		} else if matches := regexp.MustCompile(`PATCHLEVEL = ([\d]+)`).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"
}

304 305 306 307
func GetVirtualization() (string, string, error) {
	var system string
	var role string

S
Shirou WAKAYAMA 已提交
308
	if common.PathExists("/proc/xen") {
309 310 311
		system = "xen"
		role = "guest" // assume guest

S
Shirou WAKAYAMA 已提交
312
		if common.PathExists("/proc/xen/capabilities") {
W
WAKAYAMA shirou 已提交
313
			contents, err := common.ReadLines("/proc/xen/capabilities")
314
			if err == nil {
S
Shirou WAKAYAMA 已提交
315
				if common.StringContains(contents, "control_d") {
316 317 318 319 320
					role = "host"
				}
			}
		}
	}
S
Shirou WAKAYAMA 已提交
321
	if common.PathExists("/proc/modules") {
W
WAKAYAMA shirou 已提交
322
		contents, err := common.ReadLines("/proc/modules")
323
		if err == nil {
S
Shirou WAKAYAMA 已提交
324
			if common.StringContains(contents, "kvm") {
325 326
				system = "kvm"
				role = "host"
S
Shirou WAKAYAMA 已提交
327
			} else if common.StringContains(contents, "vboxdrv") {
328 329
				system = "vbox"
				role = "host"
S
Shirou WAKAYAMA 已提交
330
			} else if common.StringContains(contents, "vboxguest") {
331 332 333 334 335 336
				system = "vbox"
				role = "guest"
			}
		}
	}

S
Shirou WAKAYAMA 已提交
337
	if common.PathExists("/proc/cpuinfo") {
W
WAKAYAMA shirou 已提交
338
		contents, err := common.ReadLines("/proc/cpuinfo")
339
		if err == nil {
S
Shirou WAKAYAMA 已提交
340 341 342
			if common.StringContains(contents, "QEMU Virtual CPU") ||
				common.StringContains(contents, "Common KVM processor") ||
				common.StringContains(contents, "Common 32-bit KVM processor") {
343 344 345 346 347 348
				system = "kvm"
				role = "guest"
			}
		}
	}

S
Shirou WAKAYAMA 已提交
349
	if common.PathExists("/proc/bc/0") {
350 351
		system = "openvz"
		role = "host"
S
Shirou WAKAYAMA 已提交
352
	} else if common.PathExists("/proc/vz") {
353 354 355 356 357 358
		system = "openvz"
		role = "guest"
	}

	// not use dmidecode because it requires root

S
Shirou WAKAYAMA 已提交
359
	if common.PathExists("/proc/self/status") {
W
WAKAYAMA shirou 已提交
360
		contents, err := common.ReadLines("/proc/self/status")
361 362
		if err == nil {

S
Shirou WAKAYAMA 已提交
363 364
			if common.StringContains(contents, "s_context:") ||
				common.StringContains(contents, "VxID:") {
365 366 367 368 369 370
				system = "linux-vserver"
			}
			// TODO: guest or host
		}
	}

S
Shirou WAKAYAMA 已提交
371
	if common.PathExists("/proc/self/cgroup") {
W
WAKAYAMA shirou 已提交
372
		contents, err := common.ReadLines("/proc/self/cgroup")
373 374
		if err == nil {

S
Shirou WAKAYAMA 已提交
375 376
			if common.StringContains(contents, "lxc") ||
				common.StringContains(contents, "docker") {
377 378
				system = "lxc"
				role = "guest"
S
Shirou WAKAYAMA 已提交
379
			} else if common.PathExists("/usr/bin/lxc-version") { // TODO: which
380 381 382 383 384 385 386 387
				system = "lxc"
				role = "host"
			}
		}
	}

	return system, role, nil
}