カーネルのパラメータを引いたり設定したりする時に便利なのが sysctl
コマンドです。
$ sysctl kern.ostype
kern.ostype: Darwin
このコマンドのシステムコールをGo言語から叩いて、OSの種類を引いてみましょう。
func main() {
ret, _ := syscall.Sysctl("kern.ostype")
fmt.Printf("%s\n", ret)
}
Darwin
問題ないですね。
数字を返すものを叩いてみましょう。
$ sysctl machdep.cpu.feature_bits
machdep.cpu.feature_bits: 9221959987971750911
func main() {
ret, _ := syscall.Sysctl("machdep.cpu.feature_bits")
val := *(*uint64)(unsafe.Pointer(&[]byte(ret)[0]))
fmt.Printf("%d\n", val)
}
出力結果
9221959987971750911
unsafeパッケージは使いたくないだって?
アーキテクチャのエンディアン固定になっちゃうけどなぁ。
val := binary.LittleEndian.Uint64([]byte(ret))
9221959987971750911
次は extfeature_bits
を見たい?
$ sysctl machdep.cpu.extfeature_bits
machdep.cpu.extfeature_bits: 1241984796928
func main() {
ret, _ := syscall.Sysctl("machdep.cpu.extfeature_bits")
val := binary.LittleEndian.Uint64([]byte(ret))
fmt.Printf("%d\n", val)
}
実行してみます
panic: runtime error: index out of range
goroutine 1 [running]:
encoding/binary.binary.littleEndian.Uint64(...)
/usr/local/Cellar/go/1.9.2/libexec/src/encoding/binary/binary.go:76
main.main()
/private/tmp/main.go:16 +0x1d7
exit status 2
!!!> index out of range <!!!
なにが起きたのか。
syscall.Sysctl
の返り値 (string
) の長さを見てみると、すぐにわかります。
ret, _ := syscall.Sysctl("machdep.cpu.feature_bits")
fmt.Printf("%d\n", len(ret))
ret, _ = syscall.Sysctl("machdep.cpu.extfeature_bits")
fmt.Printf("%d\n", len(ret))
8
7
え、つまりこれは…
https://github.com/golang/go/blob/8776be153540cf450eafd847cf8efde0a01774dc/src/syscall/syscall_bsd.go#L474
if n > 0 && buf[n-1] == '\x00' {
n--
}
return string(buf[0:n]), nil
な、なんということを…
つまりstructでも…?
ret, _ := syscall.Sysctl("kern.boottime")
fmt.Printf("%d\n", len(ret))
15
お、おう…
わかった… わかったよ…
sysctl.Sysctl
は最後のNULを落とす。
- 文字列を返すような場合はよい挙動だが、数字や構造体の場合は注意が必要。64bit整数を返す場合、その値に依存して8byteだったり7byteだったりする。
unsafe.Pointer
で数字や構造体にキャストする分には問題ないように思われる (ほんまやろか?)。 + "\x00"
してから処理したほうがいいかもしれない。
困っている人もいる (例) が、指摘されているように sysctl
パッケージは変更を受けつけていないので、この挙動が変わることや別の関数が追加されることはなさそう。
そもそも string
で返すのなんかおかしくない? []byte
で欲しいよね…
それあります!
golang.org/x/sys
パッケージの unix.SysctlRaw
を使いましょう。
https://github.com/golang/sys/blob/53aa286056ef226755cd898109dbcdaba8ac0b81/unix/syscall_bsd.go#L524
func main() {
ret, _ := unix.SysctlRaw("vm.loadavg")
fmt.Printf("%d\n", len(ret))
ret, _ = unix.SysctlRaw("kern.boottime")
fmt.Printf("%d\n", len(ret))
ret, _ = unix.SysctlRaw("machdep.cpu.extfeature_bits")
fmt.Printf("%d\n", len(ret))
}
24
16
8
これで安心して眠れそうですね。
sysctl.Sysctl
は最後のNULを落とします。文字列として欲しい時はこれで良いが、バイト列として欲しい時は unix.SysctlRaw
を使いましょう。