host_linux.go 9.1 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"
8
	"fmt"
S
Shirou WAKAYAMA 已提交
9
	"io/ioutil"
W
WAKAYAMA Shirou 已提交
10
	"os"
11
	"os/exec"
12
	"regexp"
13
	"runtime"
14
	"strconv"
S
go fmt  
Shirou WAKAYAMA 已提交
15
	"strings"
S
Shirou WAKAYAMA 已提交
16
	"unsafe"
17 18

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

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

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

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

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

55 56 57
	return ret, nil
}

58
// BootTime returns the system boot time expressed in seconds since the epoch.
W
WAKAYAMA Shirou 已提交
59
func BootTime() (uint64, error) {
60 61
	lines, err := common.ReadLines("/proc/stat")
	if err != nil {
62
		return 0, err
S
Shirou WAKAYAMA 已提交
63
	}
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
	for _, line := range lines {
		if strings.HasPrefix(line, "btime") {
			f := strings.Fields(line)
			if len(f) != 2 {
				return 0, fmt.Errorf("wrong btime format")
			}
			b, err := strconv.ParseInt(f[1], 10, 64)
			if err != nil {
				return 0, err
			}
			return uint64(b), nil
		}
	}

	return 0, fmt.Errorf("could not find btime")
W
WAKAYAMA Shirou 已提交
79
}
S
Shirou WAKAYAMA 已提交
80 81 82 83 84 85

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

	file, err := os.Open(utmpfile)
	if err != nil {
S
Shirou WAKAYAMA 已提交
86
		return nil, err
S
Shirou WAKAYAMA 已提交
87 88 89 90
	}

	buf, err := ioutil.ReadAll(file)
	if err != nil {
S
Shirou WAKAYAMA 已提交
91
		return nil, err
S
Shirou WAKAYAMA 已提交
92 93 94 95 96 97
	}

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

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

S
Shirou WAKAYAMA 已提交
100 101 102
	for i := 0; i < count; i++ {
		b := buf[i*entrySize : i*entrySize+entrySize]

W
go fmt  
WAKAYAMA shirou 已提交
103 104 105
		var u utmp
		br := bytes.NewReader(b)
		err := binary.Read(br, binary.LittleEndian, &u)
S
Shirou WAKAYAMA 已提交
106 107 108 109
		if err != nil {
			continue
		}
		user := UserStat{
S
Shirou WAKAYAMA 已提交
110 111 112 113
			User:     common.IntToString(u.User[:]),
			Terminal: common.IntToString(u.Line[:]),
			Host:     common.IntToString(u.Host[:]),
			Started:  int(u.Tv.TvSec),
S
Shirou WAKAYAMA 已提交
114 115 116 117 118 119 120
		}
		ret = append(ret, user)
	}

	return ret, nil

}
121 122 123

func getLSB() (*LSB, error) {
	ret := &LSB{}
S
Shirou WAKAYAMA 已提交
124
	if common.PathExists("/etc/lsb-release") {
W
WAKAYAMA shirou 已提交
125
		contents, err := common.ReadLines("/etc/lsb-release")
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
		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 已提交
145
	} else if common.PathExists("/usr/bin/lsb_release") {
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
		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 已提交
172
func GetPlatformInformation() (platform string, family string, version string, err error) {
173

W
WAKAYAMA Shirou 已提交
174
	lsb, err := getLSB()
175 176
	if err != nil {
		lsb = &LSB{}
W
WAKAYAMA Shirou 已提交
177
	}
178

S
Shirou WAKAYAMA 已提交
179
	if common.PathExists("/etc/oracle-release") {
180
		platform = "oracle"
W
WAKAYAMA shirou 已提交
181
		contents, err := common.ReadLines("/etc/oracle-release")
182
		if err == nil {
183
			version = getRedhatishVersion(contents)
184
		}
S
Shirou WAKAYAMA 已提交
185
	} else if common.PathExists("/etc/enterprise-release") {
186
		platform = "oracle"
W
WAKAYAMA shirou 已提交
187
		contents, err := common.ReadLines("/etc/enterprise-release")
188
		if err == nil {
189
			version = getRedhatishVersion(contents)
190
		}
S
Shirou WAKAYAMA 已提交
191
	} else if common.PathExists("/etc/debian_version") {
S
go fmt  
Shirou WAKAYAMA 已提交
192 193 194 195 196 197 198
		if lsb.ID == "Ubuntu" {
			platform = "ubuntu"
			version = lsb.Release
		} else if lsb.ID == "LinuxMint" {
			platform = "linuxmint"
			version = lsb.Release
		} else {
S
Shirou WAKAYAMA 已提交
199
			if common.PathExists("/usr/bin/raspi-config") {
S
go fmt  
Shirou WAKAYAMA 已提交
200 201 202 203
				platform = "raspbian"
			} else {
				platform = "debian"
			}
W
WAKAYAMA shirou 已提交
204
			contents, err := common.ReadLines("/etc/debian_version")
S
go fmt  
Shirou WAKAYAMA 已提交
205 206 207
			if err == nil {
				version = contents[0]
			}
208
		}
S
Shirou WAKAYAMA 已提交
209
	} else if common.PathExists("/etc/redhat-release") {
W
WAKAYAMA shirou 已提交
210
		contents, err := common.ReadLines("/etc/redhat-release")
211
		if err == nil {
212 213
			version = getRedhatishVersion(contents)
			platform = getRedhatishPlatform(contents)
214
		}
S
Shirou WAKAYAMA 已提交
215
	} else if common.PathExists("/etc/system-release") {
W
WAKAYAMA shirou 已提交
216
		contents, err := common.ReadLines("/etc/system-release")
217
		if err == nil {
218 219
			version = getRedhatishVersion(contents)
			platform = getRedhatishPlatform(contents)
220
		}
S
Shirou WAKAYAMA 已提交
221
	} else if common.PathExists("/etc/gentoo-release") {
222
		platform = "gentoo"
W
WAKAYAMA shirou 已提交
223
		contents, err := common.ReadLines("/etc/gentoo-release")
224
		if err == nil {
225
			version = getRedhatishVersion(contents)
226
		}
F
Fabian Ruff 已提交
227 228 229 230 231 232
	} else if common.PathExists("/etc/SuSE-release") {
		contents, err := common.ReadLines("/etc/SuSE-release")
		if err == nil {
			version = getSuseVersion(contents)
			platform = getSusePlatform(contents)
		}
233
		// TODO: slackware detecion
S
Shirou WAKAYAMA 已提交
234
	} else if common.PathExists("/etc/arch-release") {
235 236
		platform = "arch"
		// TODO: exherbo detection
S
go fmt  
Shirou WAKAYAMA 已提交
237
	} else if lsb.ID == "RedHat" {
238 239
		platform = "redhat"
		version = lsb.Release
S
go fmt  
Shirou WAKAYAMA 已提交
240
	} else if lsb.ID == "Amazon" {
241
		platform = "amazon"
S
go fmt  
Shirou WAKAYAMA 已提交
242 243
		version = lsb.Release
	} else if lsb.ID == "ScientificSL" {
244
		platform = "scientific"
S
go fmt  
Shirou WAKAYAMA 已提交
245 246
		version = lsb.Release
	} else if lsb.ID == "XenServer" {
247
		platform = "xenserver"
S
go fmt  
Shirou WAKAYAMA 已提交
248 249
		version = lsb.Release
	} else if lsb.ID != "" {
250 251 252 253 254 255 256 257 258 259 260
		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 已提交
261
	case "suse", "opensuse":
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
		family = "suse"
	case "gentoo":
		family = "gentoo"
	case "slackware":
		family = "slackware"
	case "arch":
		family = "arch"
	case "exherbo":
		family = "exherbo"
	}

	return platform, family, version, nil

}

277 278 279 280 281 282 283 284 285
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 已提交
286
	return ""
287 288
}

289 290 291 292 293 294 295 296 297
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]
298
}
299

F
Fabian Ruff 已提交
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
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"
}

320 321 322 323
func GetVirtualization() (string, string, error) {
	var system string
	var role string

S
Shirou WAKAYAMA 已提交
324
	if common.PathExists("/proc/xen") {
325 326 327
		system = "xen"
		role = "guest" // assume guest

S
Shirou WAKAYAMA 已提交
328
		if common.PathExists("/proc/xen/capabilities") {
W
WAKAYAMA shirou 已提交
329
			contents, err := common.ReadLines("/proc/xen/capabilities")
330
			if err == nil {
331
				if common.StringsContains(contents, "control_d") {
332 333 334 335 336
					role = "host"
				}
			}
		}
	}
S
Shirou WAKAYAMA 已提交
337
	if common.PathExists("/proc/modules") {
W
WAKAYAMA shirou 已提交
338
		contents, err := common.ReadLines("/proc/modules")
339
		if err == nil {
340
			if common.StringsContains(contents, "kvm") {
341 342
				system = "kvm"
				role = "host"
343
			} else if common.StringsContains(contents, "vboxdrv") {
344 345
				system = "vbox"
				role = "host"
346
			} else if common.StringsContains(contents, "vboxguest") {
347 348 349 350 351 352
				system = "vbox"
				role = "guest"
			}
		}
	}

S
Shirou WAKAYAMA 已提交
353
	if common.PathExists("/proc/cpuinfo") {
W
WAKAYAMA shirou 已提交
354
		contents, err := common.ReadLines("/proc/cpuinfo")
355
		if err == nil {
356 357 358
			if common.StringsContains(contents, "QEMU Virtual CPU") ||
				common.StringsContains(contents, "Common KVM processor") ||
				common.StringsContains(contents, "Common 32-bit KVM processor") {
359 360 361 362 363 364
				system = "kvm"
				role = "guest"
			}
		}
	}

S
Shirou WAKAYAMA 已提交
365
	if common.PathExists("/proc/bc/0") {
366 367
		system = "openvz"
		role = "host"
S
Shirou WAKAYAMA 已提交
368
	} else if common.PathExists("/proc/vz") {
369 370 371 372 373 374
		system = "openvz"
		role = "guest"
	}

	// not use dmidecode because it requires root

S
Shirou WAKAYAMA 已提交
375
	if common.PathExists("/proc/self/status") {
W
WAKAYAMA shirou 已提交
376
		contents, err := common.ReadLines("/proc/self/status")
377 378
		if err == nil {

379 380
			if common.StringsContains(contents, "s_context:") ||
				common.StringsContains(contents, "VxID:") {
381 382 383 384 385 386
				system = "linux-vserver"
			}
			// TODO: guest or host
		}
	}

S
Shirou WAKAYAMA 已提交
387
	if common.PathExists("/proc/self/cgroup") {
W
WAKAYAMA shirou 已提交
388
		contents, err := common.ReadLines("/proc/self/cgroup")
389
		if err == nil {
390 391
			if common.StringsContains(contents, "lxc") ||
				common.StringsContains(contents, "docker") {
392 393
				system = "lxc"
				role = "guest"
S
Shirou WAKAYAMA 已提交
394
			} else if common.PathExists("/usr/bin/lxc-version") { // TODO: which
395 396 397 398 399 400 401 402
				system = "lxc"
				role = "host"
			}
		}
	}

	return system, role, nil
}