From 08a382b20717f7047923e1dbb4633831e6d0ec36 Mon Sep 17 00:00:00 2001 From: tycho garen Date: Sun, 5 Nov 2017 20:40:33 -0500 Subject: [PATCH 01/23] make process.processes function public --- process/process_darwin.go | 13 +++---------- process/process_freebsd.go | 8 ++++---- process/process_linux.go | 21 +++++++++++++++++++++ process/process_openbsd.go | 8 ++++---- process/process_windows.go | 14 +++++++------- 5 files changed, 39 insertions(+), 25 deletions(-) diff --git a/process/process_darwin.go b/process/process_darwin.go index aa17d1d..1e18f1d 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -390,8 +390,8 @@ func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { return &ret, common.ErrNotImplementedError } -func processes() ([]Process, error) { - results := make([]Process, 0, 50) +func Processes() ([]*Process, error) { + results := make([]*Process) mib := []int32{CTLKern, KernProc, KernProcAll, 0} buf, length, err := common.CallSyscall(mib) @@ -403,13 +403,6 @@ func processes() ([]Process, error) { k := KinfoProc{} procinfoLen := int(unsafe.Sizeof(k)) count := int(length / uint64(procinfoLen)) - /* - fmt.Println(length, procinfoLen, count) - b := buf[0*procinfoLen : 0*procinfoLen+procinfoLen] - fmt.Println(b) - kk, err := parseKinfoProc(b) - fmt.Printf("%#v", kk) - */ // parse buf to procs for i := 0; i < count; i++ { @@ -422,7 +415,7 @@ func processes() ([]Process, error) { if err != nil { continue } - results = append(results, *p) + results = append(results, p) } return results, nil diff --git a/process/process_freebsd.go b/process/process_freebsd.go index aa3e85c..c8cb1b9 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -22,7 +22,7 @@ type MemoryMapsStat struct { func Pids() ([]int32, error) { var ret []int32 - procs, err := processes() + procs, err := Processes() if err != nil { return ret, nil } @@ -278,8 +278,8 @@ func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { return &ret, common.ErrNotImplementedError } -func processes() ([]Process, error) { - results := make([]Process, 0, 50) +func Processes() ([]*Process, error) { + results := make([]*Process) mib := []int32{CTLKern, KernProc, KernProcProc, 0} buf, length, err := common.CallSyscall(mib) @@ -302,7 +302,7 @@ func processes() ([]Process, error) { continue } - results = append(results, *p) + results = append(results, p) } return results, nil diff --git a/process/process_linux.go b/process/process_linux.go index 1839eb2..9db1997 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -1017,6 +1017,27 @@ func Pids() ([]int32, error) { return readPidsFromDir(common.HostProc()) } +// Process returns a slice of pointers to Process structs for all +// currently running processes. +func Processes() ([]*Process, error) { + out := []*Process{} + + pids, err := Pids() + if err != nil { + return out, err + } + + for _, pid := range pids { + p, err := NewProcess(pid) + if err != nil { + continue + } + out = append(out, p) + } + + return out, nil +} + func readPidsFromDir(path string) ([]int32, error) { var ret []int32 diff --git a/process/process_openbsd.go b/process/process_openbsd.go index fb553a6..6bff0b3 100644 --- a/process/process_openbsd.go +++ b/process/process_openbsd.go @@ -25,7 +25,7 @@ type MemoryMapsStat struct { func Pids() ([]int32, error) { var ret []int32 - procs, err := processes() + procs, err := Processes() if err != nil { return ret, nil } @@ -268,8 +268,8 @@ func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { return &ret, common.ErrNotImplementedError } -func processes() ([]Process, error) { - results := make([]Process, 0, 50) +func Processes() ([]*Process, error) { + results := make([]*Process) buf, length, err := CallKernProcSyscall(KernProcAll, 0) @@ -292,7 +292,7 @@ func processes() ([]Process, error) { continue } - results = append(results, *p) + results = append(results, p) } return results, nil diff --git a/process/process_windows.go b/process/process_windows.go index c1cf4cb..02478a3 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -95,7 +95,7 @@ func init() { func Pids() ([]int32, error) { var ret []int32 - procs, err := processes() + procs, err := Processes() if err != nil { return ret, nil } @@ -292,11 +292,11 @@ func (p *Process) Times() (*cpu.TimesStat, error) { // below from psutil's _psutil_windows.c, and in turn from Python's // Modules/posixmodule.c - user := float64(sysTimes.UserTime.HighDateTime) * 429.4967296 + float64(sysTimes.UserTime.LowDateTime) * 1e-7 - kernel := float64(sysTimes.KernelTime.HighDateTime) * 429.4967296 + float64(sysTimes.KernelTime.LowDateTime) * 1e-7 + user := float64(sysTimes.UserTime.HighDateTime)*429.4967296 + float64(sysTimes.UserTime.LowDateTime)*1e-7 + kernel := float64(sysTimes.KernelTime.HighDateTime)*429.4967296 + float64(sysTimes.KernelTime.LowDateTime)*1e-7 return &cpu.TimesStat{ - User: user, + User: user, System: kernel, }, nil } @@ -422,7 +422,7 @@ func (p *Process) getFromSnapProcess(pid int32) (int32, int32, string, error) { } // Get processes -func processes() ([]*Process, error) { +func Processes() ([]*Process, error) { var dst []Win32_Process q := wmi.CreateQuery(&dst, "") err := wmi.Query(q, &dst) @@ -508,9 +508,9 @@ func getProcessMemoryInfo(h windows.Handle, mem *PROCESS_MEMORY_COUNTERS) (err e type SYSTEM_TIMES struct { CreateTime syscall.Filetime - ExitTime syscall.Filetime + ExitTime syscall.Filetime KernelTime syscall.Filetime - UserTime syscall.Filetime + UserTime syscall.Filetime } func getProcessCPUTimes(pid int32) (SYSTEM_TIMES, error) { From befc2c3d92445c733f2b78e326b55b436e4ce7f6 Mon Sep 17 00:00:00 2001 From: shawnps Date: Wed, 8 Nov 2017 13:29:49 +0900 Subject: [PATCH 02/23] call Fatal if length of Users() is 0 --- host/host_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/host/host_test.go b/host/host_test.go index 65f7a75..c420580 100644 --- a/host/host_test.go +++ b/host/host_test.go @@ -54,7 +54,7 @@ func TestUsers(t *testing.T) { } empty := UserStat{} if len(v) == 0 { - t.Errorf("Users is empty") + t.Fatal("Users is empty") } for _, u := range v { if u == empty { From d62768abc96623b8fd47173205abe129adfe8dc5 Mon Sep 17 00:00:00 2001 From: Marco Pfatschbacher Date: Wed, 8 Nov 2017 20:52:36 +0100 Subject: [PATCH 03/23] Fix IOCounters for OpenBSD Use SysctlRaw instead of Sysctl. The latter assumes NUL terminated strings which returns the lenght off by one. Therefore, only n-1 disks where reported. --- disk/disk_openbsd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disk/disk_openbsd.go b/disk/disk_openbsd.go index d1705ea..348a1f1 100644 --- a/disk/disk_openbsd.go +++ b/disk/disk_openbsd.go @@ -66,7 +66,7 @@ func Partitions(all bool) ([]PartitionStat, error) { func IOCounters(names ...string) (map[string]IOCountersStat, error) { ret := make(map[string]IOCountersStat) - r, err := unix.Sysctl("hw.diskstats") + r, err := unix.SysctlRaw("hw.diskstats") if err != nil { return nil, err } From 6e7aca41d7edca530703196067fea4216d65a563 Mon Sep 17 00:00:00 2001 From: Marco Pfatschbacher Date: Wed, 8 Nov 2017 21:21:11 +0100 Subject: [PATCH 04/23] Implement Connection support for OpenBSD This retrieves open TCP/UDP connections by using netstat(1) File descriptors and pids are not supported. --- net/net_openbsd.go | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/net/net_openbsd.go b/net/net_openbsd.go index 85cc70c..6437e7d 100644 --- a/net/net_openbsd.go +++ b/net/net_openbsd.go @@ -4,13 +4,18 @@ package net import ( "errors" + "fmt" "os/exec" + "regexp" "strconv" "strings" + "syscall" "github.com/shirou/gopsutil/internal/common" ) +var portMatch = regexp.MustCompile(`(.*)\.(\d+)$`) + func ParseNetstat(output string, mode string, iocs map[string]IOCountersStat) error { lines := strings.Split(output, "\n") @@ -146,8 +151,138 @@ func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) { return nil, errors.New("NetProtoCounters not implemented for openbsd") } +func parseNetstatLine(line string) (ConnectionStat, error) { + f := strings.Fields(line) + if len(f) < 5 { + return ConnectionStat{}, fmt.Errorf("wrong line,%s", line) + } + + var netType, netFamily uint32 + switch f[0] { + case "tcp": + netType = syscall.SOCK_STREAM + netFamily = syscall.AF_INET + case "udp": + netType = syscall.SOCK_DGRAM + netFamily = syscall.AF_INET + case "tcp6": + netType = syscall.SOCK_STREAM + netFamily = syscall.AF_INET6 + case "udp6": + netType = syscall.SOCK_DGRAM + netFamily = syscall.AF_INET6 + default: + return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[0]) + } + + laddr, raddr, err := parseNetstatAddr(f[3], f[4], netFamily) + if err != nil { + return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s %s", f[3], f[4]) + } + + n := ConnectionStat{ + Fd: uint32(0), // not supported + Family: uint32(netFamily), + Type: uint32(netType), + Laddr: laddr, + Raddr: raddr, + Pid: int32(0), // not supported + } + if len(f) == 6 { + n.Status = f[5] + } + + return n, nil +} + +func parseNetstatAddr(local string, remote string, family uint32) (laddr Addr, raddr Addr, err error) { + parse := func(l string) (Addr, error) { + matches := portMatch.FindStringSubmatch(l) + if matches == nil { + return Addr{}, fmt.Errorf("wrong addr, %s", l) + } + host := matches[1] + port := matches[2] + if host == "*" { + if family == syscall.AF_INET { + host = "0.0.0.0" + } else { + host = "::" + } + } + lport, err := strconv.Atoi(port) + if err != nil { + return Addr{}, err + } + return Addr{IP: host, Port: uint32(lport)}, nil + } + + laddr, err = parse(local) + if remote != "*.*" { // remote addr exists + raddr, err = parse(remote) + if err != nil { + return laddr, raddr, err + } + } + + return laddr, raddr, err +} + // Return a list of network connections opened. -// Not Implemented for OpenBSD func Connections(kind string) ([]ConnectionStat, error) { - return nil, errors.New("Connections not implemented for openbsd") + var ret []ConnectionStat + + args := []string{"-na"} + switch strings.ToLower(kind) { + default: + fallthrough + case "": + fallthrough + case "all": + fallthrough + case "inet": + + case "inet4": + args = append(args, "-finet") + case "inet6": + args = append(args, "-finet6") + case "tcp": + args = append(args, "-ptcp") + case "tcp4": + args = append(args, "-ptcp", "-finet") + case "tcp6": + args = append(args, "-ptcp", "-finet6") + case "udp": + args = append(args, "-pudp") + case "udp4": + args = append(args, "-pudp", "-finet") + case "udp6": + args = append(args, "-pudp", "-finet6") + case "unix": + return ret, common.ErrNotImplementedError + } + + netstat, err := exec.LookPath("/usr/bin/netstat") + if err != nil { + return nil, err + } + out, err := invoke.Command(netstat, args...) + + if err != nil { + return nil, err + } + lines := strings.Split(string(out), "\n") + for _, line := range lines { + if !(strings.HasPrefix(line, "tcp") || strings.HasPrefix(line, "udp")) { + continue + } + n, err := parseNetstatLine(line) + if err != nil { + continue + } + + ret = append(ret, n) + } + + return ret, nil } From 1ca44eace99a80e3293b014c9731c6e4ac7d49cc Mon Sep 17 00:00:00 2001 From: Marco Pfatschbacher Date: Wed, 8 Nov 2017 21:38:07 +0100 Subject: [PATCH 05/23] fix typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7dc2909..139c998 100644 --- a/README.rst +++ b/README.rst @@ -43,7 +43,7 @@ Available Architectures - Linux i386/amd64/arm(raspberry pi) - Windows/amd64 - Darwin i386/amd64 -- OpenBDS amd64 (Thank you @mpfz0r!) +- OpenBSD amd64 (Thank you @mpfz0r!) - Solaris amd64 (developed and tested on SmartOS/Illumos, Thank you @jen20!) All works are implemented without cgo by porting c struct to golang struct. From 5476f100bc04bc4dffb16c07d79cfa7480f6181f Mon Sep 17 00:00:00 2001 From: Marco Pfatschbacher Date: Wed, 8 Nov 2017 21:41:57 +0100 Subject: [PATCH 06/23] mark net_connections as supported for OpenBSD --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 139c998..9a707ba 100644 --- a/README.rst +++ b/README.rst @@ -187,7 +187,7 @@ boot_time x x x x x users x x x x x pids x x x x x pid_exists x x x x x -net_connections x x +net_connections x x x net_protocols x net_if_addrs net_if_stats From df6462b50e5ba633602996ce3530ba8b98e70432 Mon Sep 17 00:00:00 2001 From: Marco Pfatschbacher Date: Thu, 9 Nov 2017 13:14:12 +0100 Subject: [PATCH 07/23] Fix PR comments made by shirou --- net/net_openbsd.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/net/net_openbsd.go b/net/net_openbsd.go index 6437e7d..63caff5 100644 --- a/net/net_openbsd.go +++ b/net/net_openbsd.go @@ -97,7 +97,7 @@ func ParseNetstat(output string, mode string, } func IOCounters(pernic bool) ([]IOCountersStat, error) { - netstat, err := exec.LookPath("/usr/bin/netstat") + netstat, err := exec.LookPath("netstat") if err != nil { return nil, err } @@ -204,10 +204,13 @@ func parseNetstatAddr(local string, remote string, family uint32) (laddr Addr, r host := matches[1] port := matches[2] if host == "*" { - if family == syscall.AF_INET { + switch family { + case syscall.AF_INET: host = "0.0.0.0" - } else { + case syscall.AF_INET6: host = "::" + default: + return Addr{}, fmt.Errorf("unknown family, %d", family) } } lport, err := strconv.Atoi(port) @@ -241,7 +244,7 @@ func Connections(kind string) ([]ConnectionStat, error) { case "all": fallthrough case "inet": - + // nothing to add case "inet4": args = append(args, "-finet") case "inet6": @@ -262,7 +265,7 @@ func Connections(kind string) ([]ConnectionStat, error) { return ret, common.ErrNotImplementedError } - netstat, err := exec.LookPath("/usr/bin/netstat") + netstat, err := exec.LookPath("netstat") if err != nil { return nil, err } From 4d92a03da8b33c171cb3791555b5587f891d3a3d Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Thu, 9 Nov 2017 10:31:12 -0500 Subject: [PATCH 08/23] fix cross compiles --- process/process_darwin.go | 2 +- process/process_freebsd.go | 2 +- process/process_openbsd.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/process/process_darwin.go b/process/process_darwin.go index 1e18f1d..cc289d2 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -391,7 +391,7 @@ func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { } func Processes() ([]*Process, error) { - results := make([]*Process) + results := []*Process{} mib := []int32{CTLKern, KernProc, KernProcAll, 0} buf, length, err := common.CallSyscall(mib) diff --git a/process/process_freebsd.go b/process/process_freebsd.go index c8cb1b9..9d75717 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -279,7 +279,7 @@ func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { } func Processes() ([]*Process, error) { - results := make([]*Process) + results := []*Process{} mib := []int32{CTLKern, KernProc, KernProcProc, 0} buf, length, err := common.CallSyscall(mib) diff --git a/process/process_openbsd.go b/process/process_openbsd.go index 6bff0b3..aca4ffa 100644 --- a/process/process_openbsd.go +++ b/process/process_openbsd.go @@ -269,7 +269,7 @@ func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { } func Processes() ([]*Process, error) { - results := make([]*Process) + results := []*Process{} buf, length, err := CallKernProcSyscall(KernProcAll, 0) From 0314bc81f32c2594ac7110f4c7be348c04bed290 Mon Sep 17 00:00:00 2001 From: Lomanic Date: Sun, 12 Nov 2017 03:08:47 +0100 Subject: [PATCH 09/23] Use w32.EnumProcesses to get pids on Windows in process.Pids() --- process/process_windows.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/process/process_windows.go b/process/process_windows.go index 02478a3..320833b 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -93,17 +93,18 @@ func init() { } func Pids() ([]int32, error) { + // inspired by https://gist.github.com/henkman/3083408 var ret []int32 + ps := make([]uint32, 2048) + var read uint32 = 0 - procs, err := Processes() - if err != nil { - return ret, nil + if !w32.EnumProcesses(ps, uint32(len(ps)), &read) { + return nil, fmt.Errorf("could not get w32.EnumProcesses") } - for _, proc := range procs { - ret = append(ret, proc.Pid) + for _, pid := range ps[:read/4] { + ret = append(ret, int32(pid)) } - return ret, nil } From c9a24cf2d02ba46a400718a3bed60606df8b54b6 Mon Sep 17 00:00:00 2001 From: Lomanic Date: Sun, 12 Nov 2017 16:40:10 +0100 Subject: [PATCH 10/23] Handle case in Windows process.Pids() where buffer is too small to handle that many processes returned by w32.EnumProcesses --- process/process_windows.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/process/process_windows.go b/process/process_windows.go index 320833b..a1c3373 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -94,18 +94,28 @@ func init() { func Pids() ([]int32, error) { // inspired by https://gist.github.com/henkman/3083408 + // and https://github.com/giampaolo/psutil/blob/1c3a15f637521ba5c0031283da39c733fda53e4c/psutil/arch/windows/process_info.c#L315-L329 var ret []int32 - ps := make([]uint32, 2048) var read uint32 = 0 + var psSize uint32 = 1024 + const dwordSize uint32 = 4 - if !w32.EnumProcesses(ps, uint32(len(ps)), &read) { - return nil, fmt.Errorf("could not get w32.EnumProcesses") - } + for { + ps := make([]uint32, psSize) + if !w32.EnumProcesses(ps, uint32(len(ps)), &read) { + return nil, fmt.Errorf("could not get w32.EnumProcesses") + } + if uint32(len(ps)) == read { // ps buffer was too small to host every results, retry with a bigger one + psSize += 1024 + continue + } + for _, pid := range ps[:read/dwordSize] { + ret = append(ret, int32(pid)) + } + return ret, nil - for _, pid := range ps[:read/4] { - ret = append(ret, int32(pid)) } - return ret, nil + } func (p *Process) Ppid() (int32, error) { From da12f10f63237e264d76604ef5e6d181c0d44572 Mon Sep 17 00:00:00 2001 From: Lomanic Date: Sun, 12 Nov 2017 20:24:44 +0100 Subject: [PATCH 11/23] Use w32.EnumerateProcesses instead of slower wmi in windows process.Processes() --- process/process_windows.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/process/process_windows.go b/process/process_windows.go index a1c3373..d023030 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -94,7 +94,7 @@ func init() { func Pids() ([]int32, error) { // inspired by https://gist.github.com/henkman/3083408 - // and https://github.com/giampaolo/psutil/blob/1c3a15f637521ba5c0031283da39c733fda53e4c/psutil/arch/windows/process_info.c#L315-L329 + // and https://github.com/giampaolo/psutil/blob/1c3a15f637521ba5c0031283da39c733fda53e4c/psutil/arch/windows/process_info.c#L315-L329 var ret []int32 var read uint32 = 0 var psSize uint32 = 1024 @@ -434,19 +434,14 @@ func (p *Process) getFromSnapProcess(pid int32) (int32, int32, string, error) { // Get processes func Processes() ([]*Process, error) { - var dst []Win32_Process - q := wmi.CreateQuery(&dst, "") - err := wmi.Query(q, &dst) + pids, err := Pids() if err != nil { - return []*Process{}, err - } - if len(dst) == 0 { - return []*Process{}, fmt.Errorf("could not get Process") + return []*Process{}, fmt.Errorf("could not get Processes %s", err) } results := []*Process{} - for _, proc := range dst { - p, err := NewProcess(int32(proc.ProcessID)) + for _, pid := range pids { + p, err := NewProcess(int32(pid)) if err != nil { continue } From 00bdebfce7bfd653fa38bdbb546c224a5c84c5fd Mon Sep 17 00:00:00 2001 From: Lomanic Date: Sun, 12 Nov 2017 21:59:17 +0100 Subject: [PATCH 12/23] Use getFromSnapProcess to get process Name and Ppid on Windows instead of slow WMI --- process/process_windows.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/process/process_windows.go b/process/process_windows.go index d023030..bbd7762 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -119,12 +119,11 @@ func Pids() ([]int32, error) { } func (p *Process) Ppid() (int32, error) { - dst, err := GetWin32Proc(p.Pid) + ppid, _, _, err := getFromSnapProcess(p.Pid) if err != nil { return 0, err } - - return int32(dst[0].ParentProcessID), nil + return ppid, nil } func GetWin32Proc(pid int32) ([]Win32_Process, error) { @@ -144,11 +143,11 @@ func GetWin32Proc(pid int32) ([]Win32_Process, error) { } func (p *Process) Name() (string, error) { - dst, err := GetWin32Proc(p.Pid) + _, _, name, err := getFromSnapProcess(p.Pid) if err != nil { return "", fmt.Errorf("could not get Name: %s", err) } - return dst[0].Name, nil + return name, nil } func (p *Process) Exe() (string, error) { @@ -406,7 +405,7 @@ func (p *Process) Kill() error { return common.ErrNotImplementedError } -func (p *Process) getFromSnapProcess(pid int32) (int32, int32, string, error) { +func getFromSnapProcess(pid int32) (int32, int32, string, error) { snap := w32.CreateToolhelp32Snapshot(w32.TH32CS_SNAPPROCESS, uint32(pid)) if snap == 0 { return 0, 0, "", windows.GetLastError() From 6abd227e48f69bd2e910f32f07afc0ba69fb80b8 Mon Sep 17 00:00:00 2001 From: Daniel Nelson Date: Fri, 17 Nov 2017 10:46:16 -0800 Subject: [PATCH 13/23] [net] linux: skip if not exist error on getProcInodesAll --- net/net_linux.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/net_linux.go b/net/net_linux.go index 4fb5939..6d798fc 100644 --- a/net/net_linux.go +++ b/net/net_linux.go @@ -541,8 +541,8 @@ func getProcInodesAll(root string, max int) (map[string][]inodeMap, error) { for _, pid := range pids { t, err := getProcInodes(root, pid, max) if err != nil { - // skip if permission error - if os.IsPermission(err) { + // skip if permission error or no longer exists + if os.IsPermission(err) || os.IsNotExist(err) { continue } return ret, err From f5e19d7e16aaf9552a27fb909fc13a8dfcf22068 Mon Sep 17 00:00:00 2001 From: WAKAYAMA shirou Date: Sat, 18 Nov 2017 22:43:54 +0900 Subject: [PATCH 14/23] [process]windows: implements process.Kill using os/exec --- process/process_test.go | 20 ++++++++++++++++++++ process/process_windows.go | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/process/process_test.go b/process/process_test.go index 9947dea..84c24bd 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -3,6 +3,7 @@ package process import ( "fmt" "os" + "os/exec" "os/user" "reflect" "runtime" @@ -418,5 +419,24 @@ func Test_OpenFiles(t *testing.T) { for _, vv := range v { assert.NotEqual(t, "", vv.Path) } +} +func Test_Kill(t *testing.T) { + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command("choice", "/C", "YN", "/D", "Y", "/t", "3") + } else { + cmd = exec.Command("sleep", "3") + } + var wg sync.WaitGroup + wg.Add(1) + go func() { + assert.NotNil(t, cmd.Run()) + wg.Done() + }() + time.Sleep(100 * time.Millisecond) + p, err := NewProcess(int32(cmd.Process.Pid)) + assert.Nil(t, err) + assert.Nil(t, p.Kill()) + wg.Wait() } diff --git a/process/process_windows.go b/process/process_windows.go index bbd7762..41ceb34 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -4,6 +4,7 @@ package process import ( "fmt" + "os" "strings" "syscall" "time" @@ -402,7 +403,8 @@ func (p *Process) Terminate() error { } func (p *Process) Kill() error { - return common.ErrNotImplementedError + process := os.Process{Pid: int(p.Pid)} + return process.Kill() } func getFromSnapProcess(pid int32) (int32, int32, string, error) { From 65598d98cc1b8bd8272bf08b73563a06d775e227 Mon Sep 17 00:00:00 2001 From: leaf Date: Thu, 9 Nov 2017 15:44:26 -0800 Subject: [PATCH 15/23] To prevent hang if wmi.Query hangs, add a context-aware wrapper for it. --- cpu/cpu_windows.go | 15 +++++++++++---- disk/disk_windows.go | 6 ++++-- host/host_windows.go | 5 ++++- internal/common/common_windows.go | 19 ++++++++++++++++++- process/process_windows.go | 11 ++++++++--- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/cpu/cpu_windows.go b/cpu/cpu_windows.go index b5bf825..b012fde 100644 --- a/cpu/cpu_windows.go +++ b/cpu/cpu_windows.go @@ -3,6 +3,7 @@ package cpu import ( + "context" "fmt" "unsafe" @@ -81,8 +82,9 @@ func Info() ([]InfoStat, error) { var ret []InfoStat var dst []Win32_Processor q := wmi.CreateQuery(&dst, "") - err := wmi.Query(q, &dst) - if err != nil { + ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) + defer cancel() + if err := common.WMIQueryWithContext(ctx, q, &dst); err != nil { return ret, err } @@ -113,8 +115,11 @@ func Info() ([]InfoStat, error) { // Name property is the key by which overall, per cpu and per core metric is known. func PerfInfo() ([]Win32_PerfFormattedData_Counters_ProcessorInformation, error) { var ret []Win32_PerfFormattedData_Counters_ProcessorInformation + q := wmi.CreateQuery(&ret, "") - err := wmi.Query(q, &ret) + ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) + defer cancel() + err := common.WMIQueryWithContext(ctx, q, &ret) return ret, err } @@ -123,7 +128,9 @@ func PerfInfo() ([]Win32_PerfFormattedData_Counters_ProcessorInformation, error) func ProcInfo() ([]Win32_PerfFormattedData_PerfOS_System, error) { var ret []Win32_PerfFormattedData_PerfOS_System q := wmi.CreateQuery(&ret, "") - err := wmi.Query(q, &ret) + ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) + defer cancel() + err := common.WMIQueryWithContext(ctx, q, &ret) return ret, err } diff --git a/disk/disk_windows.go b/disk/disk_windows.go index 6cc22a2..9e5f681 100644 --- a/disk/disk_windows.go +++ b/disk/disk_windows.go @@ -4,9 +4,9 @@ package disk import ( "bytes" + "context" "unsafe" - "github.com/StackExchange/wmi" "github.com/shirou/gopsutil/internal/common" "golang.org/x/sys/windows" ) @@ -132,7 +132,9 @@ func IOCounters(names ...string) (map[string]IOCountersStat, error) { ret := make(map[string]IOCountersStat, 0) var dst []Win32_PerfFormattedData - err := wmi.Query("SELECT * FROM Win32_PerfFormattedData_PerfDisk_LogicalDisk ", &dst) + ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) + defer cancel() + err := common.WMIQueryWithContext(ctx, "SELECT * FROM Win32_PerfFormattedData_PerfDisk_LogicalDisk", &dst) if err != nil { return ret, err } diff --git a/host/host_windows.go b/host/host_windows.go index 9894302..920cec9 100644 --- a/host/host_windows.go +++ b/host/host_windows.go @@ -3,6 +3,7 @@ package host import ( + "context" "fmt" "os" "runtime" @@ -109,7 +110,9 @@ func getMachineGuid() (string, error) { func GetOSInfo() (Win32_OperatingSystem, error) { var dst []Win32_OperatingSystem q := wmi.CreateQuery(&dst, "") - err := wmi.Query(q, &dst) + ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) + defer cancel() + err := common.WMIQueryWithContext(ctx, q, &dst) if err != nil { return Win32_OperatingSystem{}, err } diff --git a/internal/common/common_windows.go b/internal/common/common_windows.go index 8e0177d..1dffe61 100644 --- a/internal/common/common_windows.go +++ b/internal/common/common_windows.go @@ -3,8 +3,10 @@ package common import ( + "context" "unsafe" + "github.com/StackExchange/wmi" "golang.org/x/sys/windows" ) @@ -49,7 +51,7 @@ var ( ModNt = windows.NewLazyDLL("ntdll.dll") ModPdh = windows.NewLazyDLL("pdh.dll") ModPsapi = windows.NewLazyDLL("psapi.dll") - + ProcGetSystemTimes = Modkernel32.NewProc("GetSystemTimes") ProcNtQuerySystemInformation = ModNt.NewProc("NtQuerySystemInformation") PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery") @@ -110,3 +112,18 @@ func CreateCounter(query windows.Handle, pname, cname string) (*CounterInfo, err Counter: counter, }, nil } + +// WMIQueryWithContext - wraps wmi.Query with a timed-out context to avoid hanging +func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, connectServerArgs ...interface{}) error { + errChan := make(chan error, 1) + go func() { + errChan <- wmi.Query(query, dst, connectServerArgs...) + }() + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errChan: + return err + } +} diff --git a/process/process_windows.go b/process/process_windows.go index bbd7762..be91f97 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -3,6 +3,7 @@ package process import ( + "context" "fmt" "strings" "syscall" @@ -130,8 +131,10 @@ func GetWin32Proc(pid int32) ([]Win32_Process, error) { var dst []Win32_Process query := fmt.Sprintf("WHERE ProcessId = %d", pid) q := wmi.CreateQuery(&dst, query) - - if err := wmi.Query(q, &dst); err != nil { + ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) + defer cancel() + err := common.WMIQueryWithContext(ctx, q, &dst) + if err != nil { return []Win32_Process{}, fmt.Errorf("could not get win32Proc: %s", err) } @@ -333,7 +336,9 @@ func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) { func (p *Process) Children() ([]*Process, error) { var dst []Win32_Process query := wmi.CreateQuery(&dst, fmt.Sprintf("Where ParentProcessId = %d", p.Pid)) - err := wmi.Query(query, &dst) + ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) + defer cancel() + err := common.WMIQueryWithContext(ctx, query, &dst) if err != nil { return nil, err } From 22f3299fd74dd1df8b6539bb75f4ca5462b4cc3d Mon Sep 17 00:00:00 2001 From: Adam Medzinski Date: Mon, 27 Nov 2017 12:17:06 +0100 Subject: [PATCH 16/23] Add missing doc for cpu.TimesStat structure --- cpu/cpu.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpu/cpu.go b/cpu/cpu.go index f5bf315..f0dccd4 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -12,6 +12,9 @@ import ( "github.com/shirou/gopsutil/internal/common" ) +// TimesStat contains the amounts of time the CPU has spent performing different +// kinds of work. Time units are in USER_HZ or Jiffies (typically hundredths of +// a second). It is based on linux /proc/stat file. type TimesStat struct { CPU string `json:"cpu"` User float64 `json:"user"` From 51c7c4013b6b2317265bfeee7c21cf2e9872596e Mon Sep 17 00:00:00 2001 From: Tatiana Borisova Date: Fri, 8 Dec 2017 15:05:00 +0000 Subject: [PATCH 17/23] Fix build with bazel on OSX --- host/host_darwin_cgo.go | 2 +- host/include/smc.c | 700 ------------------------------------------------ host/include/smc.h | 254 ------------------ host/smc.c | 700 ++++++++++++++++++++++++++++++++++++++++++++++++ host/smc.h | 254 ++++++++++++++++++ 5 files changed, 955 insertions(+), 955 deletions(-) delete mode 100644 host/include/smc.c delete mode 100644 host/include/smc.h create mode 100644 host/smc.c create mode 100644 host/smc.h diff --git a/host/host_darwin_cgo.go b/host/host_darwin_cgo.go index be5e18b..9471c3a 100644 --- a/host/host_darwin_cgo.go +++ b/host/host_darwin_cgo.go @@ -4,7 +4,7 @@ package host // #cgo LDFLAGS: -framework IOKit -// #include "include/smc.c" +// #include "smc.h" import "C" func SensorsTemperatures() ([]TemperatureStat, error) { diff --git a/host/include/smc.c b/host/include/smc.c deleted file mode 100644 index 30a232b..0000000 --- a/host/include/smc.c +++ /dev/null @@ -1,700 +0,0 @@ -/* - * Apple System Management Controller (SMC) API from user space for Intel based - * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver - * for the SMC. - * - * smc.c - * libsmc - * - * Copyright (C) 2014 beltex - * - * Based off of fork from: - * osx-cpu-temp - * - * With credits to: - * - * Copyright (C) 2006 devnull - * Apple System Management Control (SMC) Tool - * - * Copyright (C) 2006 Hendrik Holtmann - * smcFanControl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include "smc.h" - - -//------------------------------------------------------------------------------ -// MARK: MACROS -//------------------------------------------------------------------------------ - - -/** -Name of the SMC IOService as seen in the IORegistry. You can view it either via -command line with ioreg or through the IORegistryExplorer app (found on Apple's -developer site - Hardware IO Tools for Xcode) -*/ -#define IOSERVICE_SMC "AppleSMC" - - -/** -IOService for getting machine model name -*/ -#define IOSERVICE_MODEL "IOPlatformExpertDevice" - - -/** -SMC data types - 4 byte multi-character constants - -Sources: See TMP SMC keys in smc.h - -http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types -*/ -#define DATA_TYPE_UINT8 "ui8 " -#define DATA_TYPE_UINT16 "ui16" -#define DATA_TYPE_UINT32 "ui32" -#define DATA_TYPE_FLAG "flag" -#define DATA_TYPE_FPE2 "fpe2" -#define DATA_TYPE_SFDS "{fds" -#define DATA_TYPE_SP78 "sp78" - - -//------------------------------------------------------------------------------ -// MARK: GLOBAL VARS -//------------------------------------------------------------------------------ - - -/** -Our connection to the SMC -*/ -static io_connect_t conn; - - -/** -Number of characters in an SMC key -*/ -static const int SMC_KEY_SIZE = 4; - - -/** -Number of characters in a data type "key" returned from the SMC. See data type -macros. -*/ -static const int DATA_TYPE_SIZE = 4; - - -//------------------------------------------------------------------------------ -// MARK: ENUMS -//------------------------------------------------------------------------------ - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. - -These are SMC specific return codes -*/ -typedef enum { - kSMCSuccess = 0, - kSMCError = 1, - kSMCKeyNotFound = 0x84 -} kSMC_t; - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. - -Function selectors. Used to tell the SMC which function inside it to call. -*/ -typedef enum { - kSMCUserClientOpen = 0, - kSMCUserClientClose = 1, - kSMCHandleYPCEvent = 2, - kSMCReadKey = 5, - kSMCWriteKey = 6, - kSMCGetKeyCount = 7, - kSMCGetKeyFromIndex = 8, - kSMCGetKeyInfo = 9 -} selector_t; - - -//------------------------------------------------------------------------------ -// MARK: STRUCTS -//------------------------------------------------------------------------------ - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. -*/ -typedef struct { - unsigned char major; - unsigned char minor; - unsigned char build; - unsigned char reserved; - unsigned short release; -} SMCVersion; - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. -*/ -typedef struct { - uint16_t version; - uint16_t length; - uint32_t cpuPLimit; - uint32_t gpuPLimit; - uint32_t memPLimit; -} SMCPLimitData; - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. - -- dataSize : How many values written to SMCParamStruct.bytes -- dataType : Type of data written to SMCParamStruct.bytes. This lets us know how - to interpret it (translate it to human readable) -*/ -typedef struct { - IOByteCount dataSize; - uint32_t dataType; - uint8_t dataAttributes; -} SMCKeyInfoData; - - -/** -Defined by AppleSMC.kext. - -This is the predefined struct that must be passed to communicate with the -AppleSMC driver. While the driver is closed source, the definition of this -struct happened to appear in the Apple PowerManagement project at around -version 211, and soon after disappeared. It can be seen in the PrivateLib.c -file under pmconfigd. - -https://www.opensource.apple.com/source/PowerManagement/PowerManagement-211/ -*/ -typedef struct { - uint32_t key; - SMCVersion vers; - SMCPLimitData pLimitData; - SMCKeyInfoData keyInfo; - uint8_t result; - uint8_t status; - uint8_t data8; - uint32_t data32; - uint8_t bytes[32]; -} SMCParamStruct; - - -/** -Used for returning data from the SMC. -*/ -typedef struct { - uint8_t data[32]; - uint32_t dataType; - uint32_t dataSize; - kSMC_t kSMC; -} smc_return_t; - - -//------------------------------------------------------------------------------ -// MARK: HELPERS - TYPE CONVERSION -//------------------------------------------------------------------------------ - - -/** -Convert data from SMC of fpe2 type to human readable. - -:param: data Data from the SMC to be converted. Assumed data size of 2. -:returns: Converted data -*/ -static unsigned int from_fpe2(uint8_t data[32]) -{ - unsigned int ans = 0; - - // Data type for fan calls - fpe2 - // This is assumend to mean floating point, with 2 exponent bits - // http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types - ans += data[0] << 6; - ans += data[1] << 2; - - return ans; -} - - -/** -Convert to fpe2 data type to be passed to SMC. - -:param: val Value to convert -:param: data Pointer to data array to place result -*/ -static void to_fpe2(unsigned int val, uint8_t *data) -{ - data[0] = val >> 6; - data[1] = (val << 2) ^ (data[0] << 8); -} - - -/** -Convert SMC key to uint32_t. This must be done to pass it to the SMC. - -:param: key The SMC key to convert -:returns: uint32_t translation. - Returns zero if key is not 4 characters in length. -*/ -static uint32_t to_uint32_t(char *key) -{ - uint32_t ans = 0; - uint32_t shift = 24; - - // SMC key is expected to be 4 bytes - thus 4 chars - if (strlen(key) != SMC_KEY_SIZE) { - return 0; - } - - for (int i = 0; i < SMC_KEY_SIZE; i++) { - ans += key[i] << shift; - shift -= 8; - } - - return ans; -} - - -/** -For converting the dataType return from the SMC to human readable 4 byte -multi-character constant. -*/ -static void to_string(uint32_t val, char *dataType) -{ - int shift = 24; - - for (int i = 0; i < DATA_TYPE_SIZE; i++) { - // To get each char, we shift it into the lower 8 bits, and then & by - // 255 to insolate it - dataType[i] = (val >> shift) & 0xff; - shift -= 8; - } -} - - -//------------------------------------------------------------------------------ -// MARK: HELPERS - TMP CONVERSION -//------------------------------------------------------------------------------ - - -/** -Celsius to Fahrenheit -*/ -static double to_fahrenheit(double tmp) -{ - // http://en.wikipedia.org/wiki/Fahrenheit#Definition_and_conversions - return (tmp * 1.8) + 32; -} - - -/** -Celsius to Kelvin -*/ -static double to_kelvin(double tmp) -{ - // http://en.wikipedia.org/wiki/Kelvin - return tmp + 273.15; -} - - -//------------------------------------------------------------------------------ -// MARK: "PRIVATE" FUNCTIONS -//------------------------------------------------------------------------------ - - -/** -Make a call to the SMC - -:param: inputStruct Struct that holds data telling the SMC what you want -:param: outputStruct Struct holding the SMC's response -:returns: I/O Kit return code -*/ -static kern_return_t call_smc(SMCParamStruct *inputStruct, - SMCParamStruct *outputStruct) -{ - kern_return_t result; - size_t inputStructCnt = sizeof(SMCParamStruct); - size_t outputStructCnt = sizeof(SMCParamStruct); - - result = IOConnectCallStructMethod(conn, kSMCHandleYPCEvent, - inputStruct, - inputStructCnt, - outputStruct, - &outputStructCnt); - - if (result != kIOReturnSuccess) { - // IOReturn error code lookup. See "Accessing Hardware From Applications - // -> Handling Errors" Apple doc - result = err_get_code(result); - } - - return result; -} - - -/** -Read data from the SMC - -:param: key The SMC key -*/ -static kern_return_t read_smc(char *key, smc_return_t *result_smc) -{ - kern_return_t result; - SMCParamStruct inputStruct; - SMCParamStruct outputStruct; - - memset(&inputStruct, 0, sizeof(SMCParamStruct)); - memset(&outputStruct, 0, sizeof(SMCParamStruct)); - memset(result_smc, 0, sizeof(smc_return_t)); - - // First call to AppleSMC - get key info - inputStruct.key = to_uint32_t(key); - inputStruct.data8 = kSMCGetKeyInfo; - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { - return result; - } - - // Store data for return - result_smc->dataSize = outputStruct.keyInfo.dataSize; - result_smc->dataType = outputStruct.keyInfo.dataType; - - - // Second call to AppleSMC - now we can get the data - inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; - inputStruct.data8 = kSMCReadKey; - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { - return result; - } - - memcpy(result_smc->data, outputStruct.bytes, sizeof(outputStruct.bytes)); - - return result; -} - - -/** -Write data to the SMC. - -:returns: IOReturn IOKit return code -*/ -static kern_return_t write_smc(char *key, smc_return_t *result_smc) -{ - kern_return_t result; - SMCParamStruct inputStruct; - SMCParamStruct outputStruct; - - memset(&inputStruct, 0, sizeof(SMCParamStruct)); - memset(&outputStruct, 0, sizeof(SMCParamStruct)); - - // First call to AppleSMC - get key info - inputStruct.key = to_uint32_t(key); - inputStruct.data8 = kSMCGetKeyInfo; - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { - return result; - } - - // Check data is correct - if (result_smc->dataSize != outputStruct.keyInfo.dataSize || - result_smc->dataType != outputStruct.keyInfo.dataType) { - return kIOReturnBadArgument; - } - - // Second call to AppleSMC - now we can write the data - inputStruct.data8 = kSMCWriteKey; - inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; - - // Set data to write - memcpy(inputStruct.bytes, result_smc->data, sizeof(result_smc->data)); - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - return result; -} - - -/** -Get the model name of the machine. -*/ -static kern_return_t get_machine_model(io_name_t model) -{ - io_service_t service; - kern_return_t result; - - service = IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching(IOSERVICE_MODEL)); - - if (service == 0) { - printf("ERROR: %s NOT FOUND\n", IOSERVICE_MODEL); - return kIOReturnError; - } - - // Get the model name - result = IORegistryEntryGetName(service, model); - IOObjectRelease(service); - - return result; -} - - -//------------------------------------------------------------------------------ -// MARK: "PUBLIC" FUNCTIONS -//------------------------------------------------------------------------------ - - -kern_return_t open_smc(void) -{ - kern_return_t result; - io_service_t service; - - service = IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching(IOSERVICE_SMC)); - - if (service == 0) { - // NOTE: IOServiceMatching documents 0 on failure - printf("ERROR: %s NOT FOUND\n", IOSERVICE_SMC); - return kIOReturnError; - } - - result = IOServiceOpen(service, mach_task_self(), 0, &conn); - IOObjectRelease(service); - - return result; -} - - -kern_return_t close_smc(void) -{ - return IOServiceClose(conn); -} - - -bool is_key_valid(char *key) -{ - bool ans = false; - kern_return_t result; - smc_return_t result_smc; - - if (strlen(key) != SMC_KEY_SIZE) { - printf("ERROR: Invalid key size - must be 4 chars\n"); - return ans; - } - - // Try a read and see if it succeeds - result = read_smc(key, &result_smc); - - if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { - ans = true; - } - - return ans; -} - - -double get_tmp(char *key, tmp_unit_t unit) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 2 && - result_smc.dataType == to_uint32_t(DATA_TYPE_SP78))) { - // Error - return 0.0; - } - - // TODO: Create from_sp78() convert function - double tmp = result_smc.data[0]; - - switch (unit) { - case CELSIUS: - break; - case FAHRENHEIT: - tmp = to_fahrenheit(tmp); - break; - case KELVIN: - tmp = to_kelvin(tmp); - break; - } - - return tmp; -} - - -bool is_battery_powered(void) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(BATT_PWR, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 1 && - result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { - // Error - return false; - } - - return result_smc.data[0]; -} - - -bool is_optical_disk_drive_full(void) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(ODD_FULL, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 1 && - result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { - // Error - return false; - } - - return result_smc.data[0]; -} - - -//------------------------------------------------------------------------------ -// MARK: FAN FUNCTIONS -//------------------------------------------------------------------------------ - - -bool get_fan_name(unsigned int fan_num, fan_name_t name) -{ - char key[5]; - kern_return_t result; - smc_return_t result_smc; - - sprintf(key, "F%dID", fan_num); - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 16 && - result_smc.dataType == to_uint32_t(DATA_TYPE_SFDS))) { - return false; - } - - - /* - We know the data size is 16 bytes and the type is "{fds", a custom - struct defined by the AppleSMC.kext. See TMP enum sources for the - struct. - - The last 12 bytes contain the name of the fan, an array of chars, hence - the loop range. - */ - int index = 0; - for (int i = 4; i < 16; i++) { - // Check if at the end (name may not be full 12 bytes) - // Could check for 0 (null), but instead we check for 32 (space). This - // is a hack to remove whitespace. :) - if (result_smc.data[i] == 32) { - break; - } - - name[index] = result_smc.data[i]; - index++; - } - - return true; -} - - -int get_num_fans(void) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(NUM_FANS, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 1 && - result_smc.dataType == to_uint32_t(DATA_TYPE_UINT8))) { - // Error - return -1; - } - - return result_smc.data[0]; -} - - -unsigned int get_fan_rpm(unsigned int fan_num) -{ - char key[5]; - kern_return_t result; - smc_return_t result_smc; - - sprintf(key, "F%dAc", fan_num); - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 2 && - result_smc.dataType == to_uint32_t(DATA_TYPE_FPE2))) { - // Error - return 0; - } - - return from_fpe2(result_smc.data); -} - - -bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth) -{ - // TODO: Add rpm val safety check - char key[5]; - bool ans = false; - kern_return_t result; - smc_return_t result_smc; - - memset(&result_smc, 0, sizeof(smc_return_t)); - - // TODO: Don't use magic number - result_smc.dataSize = 2; - result_smc.dataType = to_uint32_t(DATA_TYPE_FPE2); - to_fpe2(rpm, result_smc.data); - - sprintf(key, "F%dMn", fan_num); - result = write_smc(key, &result_smc); - - if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { - ans = true; - } - - return ans; -} diff --git a/host/include/smc.h b/host/include/smc.h deleted file mode 100644 index b156368..0000000 --- a/host/include/smc.h +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Apple System Management Controller (SMC) API from user space for Intel based - * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver - * for the SMC. - * - * smc.h - * libsmc - * - * Copyright (C) 2014 beltex - * - * Based off of fork from: - * osx-cpu-temp - * - * With credits to: - * - * Copyright (C) 2006 devnull - * Apple System Management Control (SMC) Tool - * - * Copyright (C) 2006 Hendrik Holtmann - * smcFanControl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include - - -//------------------------------------------------------------------------------ -// MARK: MACROS -//------------------------------------------------------------------------------ - - -/** -SMC keys for temperature sensors - 4 byte multi-character constants - -Not applicable to all Mac's of course. In adition, the definition of the codes -may not be 100% accurate necessarily. Finally, list is incomplete. - -Presumed letter translations: - -- T = Temperature (if first char) -- C = CPU -- G = GPU -- P = Proximity -- D = Diode -- H = Heatsink - -Sources: - -- https://www.apple.com/downloads/dashboard/status/istatpro.html -- https://github.com/hholtmann/smcFanControl -- https://github.com/jedda/OSX-Monitoring-Tools -- http://www.parhelia.ch/blog/statics/k3_keys.html -*/ -#define AMBIENT_AIR_0 "TA0P" -#define AMBIENT_AIR_1 "TA1P" -#define CPU_0_DIODE "TC0D" -#define CPU_0_HEATSINK "TC0H" -#define CPU_0_PROXIMITY "TC0P" -#define ENCLOSURE_BASE_0 "TB0T" -#define ENCLOSURE_BASE_1 "TB1T" -#define ENCLOSURE_BASE_2 "TB2T" -#define ENCLOSURE_BASE_3 "TB3T" -#define GPU_0_DIODE "TG0D" -#define GPU_0_HEATSINK "TG0H" -#define GPU_0_PROXIMITY "TG0P" -#define HARD_DRIVE_BAY "TH0P" -#define MEMORY_SLOT_0 "TM0S" -#define MEMORY_SLOTS_PROXIMITY "TM0P" -#define NORTHBRIDGE "TN0H" -#define NORTHBRIDGE_DIODE "TN0D" -#define NORTHBRIDGE_PROXIMITY "TN0P" -#define THUNDERBOLT_0 "TI0P" -#define THUNDERBOLT_1 "TI1P" -#define WIRELESS_MODULE "TW0P" - - -/** -SMC keys for fans - 4 byte multi-character constants - -Number of fans on Macs vary of course, thus not all keys will be applicable. - -Presumed letter translations: - -- F = Fan -- Ac = Acutal -- Mn = Min -- Mx = Max -- Sf = Safe -- Tg = Target - -Sources: See TMP SMC keys -*/ -#define FAN_0 "F0Ac" -#define FAN_0_MIN_RPM "F0Mn" -#define FAN_0_MAX_RPM "F0Mx" -#define FAN_0_SAFE_RPM "F0Sf" -#define FAN_0_TARGET_RPM "F0Tg" -#define FAN_1 "F1Ac" -#define FAN_1_MIN_RPM "F1Mn" -#define FAN_1_MAX_RPM "F1Mx" -#define FAN_1_SAFE_RPM "F1Sf" -#define FAN_1_TARGET_RPM "F1Tg" -#define FAN_2 "F2Ac" -#define FAN_2_MIN_RPM "F2Mn" -#define FAN_2_MAX_RPM "F2Mx" -#define FAN_2_SAFE_RPM "F2Sf" -#define FAN_2_TARGET_RPM "F2Tg" -#define NUM_FANS "FNum" -#define FORCE_BITS "FS! " - - -/** -Misc SMC keys - 4 byte multi-character constants - -Sources: See TMP SMC keys -*/ -#define BATT_PWR "BATP" -#define NUM_KEYS "#KEY" -#define ODD_FULL "MSDI" - - -//------------------------------------------------------------------------------ -// MARK: TYPES -//------------------------------------------------------------------------------ - - -typedef char fan_name_t[13]; - - -//------------------------------------------------------------------------------ -// MARK: ENUMS -//------------------------------------------------------------------------------ - - -typedef enum { - CELSIUS, - FAHRENHEIT, - KELVIN -} tmp_unit_t; - - -//------------------------------------------------------------------------------ -// MARK: PROTOTYPES -//------------------------------------------------------------------------------ - - -/** -Open a connection to the SMC - -:returns: kIOReturnSuccess on successful connection to the SMC. -*/ -kern_return_t open_smc(void); - - -/** -Close connection to the SMC - -:returns: kIOReturnSuccess on successful close of connection to the SMC. -*/ -kern_return_t close_smc(void); - - -/** -Check if an SMC key is valid. Useful for determining if a certain machine has -particular sensor or fan for example. - -:param: key The SMC key to check. 4 byte multi-character constant. Must be 4 - characters in length. -:returns: True if the key is found, false otherwise -*/ -bool is_key_valid(char *key); - - -/** -Get the current temperature from a sensor - -:param: key The temperature sensor to read from -:param: unit The unit for the temperature value. -:returns: Temperature of sensor. If the sensor is not found, or an error - occurs, return will be zero -*/ -double get_tmp(char *key, tmp_unit_t unit); - - -/** -Is the machine being powered by the battery? - -:returns: True if it is, false otherwise -*/ -bool is_battery_powered(void); - - -/** -Is there a CD in the optical disk drive (ODD)? - -:returns: True if there is, false otherwise -*/ -bool is_optical_disk_drive_full(void); - - -/** -Get the name of a fan. - -:param: fanNum The number of the fan to check -:param: name The name of the fan. Return will be empty on error. -:returns: True if successful, false otherwise. -*/ -bool get_fan_name(unsigned int fan_num, fan_name_t name); - - -/** -Get the number of fans on this machine. - -:returns: The number of fans. If an error occurs, return will be -1. -*/ -int get_num_fans(void); - - -/** -Get the current speed (RPM - revolutions per minute) of a fan. - -:param: fan_num The number of the fan to check -:returns: The fan RPM. If the fan is not found, or an error occurs, return - will be zero -*/ -UInt get_fan_rpm(UInt fan_num); - - -/** -Set the minimum speed (RPM - revolutions per minute) of a fan. This method -requires root privileges. By minimum we mean that OS X can interject and -raise the fan speed if needed, however it will not go below this. - -WARNING: You are playing with hardware here, BE CAREFUL. - -:param: fan_num The number of the fan to set -:param: rpm The speed you would like to set the fan to. -:param: auth Should the function do authentication? -:return: True if successful, false otherwise -*/ -bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth); diff --git a/host/smc.c b/host/smc.c new file mode 100644 index 0000000..30a232b --- /dev/null +++ b/host/smc.c @@ -0,0 +1,700 @@ +/* + * Apple System Management Controller (SMC) API from user space for Intel based + * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver + * for the SMC. + * + * smc.c + * libsmc + * + * Copyright (C) 2014 beltex + * + * Based off of fork from: + * osx-cpu-temp + * + * With credits to: + * + * Copyright (C) 2006 devnull + * Apple System Management Control (SMC) Tool + * + * Copyright (C) 2006 Hendrik Holtmann + * smcFanControl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include "smc.h" + + +//------------------------------------------------------------------------------ +// MARK: MACROS +//------------------------------------------------------------------------------ + + +/** +Name of the SMC IOService as seen in the IORegistry. You can view it either via +command line with ioreg or through the IORegistryExplorer app (found on Apple's +developer site - Hardware IO Tools for Xcode) +*/ +#define IOSERVICE_SMC "AppleSMC" + + +/** +IOService for getting machine model name +*/ +#define IOSERVICE_MODEL "IOPlatformExpertDevice" + + +/** +SMC data types - 4 byte multi-character constants + +Sources: See TMP SMC keys in smc.h + +http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types +*/ +#define DATA_TYPE_UINT8 "ui8 " +#define DATA_TYPE_UINT16 "ui16" +#define DATA_TYPE_UINT32 "ui32" +#define DATA_TYPE_FLAG "flag" +#define DATA_TYPE_FPE2 "fpe2" +#define DATA_TYPE_SFDS "{fds" +#define DATA_TYPE_SP78 "sp78" + + +//------------------------------------------------------------------------------ +// MARK: GLOBAL VARS +//------------------------------------------------------------------------------ + + +/** +Our connection to the SMC +*/ +static io_connect_t conn; + + +/** +Number of characters in an SMC key +*/ +static const int SMC_KEY_SIZE = 4; + + +/** +Number of characters in a data type "key" returned from the SMC. See data type +macros. +*/ +static const int DATA_TYPE_SIZE = 4; + + +//------------------------------------------------------------------------------ +// MARK: ENUMS +//------------------------------------------------------------------------------ + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. + +These are SMC specific return codes +*/ +typedef enum { + kSMCSuccess = 0, + kSMCError = 1, + kSMCKeyNotFound = 0x84 +} kSMC_t; + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. + +Function selectors. Used to tell the SMC which function inside it to call. +*/ +typedef enum { + kSMCUserClientOpen = 0, + kSMCUserClientClose = 1, + kSMCHandleYPCEvent = 2, + kSMCReadKey = 5, + kSMCWriteKey = 6, + kSMCGetKeyCount = 7, + kSMCGetKeyFromIndex = 8, + kSMCGetKeyInfo = 9 +} selector_t; + + +//------------------------------------------------------------------------------ +// MARK: STRUCTS +//------------------------------------------------------------------------------ + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. +*/ +typedef struct { + unsigned char major; + unsigned char minor; + unsigned char build; + unsigned char reserved; + unsigned short release; +} SMCVersion; + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. +*/ +typedef struct { + uint16_t version; + uint16_t length; + uint32_t cpuPLimit; + uint32_t gpuPLimit; + uint32_t memPLimit; +} SMCPLimitData; + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. + +- dataSize : How many values written to SMCParamStruct.bytes +- dataType : Type of data written to SMCParamStruct.bytes. This lets us know how + to interpret it (translate it to human readable) +*/ +typedef struct { + IOByteCount dataSize; + uint32_t dataType; + uint8_t dataAttributes; +} SMCKeyInfoData; + + +/** +Defined by AppleSMC.kext. + +This is the predefined struct that must be passed to communicate with the +AppleSMC driver. While the driver is closed source, the definition of this +struct happened to appear in the Apple PowerManagement project at around +version 211, and soon after disappeared. It can be seen in the PrivateLib.c +file under pmconfigd. + +https://www.opensource.apple.com/source/PowerManagement/PowerManagement-211/ +*/ +typedef struct { + uint32_t key; + SMCVersion vers; + SMCPLimitData pLimitData; + SMCKeyInfoData keyInfo; + uint8_t result; + uint8_t status; + uint8_t data8; + uint32_t data32; + uint8_t bytes[32]; +} SMCParamStruct; + + +/** +Used for returning data from the SMC. +*/ +typedef struct { + uint8_t data[32]; + uint32_t dataType; + uint32_t dataSize; + kSMC_t kSMC; +} smc_return_t; + + +//------------------------------------------------------------------------------ +// MARK: HELPERS - TYPE CONVERSION +//------------------------------------------------------------------------------ + + +/** +Convert data from SMC of fpe2 type to human readable. + +:param: data Data from the SMC to be converted. Assumed data size of 2. +:returns: Converted data +*/ +static unsigned int from_fpe2(uint8_t data[32]) +{ + unsigned int ans = 0; + + // Data type for fan calls - fpe2 + // This is assumend to mean floating point, with 2 exponent bits + // http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types + ans += data[0] << 6; + ans += data[1] << 2; + + return ans; +} + + +/** +Convert to fpe2 data type to be passed to SMC. + +:param: val Value to convert +:param: data Pointer to data array to place result +*/ +static void to_fpe2(unsigned int val, uint8_t *data) +{ + data[0] = val >> 6; + data[1] = (val << 2) ^ (data[0] << 8); +} + + +/** +Convert SMC key to uint32_t. This must be done to pass it to the SMC. + +:param: key The SMC key to convert +:returns: uint32_t translation. + Returns zero if key is not 4 characters in length. +*/ +static uint32_t to_uint32_t(char *key) +{ + uint32_t ans = 0; + uint32_t shift = 24; + + // SMC key is expected to be 4 bytes - thus 4 chars + if (strlen(key) != SMC_KEY_SIZE) { + return 0; + } + + for (int i = 0; i < SMC_KEY_SIZE; i++) { + ans += key[i] << shift; + shift -= 8; + } + + return ans; +} + + +/** +For converting the dataType return from the SMC to human readable 4 byte +multi-character constant. +*/ +static void to_string(uint32_t val, char *dataType) +{ + int shift = 24; + + for (int i = 0; i < DATA_TYPE_SIZE; i++) { + // To get each char, we shift it into the lower 8 bits, and then & by + // 255 to insolate it + dataType[i] = (val >> shift) & 0xff; + shift -= 8; + } +} + + +//------------------------------------------------------------------------------ +// MARK: HELPERS - TMP CONVERSION +//------------------------------------------------------------------------------ + + +/** +Celsius to Fahrenheit +*/ +static double to_fahrenheit(double tmp) +{ + // http://en.wikipedia.org/wiki/Fahrenheit#Definition_and_conversions + return (tmp * 1.8) + 32; +} + + +/** +Celsius to Kelvin +*/ +static double to_kelvin(double tmp) +{ + // http://en.wikipedia.org/wiki/Kelvin + return tmp + 273.15; +} + + +//------------------------------------------------------------------------------ +// MARK: "PRIVATE" FUNCTIONS +//------------------------------------------------------------------------------ + + +/** +Make a call to the SMC + +:param: inputStruct Struct that holds data telling the SMC what you want +:param: outputStruct Struct holding the SMC's response +:returns: I/O Kit return code +*/ +static kern_return_t call_smc(SMCParamStruct *inputStruct, + SMCParamStruct *outputStruct) +{ + kern_return_t result; + size_t inputStructCnt = sizeof(SMCParamStruct); + size_t outputStructCnt = sizeof(SMCParamStruct); + + result = IOConnectCallStructMethod(conn, kSMCHandleYPCEvent, + inputStruct, + inputStructCnt, + outputStruct, + &outputStructCnt); + + if (result != kIOReturnSuccess) { + // IOReturn error code lookup. See "Accessing Hardware From Applications + // -> Handling Errors" Apple doc + result = err_get_code(result); + } + + return result; +} + + +/** +Read data from the SMC + +:param: key The SMC key +*/ +static kern_return_t read_smc(char *key, smc_return_t *result_smc) +{ + kern_return_t result; + SMCParamStruct inputStruct; + SMCParamStruct outputStruct; + + memset(&inputStruct, 0, sizeof(SMCParamStruct)); + memset(&outputStruct, 0, sizeof(SMCParamStruct)); + memset(result_smc, 0, sizeof(smc_return_t)); + + // First call to AppleSMC - get key info + inputStruct.key = to_uint32_t(key); + inputStruct.data8 = kSMCGetKeyInfo; + + result = call_smc(&inputStruct, &outputStruct); + result_smc->kSMC = outputStruct.result; + + if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { + return result; + } + + // Store data for return + result_smc->dataSize = outputStruct.keyInfo.dataSize; + result_smc->dataType = outputStruct.keyInfo.dataType; + + + // Second call to AppleSMC - now we can get the data + inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; + inputStruct.data8 = kSMCReadKey; + + result = call_smc(&inputStruct, &outputStruct); + result_smc->kSMC = outputStruct.result; + + if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { + return result; + } + + memcpy(result_smc->data, outputStruct.bytes, sizeof(outputStruct.bytes)); + + return result; +} + + +/** +Write data to the SMC. + +:returns: IOReturn IOKit return code +*/ +static kern_return_t write_smc(char *key, smc_return_t *result_smc) +{ + kern_return_t result; + SMCParamStruct inputStruct; + SMCParamStruct outputStruct; + + memset(&inputStruct, 0, sizeof(SMCParamStruct)); + memset(&outputStruct, 0, sizeof(SMCParamStruct)); + + // First call to AppleSMC - get key info + inputStruct.key = to_uint32_t(key); + inputStruct.data8 = kSMCGetKeyInfo; + + result = call_smc(&inputStruct, &outputStruct); + result_smc->kSMC = outputStruct.result; + + if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { + return result; + } + + // Check data is correct + if (result_smc->dataSize != outputStruct.keyInfo.dataSize || + result_smc->dataType != outputStruct.keyInfo.dataType) { + return kIOReturnBadArgument; + } + + // Second call to AppleSMC - now we can write the data + inputStruct.data8 = kSMCWriteKey; + inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; + + // Set data to write + memcpy(inputStruct.bytes, result_smc->data, sizeof(result_smc->data)); + + result = call_smc(&inputStruct, &outputStruct); + result_smc->kSMC = outputStruct.result; + + return result; +} + + +/** +Get the model name of the machine. +*/ +static kern_return_t get_machine_model(io_name_t model) +{ + io_service_t service; + kern_return_t result; + + service = IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching(IOSERVICE_MODEL)); + + if (service == 0) { + printf("ERROR: %s NOT FOUND\n", IOSERVICE_MODEL); + return kIOReturnError; + } + + // Get the model name + result = IORegistryEntryGetName(service, model); + IOObjectRelease(service); + + return result; +} + + +//------------------------------------------------------------------------------ +// MARK: "PUBLIC" FUNCTIONS +//------------------------------------------------------------------------------ + + +kern_return_t open_smc(void) +{ + kern_return_t result; + io_service_t service; + + service = IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching(IOSERVICE_SMC)); + + if (service == 0) { + // NOTE: IOServiceMatching documents 0 on failure + printf("ERROR: %s NOT FOUND\n", IOSERVICE_SMC); + return kIOReturnError; + } + + result = IOServiceOpen(service, mach_task_self(), 0, &conn); + IOObjectRelease(service); + + return result; +} + + +kern_return_t close_smc(void) +{ + return IOServiceClose(conn); +} + + +bool is_key_valid(char *key) +{ + bool ans = false; + kern_return_t result; + smc_return_t result_smc; + + if (strlen(key) != SMC_KEY_SIZE) { + printf("ERROR: Invalid key size - must be 4 chars\n"); + return ans; + } + + // Try a read and see if it succeeds + result = read_smc(key, &result_smc); + + if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { + ans = true; + } + + return ans; +} + + +double get_tmp(char *key, tmp_unit_t unit) +{ + kern_return_t result; + smc_return_t result_smc; + + result = read_smc(key, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 2 && + result_smc.dataType == to_uint32_t(DATA_TYPE_SP78))) { + // Error + return 0.0; + } + + // TODO: Create from_sp78() convert function + double tmp = result_smc.data[0]; + + switch (unit) { + case CELSIUS: + break; + case FAHRENHEIT: + tmp = to_fahrenheit(tmp); + break; + case KELVIN: + tmp = to_kelvin(tmp); + break; + } + + return tmp; +} + + +bool is_battery_powered(void) +{ + kern_return_t result; + smc_return_t result_smc; + + result = read_smc(BATT_PWR, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 1 && + result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { + // Error + return false; + } + + return result_smc.data[0]; +} + + +bool is_optical_disk_drive_full(void) +{ + kern_return_t result; + smc_return_t result_smc; + + result = read_smc(ODD_FULL, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 1 && + result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { + // Error + return false; + } + + return result_smc.data[0]; +} + + +//------------------------------------------------------------------------------ +// MARK: FAN FUNCTIONS +//------------------------------------------------------------------------------ + + +bool get_fan_name(unsigned int fan_num, fan_name_t name) +{ + char key[5]; + kern_return_t result; + smc_return_t result_smc; + + sprintf(key, "F%dID", fan_num); + result = read_smc(key, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 16 && + result_smc.dataType == to_uint32_t(DATA_TYPE_SFDS))) { + return false; + } + + + /* + We know the data size is 16 bytes and the type is "{fds", a custom + struct defined by the AppleSMC.kext. See TMP enum sources for the + struct. + + The last 12 bytes contain the name of the fan, an array of chars, hence + the loop range. + */ + int index = 0; + for (int i = 4; i < 16; i++) { + // Check if at the end (name may not be full 12 bytes) + // Could check for 0 (null), but instead we check for 32 (space). This + // is a hack to remove whitespace. :) + if (result_smc.data[i] == 32) { + break; + } + + name[index] = result_smc.data[i]; + index++; + } + + return true; +} + + +int get_num_fans(void) +{ + kern_return_t result; + smc_return_t result_smc; + + result = read_smc(NUM_FANS, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 1 && + result_smc.dataType == to_uint32_t(DATA_TYPE_UINT8))) { + // Error + return -1; + } + + return result_smc.data[0]; +} + + +unsigned int get_fan_rpm(unsigned int fan_num) +{ + char key[5]; + kern_return_t result; + smc_return_t result_smc; + + sprintf(key, "F%dAc", fan_num); + result = read_smc(key, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 2 && + result_smc.dataType == to_uint32_t(DATA_TYPE_FPE2))) { + // Error + return 0; + } + + return from_fpe2(result_smc.data); +} + + +bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth) +{ + // TODO: Add rpm val safety check + char key[5]; + bool ans = false; + kern_return_t result; + smc_return_t result_smc; + + memset(&result_smc, 0, sizeof(smc_return_t)); + + // TODO: Don't use magic number + result_smc.dataSize = 2; + result_smc.dataType = to_uint32_t(DATA_TYPE_FPE2); + to_fpe2(rpm, result_smc.data); + + sprintf(key, "F%dMn", fan_num); + result = write_smc(key, &result_smc); + + if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { + ans = true; + } + + return ans; +} diff --git a/host/smc.h b/host/smc.h new file mode 100644 index 0000000..b156368 --- /dev/null +++ b/host/smc.h @@ -0,0 +1,254 @@ +/* + * Apple System Management Controller (SMC) API from user space for Intel based + * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver + * for the SMC. + * + * smc.h + * libsmc + * + * Copyright (C) 2014 beltex + * + * Based off of fork from: + * osx-cpu-temp + * + * With credits to: + * + * Copyright (C) 2006 devnull + * Apple System Management Control (SMC) Tool + * + * Copyright (C) 2006 Hendrik Holtmann + * smcFanControl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + + +//------------------------------------------------------------------------------ +// MARK: MACROS +//------------------------------------------------------------------------------ + + +/** +SMC keys for temperature sensors - 4 byte multi-character constants + +Not applicable to all Mac's of course. In adition, the definition of the codes +may not be 100% accurate necessarily. Finally, list is incomplete. + +Presumed letter translations: + +- T = Temperature (if first char) +- C = CPU +- G = GPU +- P = Proximity +- D = Diode +- H = Heatsink + +Sources: + +- https://www.apple.com/downloads/dashboard/status/istatpro.html +- https://github.com/hholtmann/smcFanControl +- https://github.com/jedda/OSX-Monitoring-Tools +- http://www.parhelia.ch/blog/statics/k3_keys.html +*/ +#define AMBIENT_AIR_0 "TA0P" +#define AMBIENT_AIR_1 "TA1P" +#define CPU_0_DIODE "TC0D" +#define CPU_0_HEATSINK "TC0H" +#define CPU_0_PROXIMITY "TC0P" +#define ENCLOSURE_BASE_0 "TB0T" +#define ENCLOSURE_BASE_1 "TB1T" +#define ENCLOSURE_BASE_2 "TB2T" +#define ENCLOSURE_BASE_3 "TB3T" +#define GPU_0_DIODE "TG0D" +#define GPU_0_HEATSINK "TG0H" +#define GPU_0_PROXIMITY "TG0P" +#define HARD_DRIVE_BAY "TH0P" +#define MEMORY_SLOT_0 "TM0S" +#define MEMORY_SLOTS_PROXIMITY "TM0P" +#define NORTHBRIDGE "TN0H" +#define NORTHBRIDGE_DIODE "TN0D" +#define NORTHBRIDGE_PROXIMITY "TN0P" +#define THUNDERBOLT_0 "TI0P" +#define THUNDERBOLT_1 "TI1P" +#define WIRELESS_MODULE "TW0P" + + +/** +SMC keys for fans - 4 byte multi-character constants + +Number of fans on Macs vary of course, thus not all keys will be applicable. + +Presumed letter translations: + +- F = Fan +- Ac = Acutal +- Mn = Min +- Mx = Max +- Sf = Safe +- Tg = Target + +Sources: See TMP SMC keys +*/ +#define FAN_0 "F0Ac" +#define FAN_0_MIN_RPM "F0Mn" +#define FAN_0_MAX_RPM "F0Mx" +#define FAN_0_SAFE_RPM "F0Sf" +#define FAN_0_TARGET_RPM "F0Tg" +#define FAN_1 "F1Ac" +#define FAN_1_MIN_RPM "F1Mn" +#define FAN_1_MAX_RPM "F1Mx" +#define FAN_1_SAFE_RPM "F1Sf" +#define FAN_1_TARGET_RPM "F1Tg" +#define FAN_2 "F2Ac" +#define FAN_2_MIN_RPM "F2Mn" +#define FAN_2_MAX_RPM "F2Mx" +#define FAN_2_SAFE_RPM "F2Sf" +#define FAN_2_TARGET_RPM "F2Tg" +#define NUM_FANS "FNum" +#define FORCE_BITS "FS! " + + +/** +Misc SMC keys - 4 byte multi-character constants + +Sources: See TMP SMC keys +*/ +#define BATT_PWR "BATP" +#define NUM_KEYS "#KEY" +#define ODD_FULL "MSDI" + + +//------------------------------------------------------------------------------ +// MARK: TYPES +//------------------------------------------------------------------------------ + + +typedef char fan_name_t[13]; + + +//------------------------------------------------------------------------------ +// MARK: ENUMS +//------------------------------------------------------------------------------ + + +typedef enum { + CELSIUS, + FAHRENHEIT, + KELVIN +} tmp_unit_t; + + +//------------------------------------------------------------------------------ +// MARK: PROTOTYPES +//------------------------------------------------------------------------------ + + +/** +Open a connection to the SMC + +:returns: kIOReturnSuccess on successful connection to the SMC. +*/ +kern_return_t open_smc(void); + + +/** +Close connection to the SMC + +:returns: kIOReturnSuccess on successful close of connection to the SMC. +*/ +kern_return_t close_smc(void); + + +/** +Check if an SMC key is valid. Useful for determining if a certain machine has +particular sensor or fan for example. + +:param: key The SMC key to check. 4 byte multi-character constant. Must be 4 + characters in length. +:returns: True if the key is found, false otherwise +*/ +bool is_key_valid(char *key); + + +/** +Get the current temperature from a sensor + +:param: key The temperature sensor to read from +:param: unit The unit for the temperature value. +:returns: Temperature of sensor. If the sensor is not found, or an error + occurs, return will be zero +*/ +double get_tmp(char *key, tmp_unit_t unit); + + +/** +Is the machine being powered by the battery? + +:returns: True if it is, false otherwise +*/ +bool is_battery_powered(void); + + +/** +Is there a CD in the optical disk drive (ODD)? + +:returns: True if there is, false otherwise +*/ +bool is_optical_disk_drive_full(void); + + +/** +Get the name of a fan. + +:param: fanNum The number of the fan to check +:param: name The name of the fan. Return will be empty on error. +:returns: True if successful, false otherwise. +*/ +bool get_fan_name(unsigned int fan_num, fan_name_t name); + + +/** +Get the number of fans on this machine. + +:returns: The number of fans. If an error occurs, return will be -1. +*/ +int get_num_fans(void); + + +/** +Get the current speed (RPM - revolutions per minute) of a fan. + +:param: fan_num The number of the fan to check +:returns: The fan RPM. If the fan is not found, or an error occurs, return + will be zero +*/ +UInt get_fan_rpm(UInt fan_num); + + +/** +Set the minimum speed (RPM - revolutions per minute) of a fan. This method +requires root privileges. By minimum we mean that OS X can interject and +raise the fan speed if needed, however it will not go below this. + +WARNING: You are playing with hardware here, BE CAREFUL. + +:param: fan_num The number of the fan to set +:param: rpm The speed you would like to set the fan to. +:param: auth Should the function do authentication? +:return: True if successful, false otherwise +*/ +bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth); From b8c456e53432efdbba4ee9e55d27f69cb2dcf1ee Mon Sep 17 00:00:00 2001 From: shirou Date: Sat, 9 Dec 2017 16:50:03 +0900 Subject: [PATCH 18/23] Revert "Fix build with bazel on OSX" This reverts commit 51c7c4013b6b2317265bfeee7c21cf2e9872596e. --- host/host_darwin_cgo.go | 2 +- host/include/smc.c | 700 ++++++++++++++++++++++++++++++++++++++++++++++++ host/include/smc.h | 254 ++++++++++++++++++ host/smc.c | 700 ------------------------------------------------ host/smc.h | 254 ------------------ 5 files changed, 955 insertions(+), 955 deletions(-) create mode 100644 host/include/smc.c create mode 100644 host/include/smc.h delete mode 100644 host/smc.c delete mode 100644 host/smc.h diff --git a/host/host_darwin_cgo.go b/host/host_darwin_cgo.go index 9471c3a..be5e18b 100644 --- a/host/host_darwin_cgo.go +++ b/host/host_darwin_cgo.go @@ -4,7 +4,7 @@ package host // #cgo LDFLAGS: -framework IOKit -// #include "smc.h" +// #include "include/smc.c" import "C" func SensorsTemperatures() ([]TemperatureStat, error) { diff --git a/host/include/smc.c b/host/include/smc.c new file mode 100644 index 0000000..30a232b --- /dev/null +++ b/host/include/smc.c @@ -0,0 +1,700 @@ +/* + * Apple System Management Controller (SMC) API from user space for Intel based + * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver + * for the SMC. + * + * smc.c + * libsmc + * + * Copyright (C) 2014 beltex + * + * Based off of fork from: + * osx-cpu-temp + * + * With credits to: + * + * Copyright (C) 2006 devnull + * Apple System Management Control (SMC) Tool + * + * Copyright (C) 2006 Hendrik Holtmann + * smcFanControl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include "smc.h" + + +//------------------------------------------------------------------------------ +// MARK: MACROS +//------------------------------------------------------------------------------ + + +/** +Name of the SMC IOService as seen in the IORegistry. You can view it either via +command line with ioreg or through the IORegistryExplorer app (found on Apple's +developer site - Hardware IO Tools for Xcode) +*/ +#define IOSERVICE_SMC "AppleSMC" + + +/** +IOService for getting machine model name +*/ +#define IOSERVICE_MODEL "IOPlatformExpertDevice" + + +/** +SMC data types - 4 byte multi-character constants + +Sources: See TMP SMC keys in smc.h + +http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types +*/ +#define DATA_TYPE_UINT8 "ui8 " +#define DATA_TYPE_UINT16 "ui16" +#define DATA_TYPE_UINT32 "ui32" +#define DATA_TYPE_FLAG "flag" +#define DATA_TYPE_FPE2 "fpe2" +#define DATA_TYPE_SFDS "{fds" +#define DATA_TYPE_SP78 "sp78" + + +//------------------------------------------------------------------------------ +// MARK: GLOBAL VARS +//------------------------------------------------------------------------------ + + +/** +Our connection to the SMC +*/ +static io_connect_t conn; + + +/** +Number of characters in an SMC key +*/ +static const int SMC_KEY_SIZE = 4; + + +/** +Number of characters in a data type "key" returned from the SMC. See data type +macros. +*/ +static const int DATA_TYPE_SIZE = 4; + + +//------------------------------------------------------------------------------ +// MARK: ENUMS +//------------------------------------------------------------------------------ + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. + +These are SMC specific return codes +*/ +typedef enum { + kSMCSuccess = 0, + kSMCError = 1, + kSMCKeyNotFound = 0x84 +} kSMC_t; + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. + +Function selectors. Used to tell the SMC which function inside it to call. +*/ +typedef enum { + kSMCUserClientOpen = 0, + kSMCUserClientClose = 1, + kSMCHandleYPCEvent = 2, + kSMCReadKey = 5, + kSMCWriteKey = 6, + kSMCGetKeyCount = 7, + kSMCGetKeyFromIndex = 8, + kSMCGetKeyInfo = 9 +} selector_t; + + +//------------------------------------------------------------------------------ +// MARK: STRUCTS +//------------------------------------------------------------------------------ + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. +*/ +typedef struct { + unsigned char major; + unsigned char minor; + unsigned char build; + unsigned char reserved; + unsigned short release; +} SMCVersion; + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. +*/ +typedef struct { + uint16_t version; + uint16_t length; + uint32_t cpuPLimit; + uint32_t gpuPLimit; + uint32_t memPLimit; +} SMCPLimitData; + + +/** +Defined by AppleSMC.kext. See SMCParamStruct. + +- dataSize : How many values written to SMCParamStruct.bytes +- dataType : Type of data written to SMCParamStruct.bytes. This lets us know how + to interpret it (translate it to human readable) +*/ +typedef struct { + IOByteCount dataSize; + uint32_t dataType; + uint8_t dataAttributes; +} SMCKeyInfoData; + + +/** +Defined by AppleSMC.kext. + +This is the predefined struct that must be passed to communicate with the +AppleSMC driver. While the driver is closed source, the definition of this +struct happened to appear in the Apple PowerManagement project at around +version 211, and soon after disappeared. It can be seen in the PrivateLib.c +file under pmconfigd. + +https://www.opensource.apple.com/source/PowerManagement/PowerManagement-211/ +*/ +typedef struct { + uint32_t key; + SMCVersion vers; + SMCPLimitData pLimitData; + SMCKeyInfoData keyInfo; + uint8_t result; + uint8_t status; + uint8_t data8; + uint32_t data32; + uint8_t bytes[32]; +} SMCParamStruct; + + +/** +Used for returning data from the SMC. +*/ +typedef struct { + uint8_t data[32]; + uint32_t dataType; + uint32_t dataSize; + kSMC_t kSMC; +} smc_return_t; + + +//------------------------------------------------------------------------------ +// MARK: HELPERS - TYPE CONVERSION +//------------------------------------------------------------------------------ + + +/** +Convert data from SMC of fpe2 type to human readable. + +:param: data Data from the SMC to be converted. Assumed data size of 2. +:returns: Converted data +*/ +static unsigned int from_fpe2(uint8_t data[32]) +{ + unsigned int ans = 0; + + // Data type for fan calls - fpe2 + // This is assumend to mean floating point, with 2 exponent bits + // http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types + ans += data[0] << 6; + ans += data[1] << 2; + + return ans; +} + + +/** +Convert to fpe2 data type to be passed to SMC. + +:param: val Value to convert +:param: data Pointer to data array to place result +*/ +static void to_fpe2(unsigned int val, uint8_t *data) +{ + data[0] = val >> 6; + data[1] = (val << 2) ^ (data[0] << 8); +} + + +/** +Convert SMC key to uint32_t. This must be done to pass it to the SMC. + +:param: key The SMC key to convert +:returns: uint32_t translation. + Returns zero if key is not 4 characters in length. +*/ +static uint32_t to_uint32_t(char *key) +{ + uint32_t ans = 0; + uint32_t shift = 24; + + // SMC key is expected to be 4 bytes - thus 4 chars + if (strlen(key) != SMC_KEY_SIZE) { + return 0; + } + + for (int i = 0; i < SMC_KEY_SIZE; i++) { + ans += key[i] << shift; + shift -= 8; + } + + return ans; +} + + +/** +For converting the dataType return from the SMC to human readable 4 byte +multi-character constant. +*/ +static void to_string(uint32_t val, char *dataType) +{ + int shift = 24; + + for (int i = 0; i < DATA_TYPE_SIZE; i++) { + // To get each char, we shift it into the lower 8 bits, and then & by + // 255 to insolate it + dataType[i] = (val >> shift) & 0xff; + shift -= 8; + } +} + + +//------------------------------------------------------------------------------ +// MARK: HELPERS - TMP CONVERSION +//------------------------------------------------------------------------------ + + +/** +Celsius to Fahrenheit +*/ +static double to_fahrenheit(double tmp) +{ + // http://en.wikipedia.org/wiki/Fahrenheit#Definition_and_conversions + return (tmp * 1.8) + 32; +} + + +/** +Celsius to Kelvin +*/ +static double to_kelvin(double tmp) +{ + // http://en.wikipedia.org/wiki/Kelvin + return tmp + 273.15; +} + + +//------------------------------------------------------------------------------ +// MARK: "PRIVATE" FUNCTIONS +//------------------------------------------------------------------------------ + + +/** +Make a call to the SMC + +:param: inputStruct Struct that holds data telling the SMC what you want +:param: outputStruct Struct holding the SMC's response +:returns: I/O Kit return code +*/ +static kern_return_t call_smc(SMCParamStruct *inputStruct, + SMCParamStruct *outputStruct) +{ + kern_return_t result; + size_t inputStructCnt = sizeof(SMCParamStruct); + size_t outputStructCnt = sizeof(SMCParamStruct); + + result = IOConnectCallStructMethod(conn, kSMCHandleYPCEvent, + inputStruct, + inputStructCnt, + outputStruct, + &outputStructCnt); + + if (result != kIOReturnSuccess) { + // IOReturn error code lookup. See "Accessing Hardware From Applications + // -> Handling Errors" Apple doc + result = err_get_code(result); + } + + return result; +} + + +/** +Read data from the SMC + +:param: key The SMC key +*/ +static kern_return_t read_smc(char *key, smc_return_t *result_smc) +{ + kern_return_t result; + SMCParamStruct inputStruct; + SMCParamStruct outputStruct; + + memset(&inputStruct, 0, sizeof(SMCParamStruct)); + memset(&outputStruct, 0, sizeof(SMCParamStruct)); + memset(result_smc, 0, sizeof(smc_return_t)); + + // First call to AppleSMC - get key info + inputStruct.key = to_uint32_t(key); + inputStruct.data8 = kSMCGetKeyInfo; + + result = call_smc(&inputStruct, &outputStruct); + result_smc->kSMC = outputStruct.result; + + if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { + return result; + } + + // Store data for return + result_smc->dataSize = outputStruct.keyInfo.dataSize; + result_smc->dataType = outputStruct.keyInfo.dataType; + + + // Second call to AppleSMC - now we can get the data + inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; + inputStruct.data8 = kSMCReadKey; + + result = call_smc(&inputStruct, &outputStruct); + result_smc->kSMC = outputStruct.result; + + if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { + return result; + } + + memcpy(result_smc->data, outputStruct.bytes, sizeof(outputStruct.bytes)); + + return result; +} + + +/** +Write data to the SMC. + +:returns: IOReturn IOKit return code +*/ +static kern_return_t write_smc(char *key, smc_return_t *result_smc) +{ + kern_return_t result; + SMCParamStruct inputStruct; + SMCParamStruct outputStruct; + + memset(&inputStruct, 0, sizeof(SMCParamStruct)); + memset(&outputStruct, 0, sizeof(SMCParamStruct)); + + // First call to AppleSMC - get key info + inputStruct.key = to_uint32_t(key); + inputStruct.data8 = kSMCGetKeyInfo; + + result = call_smc(&inputStruct, &outputStruct); + result_smc->kSMC = outputStruct.result; + + if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { + return result; + } + + // Check data is correct + if (result_smc->dataSize != outputStruct.keyInfo.dataSize || + result_smc->dataType != outputStruct.keyInfo.dataType) { + return kIOReturnBadArgument; + } + + // Second call to AppleSMC - now we can write the data + inputStruct.data8 = kSMCWriteKey; + inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; + + // Set data to write + memcpy(inputStruct.bytes, result_smc->data, sizeof(result_smc->data)); + + result = call_smc(&inputStruct, &outputStruct); + result_smc->kSMC = outputStruct.result; + + return result; +} + + +/** +Get the model name of the machine. +*/ +static kern_return_t get_machine_model(io_name_t model) +{ + io_service_t service; + kern_return_t result; + + service = IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching(IOSERVICE_MODEL)); + + if (service == 0) { + printf("ERROR: %s NOT FOUND\n", IOSERVICE_MODEL); + return kIOReturnError; + } + + // Get the model name + result = IORegistryEntryGetName(service, model); + IOObjectRelease(service); + + return result; +} + + +//------------------------------------------------------------------------------ +// MARK: "PUBLIC" FUNCTIONS +//------------------------------------------------------------------------------ + + +kern_return_t open_smc(void) +{ + kern_return_t result; + io_service_t service; + + service = IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching(IOSERVICE_SMC)); + + if (service == 0) { + // NOTE: IOServiceMatching documents 0 on failure + printf("ERROR: %s NOT FOUND\n", IOSERVICE_SMC); + return kIOReturnError; + } + + result = IOServiceOpen(service, mach_task_self(), 0, &conn); + IOObjectRelease(service); + + return result; +} + + +kern_return_t close_smc(void) +{ + return IOServiceClose(conn); +} + + +bool is_key_valid(char *key) +{ + bool ans = false; + kern_return_t result; + smc_return_t result_smc; + + if (strlen(key) != SMC_KEY_SIZE) { + printf("ERROR: Invalid key size - must be 4 chars\n"); + return ans; + } + + // Try a read and see if it succeeds + result = read_smc(key, &result_smc); + + if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { + ans = true; + } + + return ans; +} + + +double get_tmp(char *key, tmp_unit_t unit) +{ + kern_return_t result; + smc_return_t result_smc; + + result = read_smc(key, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 2 && + result_smc.dataType == to_uint32_t(DATA_TYPE_SP78))) { + // Error + return 0.0; + } + + // TODO: Create from_sp78() convert function + double tmp = result_smc.data[0]; + + switch (unit) { + case CELSIUS: + break; + case FAHRENHEIT: + tmp = to_fahrenheit(tmp); + break; + case KELVIN: + tmp = to_kelvin(tmp); + break; + } + + return tmp; +} + + +bool is_battery_powered(void) +{ + kern_return_t result; + smc_return_t result_smc; + + result = read_smc(BATT_PWR, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 1 && + result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { + // Error + return false; + } + + return result_smc.data[0]; +} + + +bool is_optical_disk_drive_full(void) +{ + kern_return_t result; + smc_return_t result_smc; + + result = read_smc(ODD_FULL, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 1 && + result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { + // Error + return false; + } + + return result_smc.data[0]; +} + + +//------------------------------------------------------------------------------ +// MARK: FAN FUNCTIONS +//------------------------------------------------------------------------------ + + +bool get_fan_name(unsigned int fan_num, fan_name_t name) +{ + char key[5]; + kern_return_t result; + smc_return_t result_smc; + + sprintf(key, "F%dID", fan_num); + result = read_smc(key, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 16 && + result_smc.dataType == to_uint32_t(DATA_TYPE_SFDS))) { + return false; + } + + + /* + We know the data size is 16 bytes and the type is "{fds", a custom + struct defined by the AppleSMC.kext. See TMP enum sources for the + struct. + + The last 12 bytes contain the name of the fan, an array of chars, hence + the loop range. + */ + int index = 0; + for (int i = 4; i < 16; i++) { + // Check if at the end (name may not be full 12 bytes) + // Could check for 0 (null), but instead we check for 32 (space). This + // is a hack to remove whitespace. :) + if (result_smc.data[i] == 32) { + break; + } + + name[index] = result_smc.data[i]; + index++; + } + + return true; +} + + +int get_num_fans(void) +{ + kern_return_t result; + smc_return_t result_smc; + + result = read_smc(NUM_FANS, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 1 && + result_smc.dataType == to_uint32_t(DATA_TYPE_UINT8))) { + // Error + return -1; + } + + return result_smc.data[0]; +} + + +unsigned int get_fan_rpm(unsigned int fan_num) +{ + char key[5]; + kern_return_t result; + smc_return_t result_smc; + + sprintf(key, "F%dAc", fan_num); + result = read_smc(key, &result_smc); + + if (!(result == kIOReturnSuccess && + result_smc.dataSize == 2 && + result_smc.dataType == to_uint32_t(DATA_TYPE_FPE2))) { + // Error + return 0; + } + + return from_fpe2(result_smc.data); +} + + +bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth) +{ + // TODO: Add rpm val safety check + char key[5]; + bool ans = false; + kern_return_t result; + smc_return_t result_smc; + + memset(&result_smc, 0, sizeof(smc_return_t)); + + // TODO: Don't use magic number + result_smc.dataSize = 2; + result_smc.dataType = to_uint32_t(DATA_TYPE_FPE2); + to_fpe2(rpm, result_smc.data); + + sprintf(key, "F%dMn", fan_num); + result = write_smc(key, &result_smc); + + if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { + ans = true; + } + + return ans; +} diff --git a/host/include/smc.h b/host/include/smc.h new file mode 100644 index 0000000..b156368 --- /dev/null +++ b/host/include/smc.h @@ -0,0 +1,254 @@ +/* + * Apple System Management Controller (SMC) API from user space for Intel based + * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver + * for the SMC. + * + * smc.h + * libsmc + * + * Copyright (C) 2014 beltex + * + * Based off of fork from: + * osx-cpu-temp + * + * With credits to: + * + * Copyright (C) 2006 devnull + * Apple System Management Control (SMC) Tool + * + * Copyright (C) 2006 Hendrik Holtmann + * smcFanControl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + + +//------------------------------------------------------------------------------ +// MARK: MACROS +//------------------------------------------------------------------------------ + + +/** +SMC keys for temperature sensors - 4 byte multi-character constants + +Not applicable to all Mac's of course. In adition, the definition of the codes +may not be 100% accurate necessarily. Finally, list is incomplete. + +Presumed letter translations: + +- T = Temperature (if first char) +- C = CPU +- G = GPU +- P = Proximity +- D = Diode +- H = Heatsink + +Sources: + +- https://www.apple.com/downloads/dashboard/status/istatpro.html +- https://github.com/hholtmann/smcFanControl +- https://github.com/jedda/OSX-Monitoring-Tools +- http://www.parhelia.ch/blog/statics/k3_keys.html +*/ +#define AMBIENT_AIR_0 "TA0P" +#define AMBIENT_AIR_1 "TA1P" +#define CPU_0_DIODE "TC0D" +#define CPU_0_HEATSINK "TC0H" +#define CPU_0_PROXIMITY "TC0P" +#define ENCLOSURE_BASE_0 "TB0T" +#define ENCLOSURE_BASE_1 "TB1T" +#define ENCLOSURE_BASE_2 "TB2T" +#define ENCLOSURE_BASE_3 "TB3T" +#define GPU_0_DIODE "TG0D" +#define GPU_0_HEATSINK "TG0H" +#define GPU_0_PROXIMITY "TG0P" +#define HARD_DRIVE_BAY "TH0P" +#define MEMORY_SLOT_0 "TM0S" +#define MEMORY_SLOTS_PROXIMITY "TM0P" +#define NORTHBRIDGE "TN0H" +#define NORTHBRIDGE_DIODE "TN0D" +#define NORTHBRIDGE_PROXIMITY "TN0P" +#define THUNDERBOLT_0 "TI0P" +#define THUNDERBOLT_1 "TI1P" +#define WIRELESS_MODULE "TW0P" + + +/** +SMC keys for fans - 4 byte multi-character constants + +Number of fans on Macs vary of course, thus not all keys will be applicable. + +Presumed letter translations: + +- F = Fan +- Ac = Acutal +- Mn = Min +- Mx = Max +- Sf = Safe +- Tg = Target + +Sources: See TMP SMC keys +*/ +#define FAN_0 "F0Ac" +#define FAN_0_MIN_RPM "F0Mn" +#define FAN_0_MAX_RPM "F0Mx" +#define FAN_0_SAFE_RPM "F0Sf" +#define FAN_0_TARGET_RPM "F0Tg" +#define FAN_1 "F1Ac" +#define FAN_1_MIN_RPM "F1Mn" +#define FAN_1_MAX_RPM "F1Mx" +#define FAN_1_SAFE_RPM "F1Sf" +#define FAN_1_TARGET_RPM "F1Tg" +#define FAN_2 "F2Ac" +#define FAN_2_MIN_RPM "F2Mn" +#define FAN_2_MAX_RPM "F2Mx" +#define FAN_2_SAFE_RPM "F2Sf" +#define FAN_2_TARGET_RPM "F2Tg" +#define NUM_FANS "FNum" +#define FORCE_BITS "FS! " + + +/** +Misc SMC keys - 4 byte multi-character constants + +Sources: See TMP SMC keys +*/ +#define BATT_PWR "BATP" +#define NUM_KEYS "#KEY" +#define ODD_FULL "MSDI" + + +//------------------------------------------------------------------------------ +// MARK: TYPES +//------------------------------------------------------------------------------ + + +typedef char fan_name_t[13]; + + +//------------------------------------------------------------------------------ +// MARK: ENUMS +//------------------------------------------------------------------------------ + + +typedef enum { + CELSIUS, + FAHRENHEIT, + KELVIN +} tmp_unit_t; + + +//------------------------------------------------------------------------------ +// MARK: PROTOTYPES +//------------------------------------------------------------------------------ + + +/** +Open a connection to the SMC + +:returns: kIOReturnSuccess on successful connection to the SMC. +*/ +kern_return_t open_smc(void); + + +/** +Close connection to the SMC + +:returns: kIOReturnSuccess on successful close of connection to the SMC. +*/ +kern_return_t close_smc(void); + + +/** +Check if an SMC key is valid. Useful for determining if a certain machine has +particular sensor or fan for example. + +:param: key The SMC key to check. 4 byte multi-character constant. Must be 4 + characters in length. +:returns: True if the key is found, false otherwise +*/ +bool is_key_valid(char *key); + + +/** +Get the current temperature from a sensor + +:param: key The temperature sensor to read from +:param: unit The unit for the temperature value. +:returns: Temperature of sensor. If the sensor is not found, or an error + occurs, return will be zero +*/ +double get_tmp(char *key, tmp_unit_t unit); + + +/** +Is the machine being powered by the battery? + +:returns: True if it is, false otherwise +*/ +bool is_battery_powered(void); + + +/** +Is there a CD in the optical disk drive (ODD)? + +:returns: True if there is, false otherwise +*/ +bool is_optical_disk_drive_full(void); + + +/** +Get the name of a fan. + +:param: fanNum The number of the fan to check +:param: name The name of the fan. Return will be empty on error. +:returns: True if successful, false otherwise. +*/ +bool get_fan_name(unsigned int fan_num, fan_name_t name); + + +/** +Get the number of fans on this machine. + +:returns: The number of fans. If an error occurs, return will be -1. +*/ +int get_num_fans(void); + + +/** +Get the current speed (RPM - revolutions per minute) of a fan. + +:param: fan_num The number of the fan to check +:returns: The fan RPM. If the fan is not found, or an error occurs, return + will be zero +*/ +UInt get_fan_rpm(UInt fan_num); + + +/** +Set the minimum speed (RPM - revolutions per minute) of a fan. This method +requires root privileges. By minimum we mean that OS X can interject and +raise the fan speed if needed, however it will not go below this. + +WARNING: You are playing with hardware here, BE CAREFUL. + +:param: fan_num The number of the fan to set +:param: rpm The speed you would like to set the fan to. +:param: auth Should the function do authentication? +:return: True if successful, false otherwise +*/ +bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth); diff --git a/host/smc.c b/host/smc.c deleted file mode 100644 index 30a232b..0000000 --- a/host/smc.c +++ /dev/null @@ -1,700 +0,0 @@ -/* - * Apple System Management Controller (SMC) API from user space for Intel based - * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver - * for the SMC. - * - * smc.c - * libsmc - * - * Copyright (C) 2014 beltex - * - * Based off of fork from: - * osx-cpu-temp - * - * With credits to: - * - * Copyright (C) 2006 devnull - * Apple System Management Control (SMC) Tool - * - * Copyright (C) 2006 Hendrik Holtmann - * smcFanControl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include "smc.h" - - -//------------------------------------------------------------------------------ -// MARK: MACROS -//------------------------------------------------------------------------------ - - -/** -Name of the SMC IOService as seen in the IORegistry. You can view it either via -command line with ioreg or through the IORegistryExplorer app (found on Apple's -developer site - Hardware IO Tools for Xcode) -*/ -#define IOSERVICE_SMC "AppleSMC" - - -/** -IOService for getting machine model name -*/ -#define IOSERVICE_MODEL "IOPlatformExpertDevice" - - -/** -SMC data types - 4 byte multi-character constants - -Sources: See TMP SMC keys in smc.h - -http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types -*/ -#define DATA_TYPE_UINT8 "ui8 " -#define DATA_TYPE_UINT16 "ui16" -#define DATA_TYPE_UINT32 "ui32" -#define DATA_TYPE_FLAG "flag" -#define DATA_TYPE_FPE2 "fpe2" -#define DATA_TYPE_SFDS "{fds" -#define DATA_TYPE_SP78 "sp78" - - -//------------------------------------------------------------------------------ -// MARK: GLOBAL VARS -//------------------------------------------------------------------------------ - - -/** -Our connection to the SMC -*/ -static io_connect_t conn; - - -/** -Number of characters in an SMC key -*/ -static const int SMC_KEY_SIZE = 4; - - -/** -Number of characters in a data type "key" returned from the SMC. See data type -macros. -*/ -static const int DATA_TYPE_SIZE = 4; - - -//------------------------------------------------------------------------------ -// MARK: ENUMS -//------------------------------------------------------------------------------ - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. - -These are SMC specific return codes -*/ -typedef enum { - kSMCSuccess = 0, - kSMCError = 1, - kSMCKeyNotFound = 0x84 -} kSMC_t; - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. - -Function selectors. Used to tell the SMC which function inside it to call. -*/ -typedef enum { - kSMCUserClientOpen = 0, - kSMCUserClientClose = 1, - kSMCHandleYPCEvent = 2, - kSMCReadKey = 5, - kSMCWriteKey = 6, - kSMCGetKeyCount = 7, - kSMCGetKeyFromIndex = 8, - kSMCGetKeyInfo = 9 -} selector_t; - - -//------------------------------------------------------------------------------ -// MARK: STRUCTS -//------------------------------------------------------------------------------ - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. -*/ -typedef struct { - unsigned char major; - unsigned char minor; - unsigned char build; - unsigned char reserved; - unsigned short release; -} SMCVersion; - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. -*/ -typedef struct { - uint16_t version; - uint16_t length; - uint32_t cpuPLimit; - uint32_t gpuPLimit; - uint32_t memPLimit; -} SMCPLimitData; - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. - -- dataSize : How many values written to SMCParamStruct.bytes -- dataType : Type of data written to SMCParamStruct.bytes. This lets us know how - to interpret it (translate it to human readable) -*/ -typedef struct { - IOByteCount dataSize; - uint32_t dataType; - uint8_t dataAttributes; -} SMCKeyInfoData; - - -/** -Defined by AppleSMC.kext. - -This is the predefined struct that must be passed to communicate with the -AppleSMC driver. While the driver is closed source, the definition of this -struct happened to appear in the Apple PowerManagement project at around -version 211, and soon after disappeared. It can be seen in the PrivateLib.c -file under pmconfigd. - -https://www.opensource.apple.com/source/PowerManagement/PowerManagement-211/ -*/ -typedef struct { - uint32_t key; - SMCVersion vers; - SMCPLimitData pLimitData; - SMCKeyInfoData keyInfo; - uint8_t result; - uint8_t status; - uint8_t data8; - uint32_t data32; - uint8_t bytes[32]; -} SMCParamStruct; - - -/** -Used for returning data from the SMC. -*/ -typedef struct { - uint8_t data[32]; - uint32_t dataType; - uint32_t dataSize; - kSMC_t kSMC; -} smc_return_t; - - -//------------------------------------------------------------------------------ -// MARK: HELPERS - TYPE CONVERSION -//------------------------------------------------------------------------------ - - -/** -Convert data from SMC of fpe2 type to human readable. - -:param: data Data from the SMC to be converted. Assumed data size of 2. -:returns: Converted data -*/ -static unsigned int from_fpe2(uint8_t data[32]) -{ - unsigned int ans = 0; - - // Data type for fan calls - fpe2 - // This is assumend to mean floating point, with 2 exponent bits - // http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types - ans += data[0] << 6; - ans += data[1] << 2; - - return ans; -} - - -/** -Convert to fpe2 data type to be passed to SMC. - -:param: val Value to convert -:param: data Pointer to data array to place result -*/ -static void to_fpe2(unsigned int val, uint8_t *data) -{ - data[0] = val >> 6; - data[1] = (val << 2) ^ (data[0] << 8); -} - - -/** -Convert SMC key to uint32_t. This must be done to pass it to the SMC. - -:param: key The SMC key to convert -:returns: uint32_t translation. - Returns zero if key is not 4 characters in length. -*/ -static uint32_t to_uint32_t(char *key) -{ - uint32_t ans = 0; - uint32_t shift = 24; - - // SMC key is expected to be 4 bytes - thus 4 chars - if (strlen(key) != SMC_KEY_SIZE) { - return 0; - } - - for (int i = 0; i < SMC_KEY_SIZE; i++) { - ans += key[i] << shift; - shift -= 8; - } - - return ans; -} - - -/** -For converting the dataType return from the SMC to human readable 4 byte -multi-character constant. -*/ -static void to_string(uint32_t val, char *dataType) -{ - int shift = 24; - - for (int i = 0; i < DATA_TYPE_SIZE; i++) { - // To get each char, we shift it into the lower 8 bits, and then & by - // 255 to insolate it - dataType[i] = (val >> shift) & 0xff; - shift -= 8; - } -} - - -//------------------------------------------------------------------------------ -// MARK: HELPERS - TMP CONVERSION -//------------------------------------------------------------------------------ - - -/** -Celsius to Fahrenheit -*/ -static double to_fahrenheit(double tmp) -{ - // http://en.wikipedia.org/wiki/Fahrenheit#Definition_and_conversions - return (tmp * 1.8) + 32; -} - - -/** -Celsius to Kelvin -*/ -static double to_kelvin(double tmp) -{ - // http://en.wikipedia.org/wiki/Kelvin - return tmp + 273.15; -} - - -//------------------------------------------------------------------------------ -// MARK: "PRIVATE" FUNCTIONS -//------------------------------------------------------------------------------ - - -/** -Make a call to the SMC - -:param: inputStruct Struct that holds data telling the SMC what you want -:param: outputStruct Struct holding the SMC's response -:returns: I/O Kit return code -*/ -static kern_return_t call_smc(SMCParamStruct *inputStruct, - SMCParamStruct *outputStruct) -{ - kern_return_t result; - size_t inputStructCnt = sizeof(SMCParamStruct); - size_t outputStructCnt = sizeof(SMCParamStruct); - - result = IOConnectCallStructMethod(conn, kSMCHandleYPCEvent, - inputStruct, - inputStructCnt, - outputStruct, - &outputStructCnt); - - if (result != kIOReturnSuccess) { - // IOReturn error code lookup. See "Accessing Hardware From Applications - // -> Handling Errors" Apple doc - result = err_get_code(result); - } - - return result; -} - - -/** -Read data from the SMC - -:param: key The SMC key -*/ -static kern_return_t read_smc(char *key, smc_return_t *result_smc) -{ - kern_return_t result; - SMCParamStruct inputStruct; - SMCParamStruct outputStruct; - - memset(&inputStruct, 0, sizeof(SMCParamStruct)); - memset(&outputStruct, 0, sizeof(SMCParamStruct)); - memset(result_smc, 0, sizeof(smc_return_t)); - - // First call to AppleSMC - get key info - inputStruct.key = to_uint32_t(key); - inputStruct.data8 = kSMCGetKeyInfo; - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { - return result; - } - - // Store data for return - result_smc->dataSize = outputStruct.keyInfo.dataSize; - result_smc->dataType = outputStruct.keyInfo.dataType; - - - // Second call to AppleSMC - now we can get the data - inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; - inputStruct.data8 = kSMCReadKey; - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { - return result; - } - - memcpy(result_smc->data, outputStruct.bytes, sizeof(outputStruct.bytes)); - - return result; -} - - -/** -Write data to the SMC. - -:returns: IOReturn IOKit return code -*/ -static kern_return_t write_smc(char *key, smc_return_t *result_smc) -{ - kern_return_t result; - SMCParamStruct inputStruct; - SMCParamStruct outputStruct; - - memset(&inputStruct, 0, sizeof(SMCParamStruct)); - memset(&outputStruct, 0, sizeof(SMCParamStruct)); - - // First call to AppleSMC - get key info - inputStruct.key = to_uint32_t(key); - inputStruct.data8 = kSMCGetKeyInfo; - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { - return result; - } - - // Check data is correct - if (result_smc->dataSize != outputStruct.keyInfo.dataSize || - result_smc->dataType != outputStruct.keyInfo.dataType) { - return kIOReturnBadArgument; - } - - // Second call to AppleSMC - now we can write the data - inputStruct.data8 = kSMCWriteKey; - inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; - - // Set data to write - memcpy(inputStruct.bytes, result_smc->data, sizeof(result_smc->data)); - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - return result; -} - - -/** -Get the model name of the machine. -*/ -static kern_return_t get_machine_model(io_name_t model) -{ - io_service_t service; - kern_return_t result; - - service = IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching(IOSERVICE_MODEL)); - - if (service == 0) { - printf("ERROR: %s NOT FOUND\n", IOSERVICE_MODEL); - return kIOReturnError; - } - - // Get the model name - result = IORegistryEntryGetName(service, model); - IOObjectRelease(service); - - return result; -} - - -//------------------------------------------------------------------------------ -// MARK: "PUBLIC" FUNCTIONS -//------------------------------------------------------------------------------ - - -kern_return_t open_smc(void) -{ - kern_return_t result; - io_service_t service; - - service = IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching(IOSERVICE_SMC)); - - if (service == 0) { - // NOTE: IOServiceMatching documents 0 on failure - printf("ERROR: %s NOT FOUND\n", IOSERVICE_SMC); - return kIOReturnError; - } - - result = IOServiceOpen(service, mach_task_self(), 0, &conn); - IOObjectRelease(service); - - return result; -} - - -kern_return_t close_smc(void) -{ - return IOServiceClose(conn); -} - - -bool is_key_valid(char *key) -{ - bool ans = false; - kern_return_t result; - smc_return_t result_smc; - - if (strlen(key) != SMC_KEY_SIZE) { - printf("ERROR: Invalid key size - must be 4 chars\n"); - return ans; - } - - // Try a read and see if it succeeds - result = read_smc(key, &result_smc); - - if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { - ans = true; - } - - return ans; -} - - -double get_tmp(char *key, tmp_unit_t unit) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 2 && - result_smc.dataType == to_uint32_t(DATA_TYPE_SP78))) { - // Error - return 0.0; - } - - // TODO: Create from_sp78() convert function - double tmp = result_smc.data[0]; - - switch (unit) { - case CELSIUS: - break; - case FAHRENHEIT: - tmp = to_fahrenheit(tmp); - break; - case KELVIN: - tmp = to_kelvin(tmp); - break; - } - - return tmp; -} - - -bool is_battery_powered(void) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(BATT_PWR, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 1 && - result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { - // Error - return false; - } - - return result_smc.data[0]; -} - - -bool is_optical_disk_drive_full(void) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(ODD_FULL, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 1 && - result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { - // Error - return false; - } - - return result_smc.data[0]; -} - - -//------------------------------------------------------------------------------ -// MARK: FAN FUNCTIONS -//------------------------------------------------------------------------------ - - -bool get_fan_name(unsigned int fan_num, fan_name_t name) -{ - char key[5]; - kern_return_t result; - smc_return_t result_smc; - - sprintf(key, "F%dID", fan_num); - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 16 && - result_smc.dataType == to_uint32_t(DATA_TYPE_SFDS))) { - return false; - } - - - /* - We know the data size is 16 bytes and the type is "{fds", a custom - struct defined by the AppleSMC.kext. See TMP enum sources for the - struct. - - The last 12 bytes contain the name of the fan, an array of chars, hence - the loop range. - */ - int index = 0; - for (int i = 4; i < 16; i++) { - // Check if at the end (name may not be full 12 bytes) - // Could check for 0 (null), but instead we check for 32 (space). This - // is a hack to remove whitespace. :) - if (result_smc.data[i] == 32) { - break; - } - - name[index] = result_smc.data[i]; - index++; - } - - return true; -} - - -int get_num_fans(void) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(NUM_FANS, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 1 && - result_smc.dataType == to_uint32_t(DATA_TYPE_UINT8))) { - // Error - return -1; - } - - return result_smc.data[0]; -} - - -unsigned int get_fan_rpm(unsigned int fan_num) -{ - char key[5]; - kern_return_t result; - smc_return_t result_smc; - - sprintf(key, "F%dAc", fan_num); - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 2 && - result_smc.dataType == to_uint32_t(DATA_TYPE_FPE2))) { - // Error - return 0; - } - - return from_fpe2(result_smc.data); -} - - -bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth) -{ - // TODO: Add rpm val safety check - char key[5]; - bool ans = false; - kern_return_t result; - smc_return_t result_smc; - - memset(&result_smc, 0, sizeof(smc_return_t)); - - // TODO: Don't use magic number - result_smc.dataSize = 2; - result_smc.dataType = to_uint32_t(DATA_TYPE_FPE2); - to_fpe2(rpm, result_smc.data); - - sprintf(key, "F%dMn", fan_num); - result = write_smc(key, &result_smc); - - if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { - ans = true; - } - - return ans; -} diff --git a/host/smc.h b/host/smc.h deleted file mode 100644 index b156368..0000000 --- a/host/smc.h +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Apple System Management Controller (SMC) API from user space for Intel based - * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver - * for the SMC. - * - * smc.h - * libsmc - * - * Copyright (C) 2014 beltex - * - * Based off of fork from: - * osx-cpu-temp - * - * With credits to: - * - * Copyright (C) 2006 devnull - * Apple System Management Control (SMC) Tool - * - * Copyright (C) 2006 Hendrik Holtmann - * smcFanControl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include - - -//------------------------------------------------------------------------------ -// MARK: MACROS -//------------------------------------------------------------------------------ - - -/** -SMC keys for temperature sensors - 4 byte multi-character constants - -Not applicable to all Mac's of course. In adition, the definition of the codes -may not be 100% accurate necessarily. Finally, list is incomplete. - -Presumed letter translations: - -- T = Temperature (if first char) -- C = CPU -- G = GPU -- P = Proximity -- D = Diode -- H = Heatsink - -Sources: - -- https://www.apple.com/downloads/dashboard/status/istatpro.html -- https://github.com/hholtmann/smcFanControl -- https://github.com/jedda/OSX-Monitoring-Tools -- http://www.parhelia.ch/blog/statics/k3_keys.html -*/ -#define AMBIENT_AIR_0 "TA0P" -#define AMBIENT_AIR_1 "TA1P" -#define CPU_0_DIODE "TC0D" -#define CPU_0_HEATSINK "TC0H" -#define CPU_0_PROXIMITY "TC0P" -#define ENCLOSURE_BASE_0 "TB0T" -#define ENCLOSURE_BASE_1 "TB1T" -#define ENCLOSURE_BASE_2 "TB2T" -#define ENCLOSURE_BASE_3 "TB3T" -#define GPU_0_DIODE "TG0D" -#define GPU_0_HEATSINK "TG0H" -#define GPU_0_PROXIMITY "TG0P" -#define HARD_DRIVE_BAY "TH0P" -#define MEMORY_SLOT_0 "TM0S" -#define MEMORY_SLOTS_PROXIMITY "TM0P" -#define NORTHBRIDGE "TN0H" -#define NORTHBRIDGE_DIODE "TN0D" -#define NORTHBRIDGE_PROXIMITY "TN0P" -#define THUNDERBOLT_0 "TI0P" -#define THUNDERBOLT_1 "TI1P" -#define WIRELESS_MODULE "TW0P" - - -/** -SMC keys for fans - 4 byte multi-character constants - -Number of fans on Macs vary of course, thus not all keys will be applicable. - -Presumed letter translations: - -- F = Fan -- Ac = Acutal -- Mn = Min -- Mx = Max -- Sf = Safe -- Tg = Target - -Sources: See TMP SMC keys -*/ -#define FAN_0 "F0Ac" -#define FAN_0_MIN_RPM "F0Mn" -#define FAN_0_MAX_RPM "F0Mx" -#define FAN_0_SAFE_RPM "F0Sf" -#define FAN_0_TARGET_RPM "F0Tg" -#define FAN_1 "F1Ac" -#define FAN_1_MIN_RPM "F1Mn" -#define FAN_1_MAX_RPM "F1Mx" -#define FAN_1_SAFE_RPM "F1Sf" -#define FAN_1_TARGET_RPM "F1Tg" -#define FAN_2 "F2Ac" -#define FAN_2_MIN_RPM "F2Mn" -#define FAN_2_MAX_RPM "F2Mx" -#define FAN_2_SAFE_RPM "F2Sf" -#define FAN_2_TARGET_RPM "F2Tg" -#define NUM_FANS "FNum" -#define FORCE_BITS "FS! " - - -/** -Misc SMC keys - 4 byte multi-character constants - -Sources: See TMP SMC keys -*/ -#define BATT_PWR "BATP" -#define NUM_KEYS "#KEY" -#define ODD_FULL "MSDI" - - -//------------------------------------------------------------------------------ -// MARK: TYPES -//------------------------------------------------------------------------------ - - -typedef char fan_name_t[13]; - - -//------------------------------------------------------------------------------ -// MARK: ENUMS -//------------------------------------------------------------------------------ - - -typedef enum { - CELSIUS, - FAHRENHEIT, - KELVIN -} tmp_unit_t; - - -//------------------------------------------------------------------------------ -// MARK: PROTOTYPES -//------------------------------------------------------------------------------ - - -/** -Open a connection to the SMC - -:returns: kIOReturnSuccess on successful connection to the SMC. -*/ -kern_return_t open_smc(void); - - -/** -Close connection to the SMC - -:returns: kIOReturnSuccess on successful close of connection to the SMC. -*/ -kern_return_t close_smc(void); - - -/** -Check if an SMC key is valid. Useful for determining if a certain machine has -particular sensor or fan for example. - -:param: key The SMC key to check. 4 byte multi-character constant. Must be 4 - characters in length. -:returns: True if the key is found, false otherwise -*/ -bool is_key_valid(char *key); - - -/** -Get the current temperature from a sensor - -:param: key The temperature sensor to read from -:param: unit The unit for the temperature value. -:returns: Temperature of sensor. If the sensor is not found, or an error - occurs, return will be zero -*/ -double get_tmp(char *key, tmp_unit_t unit); - - -/** -Is the machine being powered by the battery? - -:returns: True if it is, false otherwise -*/ -bool is_battery_powered(void); - - -/** -Is there a CD in the optical disk drive (ODD)? - -:returns: True if there is, false otherwise -*/ -bool is_optical_disk_drive_full(void); - - -/** -Get the name of a fan. - -:param: fanNum The number of the fan to check -:param: name The name of the fan. Return will be empty on error. -:returns: True if successful, false otherwise. -*/ -bool get_fan_name(unsigned int fan_num, fan_name_t name); - - -/** -Get the number of fans on this machine. - -:returns: The number of fans. If an error occurs, return will be -1. -*/ -int get_num_fans(void); - - -/** -Get the current speed (RPM - revolutions per minute) of a fan. - -:param: fan_num The number of the fan to check -:returns: The fan RPM. If the fan is not found, or an error occurs, return - will be zero -*/ -UInt get_fan_rpm(UInt fan_num); - - -/** -Set the minimum speed (RPM - revolutions per minute) of a fan. This method -requires root privileges. By minimum we mean that OS X can interject and -raise the fan speed if needed, however it will not go below this. - -WARNING: You are playing with hardware here, BE CAREFUL. - -:param: fan_num The number of the fan to set -:param: rpm The speed you would like to set the fan to. -:param: auth Should the function do authentication? -:return: True if successful, false otherwise -*/ -bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth); From e3ae39aa5b8ece9cd69ff0de52471c0a87f966d5 Mon Sep 17 00:00:00 2001 From: Lomanic Date: Tue, 19 Dec 2017 22:52:41 +0100 Subject: [PATCH 19/23] Fix #442, trim sensor names and properly handle CentOS in host.SensorsTemperatures() --- host/host_linux.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/host/host_linux.go b/host/host_linux.go index 14893a4..d5ada58 100644 --- a/host/host_linux.go +++ b/host/host_linux.go @@ -541,7 +541,7 @@ func SensorsTemperatures() ([]TemperatureStat, error) { if len(files) == 0 { // CentOS has an intermediate /device directory: // https://github.com/giampaolo/psutil/issues/971 - files, err = filepath.Glob(common.HostSys("/class/hwmon/hwmon*/temp*_*")) + files, err = filepath.Glob(common.HostSys("/class/hwmon/hwmon*/device/temp*_*")) if err != nil { return temperatures, err } @@ -557,12 +557,12 @@ func SensorsTemperatures() ([]TemperatureStat, error) { if err != nil { return temperatures, err } - temperature, err := strconv.ParseFloat(string(current), 64) + temperature, err := strconv.ParseFloat(strings.TrimSpace(string(current)), 64) if err != nil { continue } temperatures = append(temperatures, TemperatureStat{ - SensorKey: string(name), + SensorKey: strings.TrimSpace(string(name)), Temperature: temperature / 1000.0, }) } From 482ca3af6d842055fe735f0db994896fca1e7505 Mon Sep 17 00:00:00 2001 From: Nick Kirsch Date: Thu, 4 Jan 2018 11:30:39 -0800 Subject: [PATCH 20/23] Parses the tgid field, which is the thread group id (aka user-space process id) on Linux. Returns error on other platforms. --- process/process.go | 2 ++ process/process_fallback.go | 3 +++ process/process_linux.go | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/process/process.go b/process/process.go index df5da2e..0e089bb 100644 --- a/process/process.go +++ b/process/process.go @@ -30,6 +30,8 @@ type Process struct { lastCPUTimes *cpu.TimesStat lastCPUTime time.Time + + tgid int32 } type OpenFilesStat struct { diff --git a/process/process_fallback.go b/process/process_fallback.go index 12bd3a6..e8f545b 100644 --- a/process/process_fallback.go +++ b/process/process_fallback.go @@ -41,6 +41,9 @@ func (p *Process) Ppid() (int32, error) { func (p *Process) Name() (string, error) { return "", common.ErrNotImplementedError } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} func (p *Process) Exe() (string, error) { return "", common.ErrNotImplementedError } diff --git a/process/process_linux.go b/process/process_linux.go index 9db1997..4b1ace1 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -100,6 +100,16 @@ func (p *Process) Name() (string, error) { return p.name, nil } +// Tgid returns tgid, a Linux-synonym for user-space Pid +func (p *Process) Tgid() (int32, error) { + if p.tgid == 0 { + if err := p.fillFromStatus(); err != nil { + return 0, err + } + } + return p.tgid, nil +} + // Exe returns executable path of the process. func (p *Process) Exe() (string, error) { return p.fillFromExe() @@ -820,6 +830,12 @@ func (p *Process) fillFromStatus() error { return err } p.parent = int32(pval) + case "Tgid": + pval, err := strconv.ParseInt(value, 10, 32) + if err != nil { + return err + } + p.tgid = int32(pval) case "Uid": p.uids = make([]int32, 0, 4) for _, i := range strings.Split(value, "\t") { From 6c35887d024f4257ed09809d3bab3f2fc50fe14f Mon Sep 17 00:00:00 2001 From: Nick Kirsch Date: Thu, 4 Jan 2018 11:51:49 -0800 Subject: [PATCH 21/23] Add ErrNotImplementedError to Darwin, FreeBSD, and OpenBSD. --- process/process_darwin.go | 3 +++ process/process_freebsd.go | 3 +++ process/process_openbsd.go | 3 +++ 3 files changed, 9 insertions(+) diff --git a/process/process_darwin.go b/process/process_darwin.go index cc289d2..2ed7a0b 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -83,6 +83,9 @@ func (p *Process) Name() (string, error) { return common.IntToString(k.Proc.P_comm[:]), nil } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} func (p *Process) Exe() (string, error) { lsof_bin, err := exec.LookPath("lsof") if err != nil { diff --git a/process/process_freebsd.go b/process/process_freebsd.go index 9d75717..4db021d 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -50,6 +50,9 @@ func (p *Process) Name() (string, error) { return common.IntToString(k.Comm[:]), nil } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} func (p *Process) Exe() (string, error) { return "", common.ErrNotImplementedError } diff --git a/process/process_openbsd.go b/process/process_openbsd.go index aca4ffa..088abef 100644 --- a/process/process_openbsd.go +++ b/process/process_openbsd.go @@ -53,6 +53,9 @@ func (p *Process) Name() (string, error) { return common.IntToString(k.Comm[:]), nil } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} func (p *Process) Exe() (string, error) { return "", common.ErrNotImplementedError } From fb24c70d366b88caa381823e8e7e387784fcda8b Mon Sep 17 00:00:00 2001 From: Nick Kirsch Date: Fri, 5 Jan 2018 11:37:36 -0800 Subject: [PATCH 22/23] Add ErrNotImplementedError for Tgid support. --- process/process_windows.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/process/process_windows.go b/process/process_windows.go index 3ce840b..14d466e 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -154,6 +154,10 @@ func (p *Process) Name() (string, error) { return name, nil } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} + func (p *Process) Exe() (string, error) { dst, err := GetWin32Proc(p.Pid) if err != nil { From 231815dfeab1ddac1419fcaf564eea078fdf9bfc Mon Sep 17 00:00:00 2001 From: shirou Date: Tue, 9 Jan 2018 11:12:00 +0900 Subject: [PATCH 23/23] [docker]: move String() to docker.go for all platforms. --- docker/docker.go | 11 +++++++++++ docker/docker_linux.go | 11 ----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 1d932cf..83716fd 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -1,6 +1,7 @@ package docker import ( + "encoding/json" "errors" "github.com/shirou/gopsutil/internal/common" @@ -50,6 +51,11 @@ type CgroupMemStat struct { MemFailCnt uint64 `json:"memoryFailcnt"` } +func (m CgroupMemStat) String() string { + s, _ := json.Marshal(m) + return string(s) +} + type CgroupDockerStat struct { ContainerID string `json:"containerID"` Name string `json:"name"` @@ -57,3 +63,8 @@ type CgroupDockerStat struct { Status string `json:"status"` Running bool `json:"running"` } + +func (c CgroupDockerStat) String() string { + s, _ := json.Marshal(c) + return string(s) +} diff --git a/docker/docker_linux.go b/docker/docker_linux.go index 000d2f2..7cc56d8 100644 --- a/docker/docker_linux.go +++ b/docker/docker_linux.go @@ -3,7 +3,6 @@ package docker import ( - "encoding/json" "fmt" "os" "os/exec" @@ -52,11 +51,6 @@ func GetDockerStat() ([]CgroupDockerStat, error) { return ret, nil } -func (c CgroupDockerStat) String() string { - s, _ := json.Marshal(c) - return string(s) -} - // GetDockerIDList returnes a list of DockerID. // This requires certain permission. func GetDockerIDList() ([]string, error) { @@ -220,11 +214,6 @@ func CgroupMemDocker(containerID string) (*CgroupMemStat, error) { return CgroupMem(containerID, common.HostSys("fs/cgroup/memory/docker")) } -func (m CgroupMemStat) String() string { - s, _ := json.Marshal(m) - return string(s) -} - // getCgroupFilePath constructs file path to get targetted stats file. func getCgroupFilePath(containerID, base, target, file string) string { if len(base) == 0 {