From ea808779c3b7d294e95f48fdd2577426285ea052 Mon Sep 17 00:00:00 2001 From: MohammadReza Palide Date: Thu, 30 Dec 2021 16:23:33 +0330 Subject: [PATCH 1/5] initial commit --- pkg/transport/network/addrresolver/client.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/transport/network/addrresolver/client.go b/pkg/transport/network/addrresolver/client.go index 5086e15fe..4fc0e72cf 100644 --- a/pkg/transport/network/addrresolver/client.go +++ b/pkg/transport/network/addrresolver/client.go @@ -24,6 +24,7 @@ import ( "github.com/skycoin/skywire/internal/httpauth" "github.com/skycoin/skywire/internal/netutil" "github.com/skycoin/skywire/internal/packetfilter" + pkgnetutil "github.com/skycoin/skywire/pkg/util/netutil" ) const ( @@ -196,7 +197,6 @@ func (c *httpClient) Delete(ctx context.Context, path string) (*http.Response, e if err != nil { return nil, err } - return c.httpClient.Do(req.WithContext(ctx)) } @@ -511,8 +511,14 @@ func (c *httpClient) Close() error { } } - if err := c.delBindSTCPR(context.Background()); err != nil { - c.log.WithError(err).Errorf("Failed to delete STCPR binding") + hasPublic, err := pkgnetutil.HasPublicIP() + if err != nil { + c.log.Errorf("Failed to check for public IP: %v", err) + } + if hasPublic { + if err := c.delBindSTCPR(context.Background()); err != nil { + c.log.WithError(err).Errorf("Failed to delete STCPR binding") + } } return nil From 99cce3cbc2c52ad12da12929523f9a653732d092 Mon Sep 17 00:00:00 2001 From: MohammadReza Palide Date: Thu, 30 Dec 2021 16:43:16 +0330 Subject: [PATCH 2/5] fix hostkeeper --- go.mod | 3 + go.sum | 3 + pkg/visor/visor.go | 13 +- .../jaypipes/ghw/.get-go-packages.sh | 3 + vendor/github.com/jaypipes/ghw/.gitignore | 3 + vendor/github.com/jaypipes/ghw/.travis.yml | 25 + vendor/github.com/jaypipes/ghw/Dockerfile | 26 + vendor/github.com/jaypipes/ghw/Makefile | 39 + vendor/github.com/jaypipes/ghw/README.md | 1318 +++++++++++++++++ vendor/github.com/jaypipes/ghw/SNAPSHOT.md | 45 + vendor/github.com/jaypipes/ghw/alias.go | 148 ++ vendor/github.com/jaypipes/ghw/doc.go | 314 ++++ vendor/github.com/jaypipes/ghw/host.go | 139 ++ .../github.com/jaypipes/ghw/pkg/bios/bios.go | 77 + .../jaypipes/ghw/pkg/bios/bios_linux.go | 16 + .../jaypipes/ghw/pkg/bios/bios_stub.go | 17 + .../jaypipes/ghw/pkg/bios/bios_windows.go | 32 + .../jaypipes/ghw/pkg/block/block.go | 250 ++++ .../jaypipes/ghw/pkg/block/block_darwin.go | 283 ++++ .../jaypipes/ghw/pkg/block/block_linux.go | 470 ++++++ .../jaypipes/ghw/pkg/block/block_stub.go | 17 + .../jaypipes/ghw/pkg/block/block_windows.go | 220 +++ .../jaypipes/ghw/pkg/chassis/chassis.go | 121 ++ .../jaypipes/ghw/pkg/chassis/chassis_linux.go | 26 + .../jaypipes/ghw/pkg/chassis/chassis_stub.go | 17 + .../ghw/pkg/chassis/chassis_windows.go | 43 + vendor/github.com/jaypipes/ghw/pkg/cpu/cpu.go | 169 +++ .../jaypipes/ghw/pkg/cpu/cpu_linux.go | 220 +++ .../jaypipes/ghw/pkg/cpu/cpu_stub.go | 17 + .../jaypipes/ghw/pkg/cpu/cpu_windows.go | 55 + vendor/github.com/jaypipes/ghw/pkg/gpu/gpu.go | 95 ++ .../jaypipes/ghw/pkg/gpu/gpu_linux.go | 152 ++ .../jaypipes/ghw/pkg/gpu/gpu_stub.go | 17 + .../jaypipes/ghw/pkg/gpu/gpu_windows.go | 131 ++ .../jaypipes/ghw/pkg/memory/memory.go | 80 + .../jaypipes/ghw/pkg/memory/memory_cache.go | 101 ++ .../ghw/pkg/memory/memory_cache_linux.go | 188 +++ .../jaypipes/ghw/pkg/memory/memory_linux.go | 237 +++ .../jaypipes/ghw/pkg/memory/memory_stub.go | 17 + .../jaypipes/ghw/pkg/memory/memory_windows.go | 72 + vendor/github.com/jaypipes/ghw/pkg/net/net.go | 83 ++ .../jaypipes/ghw/pkg/net/net_linux.go | 222 +++ .../jaypipes/ghw/pkg/net/net_stub.go | 17 + .../jaypipes/ghw/pkg/net/net_windows.go | 65 + vendor/github.com/jaypipes/ghw/pkg/pci/pci.go | 206 +++ .../jaypipes/ghw/pkg/pci/pci_linux.go | 378 +++++ .../jaypipes/ghw/pkg/pci/pci_stub.go | 30 + .../jaypipes/ghw/pkg/product/product.go | 100 ++ .../jaypipes/ghw/pkg/product/product_linux.go | 23 + .../jaypipes/ghw/pkg/product/product_stub.go | 17 + .../ghw/pkg/product/product_windows.go | 45 + .../jaypipes/ghw/pkg/topology/topology.go | 128 ++ .../ghw/pkg/topology/topology_linux.go | 100 ++ .../ghw/pkg/topology/topology_stub.go | 17 + .../ghw/pkg/topology/topology_windows.go | 156 ++ .../jaypipes/ghw/pkg/unitutil/unit.go | 37 + .../jaypipes/pcidb/.get-go-packages.sh | 3 + vendor/github.com/jaypipes/pcidb/.gitignore | 2 + vendor/github.com/jaypipes/pcidb/.travis.yml | 48 + vendor/github.com/jaypipes/pcidb/COPYING | 176 +++ vendor/github.com/jaypipes/pcidb/Gopkg.lock | 17 + vendor/github.com/jaypipes/pcidb/Gopkg.toml | 34 + vendor/github.com/jaypipes/pcidb/LICENSE | 201 +++ vendor/github.com/jaypipes/pcidb/Makefile | 46 + vendor/github.com/jaypipes/pcidb/README.md | 413 ++++++ vendor/github.com/jaypipes/pcidb/context.go | 86 ++ vendor/github.com/jaypipes/pcidb/discover.go | 111 ++ vendor/github.com/jaypipes/pcidb/main.go | 198 +++ vendor/github.com/jaypipes/pcidb/parse.go | 163 ++ .../github.com/mitchellh/go-homedir/LICENSE | 21 + .../github.com/mitchellh/go-homedir/README.md | 14 + .../mitchellh/go-homedir/homedir.go | 167 +++ vendor/howett.net/plist/.gitlab-ci.yml | 39 + vendor/howett.net/plist/LICENSE | 58 + vendor/howett.net/plist/README.md | 21 + vendor/howett.net/plist/bplist.go | 26 + vendor/howett.net/plist/bplist_generator.go | 303 ++++ vendor/howett.net/plist/bplist_parser.go | 353 +++++ vendor/howett.net/plist/decode.go | 119 ++ vendor/howett.net/plist/doc.go | 5 + vendor/howett.net/plist/encode.go | 126 ++ vendor/howett.net/plist/fuzz.go | 17 + vendor/howett.net/plist/marshal.go | 186 +++ vendor/howett.net/plist/must.go | 50 + vendor/howett.net/plist/plist.go | 85 ++ vendor/howett.net/plist/plist_types.go | 139 ++ vendor/howett.net/plist/text_generator.go | 226 +++ vendor/howett.net/plist/text_parser.go | 515 +++++++ vendor/howett.net/plist/text_tables.go | 43 + vendor/howett.net/plist/typeinfo.go | 170 +++ vendor/howett.net/plist/unmarshal.go | 317 ++++ vendor/howett.net/plist/util.go | 25 + vendor/howett.net/plist/xml_generator.go | 185 +++ vendor/howett.net/plist/xml_parser.go | 216 +++ vendor/howett.net/plist/zerocopy.go | 20 + vendor/howett.net/plist/zerocopy_appengine.go | 7 + vendor/modules.txt | 21 + 97 files changed, 11814 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/jaypipes/ghw/.get-go-packages.sh create mode 100644 vendor/github.com/jaypipes/ghw/.gitignore create mode 100644 vendor/github.com/jaypipes/ghw/.travis.yml create mode 100644 vendor/github.com/jaypipes/ghw/Dockerfile create mode 100644 vendor/github.com/jaypipes/ghw/Makefile create mode 100644 vendor/github.com/jaypipes/ghw/README.md create mode 100644 vendor/github.com/jaypipes/ghw/SNAPSHOT.md create mode 100644 vendor/github.com/jaypipes/ghw/alias.go create mode 100644 vendor/github.com/jaypipes/ghw/doc.go create mode 100644 vendor/github.com/jaypipes/ghw/host.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/bios/bios.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/bios/bios_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/bios/bios_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/bios/bios_windows.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/block/block.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/block/block_darwin.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/block/block_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/block/block_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/block/block_windows.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/chassis/chassis.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_windows.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/cpu/cpu.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_windows.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/gpu/gpu.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_windows.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/memory/memory.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/memory/memory_cache.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/memory/memory_cache_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/memory/memory_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/memory/memory_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/memory/memory_windows.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/net/net.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/net/net_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/net/net_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/net/net_windows.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/pci/pci.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/pci/pci_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/pci/pci_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/product/product.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/product/product_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/product/product_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/product/product_windows.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/topology/topology.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/topology/topology_linux.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/topology/topology_stub.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/topology/topology_windows.go create mode 100644 vendor/github.com/jaypipes/ghw/pkg/unitutil/unit.go create mode 100644 vendor/github.com/jaypipes/pcidb/.get-go-packages.sh create mode 100644 vendor/github.com/jaypipes/pcidb/.gitignore create mode 100644 vendor/github.com/jaypipes/pcidb/.travis.yml create mode 100644 vendor/github.com/jaypipes/pcidb/COPYING create mode 100644 vendor/github.com/jaypipes/pcidb/Gopkg.lock create mode 100644 vendor/github.com/jaypipes/pcidb/Gopkg.toml create mode 100644 vendor/github.com/jaypipes/pcidb/LICENSE create mode 100644 vendor/github.com/jaypipes/pcidb/Makefile create mode 100644 vendor/github.com/jaypipes/pcidb/README.md create mode 100644 vendor/github.com/jaypipes/pcidb/context.go create mode 100644 vendor/github.com/jaypipes/pcidb/discover.go create mode 100644 vendor/github.com/jaypipes/pcidb/main.go create mode 100644 vendor/github.com/jaypipes/pcidb/parse.go create mode 100644 vendor/github.com/mitchellh/go-homedir/LICENSE create mode 100644 vendor/github.com/mitchellh/go-homedir/README.md create mode 100644 vendor/github.com/mitchellh/go-homedir/homedir.go create mode 100644 vendor/howett.net/plist/.gitlab-ci.yml create mode 100644 vendor/howett.net/plist/LICENSE create mode 100644 vendor/howett.net/plist/README.md create mode 100644 vendor/howett.net/plist/bplist.go create mode 100644 vendor/howett.net/plist/bplist_generator.go create mode 100644 vendor/howett.net/plist/bplist_parser.go create mode 100644 vendor/howett.net/plist/decode.go create mode 100644 vendor/howett.net/plist/doc.go create mode 100644 vendor/howett.net/plist/encode.go create mode 100644 vendor/howett.net/plist/fuzz.go create mode 100644 vendor/howett.net/plist/marshal.go create mode 100644 vendor/howett.net/plist/must.go create mode 100644 vendor/howett.net/plist/plist.go create mode 100644 vendor/howett.net/plist/plist_types.go create mode 100644 vendor/howett.net/plist/text_generator.go create mode 100644 vendor/howett.net/plist/text_parser.go create mode 100644 vendor/howett.net/plist/text_tables.go create mode 100644 vendor/howett.net/plist/typeinfo.go create mode 100644 vendor/howett.net/plist/unmarshal.go create mode 100644 vendor/howett.net/plist/util.go create mode 100644 vendor/howett.net/plist/xml_generator.go create mode 100644 vendor/howett.net/plist/xml_parser.go create mode 100644 vendor/howett.net/plist/zerocopy.go create mode 100644 vendor/howett.net/plist/zerocopy_appengine.go diff --git a/go.mod b/go.mod index 4846d4d11..a257d1ba2 100644 --- a/go.mod +++ b/go.mod @@ -76,11 +76,13 @@ require ( github.com/google/go-querystring v1.0.0 // indirect github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jaypipes/pcidb v0.6.0 // indirect github.com/klauspost/compress v1.11.0 // indirect github.com/klauspost/cpuid v1.2.4 // indirect github.com/klauspost/pgzip v1.2.1 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nwaples/rardecode v1.0.0 // indirect @@ -102,6 +104,7 @@ require ( golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect ) // Uncomment for tests with alternate branches of 'dmsg' diff --git a/go.sum b/go.sum index 9b0596c48..b8d1625b5 100644 --- a/go.sum +++ b/go.sum @@ -273,6 +273,7 @@ github.com/james-barrow/golang-ipc v0.0.0-20210227130457-95e7cc81f5e2 h1:lnIIG50 github.com/james-barrow/golang-ipc v0.0.0-20210227130457-95e7cc81f5e2/go.mod h1:M3eGiVVY7bdtqyWT+gtbIqji7CqHi3PKJHSPl2pP40c= github.com/jaypipes/ghw v0.8.0 h1:02q1pTm9CD83vuhBsEZZhOCS128pq87uyaQeJZkp3sQ= github.com/jaypipes/ghw v0.8.0/go.mod h1:+gR9bjm3W/HnFi90liF+Fj9GpCe/Dsibl9Im8KmC7c4= +github.com/jaypipes/pcidb v0.6.0 h1:VIM7GKVaW4qba30cvB67xSCgJPTzkG8Kzw/cbs5PHWU= github.com/jaypipes/pcidb v0.6.0/go.mod h1:L2RGk04sfRhp5wvHO0gfRAMoLY/F3PKv/nwJeVoho0o= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -341,6 +342,7 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -945,6 +947,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= nhooyr.io/websocket v1.8.2 h1:LwdzfyyOZKtVFoXay6A39Acu03KmidSZ3YUUvPa13PA= nhooyr.io/websocket v1.8.2/go.mod h1:LiqdCg1Cu7TPWxEvPjPa0TGYxCsy4pHNTN9gGluwBpQ= diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 9f81d6a8f..3c7261461 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -12,7 +12,9 @@ import ( "sync" "time" + "github.com/jaypipes/ghw" "github.com/jaypipes/ghw/pkg/baseboard" + "github.com/jaypipes/ghw/pkg/product" "github.com/skycoin/dmsg" "github.com/skycoin/dmsg/cipher" dmsgdisc "github.com/skycoin/dmsg/disc" @@ -258,13 +260,22 @@ func (v *Visor) HostKeeper(skybianBuildVersion string) { } serialNumber = string(serialNumberByte) } else { - baseboardInfo, err := baseboard.New() + baseboardInfo, err := baseboard.New(ghw.WithDisableWarnings()) if err != nil { logger.Errorf("Error during get information of host due to %v", err) return } model = baseboardInfo.Vendor serialNumber = baseboardInfo.SerialNumber + if model == "" || serialNumber == "" { + productInfo, err := product.New(ghw.WithDisableWarnings()) + if err != nil { + logger.Errorf("Error during get information of host due to %v", err) + return + } + model = productInfo.Vendor + serialNumber = productInfo.SerialNumber + } } var keeperInfo HostKeeperData diff --git a/vendor/github.com/jaypipes/ghw/.get-go-packages.sh b/vendor/github.com/jaypipes/ghw/.get-go-packages.sh new file mode 100644 index 000000000..382782f8a --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/.get-go-packages.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +export GO_PACKAGES=$(go list ./... | grep -v /vendor/) diff --git a/vendor/github.com/jaypipes/ghw/.gitignore b/vendor/github.com/jaypipes/ghw/.gitignore new file mode 100644 index 000000000..34d0d840a --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/.gitignore @@ -0,0 +1,3 @@ +vendor/ +coverage*.* +*~ diff --git a/vendor/github.com/jaypipes/ghw/.travis.yml b/vendor/github.com/jaypipes/ghw/.travis.yml new file mode 100644 index 000000000..4e0286f7e --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/.travis.yml @@ -0,0 +1,25 @@ +language: go +go: "1.13" +# Using sudo triggers a real virtual machine as opposed to a container, which +# allows ghw to actually determine things like host memory or block storage... +sudo: required +script: + - source ./.get-go-packages.sh + - go test -v $GO_PACKAGES + - go run cmd/ghwc/main.go +env: + - GHW_TESTING_SKIP_GPU=1 + - GO111MODULE="on" +matrix: + include: + - os: linux + go: "1.13" + - os: linux + go: "1.14.x" + + # Tests that ghw builds on MacOSX (even though there is currently only + # support for block devices) + #- os: osx + # go: "1.13" + #- os: osx + # go: "1.14.x" diff --git a/vendor/github.com/jaypipes/ghw/Dockerfile b/vendor/github.com/jaypipes/ghw/Dockerfile new file mode 100644 index 000000000..a488b3820 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/Dockerfile @@ -0,0 +1,26 @@ +FROM golang:1.13-stretch as builder +WORKDIR /go/src/github.com/jaypipes/ghw + +# Force the go compiler to use modules. +ENV GO111MODULE=on +ENV GOPROXY=direct + +# go.mod and go.sum go into their own layers. +COPY go.mod . +COPY go.sum . + +# This ensures `go mod download` happens only when go.mod and go.sum change. +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 go build -o ghwc ./cmd/ghwc/ + +FROM alpine:3.7 +RUN apk add --no-cache ethtool + +WORKDIR /bin + +COPY --from=builder /go/src/github.com/jaypipes/ghw/ghwc /bin + +CMD ghwc diff --git a/vendor/github.com/jaypipes/ghw/Makefile b/vendor/github.com/jaypipes/ghw/Makefile new file mode 100644 index 000000000..c7e0db402 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/Makefile @@ -0,0 +1,39 @@ +VENDOR := vendor +PKGS := $(shell go list ./... | grep -v /$(VENDOR)/) +SRC = $(shell find . -type f -name '*.go' -not -path "*/$(VENDOR)/*") +BIN_DIR := $(GOPATH)/bin +GOMETALINTER := $(BIN_DIR)/gometalinter + +.PHONY: test +test: vet + go test $(PKGS) + +$(GOMETALINTER): + go get -u github.com/alecthomas/gometalinter + $(GOMETALINTER) --install &> /dev/null + +.PHONY: lint +lint: $(GOMETALINTER) + $(GOMETALINTER) ./... --vendor + +.PHONY: fmt +fmt: + @echo "Running gofmt on all sources..." + @gofmt -s -l -w $(SRC) + +.PHONY: fmtcheck +fmtcheck: + @bash -c "diff -u <(echo -n) <(gofmt -d $(SRC))" + +.PHONY: vet +vet: + go vet $(PKGS) + +.PHONY: cover +cover: + $(shell [ -e coverage.out ] && rm coverage.out) + @echo "mode: count" > coverage-all.out + $(foreach pkg,$(PKGS),\ + go test -coverprofile=coverage.out -covermode=count $(pkg);\ + tail -n +2 coverage.out >> coverage-all.out;) + go tool cover -html=coverage-all.out -o=coverage-all.html diff --git a/vendor/github.com/jaypipes/ghw/README.md b/vendor/github.com/jaypipes/ghw/README.md new file mode 100644 index 000000000..6b4f37c1c --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/README.md @@ -0,0 +1,1318 @@ +# `ghw` - Golang HardWare discovery/inspection library [![Build Status](https://travis-ci.org/jaypipes/ghw.svg?branch=master)](https://travis-ci.org/jaypipes/ghw) +![ghw mascot](images/ghw-gopher.png) +

+`ghw` is a small Golang library providing hardware inspection and discovery +for Linux and Windows. There currently exists partial support for MacOSX. + +## Design Principles + +* No root privileges needed for discovery + + `ghw` goes the extra mile to be useful without root priveleges. We query for + host hardware information as directly as possible without relying on shellouts + to programs like `dmidecode` that require root privileges to execute. + + Elevated privileges are indeed required to query for some information, but + `ghw` will never error out if blocked from reading that information. Instead, + `ghw` will print a warning message about the information that could not be + retrieved. You may disable these warning messages with `GHW_DISABLE_WARNINGS` + environment variable. + +* Well-documented code and plenty of example code + + The code itself should be well-documented with lots of usage + examples. + +* Interfaces should be consistent across modules + + Each module in the library should be structured in a consistent fashion, and + the structs returned by various library functions should have consistent + attribute and method names. + +## Inspecting != Monitoring + +`ghw` is a tool for gathering information about your hardware's **capacity** +and **capabilities**. + +It is important to point out that `ghw` does **NOT** report information that is +temporary or variable. It is **NOT** a system monitor nor is it an appropriate +tool for gathering data points for metrics that change over time. If you are +looking for a system that tracks usage of CPU, memory, network I/O or disk I/O, +there are plenty of great open source tools that do this! Check out the +[Prometheus project](https://prometheus.io/) for a great example. + +## Usage + +You can use the functions in `ghw` to determine various hardware-related +information about the host computer: + +* [Memory](#memory) +* [CPU](#cpu) +* [Block storage](#block-storage) +* [Topology](#topology) +* [Network](#network) +* [PCI](#pci) +* [GPU](#gpu) +* [Chassis](#chassis) +* [BIOS](#bios) +* [Baseboard](#baseboard) +* [Product](#product) +* [YAML and JSON serialization](#serialization) + +### Overriding the root mountpoint `ghw` uses + +The default root mountpoint that `ghw` uses when looking for information about +the host system is `/`. So, for example, when looking up CPU information on a +Linux system, `ghw.CPU()` will use the path `/proc/cpuinfo`. + +If you are calling `ghw` from a system that has an alternate root mountpoint, +you can either set the `GHW_CHROOT` environment variable to that alternate +path, or call the module constructor function with the `ghw.WithChroot()` +modifier. + +For example, if you are executing from within an application container that has +bind-mounted the root host filesystem to the mount point `/host`, you would set +`GHW_CHROOT` to `/host` so that `ghw` can find `/proc/cpuinfo` at +`/host/proc/cpuinfo`. + +Alternately, you can use the `ghw.WithChroot()` function like so: + +```go +cpu, err := ghw.CPU(ghw.WithChroot("/host")) +``` + +### Consuming snapshots + +You can make `ghw` read from snapshots (created with `ghw-snapshot`) using +environment variables or programmatically. +Please check `SNAPSHOT.md` to learn more about how ghw creates and consumes +snapshots. + +The environment variable `GHW_SNAPSHOT_PATH` let users specify a snapshot +that `ghw` will automatically consume. All the needed chroot changes will be +automatically performed. By default, the snapshot is unpacked on a temporary +directory managed by `ghw`, and cleaned up when no longer needed, avoiding +leftovers. + +The rest of the environment variables are relevant iff `GHW_SNAPSHOT_PATH` is given. +`GHW_SNAPSHOT_ROOT` let users specify the directory +on which the snapshot should be unpacked. This moves the ownership of that +directory from `ghw` to users. For this reason, `ghw` will *not* clean up automatically +the content unpacked in `GHW_SNAPSHOT_ROOT`. + +`GHW_SNAPSHOT_EXCLUSIVE` is relevant iff `GHW_SNAPSHOT_ROOT` is given. +Set it to any value to toggle it on. This tells `ghw` that the directory is meant +only to contain the given snapshot, thus `ghw` will *not* attempt to unpack it +(and will go ahead silently!) unless the directory is empty. +You can use both `GHW_SNAPSHOT_ROOT` and `GHW_SNAPSHOT_EXCLUSIVE` to make sure +`ghw` unpacks the snapshot only once regardless of how many `ghw` packages +(e.g. cpu, memory) access it. + +Set `GHW_SNAPSHOT_PRESERVE` to any value to enable it. If set, `ghw` will *not* +clean up the unpacked snapshot once done, leaving it into the system. + +```go +cpu, err := ghw.CPU(ghw.WithSnapshot(ghw.SnapshotOptions{ + Path: "/path/to/linux-amd64-d4771ed3300339bc75f856be09fc6430.tar.gz", +})) + + +myRoot := "/my/safe/directory" +cpu, err := ghw.CPU(ghw.WithSnapshot(ghw.SnapshotOptions{ + Path: "/path/to/linux-amd64-d4771ed3300339bc75f856be09fc6430.tar.gz", + Root: &myRoot, +})) + +myOtherRoot := "/my/other/safe/directory" +cpu, err := ghw.CPU(ghw.WithSnapshot(ghw.SnapshotOptions{ + Path: "/path/to/linux-amd64-d4771ed3300339bc75f856be09fc6430.tar.gz", + Root: &myOtherRoot, + Exclusive: true, +})) +``` + +### Creating snapshots + +You can create ghw snapshots in two ways. +You can just consume the `ghw-snapshot` tool, or you can create them programmatically +from your golang code. We explore now the latter case. + +Snapshotting takes two phases: +1. clone the relevant pseudofiles/pseudodirectories into a temporary tree + This tree is usually deleted once the packing is successful. +2. pack the cloned tree into a tar.gz + +```go + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/jaypipes/ghw/pkg/snapshot" +) + +// ... + +scratchDir, err := ioutil.TempDir("", "ghw-snapshot-*") +if err != nil { + fmt.Printf("Error creating clone directory: %v", err) +} +defer os.RemoveAll(scratchDir) + +// this step clones all the files and directories ghw cares about +if err := snapshot.CloneTreeInto(scratchDir); err != nil { + fmt.Printf("error cloning into %q: %v", scratchDir, err) +} + +// optionally, you may add extra content into your snapshot. +// ghw will ignore the extra content. +// Glob patterns like `filepath.Glob` are supported. +fileSpecs := []string{ + "/proc/cmdline", +} + +// options allows the client code to optionally deference symlinks, or copy +// them into the cloned tree as symlinks +var opts *snapshot.CopyFileOptions +if err := snapshot.CopyFilesInto(fileSpecs, scratchDir, opts); err != nil { + fmt.Printf("error cloning extra files into %q: %v", scratchDir, err) +} + +// automates the creation of the gzipped tarball out of the given tree. +if err := snapshot.PackFrom("my-snapshot.tgz", scratchDir); err != nil { + fmt.Printf("error packing %q into %q: %v", scratchDir, *output, err) +} +``` + +### Disabling warning messages + +When `ghw` isn't able to retrieve some information, it may print certain +warning messages to `stderr`. To disable these warnings, simply set the +`GHW_DISABLE_WARNINGS` environs variable: + +``` +$ ghwc memory +WARNING: +Could not determine total physical bytes of memory. This may +be due to the host being a virtual machine or container with no +/var/log/syslog file, or the current user may not have necessary +privileges to read the syslog. We are falling back to setting the +total physical amount of memory to the total usable amount of memory +memory (24GB physical, 24GB usable) +``` + +``` +$ GHW_DISABLE_WARNINGS=1 ghwc memory +memory (24GB physical, 24GB usable) +``` + +You can disable warning programmatically using the `WithDisableWarnings` option: + +```go + +import ( + "github.com/jaypipes/ghw" +) + +mem, err := ghw.Memory(ghw.WithDisableWarnings()) +``` + +`WithDisableWarnings` is a alias for the `WithNullAlerter` option, which in turn +leverages the more general `Alerter` feature of ghw. + +You may supply a `Alerter` to ghw to redirect all the warnings there, like +logger objects (see for example golang's stdlib `log.Logger`). +`Alerter` is in fact the minimal logging interface `ghw needs. +To learn more, please check the `option.Alerter` interface and the `ghw.WithAlerter()` +function. + +### Memory + +Information about the host computer's memory can be retrieved using the +`ghw.Memory()` function which returns a pointer to a `ghw.MemoryInfo` struct. + +The `ghw.MemoryInfo` struct contains three fields: + +* `ghw.MemoryInfo.TotalPhysicalBytes` contains the amount of physical memory on + the host +* `ghw.MemoryInfo.TotalUsableBytes` contains the amount of memory the + system can actually use. Usable memory accounts for things like the kernel's + resident memory size and some reserved system bits +* `ghw.MemoryInfo.SupportedPageSizes` is an array of integers representing the + size, in bytes, of memory pages the system supports +* `ghw.MemoryInfo.Modules` is an array of pointers to `ghw.MemoryModule` + structs, one for each physical [DIMM](https://en.wikipedia.org/wiki/DIMM). + Currently, this information is only included on Windows, with Linux support + [planned](https://github.com/jaypipes/ghw/pull/171#issuecomment-597082409). + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + memory, err := ghw.Memory() + if err != nil { + fmt.Printf("Error getting memory info: %v", err) + } + + fmt.Println(memory.String()) +} +``` + +Example output from my personal workstation: + +``` +memory (24GB physical, 24GB usable) +``` + +#### Physical versus Usable Memory + +There has been [some](https://github.com/jaypipes/ghw/pull/171) +[confusion](https://github.com/jaypipes/ghw/issues/183) regarding the +difference between the total physical bytes versus total usable bytes of +memory. + +Some of this confusion has been due to a misunderstanding of the term "usable". +As mentioned [above](#inspection!=monitoring), `ghw` does inspection of the +system's capacity. + +A host computer has two capacities when it comes to RAM. The first capacity is +the amount of RAM that is contained in all memory banks (DIMMs) that are +attached to the motherboard. `ghw.MemoryInfo.TotalPhysicalBytes` refers to this +first capacity. + +There is a (usually small) amount of RAM that is consumed by the bootloader +before the operating system is started (booted). Once the bootloader has booted +the operating system, the amount of RAM that may be used by the operating +system and its applications is fixed. `ghw.MemoryInfo.TotalUsableBytes` refers +to this second capacity. + +You can determine the amount of RAM that the bootloader used (that is not made +available to the operating system) by subtracting +`ghw.MemoryInfo.TotalUsableBytes` from `ghw.MemoryInfo.TotalPhysicalBytes`: + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + memory, err := ghw.Memory() + if err != nil { + fmt.Printf("Error getting memory info: %v", err) + } + + phys := memory.TotalPhysicalBytes + usable := memory.TotalUsableBytes + + fmt.Printf("The bootloader consumes %d bytes of RAM\n", phys - usable) +} +``` + +Example output from my personal workstation booted into a Windows10 operating +system with a Linux GRUB bootloader: + +``` +The bootloader consumes 3832720 bytes of RAM +``` + +### CPU + +The `ghw.CPU()` function returns a `ghw.CPUInfo` struct that contains +information about the CPUs on the host system. + +`ghw.CPUInfo` contains the following fields: + +* `ghw.CPUInfo.TotalCores` has the total number of physical cores the host + system contains +* `ghw.CPUInfo.TotalThreads` has the total number of hardware threads the + host system contains +* `ghw.CPUInfo.Processors` is an array of `ghw.Processor` structs, one for each + physical processor package contained in the host + +Each `ghw.Processor` struct contains a number of fields: + +* `ghw.Processor.ID` is the physical processor `uint32` ID according to the + system +* `ghw.Processor.NumCores` is the number of physical cores in the processor + package +* `ghw.Processor.NumThreads` is the number of hardware threads in the processor + package +* `ghw.Processor.Vendor` is a string containing the vendor name +* `ghw.Processor.Model` is a string containing the vendor's model name +* `ghw.Processor.Capabilities` is an array of strings indicating the features + the processor has enabled +* `ghw.Processor.Cores` is an array of `ghw.ProcessorCore` structs that are + packed onto this physical processor + +A `ghw.ProcessorCore` has the following fields: + +* `ghw.ProcessorCore.ID` is the `uint32` identifier that the host gave this + core. Note that this does *not* necessarily equate to a zero-based index of + the core within a physical package. For example, the core IDs for an Intel Core + i7 are 0, 1, 2, 8, 9, and 10 +* `ghw.ProcessorCore.Index` is the zero-based index of the core on the physical + processor package +* `ghw.ProcessorCore.NumThreads` is the number of hardware threads associated + with the core +* `ghw.ProcessorCore.LogicalProcessors` is an array of logical processor IDs + assigned to any processing unit for the core + +```go +package main + +import ( + "fmt" + "math" + "strings" + + "github.com/jaypipes/ghw" +) + +func main() { + cpu, err := ghw.CPU() + if err != nil { + fmt.Printf("Error getting CPU info: %v", err) + } + + fmt.Printf("%v\n", cpu) + + for _, proc := range cpu.Processors { + fmt.Printf(" %v\n", proc) + for _, core := range proc.Cores { + fmt.Printf(" %v\n", core) + } + if len(proc.Capabilities) > 0 { + // pretty-print the (large) block of capability strings into rows + // of 6 capability strings + rows := int(math.Ceil(float64(len(proc.Capabilities)) / float64(6))) + for row := 1; row < rows; row = row + 1 { + rowStart := (row * 6) - 1 + rowEnd := int(math.Min(float64(rowStart+6), float64(len(proc.Capabilities)))) + rowElems := proc.Capabilities[rowStart:rowEnd] + capStr := strings.Join(rowElems, " ") + if row == 1 { + fmt.Printf(" capabilities: [%s\n", capStr) + } else if rowEnd < len(proc.Capabilities) { + fmt.Printf(" %s\n", capStr) + } else { + fmt.Printf(" %s]\n", capStr) + } + } + } + } +} +``` + +Example output from my personal workstation: + +``` +cpu (1 physical package, 6 cores, 12 hardware threads) + physical package #0 (6 cores, 12 hardware threads) + processor core #0 (2 threads), logical processors [0 6] + processor core #1 (2 threads), logical processors [1 7] + processor core #2 (2 threads), logical processors [2 8] + processor core #3 (2 threads), logical processors [3 9] + processor core #4 (2 threads), logical processors [4 10] + processor core #5 (2 threads), logical processors [5 11] + capabilities: [msr pae mce cx8 apic sep + mtrr pge mca cmov pat pse36 + clflush dts acpi mmx fxsr sse + sse2 ss ht tm pbe syscall + nx pdpe1gb rdtscp lm constant_tsc arch_perfmon + pebs bts rep_good nopl xtopology nonstop_tsc + cpuid aperfmperf pni pclmulqdq dtes64 monitor + ds_cpl vmx est tm2 ssse3 cx16 + xtpr pdcm pcid sse4_1 sse4_2 popcnt + aes lahf_lm pti retpoline tpr_shadow vnmi + flexpriority ept vpid dtherm ida arat] +``` + +### Block storage + +Information about the host computer's local block storage is returned from the +`ghw.Block()` function. This function returns a pointer to a `ghw.BlockInfo` +struct. + +The `ghw.BlockInfo` struct contains two fields: + +* `ghw.BlockInfo.TotalPhysicalBytes` contains the amount of physical block + storage on the host +* `ghw.BlockInfo.Disks` is an array of pointers to `ghw.Disk` structs, one for + each disk drive found by the system + +Each `ghw.Disk` struct contains the following fields: + +* `ghw.Disk.Name` contains a string with the short name of the disk, e.g. "sda" +* `ghw.Disk.SizeBytes` contains the amount of storage the disk provides +* `ghw.Disk.PhysicalBlockSizeBytes` contains the size of the physical blocks + used on the disk, in bytes +* `ghw.Disk.IsRemovable` contains a boolean indicating if the disk drive is + removable +* `ghw.Disk.DriveType` is the type of drive. It is of type `ghw.DriveType` + which has a `ghw.DriveType.String()` method that can be called to return a + string representation of the bus. This string will be "HDD", "FDD", "ODD", + or "SSD", which correspond to a hard disk drive (rotational), floppy drive, + optical (CD/DVD) drive and solid-state drive. +* `ghw.Disk.StorageController` is the type of storage controller/drive. It is + of type `ghw.StorageController` which has a `ghw.StorageController.String()` + method that can be called to return a string representation of the bus. This + string will be "SCSI", "IDE", "virtio", "MMC", or "NVMe" +* `ghw.Disk.NUMANodeID` is the numeric index of the NUMA node this disk is + local to, or -1 +* `ghw.Disk.Vendor` contains a string with the name of the hardware vendor for + the disk drive +* `ghw.Disk.Model` contains a string with the vendor-assigned disk model name +* `ghw.Disk.SerialNumber` contains a string with the disk's serial number +* `ghw.Disk.WWN` contains a string with the disk's + [World Wide Name](https://en.wikipedia.org/wiki/World_Wide_Name) +* `ghw.Disk.Partitions` contains an array of pointers to `ghw.Partition` + structs, one for each partition on the disk + +Each `ghw.Partition` struct contains these fields: + +* `ghw.Partition.Name` contains a string with the short name of the partition, + e.g. "sda1" +* `ghw.Partition.SizeBytes` contains the amount of storage the partition + provides +* `ghw.Partition.MountPoint` contains a string with the partition's mount + point, or "" if no mount point was discovered +* `ghw.Partition.Type` contains a string indicated the filesystem type for the + partition, or "" if the system could not determine the type +* `ghw.Partition.IsReadOnly` is a bool indicating the partition is read-only +* `ghw.Partition.Disk` is a pointer to the `ghw.Disk` object associated with + the partition. This will be `nil` if the `ghw.Partition` struct was returned + by the `ghw.DiskPartitions()` library function. +* `ghw.Partition.UUID` is a string containing the volume UUID on Linux, the + partition UUID on MacOS and nothing on Windows. + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + block, err := ghw.Block() + if err != nil { + fmt.Printf("Error getting block storage info: %v", err) + } + + fmt.Printf("%v\n", block) + + for _, disk := range block.Disks { + fmt.Printf(" %v\n", disk) + for _, part := range disk.Partitions { + fmt.Printf(" %v\n", part) + } + } +} +``` + +Example output from my personal workstation: + +``` +block storage (1 disk, 2TB physical storage) + sda HDD (2TB) SCSI [@pci-0000:04:00.0-scsi-0:1:0:0 (node #0)] vendor=LSI model=Logical_Volume serial=600508e000000000f8253aac9a1abd0c WWN=0x600508e000000000f8253aac9a1abd0c + /dev/sda1 (100MB) + /dev/sda2 (187GB) + /dev/sda3 (449MB) + /dev/sda4 (1KB) + /dev/sda5 (15GB) + /dev/sda6 (2TB) [ext4] mounted@/ +``` + +> Note that `ghw` looks in the udev runtime database for some information. If +> you are using `ghw` in a container, remember to bind mount `/dev/disk` and +> `/run` into your container, otherwise `ghw` won't be able to query the udev +> DB or sysfs paths for information. + +### Topology + +> **NOTE**: Topology support is currently Linux-only. Windows support is +> [planned](https://github.com/jaypipes/ghw/issues/166). + +Information about the host computer's architecture (NUMA vs. SMP), the host's +node layout and processor caches can be retrieved from the `ghw.Topology()` +function. This function returns a pointer to a `ghw.TopologyInfo` struct. + +The `ghw.TopologyInfo` struct contains two fields: + +* `ghw.TopologyInfo.Architecture` contains an enum with the value `ghw.NUMA` or + `ghw.SMP` depending on what the topology of the system is +* `ghw.TopologyInfo.Nodes` is an array of pointers to `ghw.TopologyNode` + structs, one for each topology node (typically physical processor package) + found by the system + +Each `ghw.TopologyNode` struct contains the following fields: + +* `ghw.TopologyNode.ID` is the system's `uint32` identifier for the node +* `ghw.TopologyNode.Cores` is an array of pointers to `ghw.ProcessorCore` structs that + are contained in this node +* `ghw.TopologyNode.Caches` is an array of pointers to `ghw.MemoryCache` structs that + represent the low-level caches associated with processors and cores on the + system +* `ghw.TopologyNode.Distance` is an array of distances between NUMA nodes as reported + by the system. + +See above in the [CPU](#cpu) section for information about the +`ghw.ProcessorCore` struct and how to use and query it. + +Each `ghw.MemoryCache` struct contains the following fields: + +* `ghw.MemoryCache.Type` is an enum that contains one of `ghw.DATA`, + `ghw.INSTRUCTION` or `ghw.UNIFIED` depending on whether the cache stores CPU + instructions, program data, or both +* `ghw.MemoryCache.Level` is a positive integer indicating how close the cache + is to the processor +* `ghw.MemoryCache.SizeBytes` is an integer containing the number of bytes the + cache can contain +* `ghw.MemoryCache.LogicalProcessors` is an array of integers representing the + logical processors that use the cache + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + topology, err := ghw.Topology() + if err != nil { + fmt.Printf("Error getting topology info: %v", err) + } + + fmt.Printf("%v\n", topology) + + for _, node := range topology.Nodes { + fmt.Printf(" %v\n", node) + for _, cache := range node.Caches { + fmt.Printf(" %v\n", cache) + } + } +} +``` + +Example output from my personal workstation: + +``` +topology SMP (1 nodes) + node #0 (6 cores) + L1i cache (32 KB) shared with logical processors: 3,9 + L1i cache (32 KB) shared with logical processors: 2,8 + L1i cache (32 KB) shared with logical processors: 11,5 + L1i cache (32 KB) shared with logical processors: 10,4 + L1i cache (32 KB) shared with logical processors: 0,6 + L1i cache (32 KB) shared with logical processors: 1,7 + L1d cache (32 KB) shared with logical processors: 11,5 + L1d cache (32 KB) shared with logical processors: 10,4 + L1d cache (32 KB) shared with logical processors: 3,9 + L1d cache (32 KB) shared with logical processors: 1,7 + L1d cache (32 KB) shared with logical processors: 0,6 + L1d cache (32 KB) shared with logical processors: 2,8 + L2 cache (256 KB) shared with logical processors: 2,8 + L2 cache (256 KB) shared with logical processors: 3,9 + L2 cache (256 KB) shared with logical processors: 0,6 + L2 cache (256 KB) shared with logical processors: 10,4 + L2 cache (256 KB) shared with logical processors: 1,7 + L2 cache (256 KB) shared with logical processors: 11,5 + L3 cache (12288 KB) shared with logical processors: 0,1,10,11,2,3,4,5,6,7,8,9 +``` + +### Network + +Information about the host computer's networking hardware is returned from the +`ghw.Network()` function. This function returns a pointer to a +`ghw.NetworkInfo` struct. + +The `ghw.NetworkInfo` struct contains one field: + +* `ghw.NetworkInfo.NICs` is an array of pointers to `ghw.NIC` structs, one + for each network interface controller found for the systen + +Each `ghw.NIC` struct contains the following fields: + +* `ghw.NIC.Name` is the system's identifier for the NIC +* `ghw.NIC.MacAddress` is the MAC address for the NIC, if any +* `ghw.NIC.IsVirtual` is a boolean indicating if the NIC is a virtualized + device +* `ghw.NIC.Capabilities` is an array of pointers to `ghw.NICCapability` structs + that can describe the things the NIC supports. These capabilities match the + returned values from the `ethtool -k ` call on Linux +* `ghw.NIC.PCIAddress` is the PCI device address of the device backing the NIC. + this is not-nil only if the backing device is indeed a PCI device; more backing + devices (e.g. USB) will be added in future versions. + +The `ghw.NICCapability` struct contains the following fields: + +* `ghw.NICCapability.Name` is the string name of the capability (e.g. + "tcp-segmentation-offload") +* `ghw.NICCapability.IsEnabled` is a boolean indicating whether the capability + is currently enabled/active on the NIC +* `ghw.NICCapability.CanEnable` is a boolean indicating whether the capability + may be enabled + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + net, err := ghw.Network() + if err != nil { + fmt.Printf("Error getting network info: %v", err) + } + + fmt.Printf("%v\n", net) + + for _, nic := range net.NICs { + fmt.Printf(" %v\n", nic) + + enabledCaps := make([]int, 0) + for x, cap := range nic.Capabilities { + if cap.IsEnabled { + enabledCaps = append(enabledCaps, x) + } + } + if len(enabledCaps) > 0 { + fmt.Printf(" enabled capabilities:\n") + for _, x := range enabledCaps { + fmt.Printf(" - %s\n", nic.Capabilities[x].Name) + } + } + } +} +``` + +Example output from my personal laptop: + +``` +net (3 NICs) + docker0 + enabled capabilities: + - tx-checksumming + - tx-checksum-ip-generic + - scatter-gather + - tx-scatter-gather + - tx-scatter-gather-fraglist + - tcp-segmentation-offload + - tx-tcp-segmentation + - tx-tcp-ecn-segmentation + - tx-tcp-mangleid-segmentation + - tx-tcp6-segmentation + - udp-fragmentation-offload + - generic-segmentation-offload + - generic-receive-offload + - tx-vlan-offload + - highdma + - tx-lockless + - netns-local + - tx-gso-robust + - tx-fcoe-segmentation + - tx-gre-segmentation + - tx-gre-csum-segmentation + - tx-ipxip4-segmentation + - tx-ipxip6-segmentation + - tx-udp_tnl-segmentation + - tx-udp_tnl-csum-segmentation + - tx-gso-partial + - tx-sctp-segmentation + - tx-esp-segmentation + - tx-vlan-stag-hw-insert + enp58s0f1 + enabled capabilities: + - rx-checksumming + - generic-receive-offload + - rx-vlan-offload + - tx-vlan-offload + - highdma + wlp59s0 + enabled capabilities: + - scatter-gather + - tx-scatter-gather + - generic-segmentation-offload + - generic-receive-offload + - highdma + - netns-local +``` + +### PCI + +`ghw` contains a PCI database inspection and querying facility that allows +developers to not only gather information about devices on a local PCI bus but +also query for information about hardware device classes, vendor and product +information. + +**NOTE**: Parsing of the PCI-IDS file database is provided by the separate +[github.com/jaypipes/pcidb library](http://github.com/jaypipes/pcidb). You can +read that library's README for more information about the various structs that +are exposed on the `ghw.PCIInfo` struct. + +The `ghw.PCI()` function returns a `ghw.PCIInfo` struct. The `ghw.PCIInfo` +struct contains a number of fields that may be queried for PCI information: + +* `ghw.PCIInfo.Devices` is a slice of pointers to `ghw.PCIDevice` structs that + describe the PCI devices on the host system +* `ghw.PCIInfo.Classes` is a map, keyed by the PCI class ID (a hex-encoded + string) of pointers to `pcidb.Class` structs, one for each class of PCI + device known to `ghw` + (**DEPRECATED**, will be removed in `ghw` `v1.0`. Use the + `github.com/jaypipes/pcidb` library for exploring PCI database information) +* `ghw.PCIInfo.Vendors` is a map, keyed by the PCI vendor ID (a hex-encoded + string) of pointers to `pcidb.Vendor` structs, one for each PCI vendor + known to `ghw` + (**DEPRECATED**, will be removed in `ghw` `v1.0`. Use the + `github.com/jaypipes/pcidb` library for exploring PCI database information) +* `ghw.PCIInfo.Products` is a map, keyed by the PCI product ID (a hex-encoded + string) of pointers to `pcidb.Product` structs, one for each PCI product + known to `ghw` + (**DEPRECATED**, will be removed in `ghw` `v1.0`. Use the + `github.com/jaypipes/pcidb` library for exploring PCI database information) + +**NOTE**: PCI products are often referred to by their "device ID". We use +the term "product ID" in `ghw` because it more accurately reflects what the +identifier is for: a specific product line produced by the vendor. + +The `ghw.PCIDevice` struct has the following fields: + +* `ghw.PCIDevice.Vendor` is a pointer to a `pcidb.Vendor` struct that + describes the device's primary vendor. This will always be non-nil. +* `ghw.PCIDevice.Product` is a pointer to a `pcidb.Product` struct that + describes the device's primary product. This will always be non-nil. +* `ghw.PCIDevice.Subsystem` is a pointer to a `pcidb.Product` struct that + describes the device's secondary/sub-product. This will always be non-nil. +* `ghw.PCIDevice.Class` is a pointer to a `pcidb.Class` struct that + describes the device's class. This will always be non-nil. +* `ghw.PCIDevice.Subclass` is a pointer to a `pcidb.Subclass` struct + that describes the device's subclass. This will always be non-nil. +* `ghw.PCIDevice.ProgrammingInterface` is a pointer to a + `pcidb.ProgrammingInterface` struct that describes the device subclass' + programming interface. This will always be non-nil. + +The `ghw.PCIAddress` (which is an alias for the `ghw.pci.address.Address` +struct) contains the PCI address fields. It has a `ghw.PCIAddress.String()` +method that returns the canonical Domain:Bus:Slot.Function ([D]BSF) +representation of this Address + +#### Finding a PCI device by PCI address + +In addition to the above information, the `ghw.PCIInfo` struct has the +following method: + +* `ghw.PCIInfo.GetDevice(address string)` + +The following code snippet shows how to call the `ghw.PCIInfo.ListDevices()` +method and output a simple list of PCI address and vendor/product information: + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + pci, err := ghw.PCI() + if err != nil { + fmt.Printf("Error getting PCI info: %v", err) + } + fmt.Printf("host PCI devices:\n") + fmt.Println("====================================================") + + for _, device := range pci.Devices { + vendor := device.Vendor + vendorName := vendor.Name + if len(vendor.Name) > 20 { + vendorName = string([]byte(vendorName)[0:17]) + "..." + } + product := device.Product + productName := product.Name + if len(product.Name) > 40 { + productName = string([]byte(productName)[0:37]) + "..." + } + fmt.Printf("%-12s\t%-20s\t%-40s\n", device.Address, vendorName, productName) + } +} +``` + +on my local workstation the output of the above looks like the following: + +``` +host PCI devices: +==================================================== +0000:00:00.0 Intel Corporation 5520/5500/X58 I/O Hub to ESI Port +0000:00:01.0 Intel Corporation 5520/5500/X58 I/O Hub PCI Express Roo... +0000:00:02.0 Intel Corporation 5520/5500/X58 I/O Hub PCI Express Roo... +0000:00:03.0 Intel Corporation 5520/5500/X58 I/O Hub PCI Express Roo... +0000:00:07.0 Intel Corporation 5520/5500/X58 I/O Hub PCI Express Roo... +0000:00:10.0 Intel Corporation 7500/5520/5500/X58 Physical and Link ... +0000:00:10.1 Intel Corporation 7500/5520/5500/X58 Routing and Protoc... +0000:00:14.0 Intel Corporation 7500/5520/5500/X58 I/O Hub System Man... +0000:00:14.1 Intel Corporation 7500/5520/5500/X58 I/O Hub GPIO and S... +0000:00:14.2 Intel Corporation 7500/5520/5500/X58 I/O Hub Control St... +0000:00:14.3 Intel Corporation 7500/5520/5500/X58 I/O Hub Throttle R... +0000:00:19.0 Intel Corporation 82567LF-2 Gigabit Network Connection +0000:00:1a.0 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr... +0000:00:1a.1 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr... +0000:00:1a.2 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr... +0000:00:1a.7 Intel Corporation 82801JI (ICH10 Family) USB2 EHCI Cont... +0000:00:1b.0 Intel Corporation 82801JI (ICH10 Family) HD Audio Contr... +0000:00:1c.0 Intel Corporation 82801JI (ICH10 Family) PCI Express Ro... +0000:00:1c.1 Intel Corporation 82801JI (ICH10 Family) PCI Express Po... +0000:00:1c.4 Intel Corporation 82801JI (ICH10 Family) PCI Express Ro... +0000:00:1d.0 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr... +0000:00:1d.1 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr... +0000:00:1d.2 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr... +0000:00:1d.7 Intel Corporation 82801JI (ICH10 Family) USB2 EHCI Cont... +0000:00:1e.0 Intel Corporation 82801 PCI Bridge +0000:00:1f.0 Intel Corporation 82801JIR (ICH10R) LPC Interface Contr... +0000:00:1f.2 Intel Corporation 82801JI (ICH10 Family) SATA AHCI Cont... +0000:00:1f.3 Intel Corporation 82801JI (ICH10 Family) SMBus Controller +0000:01:00.0 NEC Corporation uPD720200 USB 3.0 Host Controller +0000:02:00.0 Marvell Technolog... 88SE9123 PCIe SATA 6.0 Gb/s controller +0000:02:00.1 Marvell Technolog... 88SE912x IDE Controller +0000:03:00.0 NVIDIA Corporation GP107 [GeForce GTX 1050 Ti] +0000:03:00.1 NVIDIA Corporation UNKNOWN +0000:04:00.0 LSI Logic / Symbi... SAS2004 PCI-Express Fusion-MPT SAS-2 ... +0000:06:00.0 Qualcomm Atheros AR5418 Wireless Network Adapter [AR50... +0000:08:03.0 LSI Corporation FW322/323 [TrueFire] 1394a Controller +0000:3f:00.0 Intel Corporation UNKNOWN +0000:3f:00.1 Intel Corporation Xeon 5600 Series QuickPath Architectu... +0000:3f:02.0 Intel Corporation Xeon 5600 Series QPI Link 0 +0000:3f:02.1 Intel Corporation Xeon 5600 Series QPI Physical 0 +0000:3f:02.2 Intel Corporation Xeon 5600 Series Mirror Port Link 0 +0000:3f:02.3 Intel Corporation Xeon 5600 Series Mirror Port Link 1 +0000:3f:03.0 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:03.1 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:03.4 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:04.0 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:04.1 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:04.2 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:04.3 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:05.0 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:05.1 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:05.2 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:05.3 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:06.0 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:06.1 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:06.2 Intel Corporation Xeon 5600 Series Integrated Memory Co... +0000:3f:06.3 Intel Corporation Xeon 5600 Series Integrated Memory Co... +``` + +The following code snippet shows how to call the `ghw.PCIInfo.GetDevice()` +method and use its returned `ghw.PCIDevice` struct pointer: + +```go +package main + +import ( + "fmt" + "os" + + "github.com/jaypipes/ghw" +) + +func main() { + pci, err := ghw.PCI() + if err != nil { + fmt.Printf("Error getting PCI info: %v", err) + } + + addr := "0000:00:00.0" + if len(os.Args) == 2 { + addr = os.Args[1] + } + fmt.Printf("PCI device information for %s\n", addr) + fmt.Println("====================================================") + deviceInfo := pci.GetDevice(addr) + if deviceInfo == nil { + fmt.Printf("could not retrieve PCI device information for %s\n", addr) + return + } + + vendor := deviceInfo.Vendor + fmt.Printf("Vendor: %s [%s]\n", vendor.Name, vendor.ID) + product := deviceInfo.Product + fmt.Printf("Product: %s [%s]\n", product.Name, product.ID) + subsystem := deviceInfo.Subsystem + subvendor := pci.Vendors[subsystem.VendorID] + subvendorName := "UNKNOWN" + if subvendor != nil { + subvendorName = subvendor.Name + } + fmt.Printf("Subsystem: %s [%s] (Subvendor: %s)\n", subsystem.Name, subsystem.ID, subvendorName) + class := deviceInfo.Class + fmt.Printf("Class: %s [%s]\n", class.Name, class.ID) + subclass := deviceInfo.Subclass + fmt.Printf("Subclass: %s [%s]\n", subclass.Name, subclass.ID) + progIface := deviceInfo.ProgrammingInterface + fmt.Printf("Programming Interface: %s [%s]\n", progIface.Name, progIface.ID) +} +``` + +Here's a sample output from my local workstation: + +``` +PCI device information for 0000:03:00.0 +==================================================== +Vendor: NVIDIA Corporation [10de] +Product: GP107 [GeForce GTX 1050 Ti] [1c82] +Subsystem: UNKNOWN [8613] (Subvendor: ASUSTeK Computer Inc.) +Class: Display controller [03] +Subclass: VGA compatible controller [00] +Programming Interface: VGA controller [00] +``` + +### GPU + +Information about the host computer's graphics hardware is returned from the +`ghw.GPU()` function. This function returns a pointer to a `ghw.GPUInfo` +struct. + +The `ghw.GPUInfo` struct contains one field: + +* `ghw.GPUInfo.GraphicCards` is an array of pointers to `ghw.GraphicsCard` + structs, one for each graphics card found for the systen + +Each `ghw.GraphicsCard` struct contains the following fields: + +* `ghw.GraphicsCard.Index` is the system's numeric zero-based index for the + card on the bus +* `ghw.GraphicsCard.Address` is the PCI address for the graphics card +* `ghw.GraphicsCard.DeviceInfo` is a pointer to a `ghw.PCIDevice` struct + describing the graphics card. This may be `nil` if no PCI device information + could be determined for the card. +* `ghw.GraphicsCard.Node` is an pointer to a `ghw.TopologyNode` struct that the + GPU/graphics card is affined to. On non-NUMA systems, this will always be + `nil`. + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + gpu, err := ghw.GPU() + if err != nil { + fmt.Printf("Error getting GPU info: %v", err) + } + + fmt.Printf("%v\n", gpu) + + for _, card := range gpu.GraphicsCards { + fmt.Printf(" %v\n", card) + } +} +``` + +Example output from my personal workstation: + +``` +gpu (1 graphics card) + card #0 @0000:03:00.0 -> class: 'Display controller' vendor: 'NVIDIA Corporation' product: 'GP107 [GeForce GTX 1050 Ti]' +``` + +**NOTE**: You can [read more](#pci) about the fields of the `ghw.PCIDevice` +struct if you'd like to dig deeper into PCI subsystem and programming interface +information + +**NOTE**: You can [read more](#topology) about the fields of the +`ghw.TopologyNode` struct if you'd like to dig deeper into the NUMA/topology +subsystem + +### Chassis + +The host's chassis information is accessible with the `ghw.Chassis()` function. This +function returns a pointer to a `ghw.ChassisInfo` struct. + +The `ghw.ChassisInfo` struct contains multiple fields: + +* `ghw.ChassisInfo.AssetTag` is a string with the chassis asset tag +* `ghw.ChassisInfo.SerialNumber` is a string with the chassis serial number +* `ghw.ChassisInfo.Type` is a string with the chassis type *code* +* `ghw.ChassisInfo.TypeDescription` is a string with a description of the chassis type +* `ghw.ChassisInfo.Vendor` is a string with the chassis vendor +* `ghw.ChassisInfo.Version` is a string with the chassis version + +**NOTE**: These fields are often missing for non-server hardware. Don't be +surprised to see empty string or "None" values. + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + chassis, err := ghw.Chassis() + if err != nil { + fmt.Printf("Error getting chassis info: %v", err) + } + + fmt.Printf("%v\n", chassis) +} +``` + +Example output from my personal workstation: + +``` +chassis type=Desktop vendor=System76 version=thelio-r1 +``` + +**NOTE**: Some of the values such as serial numbers are shown as unknown because +the Linux kernel by default disallows access to those fields if you're not running +as root. They will be populated if it runs as root or otherwise you may see warnings +like the following: + +``` +WARNING: Unable to read chassis_serial: open /sys/class/dmi/id/chassis_serial: permission denied +``` + +You can ignore them or use the [Disabling warning messages](#disabling-warning-messages) +feature to quiet things down. + +### BIOS + +The host's basis input/output system (BIOS) information is accessible with the `ghw.BIOS()` function. This +function returns a pointer to a `ghw.BIOSInfo` struct. + +The `ghw.BIOSInfo` struct contains multiple fields: + +* `ghw.BIOSInfo.Vendor` is a string with the BIOS vendor +* `ghw.BIOSInfo.Version` is a string with the BIOS version +* `ghw.BIOSInfo.Date` is a string with the date the BIOS was flashed/created + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + bios, err := ghw.BIOS() + if err != nil { + fmt.Printf("Error getting BIOS info: %v", err) + } + + fmt.Printf("%v\n", bios) +} +``` + +Example output from my personal workstation: + +``` +bios vendor=System76 version=F2 Z5 date=11/14/2018 +``` + +### Baseboard + +The host's baseboard information is accessible with the `ghw.Baseboard()` function. This +function returns a pointer to a `ghw.BaseboardInfo` struct. + +The `ghw.BaseboardInfo` struct contains multiple fields: + +* `ghw.BaseboardInfo.AssetTag` is a string with the baseboard asset tag +* `ghw.BaseboardInfo.SerialNumber` is a string with the baseboard serial number +* `ghw.BaseboardInfo.Vendor` is a string with the baseboard vendor +* `ghw.BaseboardInfo.Product` is a string with the baseboard name on Linux and + Product on Windows +* `ghw.BaseboardInfo.Version` is a string with the baseboard version + +**NOTE**: These fields are often missing for non-server hardware. Don't be +surprised to see empty string or "None" values. + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + baseboard, err := ghw.Baseboard() + if err != nil { + fmt.Printf("Error getting baseboard info: %v", err) + } + + fmt.Printf("%v\n", baseboard) +} +``` + +Example output from my personal workstation: + +``` +baseboard vendor=System76 version=thelio-r1 +``` + +**NOTE**: Some of the values such as serial numbers are shown as unknown because +the Linux kernel by default disallows access to those fields if you're not running +as root. They will be populated if it runs as root or otherwise you may see warnings +like the following: + +``` +WARNING: Unable to read board_serial: open /sys/class/dmi/id/board_serial: permission denied +``` + +You can ignore them or use the [Disabling warning messages](#disabling-warning-messages) +feature to quiet things down. + +### Product + +The host's product information is accessible with the `ghw.Product()` function. This +function returns a pointer to a `ghw.ProductInfo` struct. + +The `ghw.ProductInfo` struct contains multiple fields: + +* `ghw.ProductInfo.Family` is a string describing the product family +* `ghw.ProductInfo.Name` is a string with the product name +* `ghw.ProductInfo.SerialNumber` is a string with the product serial number +* `ghw.ProductInfo.UUID` is a string with the product UUID +* `ghw.ProductInfo.SKU` is a string with the product stock unit identifier (SKU) +* `ghw.ProductInfo.Vendor` is a string with the product vendor +* `ghw.ProductInfo.Version` is a string with the product version + +**NOTE**: These fields are often missing for non-server hardware. Don't be +surprised to see empty string, "Default string" or "None" values. + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + product, err := ghw.Product() + if err != nil { + fmt.Printf("Error getting product info: %v", err) + } + + fmt.Printf("%v\n", product) +} +``` + +Example output from my personal workstation: + +``` +product family=Default string name=Thelio vendor=System76 sku=Default string version=thelio-r1 +``` + +**NOTE**: Some of the values such as serial numbers are shown as unknown because +the Linux kernel by default disallows access to those fields if you're not running +as root. They will be populated if it runs as root or otherwise you may see warnings +like the following: + +``` +WARNING: Unable to read product_serial: open /sys/class/dmi/id/product_serial: permission denied +``` + +You can ignore them or use the [Disabling warning messages](#disabling-warning-messages) +feature to quiet things down. + +## Serialization + +All of the `ghw` `XXXInfo` structs -- e.g. `ghw.CPUInfo` -- have two methods +for producing a serialized JSON or YAML string representation of the contained +information: + +* `JSONString()` returns a string containing the information serialized into + JSON. It accepts a single boolean parameter indicating whether to use + indentation when outputting the string +* `YAMLString()` returns a string containing the information serialized into + YAML + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + mem, err := ghw.Memory() + if err != nil { + fmt.Printf("Error getting memory info: %v", err) + } + + fmt.Printf("%s", mem.YAMLString()) +} +``` + +the above example code prints the following out on my local workstation: + +``` +memory: + supported_page_sizes: + - 1073741824 + - 2097152 + total_physical_bytes: 25263415296 + total_usable_bytes: 25263415296 +``` + +## Calling external programs + +By default ghw may call external programs, for example `ethtool`, to learn about hardware capabilities. +In some rare circumstances it may be useful to opt out from this behaviour and rely only on the data +provided by pseudo-filesystems, like sysfs. +The most common use case is when we want to consume a snapshot from ghw. In these cases the information +provided by tools will be most likely inconsistent with the data from the snapshot - they will run on +a different host! +To prevent ghw from calling external tools, set the environs variable `GHW_DISABLE_TOOLS` to any value, +or, programmatically, check the `WithDisableTools` function. +The default behaviour of ghw is to call external tools when available. + +## Developers + +Contributions to `ghw` are welcomed! Fork the repo on GitHub and submit a pull +request with your proposed changes. Or, feel free to log an issue for a feature +request or bug report. + +### Running tests + +You can run unit tests easily using the `make test` command, like so: + + +``` +[jaypipes@uberbox ghw]$ make test +go test github.com/jaypipes/ghw github.com/jaypipes/ghw/cmd/ghwc +ok github.com/jaypipes/ghw 0.084s +? github.com/jaypipes/ghw/cmd/ghwc [no test files] +``` diff --git a/vendor/github.com/jaypipes/ghw/SNAPSHOT.md b/vendor/github.com/jaypipes/ghw/SNAPSHOT.md new file mode 100644 index 000000000..696a3ea63 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/SNAPSHOT.md @@ -0,0 +1,45 @@ +# ghw snapshots + +For ghw, snapshots are partial clones of the `/proc`, `/sys` (et. al.) subtrees copied from arbitrary +machines, which ghw can consume later. "partial" is because the snapshot doesn't need to contain a +complete copy of all the filesystem subtree (that is doable but inpractical). It only needs to contain +the paths ghw cares about. The snapshot concept was introduced [to make ghw easier to test](https://github.com/jaypipes/ghw/issues/66). + +## Create and consume snapshot + +The recommended way to create snapshots for ghw is to use the `ghw-snapshot` tool. +This tool is maintained by the ghw authors, and snapshots created with this tool are guaranteed to work. + +To consume the ghw snapshots, please check the `README.md` document. + +## Snapshot design and definitions + +The remainder of this document will describe how a snapshot looks like and provides rationale for all the major design decisions. +Even though this document aims to provide all the necessary information to understand how ghw creates snapshots and what you should +expect, we recommend to check also the [project issues](https://github.com/jaypipes/ghw/issues) and the `git` history to have the full picture. + +### Scope + +ghw supports snapshots only on linux platforms. This restriction may be lifted in future releases. +Snapshots must be consumable in the following supported ways: + +1. (way 1) from docker (or podman), mounting them as volumes. See `hack/run-against-snapshot.sh` +2. (way 2) using the environment variables `GHW_SNAPSHOT_*`. See `README.md` for the full documentation. + +Other combinations are possible, but are unsupported and may stop working any time. +You should depend only on the supported ways to consume snapshots. + +### Snapshot content constraints + +Stemming from the use cases, the snapshot content must have the following properties: + +0. (constraint 0) MUST contain the same information as live system (obviously). Whatever you learn from a live system, you MUST be able to learn from a snapshot. +1. (constraint 1) MUST NOT require any post processing before it is consumable besides, obviously, unpacking the `.tar.gz` on the right directory - and pointing ghw to that directory. +2. (constraint 2) MUST NOT require any special handling nor special code path in ghw. From ghw perspective running against a live system or against a snapshot should be completely transparent. +3. (constraint 3) MUST contain only data - no executable code is allowed ever. This makes snapshots trivially safe to share and consume. +4. (constraint 4) MUST NOT contain any personally-identifiable data. Data gathered into a snapshot is for testing and troubleshooting purposes and should be safe to send to troubleshooters to analyze. + +It must be noted that trivially cloning subtrees from `/proc` and `/sys` and creating a tarball out of them doesn't work +because both pseudo filesystems make use of symlinks, and [docker doesn't really play nice with symlinks](https://github.com/jaypipes/ghw/commit/f8ffd4d24e62eb9017511f072ccf51f13d4a3399). +This conflcits with (way 1) above. + diff --git a/vendor/github.com/jaypipes/ghw/alias.go b/vendor/github.com/jaypipes/ghw/alias.go new file mode 100644 index 000000000..66b4f2b95 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/alias.go @@ -0,0 +1,148 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package ghw + +import ( + "github.com/jaypipes/ghw/pkg/baseboard" + "github.com/jaypipes/ghw/pkg/bios" + "github.com/jaypipes/ghw/pkg/block" + "github.com/jaypipes/ghw/pkg/chassis" + "github.com/jaypipes/ghw/pkg/cpu" + "github.com/jaypipes/ghw/pkg/gpu" + "github.com/jaypipes/ghw/pkg/memory" + "github.com/jaypipes/ghw/pkg/net" + "github.com/jaypipes/ghw/pkg/option" + "github.com/jaypipes/ghw/pkg/pci" + pciaddress "github.com/jaypipes/ghw/pkg/pci/address" + "github.com/jaypipes/ghw/pkg/product" + "github.com/jaypipes/ghw/pkg/topology" +) + +type WithOption = option.Option + +var ( + WithChroot = option.WithChroot + WithSnapshot = option.WithSnapshot + WithAlterter = option.WithAlerter + WithNullAlterter = option.WithNullAlerter + // match the existing environ variable to minimize surprises + WithDisableWarnings = option.WithNullAlerter + WithDisableTools = option.WithDisableTools +) + +type SnapshotOptions = option.SnapshotOptions + +type CPUInfo = cpu.Info + +var ( + CPU = cpu.New +) + +type MemoryInfo = memory.Info +type MemoryCacheType = memory.CacheType +type MemoryModule = memory.Module + +const ( + MEMORY_CACHE_TYPE_UNIFIED = memory.CACHE_TYPE_UNIFIED + MEMORY_CACHE_TYPE_INSTRUCTION = memory.CACHE_TYPE_INSTRUCTION + MEMORY_CACHE_TYPE_DATA = memory.CACHE_TYPE_DATA +) + +var ( + Memory = memory.New +) + +type BlockInfo = block.Info +type Disk = block.Disk +type Partition = block.Partition + +var ( + Block = block.New +) + +type DriveType = block.DriveType + +const ( + DRIVE_TYPE_UNKNOWN = block.DRIVE_TYPE_UNKNOWN + DRIVE_TYPE_HDD = block.DRIVE_TYPE_HDD + DRIVE_TYPE_FDD = block.DRIVE_TYPE_FDD + DRIVE_TYPE_ODD = block.DRIVE_TYPE_ODD + DRIVE_TYPE_SSD = block.DRIVE_TYPE_SSD +) + +type StorageController = block.StorageController + +const ( + STORAGE_CONTROLLER_UNKNOWN = block.STORAGE_CONTROLLER_UNKNOWN + STORAGE_CONTROLLER_IDE = block.STORAGE_CONTROLLER_IDE + STORAGE_CONTROLLER_SCSI = block.STORAGE_CONTROLLER_SCSI + STORAGE_CONTROLLER_NVME = block.STORAGE_CONTROLLER_NVME + STORAGE_CONTROLLER_VIRTIO = block.STORAGE_CONTROLLER_VIRTIO + STORAGE_CONTROLLER_MMC = block.STORAGE_CONTROLLER_MMC +) + +type NetworkInfo = net.Info +type NIC = net.NIC +type NICCapability = net.NICCapability + +var ( + Network = net.New +) + +type BIOSInfo = bios.Info + +var ( + BIOS = bios.New +) + +type ChassisInfo = chassis.Info + +var ( + Chassis = chassis.New +) + +type BaseboardInfo = baseboard.Info + +var ( + Baseboard = baseboard.New +) + +type TopologyInfo = topology.Info +type TopologyNode = topology.Node + +var ( + Topology = topology.New +) + +type Architecture = topology.Architecture + +const ( + ARCHITECTURE_SMP = topology.ARCHITECTURE_SMP + ARCHITECTURE_NUMA = topology.ARCHITECTURE_NUMA +) + +type PCIInfo = pci.Info +type PCIAddress = pciaddress.Address +type PCIDevice = pci.Device + +var ( + PCI = pci.New + PCIAddressFromString = pciaddress.FromString +) + +type ProductInfo = product.Info + +var ( + Product = product.New +) + +type GPUInfo = gpu.Info +type GraphicsCard = gpu.GraphicsCard + +var ( + GPU = gpu.New +) diff --git a/vendor/github.com/jaypipes/ghw/doc.go b/vendor/github.com/jaypipes/ghw/doc.go new file mode 100644 index 000000000..9ae0c30ae --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/doc.go @@ -0,0 +1,314 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +/* + package ghw can determine various hardware-related + information about the host computer: + + * Memory + * CPU + * Block storage + * Topology + * Network + * PCI + * GPU + + Memory + + Information about the host computer's memory can be retrieved using the + Memory function which returns a pointer to a MemoryInfo struct. + + package main + + import ( + "fmt" + + "github.com/jaypipes/ghw" + ) + + func main() { + memory, err := ghw.Memory() + if err != nil { + fmt.Printf("Error getting memory info: %v", err) + } + + fmt.Println(memory.String()) + } + + CPU + + The CPU function returns a CPUInfo struct that contains information about + the CPUs on the host system. + + package main + + import ( + "fmt" + "math" + "strings" + + "github.com/jaypipes/ghw" + ) + + func main() { + cpu, err := ghw.CPU() + if err != nil { + fmt.Printf("Error getting CPU info: %v", err) + } + + fmt.Printf("%v\n", cpu) + + for _, proc := range cpu.Processors { + fmt.Printf(" %v\n", proc) + for _, core := range proc.Cores { + fmt.Printf(" %v\n", core) + } + if len(proc.Capabilities) > 0 { + // pretty-print the (large) block of capability strings into rows + // of 6 capability strings + rows := int(math.Ceil(float64(len(proc.Capabilities)) / float64(6))) + for row := 1; row < rows; row = row + 1 { + rowStart := (row * 6) - 1 + rowEnd := int(math.Min(float64(rowStart+6), float64(len(proc.Capabilities)))) + rowElems := proc.Capabilities[rowStart:rowEnd] + capStr := strings.Join(rowElems, " ") + if row == 1 { + fmt.Printf(" capabilities: [%s\n", capStr) + } else if rowEnd < len(proc.Capabilities) { + fmt.Printf(" %s\n", capStr) + } else { + fmt.Printf(" %s]\n", capStr) + } + } + } + } + } + + Block storage + + Information about the host computer's local block storage is returned from + the Block function. This function returns a pointer to a BlockInfo struct. + + package main + + import ( + "fmt" + + "github.com/jaypipes/ghw" + ) + + func main() { + block, err := ghw.Block() + if err != nil { + fmt.Printf("Error getting block storage info: %v", err) + } + + fmt.Printf("%v\n", block) + + for _, disk := range block.Disks { + fmt.Printf(" %v\n", disk) + for _, part := range disk.Partitions { + fmt.Printf(" %v\n", part) + } + } + } + + Topology + + Information about the host computer's architecture (NUMA vs. SMP), the + host's node layout and processor caches can be retrieved from the Topology + function. This function returns a pointer to a TopologyInfo struct. + + package main + + import ( + "fmt" + + "github.com/jaypipes/ghw" + ) + + func main() { + topology, err := ghw.Topology() + if err != nil { + fmt.Printf("Error getting topology info: %v", err) + } + + fmt.Printf("%v\n", topology) + + for _, node := range topology.Nodes { + fmt.Printf(" %v\n", node) + for _, cache := range node.Caches { + fmt.Printf(" %v\n", cache) + } + } + } + + Network + + Information about the host computer's networking hardware is returned from + the Network function. This function returns a pointer to a NetworkInfo + struct. + + package main + + import ( + "fmt" + + "github.com/jaypipes/ghw" + ) + + func main() { + net, err := ghw.Network() + if err != nil { + fmt.Printf("Error getting network info: %v", err) + } + + fmt.Printf("%v\n", net) + + for _, nic := range net.NICs { + fmt.Printf(" %v\n", nic) + + enabledCaps := make([]int, 0) + for x, cap := range nic.Capabilities { + if cap.IsEnabled { + enabledCaps = append(enabledCaps, x) + } + } + if len(enabledCaps) > 0 { + fmt.Printf(" enabled capabilities:\n") + for _, x := range enabledCaps { + fmt.Printf(" - %s\n", nic.Capabilities[x].Name) + } + } + } + } + + PCI + + ghw contains a PCI database inspection and querying facility that allows + developers to not only gather information about devices on a local PCI bus + but also query for information about hardware device classes, vendor and + product information. + + **NOTE**: Parsing of the PCI-IDS file database is provided by the separate + http://github.com/jaypipes/pcidb library. You can read that library's + README for more information about the various structs that are exposed on + the PCIInfo struct. + + PCIInfo.ListDevices is used to iterate over a host's PCI devices: + + package main + + import ( + "fmt" + + "github.com/jaypipes/ghw" + ) + + func main() { + pci, err := ghw.PCI() + if err != nil { + fmt.Printf("Error getting PCI info: %v", err) + } + fmt.Printf("host PCI devices:\n") + fmt.Println("====================================================") + devices := pci.ListDevices() + if len(devices) == 0 { + fmt.Printf("error: could not retrieve PCI devices\n") + return + } + + for _, device := range devices { + vendor := device.Vendor + vendorName := vendor.Name + if len(vendor.Name) > 20 { + vendorName = string([]byte(vendorName)[0:17]) + "..." + } + product := device.Product + productName := product.Name + if len(product.Name) > 40 { + productName = string([]byte(productName)[0:37]) + "..." + } + fmt.Printf("%-12s\t%-20s\t%-40s\n", device.Address, vendorName, productName) + } + } + + The following code snippet shows how to call the PCIInfo.GetDevice method + and use its returned PCIDevice struct pointer: + + package main + + import ( + "fmt" + "os" + + "github.com/jaypipes/ghw" + ) + + func main() { + pci, err := ghw.PCI() + if err != nil { + fmt.Printf("Error getting PCI info: %v", err) + } + + addr := "0000:00:00.0" + if len(os.Args) == 2 { + addr = os.Args[1] + } + fmt.Printf("PCI device information for %s\n", addr) + fmt.Println("====================================================") + deviceInfo := pci.GetDevice(addr) + if deviceInfo == nil { + fmt.Printf("could not retrieve PCI device information for %s\n", addr) + return + } + + vendor := deviceInfo.Vendor + fmt.Printf("Vendor: %s [%s]\n", vendor.Name, vendor.ID) + product := deviceInfo.Product + fmt.Printf("Product: %s [%s]\n", product.Name, product.ID) + subsystem := deviceInfo.Subsystem + subvendor := pci.Vendors[subsystem.VendorID] + subvendorName := "UNKNOWN" + if subvendor != nil { + subvendorName = subvendor.Name + } + fmt.Printf("Subsystem: %s [%s] (Subvendor: %s)\n", subsystem.Name, subsystem.ID, subvendorName) + class := deviceInfo.Class + fmt.Printf("Class: %s [%s]\n", class.Name, class.ID) + subclass := deviceInfo.Subclass + fmt.Printf("Subclass: %s [%s]\n", subclass.Name, subclass.ID) + progIface := deviceInfo.ProgrammingInterface + fmt.Printf("Programming Interface: %s [%s]\n", progIface.Name, progIface.ID) + } + + GPU + + Information about the host computer's graphics hardware is returned from + the GPU function. This function returns a pointer to a GPUInfo struct. + + package main + + import ( + "fmt" + + "github.com/jaypipes/ghw" + ) + + func main() { + gpu, err := ghw.GPU() + if err != nil { + fmt.Printf("Error getting GPU info: %v", err) + } + + fmt.Printf("%v\n", gpu) + + for _, card := range gpu.GraphicsCards { + fmt.Printf(" %v\n", card) + } + } +*/ +package ghw diff --git a/vendor/github.com/jaypipes/ghw/host.go b/vendor/github.com/jaypipes/ghw/host.go new file mode 100644 index 000000000..5d82a53a1 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/host.go @@ -0,0 +1,139 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package ghw + +import ( + "fmt" + + "github.com/jaypipes/ghw/pkg/context" + + "github.com/jaypipes/ghw/pkg/baseboard" + "github.com/jaypipes/ghw/pkg/bios" + "github.com/jaypipes/ghw/pkg/block" + "github.com/jaypipes/ghw/pkg/chassis" + "github.com/jaypipes/ghw/pkg/cpu" + "github.com/jaypipes/ghw/pkg/gpu" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/memory" + "github.com/jaypipes/ghw/pkg/net" + "github.com/jaypipes/ghw/pkg/pci" + "github.com/jaypipes/ghw/pkg/product" + "github.com/jaypipes/ghw/pkg/topology" +) + +// HostInfo is a wrapper struct containing information about the host system's +// memory, block storage, CPU, etc +type HostInfo struct { + ctx *context.Context + Memory *memory.Info `json:"memory"` + Block *block.Info `json:"block"` + CPU *cpu.Info `json:"cpu"` + Topology *topology.Info `json:"topology"` + Network *net.Info `json:"network"` + GPU *gpu.Info `json:"gpu"` + Chassis *chassis.Info `json:"chassis"` + BIOS *bios.Info `json:"bios"` + Baseboard *baseboard.Info `json:"baseboard"` + Product *product.Info `json:"product"` + PCI *pci.Info `json:"pci"` +} + +// Host returns a pointer to a HostInfo struct that contains fields with +// information about the host system's CPU, memory, network devices, etc +func Host(opts ...*WithOption) (*HostInfo, error) { + ctx := context.New(opts...) + + memInfo, err := memory.New(opts...) + if err != nil { + return nil, err + } + blockInfo, err := block.New(opts...) + if err != nil { + return nil, err + } + cpuInfo, err := cpu.New(opts...) + if err != nil { + return nil, err + } + topologyInfo, err := topology.New(opts...) + if err != nil { + return nil, err + } + netInfo, err := net.New(opts...) + if err != nil { + return nil, err + } + gpuInfo, err := gpu.New(opts...) + if err != nil { + return nil, err + } + chassisInfo, err := chassis.New(opts...) + if err != nil { + return nil, err + } + biosInfo, err := bios.New(opts...) + if err != nil { + return nil, err + } + baseboardInfo, err := baseboard.New(opts...) + if err != nil { + return nil, err + } + productInfo, err := product.New(opts...) + if err != nil { + return nil, err + } + pciInfo, err := pci.New(opts...) + if err != nil { + return nil, err + } + return &HostInfo{ + ctx: ctx, + CPU: cpuInfo, + Memory: memInfo, + Block: blockInfo, + Topology: topologyInfo, + Network: netInfo, + GPU: gpuInfo, + Chassis: chassisInfo, + BIOS: biosInfo, + Baseboard: baseboardInfo, + Product: productInfo, + PCI: pciInfo, + }, nil +} + +// String returns a newline-separated output of the HostInfo's component +// structs' String-ified output +func (info *HostInfo) String() string { + return fmt.Sprintf( + "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + info.Block.String(), + info.CPU.String(), + info.GPU.String(), + info.Memory.String(), + info.Network.String(), + info.Topology.String(), + info.Chassis.String(), + info.BIOS.String(), + info.Baseboard.String(), + info.Product.String(), + info.PCI.String(), + ) +} + +// YAMLString returns a string with the host information formatted as YAML +// under a top-level "host:" key +func (i *HostInfo) YAMLString() string { + return marshal.SafeYAML(i.ctx, i) +} + +// JSONString returns a string with the host information formatted as JSON +// under a top-level "host:" key +func (i *HostInfo) JSONString(indent bool) string { + return marshal.SafeJSON(i.ctx, i, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/bios/bios.go b/vendor/github.com/jaypipes/ghw/pkg/bios/bios.go new file mode 100644 index 000000000..85a7c64b1 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/bios/bios.go @@ -0,0 +1,77 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package bios + +import ( + "fmt" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/option" + "github.com/jaypipes/ghw/pkg/util" +) + +// Info defines BIOS release information +type Info struct { + ctx *context.Context + Vendor string `json:"vendor"` + Version string `json:"version"` + Date string `json:"date"` +} + +func (i *Info) String() string { + + vendorStr := "" + if i.Vendor != "" { + vendorStr = " vendor=" + i.Vendor + } + versionStr := "" + if i.Version != "" { + versionStr = " version=" + i.Version + } + dateStr := "" + if i.Date != "" && i.Date != util.UNKNOWN { + dateStr = " date=" + i.Date + } + + res := fmt.Sprintf( + "bios%s%s%s", + vendorStr, + versionStr, + dateStr, + ) + return res +} + +// New returns a pointer to a Info struct containing information +// about the host's BIOS +func New(opts ...*option.Option) (*Info, error) { + ctx := context.New(opts...) + info := &Info{ctx: ctx} + if err := ctx.Do(info.load); err != nil { + return nil, err + } + return info, nil +} + +// simple private struct used to encapsulate BIOS information in a top-level +// "bios" YAML/JSON map/object key +type biosPrinter struct { + Info *Info `json:"bios"` +} + +// YAMLString returns a string with the BIOS information formatted as YAML +// under a top-level "dmi:" key +func (info *Info) YAMLString() string { + return marshal.SafeYAML(info.ctx, biosPrinter{info}) +} + +// JSONString returns a string with the BIOS information formatted as JSON +// under a top-level "bios:" key +func (info *Info) JSONString(indent bool) string { + return marshal.SafeJSON(info.ctx, biosPrinter{info}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/bios/bios_linux.go b/vendor/github.com/jaypipes/ghw/pkg/bios/bios_linux.go new file mode 100644 index 000000000..9788f4f7a --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/bios/bios_linux.go @@ -0,0 +1,16 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package bios + +import "github.com/jaypipes/ghw/pkg/linuxdmi" + +func (i *Info) load() error { + i.Vendor = linuxdmi.Item(i.ctx, "bios_vendor") + i.Version = linuxdmi.Item(i.ctx, "bios_version") + i.Date = linuxdmi.Item(i.ctx, "bios_date") + + return nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/bios/bios_stub.go b/vendor/github.com/jaypipes/ghw/pkg/bios/bios_stub.go new file mode 100644 index 000000000..362df4c90 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/bios/bios_stub.go @@ -0,0 +1,17 @@ +// +build !linux,!windows +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package bios + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("biosFillInfo not implemented on " + runtime.GOOS) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/bios/bios_windows.go b/vendor/github.com/jaypipes/ghw/pkg/bios/bios_windows.go new file mode 100644 index 000000000..778628e9a --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/bios/bios_windows.go @@ -0,0 +1,32 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package bios + +import ( + "github.com/StackExchange/wmi" +) + +const wqlBIOS = "SELECT InstallDate, Manufacturer, Version FROM CIM_BIOSElement" + +type win32BIOS struct { + InstallDate *string + Manufacturer *string + Version *string +} + +func (i *Info) load() error { + // Getting data from WMI + var win32BIOSDescriptions []win32BIOS + if err := wmi.Query(wqlBIOS, &win32BIOSDescriptions); err != nil { + return err + } + if len(win32BIOSDescriptions) > 0 { + i.Vendor = *win32BIOSDescriptions[0].Manufacturer + i.Version = *win32BIOSDescriptions[0].Version + i.Date = *win32BIOSDescriptions[0].InstallDate + } + return nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/block/block.go b/vendor/github.com/jaypipes/ghw/pkg/block/block.go new file mode 100644 index 000000000..7a2e4e384 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/block/block.go @@ -0,0 +1,250 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package block + +import ( + "fmt" + "math" + "strings" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/option" + "github.com/jaypipes/ghw/pkg/unitutil" + "github.com/jaypipes/ghw/pkg/util" +) + +// DriveType describes the general category of drive device +type DriveType int + +const ( + DRIVE_TYPE_UNKNOWN DriveType = iota + DRIVE_TYPE_HDD // Hard disk drive + DRIVE_TYPE_FDD // Floppy disk drive + DRIVE_TYPE_ODD // Optical disk drive + DRIVE_TYPE_SSD // Solid-state drive +) + +var ( + driveTypeString = map[DriveType]string{ + DRIVE_TYPE_UNKNOWN: "Unknown", + DRIVE_TYPE_HDD: "HDD", + DRIVE_TYPE_FDD: "FDD", + DRIVE_TYPE_ODD: "ODD", + DRIVE_TYPE_SSD: "SSD", + } +) + +func (dt DriveType) String() string { + return driveTypeString[dt] +} + +// NOTE(jaypipes): since serialized output is as "official" as we're going to +// get, let's lowercase the string output when serializing, in order to +// "normalize" the expected serialized output +func (dt DriveType) MarshalJSON() ([]byte, error) { + return []byte("\"" + strings.ToLower(dt.String()) + "\""), nil +} + +// StorageController is a category of block storage controller/driver. It +// represents more of the physical hardware interface than the storage +// protocol, which represents more of the software interface. +// +// See discussion on https://github.com/jaypipes/ghw/issues/117 +type StorageController int + +const ( + STORAGE_CONTROLLER_UNKNOWN StorageController = iota + STORAGE_CONTROLLER_IDE // Integrated Drive Electronics + STORAGE_CONTROLLER_SCSI // Small computer system interface + STORAGE_CONTROLLER_NVME // Non-volatile Memory Express + STORAGE_CONTROLLER_VIRTIO // Virtualized storage controller/driver + STORAGE_CONTROLLER_MMC // Multi-media controller (used for mobile phone storage devices) +) + +var ( + storageControllerString = map[StorageController]string{ + STORAGE_CONTROLLER_UNKNOWN: "Unknown", + STORAGE_CONTROLLER_IDE: "IDE", + STORAGE_CONTROLLER_SCSI: "SCSI", + STORAGE_CONTROLLER_NVME: "NVMe", + STORAGE_CONTROLLER_VIRTIO: "virtio", + STORAGE_CONTROLLER_MMC: "MMC", + } +) + +func (sc StorageController) String() string { + return storageControllerString[sc] +} + +// NOTE(jaypipes): since serialized output is as "official" as we're going to +// get, let's lowercase the string output when serializing, in order to +// "normalize" the expected serialized output +func (sc StorageController) MarshalJSON() ([]byte, error) { + return []byte("\"" + strings.ToLower(sc.String()) + "\""), nil +} + +// Disk describes a single disk drive on the host system. Disk drives provide +// raw block storage resources. +type Disk struct { + Name string `json:"name"` + SizeBytes uint64 `json:"size_bytes"` + PhysicalBlockSizeBytes uint64 `json:"physical_block_size_bytes"` + DriveType DriveType `json:"drive_type"` + IsRemovable bool `json:"removable"` + StorageController StorageController `json:"storage_controller"` + BusPath string `json:"bus_path"` + // TODO(jaypipes): Convert this to a TopologyNode struct pointer and then + // add to serialized output as "numa_node,omitempty" + NUMANodeID int `json:"-"` + Vendor string `json:"vendor"` + Model string `json:"model"` + SerialNumber string `json:"serial_number"` + WWN string `json:"wwn"` + Partitions []*Partition `json:"partitions"` + // TODO(jaypipes): Add PCI field for accessing PCI device information + // PCI *PCIDevice `json:"pci"` +} + +// Partition describes a logical division of a Disk. +type Partition struct { + Disk *Disk `json:"-"` + Name string `json:"name"` + Label string `json:"label"` + MountPoint string `json:"mount_point"` + SizeBytes uint64 `json:"size_bytes"` + Type string `json:"type"` + IsReadOnly bool `json:"read_only"` + UUID string `json:"uuid"` // This would be volume UUID on macOS, PartUUID on linux, empty on Windows +} + +// Info describes all disk drives and partitions in the host system. +type Info struct { + ctx *context.Context + // TODO(jaypipes): Deprecate this field and replace with TotalSizeBytes + TotalPhysicalBytes uint64 `json:"total_size_bytes"` + Disks []*Disk `json:"disks"` + Partitions []*Partition `json:"-"` +} + +// New returns a pointer to an Info struct that describes the block storage +// resources of the host system. +func New(opts ...*option.Option) (*Info, error) { + ctx := context.New(opts...) + info := &Info{ctx: ctx} + if err := ctx.Do(info.load); err != nil { + return nil, err + } + return info, nil +} + +func (i *Info) String() string { + tpbs := util.UNKNOWN + if i.TotalPhysicalBytes > 0 { + tpb := i.TotalPhysicalBytes + unit, unitStr := unitutil.AmountString(int64(tpb)) + tpb = uint64(math.Ceil(float64(tpb) / float64(unit))) + tpbs = fmt.Sprintf("%d%s", tpb, unitStr) + } + dplural := "disks" + if len(i.Disks) == 1 { + dplural = "disk" + } + return fmt.Sprintf("block storage (%d %s, %s physical storage)", + len(i.Disks), dplural, tpbs) +} + +func (d *Disk) String() string { + sizeStr := util.UNKNOWN + if d.SizeBytes > 0 { + size := d.SizeBytes + unit, unitStr := unitutil.AmountString(int64(size)) + size = uint64(math.Ceil(float64(size) / float64(unit))) + sizeStr = fmt.Sprintf("%d%s", size, unitStr) + } + atNode := "" + if d.NUMANodeID >= 0 { + atNode = fmt.Sprintf(" (node #%d)", d.NUMANodeID) + } + vendor := "" + if d.Vendor != "" { + vendor = " vendor=" + d.Vendor + } + model := "" + if d.Model != util.UNKNOWN { + model = " model=" + d.Model + } + serial := "" + if d.SerialNumber != util.UNKNOWN { + serial = " serial=" + d.SerialNumber + } + wwn := "" + if d.WWN != util.UNKNOWN { + wwn = " WWN=" + d.WWN + } + removable := "" + if d.IsRemovable { + removable = " removable=true" + } + return fmt.Sprintf( + "%s %s (%s) %s [@%s%s]%s%s%s%s%s", + d.Name, + d.DriveType.String(), + sizeStr, + d.StorageController.String(), + d.BusPath, + atNode, + vendor, + model, + serial, + wwn, + removable, + ) +} + +func (p *Partition) String() string { + typeStr := "" + if p.Type != "" { + typeStr = fmt.Sprintf("[%s]", p.Type) + } + mountStr := "" + if p.MountPoint != "" { + mountStr = fmt.Sprintf(" mounted@%s", p.MountPoint) + } + sizeStr := util.UNKNOWN + if p.SizeBytes > 0 { + size := p.SizeBytes + unit, unitStr := unitutil.AmountString(int64(size)) + size = uint64(math.Ceil(float64(size) / float64(unit))) + sizeStr = fmt.Sprintf("%d%s", size, unitStr) + } + return fmt.Sprintf( + "%s (%s) %s%s", + p.Name, + sizeStr, + typeStr, + mountStr, + ) +} + +// simple private struct used to encapsulate block information in a top-level +// "block" YAML/JSON map/object key +type blockPrinter struct { + Info *Info `json:"block" yaml:"block"` +} + +// YAMLString returns a string with the block information formatted as YAML +// under a top-level "block:" key +func (i *Info) YAMLString() string { + return marshal.SafeYAML(i.ctx, blockPrinter{i}) +} + +// JSONString returns a string with the block information formatted as JSON +// under a top-level "block:" key +func (i *Info) JSONString(indent bool) string { + return marshal.SafeJSON(i.ctx, blockPrinter{i}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/block/block_darwin.go b/vendor/github.com/jaypipes/ghw/pkg/block/block_darwin.go new file mode 100644 index 000000000..b3a960906 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/block/block_darwin.go @@ -0,0 +1,283 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package block + +import ( + "fmt" + "os" + "os/exec" + "path" + "strings" + + "github.com/pkg/errors" + "howett.net/plist" +) + +type diskOrPartitionPlistNode struct { + Content string + DeviceIdentifier string + DiskUUID string + VolumeName string + VolumeUUID string + Size int64 + MountPoint string + Partitions []diskOrPartitionPlistNode + APFSVolumes []diskOrPartitionPlistNode +} + +type diskUtilListPlist struct { + AllDisks []string + AllDisksAndPartitions []diskOrPartitionPlistNode + VolumesFromDisks []string + WholeDisks []string +} + +type diskUtilInfoPlist struct { + AESHardware bool // true + Bootable bool // true + BooterDeviceIdentifier string // disk1s2 + BusProtocol string // PCI-Express + CanBeMadeBootable bool // false + CanBeMadeBootableRequiresDestroy bool // false + Content string // some-uuid-foo-bar + DeviceBlockSize int64 // 4096 + DeviceIdentifier string // disk1s1 + DeviceNode string // /dev/disk1s1 + DeviceTreePath string // IODeviceTree:/PCI0@0/RP17@1B/ANS2@0/AppleANS2Controller + DiskUUID string // some-uuid-foo-bar + Ejectable bool // false + EjectableMediaAutomaticUnderSoftwareControl bool // false + EjectableOnly bool // false + FilesystemName string // APFS + FilesystemType string // apfs + FilesystemUserVisibleName string // APFS + FreeSpace int64 // 343975677952 + GlobalPermissionsEnabled bool // true + IOKitSize int64 // 499963174912 + IORegistryEntryName string // Macintosh HD + Internal bool // true + MediaName string // + MediaType string // Generic + MountPoint string // / + ParentWholeDisk string // disk1 + PartitionMapPartition bool // false + RAIDMaster bool // false + RAIDSlice bool // false + RecoveryDeviceIdentifier string // disk1s3 + Removable bool // false + RemovableMedia bool // false + RemovableMediaOrExternalDevice bool // false + SMARTStatus string // Verified + Size int64 // 499963174912 + SolidState bool // true + SupportsGlobalPermissionsDisable bool // true + SystemImage bool // false + TotalSize int64 // 499963174912 + VolumeAllocationBlockSize int64 // 4096 + VolumeName string // Macintosh HD + VolumeSize int64 // 499963174912 + VolumeUUID string // some-uuid-foo-bar + WholeDisk bool // false + Writable bool // true + WritableMedia bool // true + WritableVolume bool // true + // also has a SMARTDeviceSpecificKeysMayVaryNotGuaranteed dict with various info + // NOTE: VolumeUUID sometimes == DiskUUID, but not always. So far Content is always a different UUID. +} + +type ioregPlist struct { + // there's a lot more than just this... + ModelNumber string `plist:"Model Number"` + SerialNumber string `plist:"Serial Number"` + VendorName string `plist:"Vendor Name"` +} + +func getDiskUtilListPlist() (*diskUtilListPlist, error) { + out, err := exec.Command("diskutil", "list", "-plist").Output() + if err != nil { + return nil, errors.Wrap(err, "diskutil list failed") + } + + var data diskUtilListPlist + if _, err := plist.Unmarshal(out, &data); err != nil { + return nil, errors.Wrap(err, "diskutil list plist unmarshal failed") + } + + return &data, nil +} + +func getDiskUtilInfoPlist(device string) (*diskUtilInfoPlist, error) { + out, err := exec.Command("diskutil", "info", "-plist", device).Output() + if err != nil { + return nil, errors.Wrapf(err, "diskutil info for %q failed", device) + } + + var data diskUtilInfoPlist + if _, err := plist.Unmarshal(out, &data); err != nil { + return nil, errors.Wrapf(err, "diskutil info plist unmarshal for %q failed", device) + } + + return &data, nil +} + +func getIoregPlist(ioDeviceTreePath string) (*ioregPlist, error) { + name := path.Base(ioDeviceTreePath) + + args := []string{ + "ioreg", + "-a", // use XML output + "-d", "1", // limit device tree output depth to root node + "-r", // root device tree at matched node + "-n", name, // match by name + } + out, err := exec.Command(args[0], args[1:]...).Output() + if err != nil { + return nil, errors.Wrapf(err, "ioreg query for %q failed", ioDeviceTreePath) + } + if out == nil || len(out) == 0 { + return nil, nil + } + + var data []ioregPlist + if _, err := plist.Unmarshal(out, &data); err != nil { + return nil, errors.Wrapf(err, "ioreg unmarshal for %q failed", ioDeviceTreePath) + } + if len(data) != 1 { + err := errors.Errorf("ioreg unmarshal resulted in %d I/O device tree nodes (expected 1)", len(data)) + return nil, err + } + + return &data[0], nil +} + +func makePartition(disk, s diskOrPartitionPlistNode, isAPFS bool) (*Partition, error) { + if s.Size < 0 { + return nil, errors.Errorf("invalid size %q of partition %q", s.Size, s.DeviceIdentifier) + } + + var partType string + if isAPFS { + partType = "APFS Volume" + } else { + partType = s.Content + } + + info, err := getDiskUtilInfoPlist(s.DeviceIdentifier) + if err != nil { + return nil, err + } + + return &Partition{ + Disk: nil, // filled in later + Name: s.DeviceIdentifier, + Label: s.VolumeName, + MountPoint: s.MountPoint, + SizeBytes: uint64(s.Size), + Type: partType, + IsReadOnly: !info.WritableVolume, + UUID: s.VolumeUUID, + }, nil +} + +// driveTypeFromPlist looks at the supplied property list struct and attempts to +// determine the disk type +func driveTypeFromPlist(infoPlist *diskUtilInfoPlist) DriveType { + dt := DRIVE_TYPE_HDD + if infoPlist.SolidState { + dt = DRIVE_TYPE_SSD + } + // TODO(jaypipes): Figure out how to determine floppy and/or CD/optical + // drive type on Mac + return dt +} + +// storageControllerFromPlist looks at the supplied property list struct and +// attempts to determine the storage controller in use for the device +func storageControllerFromPlist(infoPlist *diskUtilInfoPlist) StorageController { + sc := STORAGE_CONTROLLER_SCSI + if strings.HasSuffix(infoPlist.DeviceTreePath, "IONVMeController") { + sc = STORAGE_CONTROLLER_NVME + } + // TODO(jaypipes): I don't know if Mac even supports IDE controllers and + // the "virtio" controller is libvirt-specific + return sc +} + +func (info *Info) load() error { + listPlist, err := getDiskUtilListPlist() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + return err + } + + info.TotalPhysicalBytes = 0 + info.Disks = make([]*Disk, 0, len(listPlist.AllDisksAndPartitions)) + info.Partitions = []*Partition{} + + for _, disk := range listPlist.AllDisksAndPartitions { + if disk.Size < 0 { + return errors.Errorf("invalid size %q of disk %q", disk.Size, disk.DeviceIdentifier) + } + + infoPlist, err := getDiskUtilInfoPlist(disk.DeviceIdentifier) + if err != nil { + return err + } + if infoPlist.DeviceBlockSize < 0 { + return errors.Errorf("invalid block size %q of disk %q", infoPlist.DeviceBlockSize, disk.DeviceIdentifier) + } + + busPath := strings.TrimPrefix(infoPlist.DeviceTreePath, "IODeviceTree:") + + ioregPlist, err := getIoregPlist(infoPlist.DeviceTreePath) + if err != nil { + return err + } + if ioregPlist == nil { + continue + } + + // The NUMA node & WWN don't seem to be reported by any tools available by default in macOS. + diskReport := &Disk{ + Name: disk.DeviceIdentifier, + SizeBytes: uint64(disk.Size), + PhysicalBlockSizeBytes: uint64(infoPlist.DeviceBlockSize), + DriveType: driveTypeFromPlist(infoPlist), + IsRemovable: infoPlist.Removable, + StorageController: storageControllerFromPlist(infoPlist), + BusPath: busPath, + NUMANodeID: -1, + Vendor: ioregPlist.VendorName, + Model: ioregPlist.ModelNumber, + SerialNumber: ioregPlist.SerialNumber, + WWN: "", + Partitions: make([]*Partition, 0, len(disk.Partitions)+len(disk.APFSVolumes)), + } + + for _, partition := range disk.Partitions { + part, err := makePartition(disk, partition, false) + if err != nil { + return err + } + part.Disk = diskReport + diskReport.Partitions = append(diskReport.Partitions, part) + } + for _, volume := range disk.APFSVolumes { + part, err := makePartition(disk, volume, true) + if err != nil { + return err + } + part.Disk = diskReport + diskReport.Partitions = append(diskReport.Partitions, part) + } + + info.TotalPhysicalBytes += uint64(disk.Size) + info.Disks = append(info.Disks, diskReport) + info.Partitions = append(info.Partitions, diskReport.Partitions...) + } + + return nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/block/block_linux.go b/vendor/github.com/jaypipes/ghw/pkg/block/block_linux.go new file mode 100644 index 000000000..612d3b99b --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/block/block_linux.go @@ -0,0 +1,470 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package block + +import ( + "bufio" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/linuxpath" + "github.com/jaypipes/ghw/pkg/util" +) + +const ( + sectorSize = 512 +) + +func (i *Info) load() error { + paths := linuxpath.New(i.ctx) + i.Disks = disks(i.ctx, paths) + var tpb uint64 + for _, d := range i.Disks { + tpb += d.SizeBytes + } + i.TotalPhysicalBytes = tpb + return nil +} + +func diskPhysicalBlockSizeBytes(paths *linuxpath.Paths, disk string) uint64 { + // We can find the sector size in Linux by looking at the + // /sys/block/$DEVICE/queue/physical_block_size file in sysfs + path := filepath.Join(paths.SysBlock, disk, "queue", "physical_block_size") + contents, err := ioutil.ReadFile(path) + if err != nil { + return 0 + } + size, err := strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64) + if err != nil { + return 0 + } + return size +} + +func diskSizeBytes(paths *linuxpath.Paths, disk string) uint64 { + // We can find the number of 512-byte sectors by examining the contents of + // /sys/block/$DEVICE/size and calculate the physical bytes accordingly. + path := filepath.Join(paths.SysBlock, disk, "size") + contents, err := ioutil.ReadFile(path) + if err != nil { + return 0 + } + size, err := strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64) + if err != nil { + return 0 + } + return size * sectorSize +} + +func diskNUMANodeID(paths *linuxpath.Paths, disk string) int { + link, err := os.Readlink(filepath.Join(paths.SysBlock, disk)) + if err != nil { + return -1 + } + for partial := link; strings.HasPrefix(partial, "../devices/"); partial = filepath.Base(partial) { + if nodeContents, err := ioutil.ReadFile(filepath.Join(paths.SysBlock, partial, "numa_node")); err != nil { + if nodeInt, err := strconv.Atoi(string(nodeContents)); err != nil { + return nodeInt + } + } + } + return -1 +} + +func diskVendor(paths *linuxpath.Paths, disk string) string { + // In Linux, the vendor for a disk device is found in the + // /sys/block/$DEVICE/device/vendor file in sysfs + path := filepath.Join(paths.SysBlock, disk, "device", "vendor") + contents, err := ioutil.ReadFile(path) + if err != nil { + return util.UNKNOWN + } + return strings.TrimSpace(string(contents)) +} + +func udevInfo(paths *linuxpath.Paths, disk string) (map[string]string, error) { + // Get device major:minor numbers + devNo, err := ioutil.ReadFile(filepath.Join(paths.SysBlock, disk, "dev")) + if err != nil { + return nil, err + } + + // Look up block device in udev runtime database + udevID := "b" + strings.TrimSpace(string(devNo)) + udevBytes, err := ioutil.ReadFile(filepath.Join(paths.RunUdevData, udevID)) + if err != nil { + return nil, err + } + + udevInfo := make(map[string]string) + for _, udevLine := range strings.Split(string(udevBytes), "\n") { + if strings.HasPrefix(udevLine, "E:") { + if s := strings.SplitN(udevLine[2:], "=", 2); len(s) == 2 { + udevInfo[s[0]] = s[1] + } + } + } + return udevInfo, nil +} + +func diskModel(paths *linuxpath.Paths, disk string) string { + info, err := udevInfo(paths, disk) + if err != nil { + return util.UNKNOWN + } + + if model, ok := info["ID_MODEL"]; ok { + return model + } + return util.UNKNOWN +} + +func diskSerialNumber(paths *linuxpath.Paths, disk string) string { + info, err := udevInfo(paths, disk) + if err != nil { + return util.UNKNOWN + } + + // There are two serial number keys, ID_SERIAL and ID_SERIAL_SHORT The + // non-_SHORT version often duplicates vendor information collected + // elsewhere, so use _SHORT and fall back to ID_SERIAL if missing... + if serial, ok := info["ID_SERIAL_SHORT"]; ok { + return serial + } + if serial, ok := info["ID_SERIAL"]; ok { + return serial + } + return util.UNKNOWN +} + +func diskBusPath(paths *linuxpath.Paths, disk string) string { + info, err := udevInfo(paths, disk) + if err != nil { + return util.UNKNOWN + } + + // There are two path keys, ID_PATH and ID_PATH_TAG. + // The difference seems to be _TAG has funky characters converted to underscores. + if path, ok := info["ID_PATH"]; ok { + return path + } + return util.UNKNOWN +} + +func diskWWN(paths *linuxpath.Paths, disk string) string { + info, err := udevInfo(paths, disk) + if err != nil { + return util.UNKNOWN + } + + // Trying ID_WWN_WITH_EXTENSION and falling back to ID_WWN is the same logic lsblk uses + if wwn, ok := info["ID_WWN_WITH_EXTENSION"]; ok { + return wwn + } + if wwn, ok := info["ID_WWN"]; ok { + return wwn + } + return util.UNKNOWN +} + +// diskPartitions takes the name of a disk (note: *not* the path of the disk, +// but just the name. In other words, "sda", not "/dev/sda" and "nvme0n1" not +// "/dev/nvme0n1") and returns a slice of pointers to Partition structs +// representing the partitions in that disk +func diskPartitions(ctx *context.Context, paths *linuxpath.Paths, disk string) []*Partition { + out := make([]*Partition, 0) + path := filepath.Join(paths.SysBlock, disk) + files, err := ioutil.ReadDir(path) + if err != nil { + ctx.Warn("failed to read disk partitions: %s\n", err) + return out + } + for _, file := range files { + fname := file.Name() + if !strings.HasPrefix(fname, disk) { + continue + } + size := partitionSizeBytes(paths, disk, fname) + mp, pt, ro := partitionInfo(paths, fname) + du := diskPartUUID(ctx, fname) + p := &Partition{ + Name: fname, + SizeBytes: size, + MountPoint: mp, + Type: pt, + IsReadOnly: ro, + UUID: du, + } + out = append(out, p) + } + return out +} + +func diskPartUUID(ctx *context.Context, part string) string { + if !strings.HasPrefix(part, "/dev") { + part = "/dev/" + part + } + args := []string{ + "blkid", + "-s", + "PARTUUID", + part, + } + out, err := exec.Command(args[0], args[1:]...).Output() + if err != nil { + ctx.Warn("failed to read disk partuuid of %s : %s\n", part, err.Error()) + return "" + } + + if out == nil || len(out) == 0 { + return "" + } + + parts := strings.Split(string(out), "PARTUUID=") + if len(parts) != 2 { + ctx.Warn("failed to parse the partuuid of %s\n", part) + return "" + } + + return strings.ReplaceAll(strings.TrimSpace(parts[1]), `"`, "") +} + +func diskIsRemovable(paths *linuxpath.Paths, disk string) bool { + path := filepath.Join(paths.SysBlock, disk, "removable") + contents, err := ioutil.ReadFile(path) + if err != nil { + return false + } + removable := strings.TrimSpace(string(contents)) + if removable == "1" { + return true + } + return false +} + +func disks(ctx *context.Context, paths *linuxpath.Paths) []*Disk { + // In Linux, we could use the fdisk, lshw or blockdev commands to list disk + // information, however all of these utilities require root privileges to + // run. We can get all of this information by examining the /sys/block + // and /sys/class/block files + disks := make([]*Disk, 0) + files, err := ioutil.ReadDir(paths.SysBlock) + if err != nil { + return nil + } + for _, file := range files { + dname := file.Name() + if strings.HasPrefix(dname, "loop") { + continue + } + + driveType, storageController := diskTypes(dname) + // TODO(jaypipes): Move this into diskTypes() once abstracting + // diskIsRotational for ease of unit testing + if !diskIsRotational(ctx, paths, dname) { + driveType = DRIVE_TYPE_SSD + } + size := diskSizeBytes(paths, dname) + pbs := diskPhysicalBlockSizeBytes(paths, dname) + busPath := diskBusPath(paths, dname) + node := diskNUMANodeID(paths, dname) + vendor := diskVendor(paths, dname) + model := diskModel(paths, dname) + serialNo := diskSerialNumber(paths, dname) + wwn := diskWWN(paths, dname) + removable := diskIsRemovable(paths, dname) + + d := &Disk{ + Name: dname, + SizeBytes: size, + PhysicalBlockSizeBytes: pbs, + DriveType: driveType, + IsRemovable: removable, + StorageController: storageController, + BusPath: busPath, + NUMANodeID: node, + Vendor: vendor, + Model: model, + SerialNumber: serialNo, + WWN: wwn, + } + + parts := diskPartitions(ctx, paths, dname) + // Map this Disk object into the Partition... + for _, part := range parts { + part.Disk = d + } + d.Partitions = parts + + disks = append(disks, d) + } + + return disks +} + +// diskTypes returns the drive type, storage controller and bus type of a disk +func diskTypes(dname string) ( + DriveType, + StorageController, +) { + // The conditionals below which set the controller and drive type are + // based on information listed here: + // https://en.wikipedia.org/wiki/Device_file + driveType := DRIVE_TYPE_UNKNOWN + storageController := STORAGE_CONTROLLER_UNKNOWN + if strings.HasPrefix(dname, "fd") { + driveType = DRIVE_TYPE_FDD + } else if strings.HasPrefix(dname, "sd") { + driveType = DRIVE_TYPE_HDD + storageController = STORAGE_CONTROLLER_SCSI + } else if strings.HasPrefix(dname, "hd") { + driveType = DRIVE_TYPE_HDD + storageController = STORAGE_CONTROLLER_IDE + } else if strings.HasPrefix(dname, "vd") { + driveType = DRIVE_TYPE_HDD + storageController = STORAGE_CONTROLLER_VIRTIO + } else if strings.HasPrefix(dname, "nvme") { + driveType = DRIVE_TYPE_SSD + storageController = STORAGE_CONTROLLER_NVME + } else if strings.HasPrefix(dname, "sr") { + driveType = DRIVE_TYPE_ODD + storageController = STORAGE_CONTROLLER_SCSI + } else if strings.HasPrefix(dname, "xvd") { + driveType = DRIVE_TYPE_HDD + storageController = STORAGE_CONTROLLER_SCSI + } else if strings.HasPrefix(dname, "mmc") { + driveType = DRIVE_TYPE_SSD + storageController = STORAGE_CONTROLLER_MMC + } + + return driveType, storageController +} + +func diskIsRotational(ctx *context.Context, paths *linuxpath.Paths, devName string) bool { + path := filepath.Join(paths.SysBlock, devName, "queue", "rotational") + contents := util.SafeIntFromFile(ctx, path) + return contents == 1 +} + +// partitionSizeBytes returns the size in bytes of the partition given a disk +// name and a partition name. Note: disk name and partition name do *not* +// contain any leading "/dev" parts. In other words, they are *names*, not +// paths. +func partitionSizeBytes(paths *linuxpath.Paths, disk string, part string) uint64 { + path := filepath.Join(paths.SysBlock, disk, part, "size") + contents, err := ioutil.ReadFile(path) + if err != nil { + return 0 + } + size, err := strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64) + if err != nil { + return 0 + } + return size * sectorSize +} + +// Given a full or short partition name, returns the mount point, the type of +// the partition and whether it's readonly +func partitionInfo(paths *linuxpath.Paths, part string) (string, string, bool) { + // Allow calling PartitionInfo with either the full partition name + // "/dev/sda1" or just "sda1" + if !strings.HasPrefix(part, "/dev") { + part = "/dev/" + part + } + + // /etc/mtab entries for mounted partitions look like this: + // /dev/sda6 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0 + var r io.ReadCloser + r, err := os.Open(paths.EtcMtab) + if err != nil { + return "", "", true + } + defer util.SafeClose(r) + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + entry := parseMtabEntry(line) + if entry == nil || entry.Partition != part { + continue + } + ro := true + for _, opt := range entry.Options { + if opt == "rw" { + ro = false + break + } + } + + return entry.Mountpoint, entry.FilesystemType, ro + } + return "", "", true +} + +type mtabEntry struct { + Partition string + Mountpoint string + FilesystemType string + Options []string +} + +func parseMtabEntry(line string) *mtabEntry { + // /etc/mtab entries for mounted partitions look like this: + // /dev/sda6 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0 + if line[0] != '/' { + return nil + } + fields := strings.Fields(line) + + if len(fields) < 4 { + return nil + } + + // We do some special parsing of the mountpoint, which may contain space, + // tab and newline characters, encoded into the mtab entry line using their + // octal-to-string representations. From the GNU mtab man pages: + // + // "Therefore these characters are encoded in the files and the getmntent + // function takes care of the decoding while reading the entries back in. + // '\040' is used to encode a space character, '\011' to encode a tab + // character, '\012' to encode a newline character, and '\\' to encode a + // backslash." + mp := fields[1] + r := strings.NewReplacer( + "\\011", "\t", "\\012", "\n", "\\040", " ", "\\\\", "\\", + ) + mp = r.Replace(mp) + + res := &mtabEntry{ + Partition: fields[0], + Mountpoint: mp, + FilesystemType: fields[2], + } + opts := strings.Split(fields[3], ",") + res.Options = opts + return res +} + +func partitionMountPoint(paths *linuxpath.Paths, part string) string { + mp, _, _ := partitionInfo(paths, part) + return mp +} + +func partitionType(paths *linuxpath.Paths, part string) string { + _, pt, _ := partitionInfo(paths, part) + return pt +} + +func partitionIsReadOnly(paths *linuxpath.Paths, part string) bool { + _, _, ro := partitionInfo(paths, part) + return ro +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/block/block_stub.go b/vendor/github.com/jaypipes/ghw/pkg/block/block_stub.go new file mode 100644 index 000000000..033725de4 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/block/block_stub.go @@ -0,0 +1,17 @@ +// +build !linux,!darwin,!windows +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package block + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("blockFillInfo not implemented on " + runtime.GOOS) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/block/block_windows.go b/vendor/github.com/jaypipes/ghw/pkg/block/block_windows.go new file mode 100644 index 000000000..75c6f04c7 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/block/block_windows.go @@ -0,0 +1,220 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package block + +import ( + "strings" + + "github.com/StackExchange/wmi" + + "github.com/jaypipes/ghw/pkg/util" +) + +const wqlDiskDrive = "SELECT Caption, CreationClassName, DefaultBlockSize, Description, DeviceID, Index, InterfaceType, Manufacturer, MediaType, Model, Name, Partitions, SerialNumber, Size, TotalCylinders, TotalHeads, TotalSectors, TotalTracks, TracksPerCylinder FROM Win32_DiskDrive" + +type win32DiskDrive struct { + Caption *string + CreationClassName *string + DefaultBlockSize *uint64 + Description *string + DeviceID *string + Index *uint32 // Used to link with partition + InterfaceType *string + Manufacturer *string + MediaType *string + Model *string + Name *string + Partitions *int32 + SerialNumber *string + Size *uint64 + TotalCylinders *int64 + TotalHeads *int32 + TotalSectors *int64 + TotalTracks *int64 + TracksPerCylinder *int32 +} + +const wqlDiskPartition = "SELECT Access, BlockSize, Caption, CreationClassName, Description, DeviceID, DiskIndex, Index, Name, Size, SystemName, Type FROM Win32_DiskPartition" + +type win32DiskPartition struct { + Access *uint16 + BlockSize *uint64 + Caption *string + CreationClassName *string + Description *string + DeviceID *string + DiskIndex *uint32 // Used to link with Disk Drive + Index *uint32 + Name *string + Size *int64 + SystemName *string + Type *string +} + +const wqlLogicalDiskToPartition = "SELECT Antecedent, Dependent FROM Win32_LogicalDiskToPartition" + +type win32LogicalDiskToPartition struct { + Antecedent *string + Dependent *string +} + +const wqlLogicalDisk = "SELECT Caption, CreationClassName, Description, DeviceID, FileSystem, FreeSpace, Name, Size, SystemName FROM Win32_LogicalDisk" + +type win32LogicalDisk struct { + Caption *string + CreationClassName *string + Description *string + DeviceID *string + FileSystem *string + FreeSpace *uint64 + Name *string + Size *uint64 + SystemName *string +} + +func (i *Info) load() error { + win32DiskDriveDescriptions, err := getDiskDrives() + if err != nil { + return err + } + + win32DiskPartitionDescriptions, err := getDiskPartitions() + if err != nil { + return err + } + + win32LogicalDiskToPartitionDescriptions, err := getLogicalDisksToPartitions() + if err != nil { + return err + } + + win32LogicalDiskDescriptions, err := getLogicalDisks() + if err != nil { + return err + } + + // Converting into standard structures + disks := make([]*Disk, 0) + for _, diskdrive := range win32DiskDriveDescriptions { + disk := &Disk{ + Name: strings.TrimSpace(*diskdrive.DeviceID), + SizeBytes: *diskdrive.Size, + PhysicalBlockSizeBytes: *diskdrive.DefaultBlockSize, + DriveType: toDriveType(*diskdrive.MediaType, *diskdrive.Caption), + StorageController: toStorageController(*diskdrive.InterfaceType), + BusPath: util.UNKNOWN, // TODO: add information + NUMANodeID: -1, + Vendor: strings.TrimSpace(*diskdrive.Manufacturer), + Model: strings.TrimSpace(*diskdrive.Caption), + SerialNumber: strings.TrimSpace(*diskdrive.SerialNumber), + WWN: util.UNKNOWN, // TODO: add information + Partitions: make([]*Partition, 0), + } + for _, diskpartition := range win32DiskPartitionDescriptions { + // Finding disk partition linked to current disk drive + if diskdrive.Index == diskpartition.DiskIndex { + disk.PhysicalBlockSizeBytes = *diskpartition.BlockSize + // Finding logical partition linked to current disk partition + for _, logicaldisk := range win32LogicalDiskDescriptions { + for _, logicaldisktodiskpartition := range win32LogicalDiskToPartitionDescriptions { + var desiredAntecedent = "\\\\" + *diskpartition.SystemName + "\\root\\cimv2:" + *diskpartition.CreationClassName + ".DeviceID=\"" + *diskpartition.DeviceID + "\"" + var desiredDependent = "\\\\" + *logicaldisk.SystemName + "\\root\\cimv2:" + *logicaldisk.CreationClassName + ".DeviceID=\"" + *logicaldisk.DeviceID + "\"" + if *logicaldisktodiskpartition.Antecedent == desiredAntecedent && *logicaldisktodiskpartition.Dependent == desiredDependent { + // Appending Partition + p := &Partition{ + Name: strings.TrimSpace(*logicaldisk.Caption), + Label: strings.TrimSpace(*logicaldisk.Caption), + SizeBytes: *logicaldisk.Size, + MountPoint: *logicaldisk.DeviceID, + Type: *diskpartition.Type, + IsReadOnly: toReadOnly(*diskpartition.Access), + UUID: "", + } + disk.Partitions = append(disk.Partitions, p) + break + } + } + } + } + } + disks = append(disks, disk) + } + + i.Disks = disks + var tpb uint64 + for _, d := range i.Disks { + tpb += d.SizeBytes + } + i.TotalPhysicalBytes = tpb + return nil +} + +func getDiskDrives() ([]win32DiskDrive, error) { + // Getting disks drives data from WMI + var win3232DiskDriveDescriptions []win32DiskDrive + if err := wmi.Query(wqlDiskDrive, &win3232DiskDriveDescriptions); err != nil { + return nil, err + } + return win3232DiskDriveDescriptions, nil +} + +func getDiskPartitions() ([]win32DiskPartition, error) { + // Getting disk partitions from WMI + var win32DiskPartitionDescriptions []win32DiskPartition + if err := wmi.Query(wqlDiskPartition, &win32DiskPartitionDescriptions); err != nil { + return nil, err + } + return win32DiskPartitionDescriptions, nil +} + +func getLogicalDisksToPartitions() ([]win32LogicalDiskToPartition, error) { + // Getting links between logical disks and partitions from WMI + var win32LogicalDiskToPartitionDescriptions []win32LogicalDiskToPartition + if err := wmi.Query(wqlLogicalDiskToPartition, &win32LogicalDiskToPartitionDescriptions); err != nil { + return nil, err + } + return win32LogicalDiskToPartitionDescriptions, nil +} + +func getLogicalDisks() ([]win32LogicalDisk, error) { + // Getting logical disks from WMI + var win32LogicalDiskDescriptions []win32LogicalDisk + if err := wmi.Query(wqlLogicalDisk, &win32LogicalDiskDescriptions); err != nil { + return nil, err + } + return win32LogicalDiskDescriptions, nil +} + +func toDriveType(mediaType string, caption string) DriveType { + mediaType = strings.ToLower(mediaType) + caption = strings.ToLower(caption) + if strings.Contains(mediaType, "fixed") || strings.Contains(mediaType, "ssd") || strings.Contains(caption, "ssd") { + return DRIVE_TYPE_SSD + } else if strings.ContainsAny(mediaType, "hdd") { + return DRIVE_TYPE_HDD + } + return DRIVE_TYPE_UNKNOWN +} + +// TODO: improve +func toStorageController(interfaceType string) StorageController { + var storageController StorageController + switch interfaceType { + case "SCSI": + storageController = STORAGE_CONTROLLER_SCSI + case "IDE": + storageController = STORAGE_CONTROLLER_IDE + default: + storageController = STORAGE_CONTROLLER_UNKNOWN + } + return storageController +} + +// TODO: improve +func toReadOnly(access uint16) bool { + // See Access property from: https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-diskpartition + return access == 0x1 +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis.go b/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis.go new file mode 100644 index 000000000..e11f0447a --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis.go @@ -0,0 +1,121 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package chassis + +import ( + "fmt" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/option" + "github.com/jaypipes/ghw/pkg/util" +) + +var ( + chassisTypeDescriptions = map[string]string{ + "1": "Other", + "2": "Unknown", + "3": "Desktop", + "4": "Low profile desktop", + "5": "Pizza box", + "6": "Mini tower", + "7": "Tower", + "8": "Portable", + "9": "Laptop", + "10": "Notebook", + "11": "Hand held", + "12": "Docking station", + "13": "All in one", + "14": "Sub notebook", + "15": "Space-saving", + "16": "Lunch box", + "17": "Main server chassis", + "18": "Expansion chassis", + "19": "SubChassis", + "20": "Bus Expansion chassis", + "21": "Peripheral chassis", + "22": "RAID chassis", + "23": "Rack mount chassis", + "24": "Sealed-case PC", + "25": "Multi-system chassis", + "26": "Compact PCI", + "27": "Advanced TCA", + "28": "Blade", + "29": "Blade enclosure", + "30": "Tablet", + "31": "Convertible", + "32": "Detachable", + "33": "IoT gateway", + "34": "Embedded PC", + "35": "Mini PC", + "36": "Stick PC", + } +) + +// Info defines chassis release information +type Info struct { + ctx *context.Context + AssetTag string `json:"asset_tag"` + SerialNumber string `json:"serial_number"` + Type string `json:"type"` + TypeDescription string `json:"type_description"` + Vendor string `json:"vendor"` + Version string `json:"version"` +} + +func (i *Info) String() string { + vendorStr := "" + if i.Vendor != "" { + vendorStr = " vendor=" + i.Vendor + } + serialStr := "" + if i.SerialNumber != "" && i.SerialNumber != util.UNKNOWN { + serialStr = " serial=" + i.SerialNumber + } + versionStr := "" + if i.Version != "" { + versionStr = " version=" + i.Version + } + + res := fmt.Sprintf( + "chassis type=%s%s%s%s", + i.TypeDescription, + vendorStr, + serialStr, + versionStr, + ) + return res +} + +// New returns a pointer to a Info struct containing information +// about the host's chassis +func New(opts ...*option.Option) (*Info, error) { + ctx := context.New(opts...) + info := &Info{ctx: ctx} + if err := ctx.Do(info.load); err != nil { + return nil, err + } + return info, nil +} + +// simple private struct used to encapsulate chassis information in a top-level +// "chassis" YAML/JSON map/object key +type chassisPrinter struct { + Info *Info `json:"chassis"` +} + +// YAMLString returns a string with the chassis information formatted as YAML +// under a top-level "dmi:" key +func (info *Info) YAMLString() string { + return marshal.SafeYAML(info.ctx, chassisPrinter{info}) +} + +// JSONString returns a string with the chassis information formatted as JSON +// under a top-level "chassis:" key +func (info *Info) JSONString(indent bool) string { + return marshal.SafeJSON(info.ctx, chassisPrinter{info}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_linux.go b/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_linux.go new file mode 100644 index 000000000..00f64de6e --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_linux.go @@ -0,0 +1,26 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package chassis + +import ( + "github.com/jaypipes/ghw/pkg/linuxdmi" + "github.com/jaypipes/ghw/pkg/util" +) + +func (i *Info) load() error { + i.AssetTag = linuxdmi.Item(i.ctx, "chassis_asset_tag") + i.SerialNumber = linuxdmi.Item(i.ctx, "chassis_serial") + i.Type = linuxdmi.Item(i.ctx, "chassis_type") + typeDesc, found := chassisTypeDescriptions[i.Type] + if !found { + typeDesc = util.UNKNOWN + } + i.TypeDescription = typeDesc + i.Vendor = linuxdmi.Item(i.ctx, "chassis_vendor") + i.Version = linuxdmi.Item(i.ctx, "chassis_version") + + return nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_stub.go b/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_stub.go new file mode 100644 index 000000000..2e45e15a5 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_stub.go @@ -0,0 +1,17 @@ +// +build !linux,!windows +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package chassis + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("chassisFillInfo not implemented on " + runtime.GOOS) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_windows.go b/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_windows.go new file mode 100644 index 000000000..088cbed3c --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/chassis/chassis_windows.go @@ -0,0 +1,43 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package chassis + +import ( + "github.com/StackExchange/wmi" + + "github.com/jaypipes/ghw/pkg/util" +) + +const wqlChassis = "SELECT Caption, Description, Name, Manufacturer, Model, SerialNumber, Tag, TypeDescriptions, Version FROM CIM_Chassis" + +type win32Chassis struct { + Caption *string + Description *string + Name *string + Manufacturer *string + Model *string + SerialNumber *string + Tag *string + TypeDescriptions []string + Version *string +} + +func (i *Info) load() error { + // Getting data from WMI + var win32ChassisDescriptions []win32Chassis + if err := wmi.Query(wqlChassis, &win32ChassisDescriptions); err != nil { + return err + } + if len(win32ChassisDescriptions) > 0 { + i.AssetTag = *win32ChassisDescriptions[0].Tag + i.SerialNumber = *win32ChassisDescriptions[0].SerialNumber + i.Type = util.UNKNOWN // TODO: + i.TypeDescription = *win32ChassisDescriptions[0].Model + i.Vendor = *win32ChassisDescriptions[0].Manufacturer + i.Version = *win32ChassisDescriptions[0].Version + } + return nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu.go b/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu.go new file mode 100644 index 000000000..2fa0cd2d0 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu.go @@ -0,0 +1,169 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package cpu + +import ( + "fmt" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/option" +) + +// ProcessorCore describes a physical host processor core. A processor core is +// a separate processing unit within some types of central processing units +// (CPU). +type ProcessorCore struct { + // ID is the `uint32` identifier that the host gave this core. Note that + // this does *not* necessarily equate to a zero-based index of the core + // within a physical package. For example, the core IDs for an Intel Core + // i7 are 0, 1, 2, 8, 9, and 10 + ID int `json:"id"` + // Index is the zero-based index of the core on the physical processor + // package + Index int `json:"index"` + // NumThreads is the number of hardware threads associated with the core + NumThreads uint32 `json:"total_threads"` + // LogicalProcessors is a slice of ints representing the logical processor + // IDs assigned to any processing unit for the core + LogicalProcessors []int `json:"logical_processors"` +} + +// String returns a short string indicating important information about the +// processor core +func (c *ProcessorCore) String() string { + return fmt.Sprintf( + "processor core #%d (%d threads), logical processors %v", + c.Index, + c.NumThreads, + c.LogicalProcessors, + ) +} + +// Processor describes a physical host central processing unit (CPU). +type Processor struct { + // ID is the physical processor `uint32` ID according to the system + ID int `json:"id"` + // NumCores is the number of physical cores in the processor package + NumCores uint32 `json:"total_cores"` + // NumThreads is the number of hardware threads in the processor package + NumThreads uint32 `json:"total_threads"` + // Vendor is a string containing the vendor name + Vendor string `json:"vendor"` + // Model` is a string containing the vendor's model name + Model string `json:"model"` + // Capabilities is a slice of strings indicating the features the processor + // has enabled + Capabilities []string `json:"capabilities"` + // Cores is a slice of ProcessorCore` struct pointers that are packed onto + // this physical processor + Cores []*ProcessorCore `json:"cores"` +} + +// HasCapability returns true if the Processor has the supplied cpuid +// capability, false otherwise. Example of cpuid capabilities would be 'vmx' or +// 'sse4_2'. To see a list of potential cpuid capabilitiies, see the section on +// CPUID feature bits in the following article: +// +// https://en.wikipedia.org/wiki/CPUID +func (p *Processor) HasCapability(find string) bool { + for _, c := range p.Capabilities { + if c == find { + return true + } + } + return false +} + +// String returns a short string describing the Processor +func (p *Processor) String() string { + ncs := "cores" + if p.NumCores == 1 { + ncs = "core" + } + nts := "threads" + if p.NumThreads == 1 { + nts = "thread" + } + return fmt.Sprintf( + "physical package #%d (%d %s, %d hardware %s)", + p.ID, + p.NumCores, + ncs, + p.NumThreads, + nts, + ) +} + +// Info describes all central processing unit (CPU) functionality on a host. +// Returned by the `ghw.CPU()` function. +type Info struct { + ctx *context.Context + // TotalCores is the total number of physical cores the host system + // contains + TotalCores uint32 `json:"total_cores"` + // TotalThreads is the total number of hardware threads the host system + // contains + TotalThreads uint32 `json:"total_threads"` + // Processors is a slice of Processor struct pointers, one for each + // physical processor package contained in the host + Processors []*Processor `json:"processors"` +} + +// New returns a pointer to an Info struct that contains information about the +// CPUs on the host system +func New(opts ...*option.Option) (*Info, error) { + ctx := context.New(opts...) + info := &Info{ctx: ctx} + if err := ctx.Do(info.load); err != nil { + return nil, err + } + return info, nil +} + +// String returns a short string indicating a summary of CPU information +func (i *Info) String() string { + nps := "packages" + if len(i.Processors) == 1 { + nps = "package" + } + ncs := "cores" + if i.TotalCores == 1 { + ncs = "core" + } + nts := "threads" + if i.TotalThreads == 1 { + nts = "thread" + } + return fmt.Sprintf( + "cpu (%d physical %s, %d %s, %d hardware %s)", + len(i.Processors), + nps, + i.TotalCores, + ncs, + i.TotalThreads, + nts, + ) +} + +// simple private struct used to encapsulate cpu information in a top-level +// "cpu" YAML/JSON map/object key +type cpuPrinter struct { + Info *Info `json:"cpu"` +} + +// YAMLString returns a string with the cpu information formatted as YAML +// under a top-level "cpu:" key +func (i *Info) YAMLString() string { + return marshal.SafeYAML(i.ctx, cpuPrinter{i}) +} + +// JSONString returns a string with the cpu information formatted as JSON +// under a top-level "cpu:" key +func (i *Info) JSONString(indent bool) string { + return marshal.SafeJSON(i.ctx, cpuPrinter{i}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_linux.go b/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_linux.go new file mode 100644 index 000000000..44e4ced74 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_linux.go @@ -0,0 +1,220 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package cpu + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/linuxpath" + "github.com/jaypipes/ghw/pkg/util" +) + +func (i *Info) load() error { + i.Processors = processorsGet(i.ctx) + var totCores uint32 + var totThreads uint32 + for _, p := range i.Processors { + totCores += p.NumCores + totThreads += p.NumThreads + } + i.TotalCores = totCores + i.TotalThreads = totThreads + return nil +} + +func processorsGet(ctx *context.Context) []*Processor { + procs := make([]*Processor, 0) + paths := linuxpath.New(ctx) + + r, err := os.Open(paths.ProcCpuinfo) + if err != nil { + return nil + } + defer util.SafeClose(r) + + // An array of maps of attributes describing the logical processor + procAttrs := make([]map[string]string, 0) + curProcAttrs := make(map[string]string) + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + // Output of /proc/cpuinfo has a blank newline to separate logical + // processors, so here we collect up all the attributes we've + // collected for this logical processor block + procAttrs = append(procAttrs, curProcAttrs) + // Reset the current set of processor attributes... + curProcAttrs = make(map[string]string) + continue + } + parts := strings.Split(line, ":") + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + curProcAttrs[key] = value + } + + // Build a set of physical processor IDs which represent the physical + // package of the CPU + setPhysicalIDs := make(map[int]bool) + for _, attrs := range procAttrs { + pid, err := strconv.Atoi(attrs["physical id"]) + if err != nil { + continue + } + setPhysicalIDs[pid] = true + } + + for pid := range setPhysicalIDs { + p := &Processor{ + ID: pid, + } + // The indexes into the array of attribute maps for each logical + // processor within the physical processor + lps := make([]int, 0) + for x := range procAttrs { + lppid, err := strconv.Atoi(procAttrs[x]["physical id"]) + if err != nil { + continue + } + if pid == lppid { + lps = append(lps, x) + } + } + first := procAttrs[lps[0]] + p.Model = first["model name"] + p.Vendor = first["vendor_id"] + numCores, err := strconv.Atoi(first["cpu cores"]) + if err != nil { + continue + } + p.NumCores = uint32(numCores) + numThreads, err := strconv.Atoi(first["siblings"]) + if err != nil { + continue + } + p.NumThreads = uint32(numThreads) + + // The flags field is a space-separated list of CPU capabilities + p.Capabilities = strings.Split(first["flags"], " ") + + cores := make([]*ProcessorCore, 0) + for _, lpidx := range lps { + lpid, err := strconv.Atoi(procAttrs[lpidx]["processor"]) + if err != nil { + continue + } + coreID, err := strconv.Atoi(procAttrs[lpidx]["core id"]) + if err != nil { + continue + } + var core *ProcessorCore + for _, c := range cores { + if c.ID == coreID { + c.LogicalProcessors = append( + c.LogicalProcessors, + lpid, + ) + c.NumThreads = uint32(len(c.LogicalProcessors)) + core = c + } + } + if core == nil { + coreLps := make([]int, 1) + coreLps[0] = lpid + core = &ProcessorCore{ + ID: coreID, + Index: len(cores), + NumThreads: 1, + LogicalProcessors: coreLps, + } + cores = append(cores, core) + } + } + p.Cores = cores + procs = append(procs, p) + } + return procs +} + +func CoresForNode(ctx *context.Context, nodeID int) ([]*ProcessorCore, error) { + // The /sys/devices/system/node/nodeX directory contains a subdirectory + // called 'cpuX' for each logical processor assigned to the node. Each of + // those subdirectories contains a topology subdirectory which has a + // core_id file that indicates the 0-based identifier of the physical core + // the logical processor (hardware thread) is on. + paths := linuxpath.New(ctx) + path := filepath.Join( + paths.SysDevicesSystemNode, + fmt.Sprintf("node%d", nodeID), + ) + cores := make([]*ProcessorCore, 0) + + findCoreByID := func(coreID int) *ProcessorCore { + for _, c := range cores { + if c.ID == coreID { + return c + } + } + + c := &ProcessorCore{ + ID: coreID, + Index: len(cores), + LogicalProcessors: make([]int, 0), + } + cores = append(cores, c) + return c + } + + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + for _, file := range files { + filename := file.Name() + if !strings.HasPrefix(filename, "cpu") { + continue + } + if filename == "cpumap" || filename == "cpulist" { + // There are two files in the node directory that start with 'cpu' + // but are not subdirectories ('cpulist' and 'cpumap'). Ignore + // these files. + continue + } + // Grab the logical processor ID by cutting the integer from the + // /sys/devices/system/node/nodeX/cpuX filename + cpuPath := filepath.Join(path, filename) + procID, err := strconv.Atoi(filename[3:]) + if err != nil { + _, _ = fmt.Fprintf( + os.Stderr, + "failed to determine procID from %s. Expected integer after 3rd char.", + filename, + ) + continue + } + coreIDPath := filepath.Join(cpuPath, "topology", "core_id") + coreID := util.SafeIntFromFile(ctx, coreIDPath) + core := findCoreByID(coreID) + core.LogicalProcessors = append( + core.LogicalProcessors, + procID, + ) + } + + for _, c := range cores { + c.NumThreads = uint32(len(c.LogicalProcessors)) + } + + return cores, nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_stub.go b/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_stub.go new file mode 100644 index 000000000..9ff41cd16 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_stub.go @@ -0,0 +1,17 @@ +// +build !linux,!windows +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package cpu + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("cpu.Info.load not implemented on " + runtime.GOOS) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_windows.go b/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_windows.go new file mode 100644 index 000000000..07a7ddddb --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_windows.go @@ -0,0 +1,55 @@ +// +build !linux +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package cpu + +import ( + "github.com/StackExchange/wmi" +) + +const wmqlProcessor = "SELECT Manufacturer, Name, NumberOfLogicalProcessors, NumberOfCores FROM Win32_Processor" + +type win32Processor struct { + Manufacturer *string + Name *string + NumberOfLogicalProcessors uint32 + NumberOfCores uint32 +} + +func (i *Info) load() error { + // Getting info from WMI + var win32descriptions []win32Processor + if err := wmi.Query(wmqlProcessor, &win32descriptions); err != nil { + return err + } + // Converting into standard structures + i.Processors = processorsGet(win32descriptions) + var totCores uint32 + var totThreads uint32 + for _, p := range i.Processors { + totCores += p.NumCores + totThreads += p.NumThreads + } + i.TotalCores = totCores + i.TotalThreads = totThreads + return nil +} + +func processorsGet(win32descriptions []win32Processor) []*Processor { + var procs []*Processor + // Converting into standard structures + for index, description := range win32descriptions { + p := &Processor{ + ID: index, + Model: *description.Name, + Vendor: *description.Manufacturer, + NumCores: description.NumberOfCores, + NumThreads: description.NumberOfLogicalProcessors, + } + procs = append(procs, p) + } + return procs +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu.go b/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu.go new file mode 100644 index 000000000..65864c7e1 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu.go @@ -0,0 +1,95 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package gpu + +import ( + "fmt" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/option" + "github.com/jaypipes/ghw/pkg/pci" + "github.com/jaypipes/ghw/pkg/topology" +) + +type GraphicsCard struct { + // the PCI address where the graphics card can be found + Address string `json:"address"` + // The "index" of the card on the bus (generally not useful information, + // but might as well include it) + Index int `json:"index"` + // pointer to a PCIDevice struct that describes the vendor and product + // model, etc + // TODO(jaypipes): Rename this field to PCI, instead of DeviceInfo + DeviceInfo *pci.Device `json:"pci"` + // Topology node that the graphics card is affined to. Will be nil if the + // architecture is not NUMA. + Node *topology.Node `json:"node,omitempty"` +} + +func (card *GraphicsCard) String() string { + deviceStr := card.Address + if card.DeviceInfo != nil { + deviceStr = card.DeviceInfo.String() + } + nodeStr := "" + if card.Node != nil { + nodeStr = fmt.Sprintf(" [affined to NUMA node %d]", card.Node.ID) + } + return fmt.Sprintf( + "card #%d %s@%s", + card.Index, + nodeStr, + deviceStr, + ) +} + +type Info struct { + ctx *context.Context + GraphicsCards []*GraphicsCard `json:"cards"` +} + +// New returns a pointer to an Info struct that contains information about the +// graphics cards on the host system +func New(opts ...*option.Option) (*Info, error) { + ctx := context.New(opts...) + info := &Info{ctx: ctx} + if err := ctx.Do(info.load); err != nil { + return nil, err + } + return info, nil +} + +func (i *Info) String() string { + numCardsStr := "cards" + if len(i.GraphicsCards) == 1 { + numCardsStr = "card" + } + return fmt.Sprintf( + "gpu (%d graphics %s)", + len(i.GraphicsCards), + numCardsStr, + ) +} + +// simple private struct used to encapsulate gpu information in a top-level +// "gpu" YAML/JSON map/object key +type gpuPrinter struct { + Info *Info `json:"gpu"` +} + +// YAMLString returns a string with the gpu information formatted as YAML +// under a top-level "gpu:" key +func (i *Info) YAMLString() string { + return marshal.SafeYAML(i.ctx, gpuPrinter{i}) +} + +// JSONString returns a string with the gpu information formatted as JSON +// under a top-level "gpu:" key +func (i *Info) JSONString(indent bool) string { + return marshal.SafeJSON(i.ctx, gpuPrinter{i}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_linux.go b/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_linux.go new file mode 100644 index 000000000..d5959916b --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_linux.go @@ -0,0 +1,152 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package gpu + +import ( + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/linuxpath" + "github.com/jaypipes/ghw/pkg/pci" + "github.com/jaypipes/ghw/pkg/topology" + "github.com/jaypipes/ghw/pkg/util" +) + +const ( + _WARN_NO_SYS_CLASS_DRM = ` +/sys/class/drm does not exist on this system (likely the host system is a +virtual machine or container with no graphics). Therefore, +GPUInfo.GraphicsCards will be an empty array. +` +) + +func (i *Info) load() error { + // In Linux, each graphics card is listed under the /sys/class/drm + // directory as a symbolic link named "cardN", where N is a zero-based + // index of the card in the system. "DRM" stands for Direct Rendering + // Manager and is the Linux subsystem that is responsible for graphics I/O + // + // Each card may have multiple symbolic + // links in this directory representing the interfaces from the graphics + // card over a particular wire protocol (HDMI, DisplayPort, etc). These + // symbolic links are named cardN--. For + // instance, on one of my local workstations with an NVIDIA GTX 1050ti + // graphics card with one HDMI, one DisplayPort, and one DVI interface to + // the card, I see the following in /sys/class/drm: + // + // $ ll /sys/class/drm/ + // total 0 + // drwxr-xr-x 2 root root 0 Jul 16 11:50 ./ + // drwxr-xr-x 75 root root 0 Jul 16 11:50 ../ + // lrwxrwxrwx 1 root root 0 Jul 16 11:50 card0 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/ + // lrwxrwxrwx 1 root root 0 Jul 16 11:50 card0-DP-1 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/card0-DP-1/ + // lrwxrwxrwx 1 root root 0 Jul 16 11:50 card0-DVI-D-1 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/card0-DVI-D-1/ + // lrwxrwxrwx 1 root root 0 Jul 16 11:50 card0-HDMI-A-1 -> ../../devices/pci0000:00/0000:00:03.0/0000:03:00.0/drm/card0/card0-HDMI-A-1/ + // + // In this routine, we are only interested in the first link (card0), which + // we follow to gather information about the actual device from the PCI + // subsystem (we query the modalias file of the PCI device's sysfs + // directory using the `ghw.PCIInfo.GetDevice()` function. + paths := linuxpath.New(i.ctx) + links, err := ioutil.ReadDir(paths.SysClassDRM) + if err != nil { + i.ctx.Warn(_WARN_NO_SYS_CLASS_DRM) + return nil + } + cards := make([]*GraphicsCard, 0) + for _, link := range links { + lname := link.Name() + if !strings.HasPrefix(lname, "card") { + continue + } + if strings.ContainsRune(lname, '-') { + continue + } + // Grab the card's zero-based integer index + lnameBytes := []byte(lname) + cardIdx, err := strconv.Atoi(string(lnameBytes[4:])) + if err != nil { + cardIdx = -1 + } + + // Calculate the card's PCI address by looking at the symbolic link's + // target + lpath := filepath.Join(paths.SysClassDRM, lname) + dest, err := os.Readlink(lpath) + if err != nil { + continue + } + pathParts := strings.Split(dest, "/") + numParts := len(pathParts) + pciAddress := pathParts[numParts-3] + card := &GraphicsCard{ + Address: pciAddress, + Index: cardIdx, + } + cards = append(cards, card) + } + gpuFillNUMANodes(i.ctx, cards) + gpuFillPCIDevice(i.ctx, cards) + i.GraphicsCards = cards + return nil +} + +// Loops through each GraphicsCard struct and attempts to fill the DeviceInfo +// attribute with PCI device information +func gpuFillPCIDevice(ctx *context.Context, cards []*GraphicsCard) { + pci, err := pci.NewWithContext(ctx) + if err != nil { + return + } + for _, card := range cards { + if card.DeviceInfo == nil { + card.DeviceInfo = pci.GetDevice(card.Address) + } + } +} + +// Loops through each GraphicsCard struct and find which NUMA node the card is +// affined to, setting the GraphicsCard.Node field accordingly. If the host +// system is not a NUMA system, the Node field will be set to nil. +func gpuFillNUMANodes(ctx *context.Context, cards []*GraphicsCard) { + paths := linuxpath.New(ctx) + topo, err := topology.NewWithContext(ctx) + if err != nil { + // Problem getting topology information so just set the graphics card's + // node to nil + for _, card := range cards { + if topo.Architecture != topology.ARCHITECTURE_NUMA { + card.Node = nil + } + } + return + } + for _, card := range cards { + // Each graphics card on a NUMA system will have a pseudo-file + // called /sys/class/drm/card$CARD_INDEX/device/numa_node which + // contains the NUMA node that the card is affined to + cardIndexStr := strconv.Itoa(card.Index) + fpath := filepath.Join( + paths.SysClassDRM, + "card"+cardIndexStr, + "device", + "numa_node", + ) + nodeIdx := util.SafeIntFromFile(ctx, fpath) + if nodeIdx == -1 { + continue + } + for _, node := range topo.Nodes { + if nodeIdx == int(node.ID) { + card.Node = node + } + } + } +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_stub.go b/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_stub.go new file mode 100644 index 000000000..9604637d8 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_stub.go @@ -0,0 +1,17 @@ +// +build !linux,!windows +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package gpu + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("gpuFillInfo not implemented on " + runtime.GOOS) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_windows.go b/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_windows.go new file mode 100644 index 000000000..5fb542814 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/gpu/gpu_windows.go @@ -0,0 +1,131 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package gpu + +import ( + "strings" + + "github.com/StackExchange/wmi" + "github.com/jaypipes/pcidb" + + "github.com/jaypipes/ghw/pkg/pci" + "github.com/jaypipes/ghw/pkg/util" +) + +const wqlVideoController = "SELECT Caption, CreationClassName, Description, DeviceID, Name, PNPDeviceID, SystemCreationClassName, SystemName, VideoArchitecture, VideoMemoryType, VideoModeDescription, VideoProcessor FROM Win32_VideoController" + +type win32VideoController struct { + Caption string + CreationClassName string + Description string + DeviceID string + Name string + PNPDeviceID string + SystemCreationClassName string + SystemName string + VideoArchitecture uint16 + VideoMemoryType uint16 + VideoModeDescription string + VideoProcessor string +} + +const wqlPnPEntity = "SELECT Caption, CreationClassName, Description, DeviceID, Manufacturer, Name, PNPClass, PNPDeviceID FROM Win32_PnPEntity" + +type win32PnPEntity struct { + Caption string + CreationClassName string + Description string + DeviceID string + Manufacturer string + Name string + PNPClass string + PNPDeviceID string +} + +func (i *Info) load() error { + // Getting data from WMI + var win32VideoControllerDescriptions []win32VideoController + if err := wmi.Query(wqlVideoController, &win32VideoControllerDescriptions); err != nil { + return err + } + + // Building dynamic WHERE clause with addresses to create a single query collecting all desired data + queryAddresses := []string{} + for _, description := range win32VideoControllerDescriptions { + var queryAddres = strings.Replace(description.PNPDeviceID, "\\", `\\`, -1) + queryAddresses = append(queryAddresses, "PNPDeviceID='"+queryAddres+"'") + } + whereClause := strings.Join(queryAddresses[:], " OR ") + + // Getting data from WMI + var win32PnPDescriptions []win32PnPEntity + var wqlPnPDevice = wqlPnPEntity + " WHERE " + whereClause + if err := wmi.Query(wqlPnPDevice, &win32PnPDescriptions); err != nil { + return err + } + + // Converting into standard structures + cards := make([]*GraphicsCard, 0) + for _, description := range win32VideoControllerDescriptions { + card := &GraphicsCard{ + Address: description.DeviceID, // https://stackoverflow.com/questions/32073667/how-do-i-discover-the-pcie-bus-topology-and-slot-numbers-on-the-board + Index: 0, + DeviceInfo: GetDevice(description.PNPDeviceID, win32PnPDescriptions), + } + cards = append(cards, card) + } + i.GraphicsCards = cards + return nil +} + +func GetDevice(id string, entities []win32PnPEntity) *pci.Device { + // Backslashing PnP address ID as requested by JSON and VMI query: https://docs.microsoft.com/en-us/windows/win32/wmisdk/where-clause + var queryAddress = strings.Replace(id, "\\", `\\`, -1) + // Preparing default structure + var device = &pci.Device{ + Address: queryAddress, + Vendor: &pcidb.Vendor{ + ID: util.UNKNOWN, + Name: util.UNKNOWN, + Products: []*pcidb.Product{}, + }, + Subsystem: &pcidb.Product{ + ID: util.UNKNOWN, + Name: util.UNKNOWN, + Subsystems: []*pcidb.Product{}, + }, + Product: &pcidb.Product{ + ID: util.UNKNOWN, + Name: util.UNKNOWN, + Subsystems: []*pcidb.Product{}, + }, + Class: &pcidb.Class{ + ID: util.UNKNOWN, + Name: util.UNKNOWN, + Subclasses: []*pcidb.Subclass{}, + }, + Subclass: &pcidb.Subclass{ + ID: util.UNKNOWN, + Name: util.UNKNOWN, + ProgrammingInterfaces: []*pcidb.ProgrammingInterface{}, + }, + ProgrammingInterface: &pcidb.ProgrammingInterface{ + ID: util.UNKNOWN, + Name: util.UNKNOWN, + }, + } + // If an entity is found we get its data inside the standard structure + for _, description := range entities { + if id == description.PNPDeviceID { + device.Vendor.ID = description.Manufacturer + device.Vendor.Name = description.Manufacturer + device.Product.ID = description.Name + device.Product.Name = description.Description + break + } + } + return device +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/memory/memory.go b/vendor/github.com/jaypipes/ghw/pkg/memory/memory.go new file mode 100644 index 000000000..93605d2e5 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/memory/memory.go @@ -0,0 +1,80 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package memory + +import ( + "fmt" + "math" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/option" + "github.com/jaypipes/ghw/pkg/unitutil" + "github.com/jaypipes/ghw/pkg/util" +) + +type Module struct { + Label string `json:"label"` + Location string `json:"location"` + SerialNumber string `json:"serial_number"` + SizeBytes int64 `json:"size_bytes"` + Vendor string `json:"vendor"` +} + +type Info struct { + ctx *context.Context + TotalPhysicalBytes int64 `json:"total_physical_bytes"` + TotalUsableBytes int64 `json:"total_usable_bytes"` + // An array of sizes, in bytes, of memory pages supported by the host + SupportedPageSizes []uint64 `json:"supported_page_sizes"` + Modules []*Module `json:"modules"` +} + +func New(opts ...*option.Option) (*Info, error) { + ctx := context.New(opts...) + info := &Info{ctx: ctx} + if err := ctx.Do(info.load); err != nil { + return nil, err + } + return info, nil +} + +func (i *Info) String() string { + tpbs := util.UNKNOWN + if i.TotalPhysicalBytes > 0 { + tpb := i.TotalPhysicalBytes + unit, unitStr := unitutil.AmountString(tpb) + tpb = int64(math.Ceil(float64(i.TotalPhysicalBytes) / float64(unit))) + tpbs = fmt.Sprintf("%d%s", tpb, unitStr) + } + tubs := util.UNKNOWN + if i.TotalUsableBytes > 0 { + tub := i.TotalUsableBytes + unit, unitStr := unitutil.AmountString(tub) + tub = int64(math.Ceil(float64(i.TotalUsableBytes) / float64(unit))) + tubs = fmt.Sprintf("%d%s", tub, unitStr) + } + return fmt.Sprintf("memory (%s physical, %s usable)", tpbs, tubs) +} + +// simple private struct used to encapsulate memory information in a top-level +// "memory" YAML/JSON map/object key +type memoryPrinter struct { + Info *Info `json:"memory"` +} + +// YAMLString returns a string with the memory information formatted as YAML +// under a top-level "memory:" key +func (i *Info) YAMLString() string { + return marshal.SafeYAML(i.ctx, memoryPrinter{i}) +} + +// JSONString returns a string with the memory information formatted as JSON +// under a top-level "memory:" key +func (i *Info) JSONString(indent bool) string { + return marshal.SafeJSON(i.ctx, memoryPrinter{i}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/memory/memory_cache.go b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_cache.go new file mode 100644 index 000000000..5adbb9cd1 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_cache.go @@ -0,0 +1,101 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package memory + +import ( + "fmt" + "strconv" + "strings" + + "github.com/jaypipes/ghw/pkg/unitutil" +) + +type CacheType int + +const ( + CACHE_TYPE_UNIFIED CacheType = iota + CACHE_TYPE_INSTRUCTION + CACHE_TYPE_DATA +) + +var ( + memoryCacheTypeString = map[CacheType]string{ + CACHE_TYPE_UNIFIED: "Unified", + CACHE_TYPE_INSTRUCTION: "Instruction", + CACHE_TYPE_DATA: "Data", + } +) + +func (a CacheType) String() string { + return memoryCacheTypeString[a] +} + +// NOTE(jaypipes): since serialized output is as "official" as we're going to +// get, let's lowercase the string output when serializing, in order to +// "normalize" the expected serialized output +func (a CacheType) MarshalJSON() ([]byte, error) { + return []byte("\"" + strings.ToLower(a.String()) + "\""), nil +} + +type SortByCacheLevelTypeFirstProcessor []*Cache + +func (a SortByCacheLevelTypeFirstProcessor) Len() int { return len(a) } +func (a SortByCacheLevelTypeFirstProcessor) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a SortByCacheLevelTypeFirstProcessor) Less(i, j int) bool { + if a[i].Level < a[j].Level { + return true + } else if a[i].Level == a[j].Level { + if a[i].Type < a[j].Type { + return true + } else if a[i].Type == a[j].Type { + // NOTE(jaypipes): len(LogicalProcessors) is always >0 and is always + // sorted lowest LP ID to highest LP ID + return a[i].LogicalProcessors[0] < a[j].LogicalProcessors[0] + } + } + return false +} + +type SortByLogicalProcessorId []uint32 + +func (a SortByLogicalProcessorId) Len() int { return len(a) } +func (a SortByLogicalProcessorId) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a SortByLogicalProcessorId) Less(i, j int) bool { return a[i] < a[j] } + +type Cache struct { + Level uint8 `json:"level"` + Type CacheType `json:"type"` + SizeBytes uint64 `json:"size_bytes"` + // The set of logical processors (hardware threads) that have access to the + // cache + LogicalProcessors []uint32 `json:"logical_processors"` +} + +func (c *Cache) String() string { + sizeKb := c.SizeBytes / uint64(unitutil.KB) + typeStr := "" + if c.Type == CACHE_TYPE_INSTRUCTION { + typeStr = "i" + } else if c.Type == CACHE_TYPE_DATA { + typeStr = "d" + } + cacheIDStr := fmt.Sprintf("L%d%s", c.Level, typeStr) + processorMapStr := "" + if c.LogicalProcessors != nil { + lpStrings := make([]string, len(c.LogicalProcessors)) + for x, lpid := range c.LogicalProcessors { + lpStrings[x] = strconv.Itoa(int(lpid)) + } + processorMapStr = " shared with logical processors: " + strings.Join(lpStrings, ",") + } + return fmt.Sprintf( + "%s cache (%d KB)%s", + cacheIDStr, + sizeKb, + processorMapStr, + ) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/memory/memory_cache_linux.go b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_cache_linux.go new file mode 100644 index 000000000..88ab5e56c --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_cache_linux.go @@ -0,0 +1,188 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package memory + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/linuxpath" + "github.com/jaypipes/ghw/pkg/unitutil" +) + +func CachesForNode(ctx *context.Context, nodeID int) ([]*Cache, error) { + // The /sys/devices/node/nodeX directory contains a subdirectory called + // 'cpuX' for each logical processor assigned to the node. Each of those + // subdirectories containers a 'cache' subdirectory which contains a number + // of subdirectories beginning with 'index' and ending in the cache's + // internal 0-based identifier. Those subdirectories contain a number of + // files, including 'shared_cpu_list', 'size', and 'type' which we use to + // determine cache characteristics. + paths := linuxpath.New(ctx) + path := filepath.Join( + paths.SysDevicesSystemNode, + fmt.Sprintf("node%d", nodeID), + ) + caches := make(map[string]*Cache) + + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + for _, file := range files { + filename := file.Name() + if !strings.HasPrefix(filename, "cpu") { + continue + } + if filename == "cpumap" || filename == "cpulist" { + // There are two files in the node directory that start with 'cpu' + // but are not subdirectories ('cpulist' and 'cpumap'). Ignore + // these files. + continue + } + // Grab the logical processor ID by cutting the integer from the + // /sys/devices/system/node/nodeX/cpuX filename + cpuPath := filepath.Join(path, filename) + lpID, _ := strconv.Atoi(filename[3:]) + + // Inspect the caches for each logical processor. There will be a + // /sys/devices/system/node/nodeX/cpuX/cache directory containing a + // number of directories beginning with the prefix "index" followed by + // a number. The number indicates the level of the cache, which + // indicates the "distance" from the processor. Each of these + // directories contains information about the size of that level of + // cache and the processors mapped to it. + cachePath := filepath.Join(cpuPath, "cache") + if _, err = os.Stat(cachePath); errors.Is(err, os.ErrNotExist) { + continue + } + cacheDirFiles, err := ioutil.ReadDir(cachePath) + if err != nil { + return nil, err + } + for _, cacheDirFile := range cacheDirFiles { + cacheDirFileName := cacheDirFile.Name() + if !strings.HasPrefix(cacheDirFileName, "index") { + continue + } + cacheIndex, _ := strconv.Atoi(cacheDirFileName[5:]) + + // The cache information is repeated for each node, so here, we + // just ensure that we only have a one Cache object for each + // unique combination of level, type and processor map + level := memoryCacheLevel(paths, nodeID, lpID, cacheIndex) + cacheType := memoryCacheType(paths, nodeID, lpID, cacheIndex) + sharedCpuMap := memoryCacheSharedCPUMap(paths, nodeID, lpID, cacheIndex) + cacheKey := fmt.Sprintf("%d-%d-%s", level, cacheType, sharedCpuMap) + + cache, exists := caches[cacheKey] + if !exists { + size := memoryCacheSize(paths, nodeID, lpID, level) + cache = &Cache{ + Level: uint8(level), + Type: cacheType, + SizeBytes: uint64(size) * uint64(unitutil.KB), + LogicalProcessors: make([]uint32, 0), + } + caches[cacheKey] = cache + } + cache.LogicalProcessors = append( + cache.LogicalProcessors, + uint32(lpID), + ) + } + } + + cacheVals := make([]*Cache, len(caches)) + x := 0 + for _, c := range caches { + // ensure the cache's processor set is sorted by logical process ID + sort.Sort(SortByLogicalProcessorId(c.LogicalProcessors)) + cacheVals[x] = c + x++ + } + + return cacheVals, nil +} + +func memoryCacheLevel(paths *linuxpath.Paths, nodeID int, lpID int, cacheIndex int) int { + levelPath := filepath.Join( + paths.NodeCPUCacheIndex(nodeID, lpID, cacheIndex), + "level", + ) + levelContents, err := ioutil.ReadFile(levelPath) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", err) + return -1 + } + // levelContents is now a []byte with the last byte being a newline + // character. Trim that off and convert the contents to an integer. + level, err := strconv.Atoi(string(levelContents[:len(levelContents)-1])) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Unable to parse int from %s\n", levelContents) + return -1 + } + return level +} + +func memoryCacheSize(paths *linuxpath.Paths, nodeID int, lpID int, cacheIndex int) int { + sizePath := filepath.Join( + paths.NodeCPUCacheIndex(nodeID, lpID, cacheIndex), + "size", + ) + sizeContents, err := ioutil.ReadFile(sizePath) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", err) + return -1 + } + // size comes as XK\n, so we trim off the K and the newline. + size, err := strconv.Atoi(string(sizeContents[:len(sizeContents)-2])) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Unable to parse int from %s\n", sizeContents) + return -1 + } + return size +} + +func memoryCacheType(paths *linuxpath.Paths, nodeID int, lpID int, cacheIndex int) CacheType { + typePath := filepath.Join( + paths.NodeCPUCacheIndex(nodeID, lpID, cacheIndex), + "type", + ) + cacheTypeContents, err := ioutil.ReadFile(typePath) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", err) + return CACHE_TYPE_UNIFIED + } + switch string(cacheTypeContents[:len(cacheTypeContents)-1]) { + case "Data": + return CACHE_TYPE_DATA + case "Instruction": + return CACHE_TYPE_INSTRUCTION + default: + return CACHE_TYPE_UNIFIED + } +} + +func memoryCacheSharedCPUMap(paths *linuxpath.Paths, nodeID int, lpID int, cacheIndex int) string { + scpuPath := filepath.Join( + paths.NodeCPUCacheIndex(nodeID, lpID, cacheIndex), + "shared_cpu_map", + ) + sharedCpuMap, err := ioutil.ReadFile(scpuPath) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", err) + return "" + } + return string(sharedCpuMap[:len(sharedCpuMap)-1]) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/memory/memory_linux.go b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_linux.go new file mode 100644 index 000000000..2fb85b71d --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_linux.go @@ -0,0 +1,237 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package memory + +import ( + "bufio" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + + "github.com/jaypipes/ghw/pkg/linuxpath" + "github.com/jaypipes/ghw/pkg/unitutil" + "github.com/jaypipes/ghw/pkg/util" +) + +const ( + _WARN_CANNOT_DETERMINE_PHYSICAL_MEMORY = ` +Could not determine total physical bytes of memory. This may +be due to the host being a virtual machine or container with no +/var/log/syslog file or /sys/devices/system/memory directory, or +the current user may not have necessary privileges to read the syslog. +We are falling back to setting the total physical amount of memory to +the total usable amount of memory +` +) + +var ( + // System log lines will look similar to the following: + // ... kernel: [0.000000] Memory: 24633272K/25155024K ... + _REGEX_SYSLOG_MEMLINE = regexp.MustCompile(`Memory:\s+\d+K\/(\d+)K`) +) + +func (i *Info) load() error { + paths := linuxpath.New(i.ctx) + tub := memTotalUsableBytes(paths) + if tub < 1 { + return fmt.Errorf("Could not determine total usable bytes of memory") + } + i.TotalUsableBytes = tub + tpb := memTotalPhysicalBytes(paths) + i.TotalPhysicalBytes = tpb + if tpb < 1 { + i.ctx.Warn(_WARN_CANNOT_DETERMINE_PHYSICAL_MEMORY) + i.TotalPhysicalBytes = tub + } + i.SupportedPageSizes = memSupportedPageSizes(paths) + return nil +} + +func memTotalPhysicalBytes(paths *linuxpath.Paths) (total int64) { + defer func() { + // fallback to the syslog file approach in case of error + if total < 0 { + total = memTotalPhysicalBytesFromSyslog(paths) + } + }() + + // detect physical memory from /sys/devices/system/memory + dir := paths.SysDevicesSystemMemory + + // get the memory block size in byte in hexadecimal notation + blockSize := filepath.Join(dir, "block_size_bytes") + + d, err := ioutil.ReadFile(blockSize) + if err != nil { + return -1 + } + blockSizeBytes, err := strconv.ParseUint(strings.TrimSpace(string(d)), 16, 64) + if err != nil { + return -1 + } + + // iterate over memory's block /sys/devices/system/memory/memory*, + // if the memory block state is 'online' we increment the total + // with the memory block size to determine the amount of physical + // memory available on this system + sysMemory, err := filepath.Glob(filepath.Join(dir, "memory*")) + if err != nil { + return -1 + } else if sysMemory == nil { + return -1 + } + + for _, path := range sysMemory { + s, err := ioutil.ReadFile(filepath.Join(path, "state")) + if err != nil { + return -1 + } + if strings.TrimSpace(string(s)) != "online" { + continue + } + total += int64(blockSizeBytes) + } + + return total +} + +func memTotalPhysicalBytesFromSyslog(paths *linuxpath.Paths) int64 { + // In Linux, the total physical memory can be determined by looking at the + // output of dmidecode, however dmidecode requires root privileges to run, + // so instead we examine the system logs for startup information containing + // total physical memory and cache the results of this. + findPhysicalKb := func(line string) int64 { + matches := _REGEX_SYSLOG_MEMLINE.FindStringSubmatch(line) + if len(matches) == 2 { + i, err := strconv.Atoi(matches[1]) + if err != nil { + return -1 + } + return int64(i * 1024) + } + return -1 + } + + // /var/log will contain a file called syslog and 0 or more files called + // syslog.$NUMBER or syslog.$NUMBER.gz containing system log records. We + // search each, stopping when we match a system log record line that + // contains physical memory information. + logDir := paths.VarLog + logFiles, err := ioutil.ReadDir(logDir) + if err != nil { + return -1 + } + for _, file := range logFiles { + if strings.HasPrefix(file.Name(), "syslog") { + fullPath := filepath.Join(logDir, file.Name()) + unzip := strings.HasSuffix(file.Name(), ".gz") + var r io.ReadCloser + r, err = os.Open(fullPath) + if err != nil { + return -1 + } + defer util.SafeClose(r) + if unzip { + r, err = gzip.NewReader(r) + if err != nil { + return -1 + } + } + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + size := findPhysicalKb(line) + if size > 0 { + return size + } + } + } + } + return -1 +} + +func memTotalUsableBytes(paths *linuxpath.Paths) int64 { + // In Linux, /proc/meminfo contains a set of memory-related amounts, with + // lines looking like the following: + // + // $ cat /proc/meminfo + // MemTotal: 24677596 kB + // MemFree: 21244356 kB + // MemAvailable: 22085432 kB + // ... + // HugePages_Total: 0 + // HugePages_Free: 0 + // HugePages_Rsvd: 0 + // HugePages_Surp: 0 + // ... + // + // It's worth noting that /proc/meminfo returns exact information, not + // "theoretical" information. For instance, on the above system, I have + // 24GB of RAM but MemTotal is indicating only around 23GB. This is because + // MemTotal contains the exact amount of *usable* memory after accounting + // for the kernel's resident memory size and a few reserved bits. For more + // information, see: + // + // https://www.kernel.org/doc/Documentation/filesystems/proc.txt + filePath := paths.ProcMeminfo + r, err := os.Open(filePath) + if err != nil { + return -1 + } + defer util.SafeClose(r) + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Fields(line) + key := strings.Trim(parts[0], ": \t") + if key != "MemTotal" { + continue + } + value, err := strconv.Atoi(strings.TrimSpace(parts[1])) + if err != nil { + return -1 + } + inKb := (len(parts) == 3 && strings.TrimSpace(parts[2]) == "kB") + if inKb { + value = value * int(unitutil.KB) + } + return int64(value) + } + return -1 +} + +func memSupportedPageSizes(paths *linuxpath.Paths) []uint64 { + // In Linux, /sys/kernel/mm/hugepages contains a directory per page size + // supported by the kernel. The directory name corresponds to the pattern + // 'hugepages-{pagesize}kb' + dir := paths.SysKernelMMHugepages + out := make([]uint64, 0) + + files, err := ioutil.ReadDir(dir) + if err != nil { + return out + } + for _, file := range files { + parts := strings.Split(file.Name(), "-") + sizeStr := parts[1] + // Cut off the 'kb' + sizeStr = sizeStr[0 : len(sizeStr)-2] + size, err := strconv.Atoi(sizeStr) + if err != nil { + return out + } + out = append(out, uint64(size*int(unitutil.KB))) + } + return out +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/memory/memory_stub.go b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_stub.go new file mode 100644 index 000000000..26a28b056 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_stub.go @@ -0,0 +1,17 @@ +// +build !linux,!windows +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package memory + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("mem.Info.load not implemented on " + runtime.GOOS) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/memory/memory_windows.go b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_windows.go new file mode 100644 index 000000000..c3a3945ca --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/memory/memory_windows.go @@ -0,0 +1,72 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package memory + +import ( + "github.com/StackExchange/wmi" + + "github.com/jaypipes/ghw/pkg/unitutil" +) + +const wqlOperatingSystem = "SELECT TotalVisibleMemorySize FROM Win32_OperatingSystem" + +type win32OperatingSystem struct { + TotalVisibleMemorySize *uint64 +} + +const wqlPhysicalMemory = "SELECT BankLabel, Capacity, DataWidth, Description, DeviceLocator, Manufacturer, Model, Name, PartNumber, PositionInRow, SerialNumber, Speed, Tag, TotalWidth FROM Win32_PhysicalMemory" + +type win32PhysicalMemory struct { + BankLabel *string + Capacity *uint64 + DataWidth *uint16 + Description *string + DeviceLocator *string + Manufacturer *string + Model *string + Name *string + PartNumber *string + PositionInRow *uint32 + SerialNumber *string + Speed *uint32 + Tag *string + TotalWidth *uint16 +} + +func (i *Info) load() error { + // Getting info from WMI + var win32OSDescriptions []win32OperatingSystem + if err := wmi.Query(wqlOperatingSystem, &win32OSDescriptions); err != nil { + return err + } + var win32MemDescriptions []win32PhysicalMemory + if err := wmi.Query(wqlPhysicalMemory, &win32MemDescriptions); err != nil { + return err + } + // We calculate total physical memory size by summing the DIMM sizes + var totalPhysicalBytes uint64 + i.Modules = make([]*Module, 0, len(win32MemDescriptions)) + for _, description := range win32MemDescriptions { + totalPhysicalBytes += *description.Capacity + i.Modules = append(i.Modules, &Module{ + Label: *description.BankLabel, + Location: *description.DeviceLocator, + SerialNumber: *description.SerialNumber, + SizeBytes: int64(*description.Capacity), + Vendor: *description.Manufacturer, + }) + } + var totalUsableBytes uint64 + for _, description := range win32OSDescriptions { + // TotalVisibleMemorySize is the amount of memory available for us by + // the operating system **in Kilobytes** + totalUsableBytes += *description.TotalVisibleMemorySize * uint64(unitutil.KB) + } + i.TotalUsableBytes = int64(totalUsableBytes) + i.TotalPhysicalBytes = int64(totalPhysicalBytes) + return nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/net/net.go b/vendor/github.com/jaypipes/ghw/pkg/net/net.go new file mode 100644 index 000000000..8994d112e --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/net/net.go @@ -0,0 +1,83 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package net + +import ( + "fmt" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/option" +) + +type NICCapability struct { + Name string `json:"name"` + IsEnabled bool `json:"is_enabled"` + CanEnable bool `json:"can_enable"` +} + +type NIC struct { + Name string `json:"name"` + MacAddress string `json:"mac_address"` + IsVirtual bool `json:"is_virtual"` + Capabilities []*NICCapability `json:"capabilities"` + PCIAddress *string `json:"pci_address,omitempty"` + // TODO(fromani): add other hw addresses (USB) when we support them +} + +func (n *NIC) String() string { + isVirtualStr := "" + if n.IsVirtual { + isVirtualStr = " (virtual)" + } + return fmt.Sprintf( + "%s%s", + n.Name, + isVirtualStr, + ) +} + +type Info struct { + ctx *context.Context + NICs []*NIC `json:"nics"` +} + +// New returns a pointer to an Info struct that contains information about the +// network interface controllers (NICs) on the host system +func New(opts ...*option.Option) (*Info, error) { + ctx := context.New(opts...) + info := &Info{ctx: ctx} + if err := ctx.Do(info.load); err != nil { + return nil, err + } + return info, nil +} + +func (i *Info) String() string { + return fmt.Sprintf( + "net (%d NICs)", + len(i.NICs), + ) +} + +// simple private struct used to encapsulate net information in a +// top-level "net" YAML/JSON map/object key +type netPrinter struct { + Info *Info `json:"network"` +} + +// YAMLString returns a string with the net information formatted as YAML +// under a top-level "net:" key +func (i *Info) YAMLString() string { + return marshal.SafeYAML(i.ctx, netPrinter{i}) +} + +// JSONString returns a string with the net information formatted as JSON +// under a top-level "net:" key +func (i *Info) JSONString(indent bool) string { + return marshal.SafeJSON(i.ctx, netPrinter{i}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/net/net_linux.go b/vendor/github.com/jaypipes/ghw/pkg/net/net_linux.go new file mode 100644 index 000000000..1b338dfaf --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/net/net_linux.go @@ -0,0 +1,222 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package net + +import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/linuxpath" +) + +const ( + _WARN_ETHTOOL_NOT_INSTALLED = `ethtool not installed. Cannot grab NIC capabilities` +) + +func (i *Info) load() error { + i.NICs = nics(i.ctx) + return nil +} + +func nics(ctx *context.Context) []*NIC { + nics := make([]*NIC, 0) + + paths := linuxpath.New(ctx) + files, err := ioutil.ReadDir(paths.SysClassNet) + if err != nil { + return nics + } + + etAvailable := ctx.EnableTools + if etAvailable { + if etInstalled := ethtoolInstalled(); !etInstalled { + ctx.Warn(_WARN_ETHTOOL_NOT_INSTALLED) + etAvailable = false + } + } + + for _, file := range files { + filename := file.Name() + // Ignore loopback... + if filename == "lo" { + continue + } + + netPath := filepath.Join(paths.SysClassNet, filename) + dest, _ := os.Readlink(netPath) + isVirtual := false + if strings.Contains(dest, "devices/virtual/net") { + isVirtual = true + } + + nic := &NIC{ + Name: filename, + IsVirtual: isVirtual, + } + + mac := netDeviceMacAddress(paths, filename) + nic.MacAddress = mac + if etAvailable { + nic.Capabilities = netDeviceCapabilities(ctx, filename) + } else { + nic.Capabilities = []*NICCapability{} + } + + nic.PCIAddress = netDevicePCIAddress(paths.SysClassNet, filename) + + nics = append(nics, nic) + } + return nics +} + +func netDeviceMacAddress(paths *linuxpath.Paths, dev string) string { + // Instead of use udevadm, we can get the device's MAC address by examing + // the /sys/class/net/$DEVICE/address file in sysfs. However, for devices + // that have addr_assign_type != 0, return None since the MAC address is + // random. + aatPath := filepath.Join(paths.SysClassNet, dev, "addr_assign_type") + contents, err := ioutil.ReadFile(aatPath) + if err != nil { + return "" + } + if strings.TrimSpace(string(contents)) != "0" { + return "" + } + addrPath := filepath.Join(paths.SysClassNet, dev, "address") + contents, err = ioutil.ReadFile(addrPath) + if err != nil { + return "" + } + return strings.TrimSpace(string(contents)) +} + +func ethtoolInstalled() bool { + _, err := exec.LookPath("ethtool") + return err == nil +} + +func netDeviceCapabilities(ctx *context.Context, dev string) []*NICCapability { + caps := make([]*NICCapability, 0) + path, _ := exec.LookPath("ethtool") + cmd := exec.Command(path, "-k", dev) + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + msg := fmt.Sprintf("could not grab NIC capabilities for %s: %s", dev, err) + ctx.Warn(msg) + return caps + } + + // The out variable will now contain something that looks like the + // following. + // + // Features for enp58s0f1: + // rx-checksumming: on + // tx-checksumming: off + // tx-checksum-ipv4: off + // tx-checksum-ip-generic: off [fixed] + // tx-checksum-ipv6: off + // tx-checksum-fcoe-crc: off [fixed] + // tx-checksum-sctp: off [fixed] + // scatter-gather: off + // tx-scatter-gather: off + // tx-scatter-gather-fraglist: off [fixed] + // tcp-segmentation-offload: off + // tx-tcp-segmentation: off + // tx-tcp-ecn-segmentation: off [fixed] + // tx-tcp-mangleid-segmentation: off + // tx-tcp6-segmentation: off + // < snipped > + scanner := bufio.NewScanner(&out) + // Skip the first line... + scanner.Scan() + for scanner.Scan() { + line := strings.TrimPrefix(scanner.Text(), "\t") + caps = append(caps, netParseEthtoolFeature(line)) + } + return caps +} + +// netParseEthtoolFeature parses a line from the ethtool -k output and returns +// a NICCapability. +// +// The supplied line will look like the following: +// +// tx-checksum-ip-generic: off [fixed] +// +// [fixed] indicates that the feature may not be turned on/off. Note: it makes +// no difference whether a privileged user runs `ethtool -k` when determining +// whether [fixed] appears for a feature. +func netParseEthtoolFeature(line string) *NICCapability { + parts := strings.Fields(line) + cap := strings.TrimSuffix(parts[0], ":") + enabled := parts[1] == "on" + fixed := len(parts) == 3 && parts[2] == "[fixed]" + return &NICCapability{ + Name: cap, + IsEnabled: enabled, + CanEnable: !fixed, + } +} + +func netDevicePCIAddress(netDevDir, netDevName string) *string { + // what we do here is not that hard in the end: we need to navigate the sysfs + // up to the directory belonging to the device backing the network interface. + // we can make few relatively safe assumptions, but the safest way is follow + // the right links. And so we go. + // First of all, knowing the network device name we need to resolve the backing + // device path to its full sysfs path. + // say we start with netDevDir="/sys/class/net" and netDevName="enp0s31f6" + netPath := filepath.Join(netDevDir, netDevName) + dest, err := os.Readlink(netPath) + if err != nil { + // bail out with empty value + return nil + } + // now we have something like dest="../../devices/pci0000:00/0000:00:1f.6/net/enp0s31f6" + // remember the path is relative to netDevDir="/sys/class/net" + + netDev := filepath.Clean(filepath.Join(netDevDir, dest)) + // so we clean "/sys/class/net/../../devices/pci0000:00/0000:00:1f.6/net/enp0s31f6" + // leading to "/sys/devices/pci0000:00/0000:00:1f.6/net/enp0s31f6" + // still not there. We need to access the data of the pci device. So we jump into the path + // linked to the "device" pseudofile + dest, err = os.Readlink(filepath.Join(netDev, "device")) + if err != nil { + // bail out with empty value + return nil + } + // we expect something like="../../../0000:00:1f.6" + + devPath := filepath.Clean(filepath.Join(netDev, dest)) + // so we clean "/sys/devices/pci0000:00/0000:00:1f.6/net/enp0s31f6/../../../0000:00:1f.6" + // leading to "/sys/devices/pci0000:00/0000:00:1f.6/" + // finally here! + + // to which bus is this device connected to? + dest, err = os.Readlink(filepath.Join(devPath, "subsystem")) + if err != nil { + // bail out with empty value + return nil + } + // ok, this is hacky, but since we need the last *two* path components and we know we + // are running on linux... + if !strings.HasSuffix(dest, "/bus/pci") { + // unsupported and unexpected bus! + return nil + } + + pciAddr := filepath.Base(devPath) + return &pciAddr +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/net/net_stub.go b/vendor/github.com/jaypipes/ghw/pkg/net/net_stub.go new file mode 100644 index 000000000..a19b76999 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/net/net_stub.go @@ -0,0 +1,17 @@ +// +build !linux,!windows +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package net + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("netFillInfo not implemented on " + runtime.GOOS) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/net/net_windows.go b/vendor/github.com/jaypipes/ghw/pkg/net/net_windows.go new file mode 100644 index 000000000..70d099585 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/net/net_windows.go @@ -0,0 +1,65 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package net + +import ( + "strings" + + "github.com/StackExchange/wmi" +) + +const wqlNetworkAdapter = "SELECT Description, DeviceID, Index, InterfaceIndex, MACAddress, Manufacturer, Name, NetConnectionID, ProductName, ServiceName FROM Win32_NetworkAdapter" + +type win32NetworkAdapter struct { + Description *string + DeviceID *string + Index *uint32 + InterfaceIndex *uint32 + MACAddress *string + Manufacturer *string + Name *string + NetConnectionID *string + ProductName *string + ServiceName *string +} + +func (i *Info) load() error { + // Getting info from WMI + var win32NetDescriptions []win32NetworkAdapter + if err := wmi.Query(wqlNetworkAdapter, &win32NetDescriptions); err != nil { + return err + } + + i.NICs = nics(win32NetDescriptions) + return nil +} + +func nics(win32NetDescriptions []win32NetworkAdapter) []*NIC { + // Converting into standard structures + nics := make([]*NIC, 0) + for _, nicDescription := range win32NetDescriptions { + nic := &NIC{ + Name: netDeviceName(nicDescription), + MacAddress: *nicDescription.MACAddress, + IsVirtual: false, + Capabilities: []*NICCapability{}, + } + // Appenging NIC to NICs + nics = append(nics, nic) + } + + return nics +} + +func netDeviceName(description win32NetworkAdapter) string { + var name string + if strings.TrimSpace(*description.NetConnectionID) != "" { + name = *description.NetConnectionID + " - " + *description.Description + } else { + name = *description.Description + } + return name +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/pci/pci.go b/vendor/github.com/jaypipes/ghw/pkg/pci/pci.go new file mode 100644 index 000000000..c8176e687 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/pci/pci.go @@ -0,0 +1,206 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package pci + +import ( + "encoding/json" + "fmt" + "regexp" + + "github.com/jaypipes/pcidb" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/option" + pciaddr "github.com/jaypipes/ghw/pkg/pci/address" + "github.com/jaypipes/ghw/pkg/topology" + "github.com/jaypipes/ghw/pkg/util" +) + +// backward compatibility, to be removed in 1.0.0 +type Address pciaddr.Address + +// backward compatibility, to be removed in 1.0.0 +var AddressFromString = pciaddr.FromString + +var ( + regexAddress *regexp.Regexp = regexp.MustCompile( + `^(([0-9a-f]{0,4}):)?([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`, + ) +) + +type Device struct { + // The PCI address of the device + Address string `json:"address"` + Vendor *pcidb.Vendor `json:"vendor"` + Product *pcidb.Product `json:"product"` + Revision string `json:"revision"` + Subsystem *pcidb.Product `json:"subsystem"` + // optional subvendor/sub-device information + Class *pcidb.Class `json:"class"` + // optional sub-class for the device + Subclass *pcidb.Subclass `json:"subclass"` + // optional programming interface + ProgrammingInterface *pcidb.ProgrammingInterface `json:"programming_interface"` + // Topology node that the PCI device is affined to. Will be nil if the + // architecture is not NUMA. + Node *topology.Node `json:"node,omitempty"` +} + +type devIdent struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type devMarshallable struct { + Address string `json:"address"` + Vendor devIdent `json:"vendor"` + Product devIdent `json:"product"` + Revision string `json:"revision"` + Subsystem devIdent `json:"subsystem"` + Class devIdent `json:"class"` + Subclass devIdent `json:"subclass"` + Interface devIdent `json:"programming_interface"` +} + +// NOTE(jaypipes) Device has a custom JSON marshaller because we don't want +// to serialize the entire PCIDB information for the Vendor (which includes all +// of the vendor's products, etc). Instead, we simply serialize the ID and +// human-readable name of the vendor, product, class, etc. +func (d *Device) MarshalJSON() ([]byte, error) { + dm := devMarshallable{ + Address: d.Address, + Vendor: devIdent{ + ID: d.Vendor.ID, + Name: d.Vendor.Name, + }, + Product: devIdent{ + ID: d.Product.ID, + Name: d.Product.Name, + }, + Revision: d.Revision, + Subsystem: devIdent{ + ID: d.Subsystem.ID, + Name: d.Subsystem.Name, + }, + Class: devIdent{ + ID: d.Class.ID, + Name: d.Class.Name, + }, + Subclass: devIdent{ + ID: d.Subclass.ID, + Name: d.Subclass.Name, + }, + Interface: devIdent{ + ID: d.ProgrammingInterface.ID, + Name: d.ProgrammingInterface.Name, + }, + } + return json.Marshal(dm) +} + +func (d *Device) String() string { + vendorName := util.UNKNOWN + if d.Vendor != nil { + vendorName = d.Vendor.Name + } + productName := util.UNKNOWN + if d.Product != nil { + productName = d.Product.Name + } + className := util.UNKNOWN + if d.Class != nil { + className = d.Class.Name + } + return fmt.Sprintf( + "%s -> class: '%s' vendor: '%s' product: '%s'", + d.Address, + className, + vendorName, + productName, + ) +} + +type Info struct { + arch topology.Architecture + ctx *context.Context + // All PCI devices on the host system + Devices []*Device + // hash of class ID -> class information + // DEPRECATED. Will be removed in v1.0. Please use + // github.com/jaypipes/pcidb to explore PCIDB information + Classes map[string]*pcidb.Class `json:"-"` + // hash of vendor ID -> vendor information + // DEPRECATED. Will be removed in v1.0. Please use + // github.com/jaypipes/pcidb to explore PCIDB information + Vendors map[string]*pcidb.Vendor `json:"-"` + // hash of vendor ID + product/device ID -> product information + // DEPRECATED. Will be removed in v1.0. Please use + // github.com/jaypipes/pcidb to explore PCIDB information + Products map[string]*pcidb.Product `json:"-"` +} + +func (i *Info) String() string { + return fmt.Sprintf("PCI (%d devices)", len(i.Devices)) +} + +// New returns a pointer to an Info struct that contains information about the +// PCI devices on the host system +func New(opts ...*option.Option) (*Info, error) { + return NewWithContext(context.New(opts...)) +} + +// NewWithContext returns a pointer to an Info struct that contains information about +// the PCI devices on the host system. Use this function when you want to consume +// the topology package from another package (e.g. gpu) +func NewWithContext(ctx *context.Context) (*Info, error) { + // by default we don't report NUMA information; + // we will only if are sure we are running on NUMA architecture + arch := topology.ARCHITECTURE_SMP + topo, err := topology.NewWithContext(ctx) + if err == nil { + arch = topo.Architecture + } else { + ctx.Warn("error detecting system topology: %v", err) + } + info := &Info{ + arch: arch, + ctx: ctx, + } + if err := ctx.Do(info.load); err != nil { + return nil, err + } + return info, nil +} + +// lookupDevice gets a device from cached data +func (info *Info) lookupDevice(address string) *Device { + for _, dev := range info.Devices { + if dev.Address == address { + return dev + } + } + return nil +} + +// simple private struct used to encapsulate PCI information in a top-level +// "pci" YAML/JSON map/object key +type pciPrinter struct { + Info *Info `json:"pci"` +} + +// YAMLString returns a string with the PCI information formatted as YAML +// under a top-level "pci:" key +func (i *Info) YAMLString() string { + return marshal.SafeYAML(i.ctx, pciPrinter{i}) +} + +// JSONString returns a string with the PCI information formatted as JSON +// under a top-level "pci:" key +func (i *Info) JSONString(indent bool) string { + return marshal.SafeJSON(i.ctx, pciPrinter{i}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/pci/pci_linux.go b/vendor/github.com/jaypipes/ghw/pkg/pci/pci_linux.go new file mode 100644 index 000000000..4c6e34a6a --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/pci/pci_linux.go @@ -0,0 +1,378 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package pci + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/jaypipes/pcidb" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/linuxpath" + pciaddr "github.com/jaypipes/ghw/pkg/pci/address" + "github.com/jaypipes/ghw/pkg/topology" + "github.com/jaypipes/ghw/pkg/util" +) + +func (i *Info) load() error { + db, err := pcidb.New(pcidb.WithChroot(i.ctx.Chroot)) + if err != nil { + return err + } + i.Classes = db.Classes + i.Vendors = db.Vendors + i.Products = db.Products + i.Devices = i.ListDevices() + return nil +} + +func getDeviceModaliasPath(ctx *context.Context, address string) string { + paths := linuxpath.New(ctx) + pciAddr := pciaddr.FromString(address) + if pciAddr == nil { + return "" + } + return filepath.Join( + paths.SysBusPciDevices, + pciAddr.Domain+":"+pciAddr.Bus+":"+pciAddr.Slot+"."+pciAddr.Function, + "modalias", + ) +} + +func getDeviceRevision(ctx *context.Context, address string) string { + paths := linuxpath.New(ctx) + pciAddr := pciaddr.FromString(address) + if pciAddr == nil { + return "" + } + revisionPath := filepath.Join( + paths.SysBusPciDevices, + pciAddr.String(), + "revision", + ) + + if _, err := os.Stat(revisionPath); err != nil { + return "" + } + revision, err := ioutil.ReadFile(revisionPath) + if err != nil { + return "" + } + return strings.TrimSpace(string(revision)) +} + +func getDeviceNUMANode(ctx *context.Context, address string) *topology.Node { + paths := linuxpath.New(ctx) + pciAddr := AddressFromString(address) + if pciAddr == nil { + return nil + } + numaNodePath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "numa_node") + + if _, err := os.Stat(numaNodePath); err != nil { + return nil + } + + nodeIdx := util.SafeIntFromFile(ctx, numaNodePath) + if nodeIdx == -1 { + return nil + } + + return &topology.Node{ + ID: nodeIdx, + } +} + +type deviceModaliasInfo struct { + vendorID string + productID string + subproductID string + subvendorID string + classID string + subclassID string + progIfaceID string +} + +func parseModaliasFile(fp string) *deviceModaliasInfo { + if _, err := os.Stat(fp); err != nil { + return nil + } + data, err := ioutil.ReadFile(fp) + if err != nil { + return nil + } + + return parseModaliasData(string(data)) +} + +func parseModaliasData(data string) *deviceModaliasInfo { + // The modalias file is an encoded file that looks like this: + // + // $ cat /sys/devices/pci0000\:00/0000\:00\:03.0/0000\:03\:00.0/modalias + // pci:v000010DEd00001C82sv00001043sd00008613bc03sc00i00 + // + // It is interpreted like so: + // + // pci: -- ignore + // v000010DE -- PCI vendor ID + // d00001C82 -- PCI device ID (the product/model ID) + // sv00001043 -- PCI subsystem vendor ID + // sd00008613 -- PCI subsystem device ID (subdevice product/model ID) + // bc03 -- PCI base class + // sc00 -- PCI subclass + // i00 -- programming interface + vendorID := strings.ToLower(data[9:13]) + productID := strings.ToLower(data[18:22]) + subvendorID := strings.ToLower(data[28:32]) + subproductID := strings.ToLower(data[38:42]) + classID := data[44:46] + subclassID := data[48:50] + progIfaceID := data[51:53] + return &deviceModaliasInfo{ + vendorID: vendorID, + productID: productID, + subproductID: subproductID, + subvendorID: subvendorID, + classID: classID, + subclassID: subclassID, + progIfaceID: progIfaceID, + } +} + +// Returns a pointer to a pcidb.Vendor struct matching the supplied vendor +// ID string. If no such vendor ID string could be found, returns the +// pcidb.Vendor struct populated with "unknown" vendor Name attribute and +// empty Products attribute. +func findPCIVendor(info *Info, vendorID string) *pcidb.Vendor { + vendor := info.Vendors[vendorID] + if vendor == nil { + return &pcidb.Vendor{ + ID: vendorID, + Name: util.UNKNOWN, + Products: []*pcidb.Product{}, + } + } + return vendor +} + +// Returns a pointer to a pcidb.Product struct matching the supplied vendor +// and product ID strings. If no such product could be found, returns the +// pcidb.Product struct populated with "unknown" product Name attribute and +// empty Subsystems attribute. +func findPCIProduct( + info *Info, + vendorID string, + productID string, +) *pcidb.Product { + product := info.Products[vendorID+productID] + if product == nil { + return &pcidb.Product{ + ID: productID, + Name: util.UNKNOWN, + Subsystems: []*pcidb.Product{}, + } + } + return product +} + +// Returns a pointer to a pcidb.Product struct matching the supplied vendor, +// product, subvendor and subproduct ID strings. If no such product could be +// found, returns the pcidb.Product struct populated with "unknown" product +// Name attribute and empty Subsystems attribute. +func findPCISubsystem( + info *Info, + vendorID string, + productID string, + subvendorID string, + subproductID string, +) *pcidb.Product { + product := info.Products[vendorID+productID] + subvendor := info.Vendors[subvendorID] + if subvendor != nil && product != nil { + for _, p := range product.Subsystems { + if p.ID == subproductID { + return p + } + } + } + return &pcidb.Product{ + VendorID: subvendorID, + ID: subproductID, + Name: util.UNKNOWN, + } +} + +// Returns a pointer to a pcidb.Class struct matching the supplied class ID +// string. If no such class ID string could be found, returns the +// pcidb.Class struct populated with "unknown" class Name attribute and +// empty Subclasses attribute. +func findPCIClass(info *Info, classID string) *pcidb.Class { + class := info.Classes[classID] + if class == nil { + return &pcidb.Class{ + ID: classID, + Name: util.UNKNOWN, + Subclasses: []*pcidb.Subclass{}, + } + } + return class +} + +// Returns a pointer to a pcidb.Subclass struct matching the supplied class +// and subclass ID strings. If no such subclass could be found, returns the +// pcidb.Subclass struct populated with "unknown" subclass Name attribute +// and empty ProgrammingInterfaces attribute. +func findPCISubclass( + info *Info, + classID string, + subclassID string, +) *pcidb.Subclass { + class := info.Classes[classID] + if class != nil { + for _, sc := range class.Subclasses { + if sc.ID == subclassID { + return sc + } + } + } + return &pcidb.Subclass{ + ID: subclassID, + Name: util.UNKNOWN, + ProgrammingInterfaces: []*pcidb.ProgrammingInterface{}, + } +} + +// Returns a pointer to a pcidb.ProgrammingInterface struct matching the +// supplied class, subclass and programming interface ID strings. If no such +// programming interface could be found, returns the +// pcidb.ProgrammingInterface struct populated with "unknown" Name attribute +func findPCIProgrammingInterface( + info *Info, + classID string, + subclassID string, + progIfaceID string, +) *pcidb.ProgrammingInterface { + subclass := findPCISubclass(info, classID, subclassID) + for _, pi := range subclass.ProgrammingInterfaces { + if pi.ID == progIfaceID { + return pi + } + } + return &pcidb.ProgrammingInterface{ + ID: progIfaceID, + Name: util.UNKNOWN, + } +} + +// GetDevice returns a pointer to a Device struct that describes the PCI +// device at the requested address. If no such device could be found, returns nil. +func (info *Info) GetDevice(address string) *Device { + // check cached data first + if dev := info.lookupDevice(address); dev != nil { + return dev + } + + // no cached data, let's get the information from system. + fp := getDeviceModaliasPath(info.ctx, address) + if fp == "" { + info.ctx.Warn("error finding modalias info for device %q", address) + return nil + } + + modaliasInfo := parseModaliasFile(fp) + if modaliasInfo == nil { + info.ctx.Warn("error parsing modalias info for device %q", address) + return nil + } + + device := info.getDeviceFromModaliasInfo(address, modaliasInfo) + device.Revision = getDeviceRevision(info.ctx, address) + if info.arch == topology.ARCHITECTURE_NUMA { + device.Node = getDeviceNUMANode(info.ctx, address) + } + return device +} + +// ParseDevice returns a pointer to a Device given its describing data. +// The PCI device obtained this way may not exist in the system; +// use GetDevice to get a *Device which is found in the system +func (info *Info) ParseDevice(address, modalias string) *Device { + modaliasInfo := parseModaliasData(modalias) + if modaliasInfo == nil { + return nil + } + return info.getDeviceFromModaliasInfo(address, modaliasInfo) +} + +func (info *Info) getDeviceFromModaliasInfo(address string, modaliasInfo *deviceModaliasInfo) *Device { + vendor := findPCIVendor(info, modaliasInfo.vendorID) + product := findPCIProduct( + info, + modaliasInfo.vendorID, + modaliasInfo.productID, + ) + subsystem := findPCISubsystem( + info, + modaliasInfo.vendorID, + modaliasInfo.productID, + modaliasInfo.subvendorID, + modaliasInfo.subproductID, + ) + class := findPCIClass(info, modaliasInfo.classID) + subclass := findPCISubclass( + info, + modaliasInfo.classID, + modaliasInfo.subclassID, + ) + progIface := findPCIProgrammingInterface( + info, + modaliasInfo.classID, + modaliasInfo.subclassID, + modaliasInfo.progIfaceID, + ) + + return &Device{ + Address: address, + Vendor: vendor, + Subsystem: subsystem, + Product: product, + Class: class, + Subclass: subclass, + ProgrammingInterface: progIface, + } +} + +// ListDevices returns a list of pointers to Device structs present on the +// host system +// DEPRECATED. Will be removed in v1.0. Please use +// github.com/jaypipes/pcidb to explore PCIDB information +func (info *Info) ListDevices() []*Device { + paths := linuxpath.New(info.ctx) + devs := make([]*Device, 0) + // We scan the /sys/bus/pci/devices directory which contains a collection + // of symlinks. The names of the symlinks are all the known PCI addresses + // for the host. For each address, we grab a *Device matching the + // address and append to the returned array. + links, err := ioutil.ReadDir(paths.SysBusPciDevices) + if err != nil { + info.ctx.Warn("failed to read /sys/bus/pci/devices") + return nil + } + var dev *Device + for _, link := range links { + addr := link.Name() + dev = info.GetDevice(addr) + if dev == nil { + info.ctx.Warn("failed to get device information for PCI address %s", addr) + } else { + devs = append(devs, dev) + } + } + return devs +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/pci/pci_stub.go b/vendor/github.com/jaypipes/ghw/pkg/pci/pci_stub.go new file mode 100644 index 000000000..2588878c4 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/pci/pci_stub.go @@ -0,0 +1,30 @@ +// +build !linux +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package pci + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("pciFillInfo not implemented on " + runtime.GOOS) +} + +// GetDevice returns a pointer to a Device struct that describes the PCI +// device at the requested address. If no such device could be found, returns +// nil +func (info *Info) GetDevice(address string) *Device { + return nil +} + +// ListDevices returns a list of pointers to Device structs present on the +// host system +func (info *Info) ListDevices() []*Device { + return nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/product/product.go b/vendor/github.com/jaypipes/ghw/pkg/product/product.go new file mode 100644 index 000000000..6a2e1ee0e --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/product/product.go @@ -0,0 +1,100 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package product + +import ( + "fmt" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/option" + "github.com/jaypipes/ghw/pkg/util" +) + +// Info defines product information +type Info struct { + ctx *context.Context + Family string `json:"family"` + Name string `json:"name"` + Vendor string `json:"vendor"` + SerialNumber string `json:"serial_number"` + UUID string `json:"uuid"` + SKU string `json:"sku"` + Version string `json:"version"` +} + +func (i *Info) String() string { + familyStr := "" + if i.Family != "" { + familyStr = " family=" + i.Family + } + nameStr := "" + if i.Name != "" { + nameStr = " name=" + i.Name + } + vendorStr := "" + if i.Vendor != "" { + vendorStr = " vendor=" + i.Vendor + } + serialStr := "" + if i.SerialNumber != "" && i.SerialNumber != util.UNKNOWN { + serialStr = " serial=" + i.SerialNumber + } + uuidStr := "" + if i.UUID != "" && i.UUID != util.UNKNOWN { + uuidStr = " uuid=" + i.UUID + } + skuStr := "" + if i.SKU != "" { + skuStr = " sku=" + i.SKU + } + versionStr := "" + if i.Version != "" { + versionStr = " version=" + i.Version + } + + res := fmt.Sprintf( + "product%s%s%s%s%s%s%s", + familyStr, + nameStr, + vendorStr, + serialStr, + uuidStr, + skuStr, + versionStr, + ) + return res +} + +// New returns a pointer to a Info struct containing information +// about the host's product +func New(opts ...*option.Option) (*Info, error) { + ctx := context.New(opts...) + info := &Info{ctx: ctx} + if err := ctx.Do(info.load); err != nil { + return nil, err + } + return info, nil +} + +// simple private struct used to encapsulate product information in a top-level +// "product" YAML/JSON map/object key +type productPrinter struct { + Info *Info `json:"product"` +} + +// YAMLString returns a string with the product information formatted as YAML +// under a top-level "dmi:" key +func (info *Info) YAMLString() string { + return marshal.SafeYAML(info.ctx, productPrinter{info}) +} + +// JSONString returns a string with the product information formatted as JSON +// under a top-level "product:" key +func (info *Info) JSONString(indent bool) string { + return marshal.SafeJSON(info.ctx, productPrinter{info}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/product/product_linux.go b/vendor/github.com/jaypipes/ghw/pkg/product/product_linux.go new file mode 100644 index 000000000..36b6b4471 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/product/product_linux.go @@ -0,0 +1,23 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package product + +import ( + "github.com/jaypipes/ghw/pkg/linuxdmi" +) + +func (i *Info) load() error { + + i.Family = linuxdmi.Item(i.ctx, "product_family") + i.Name = linuxdmi.Item(i.ctx, "product_name") + i.Vendor = linuxdmi.Item(i.ctx, "sys_vendor") + i.SerialNumber = linuxdmi.Item(i.ctx, "product_serial") + i.UUID = linuxdmi.Item(i.ctx, "product_uuid") + i.SKU = linuxdmi.Item(i.ctx, "product_sku") + i.Version = linuxdmi.Item(i.ctx, "product_version") + + return nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/product/product_stub.go b/vendor/github.com/jaypipes/ghw/pkg/product/product_stub.go new file mode 100644 index 000000000..8d848b62c --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/product/product_stub.go @@ -0,0 +1,17 @@ +// +build !linux,!windows +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package product + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("productFillInfo not implemented on " + runtime.GOOS) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/product/product_windows.go b/vendor/github.com/jaypipes/ghw/pkg/product/product_windows.go new file mode 100644 index 000000000..c919cb0f6 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/product/product_windows.go @@ -0,0 +1,45 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package product + +import ( + "github.com/StackExchange/wmi" + + "github.com/jaypipes/ghw/pkg/util" +) + +const wqlProduct = "SELECT Caption, Description, IdentifyingNumber, Name, SKUNumber, Vendor, Version, UUID FROM Win32_ComputerSystemProduct" + +type win32Product struct { + Caption *string + Description *string + IdentifyingNumber *string + Name *string + SKUNumber *string + Vendor *string + Version *string + UUID *string +} + +func (i *Info) load() error { + // Getting data from WMI + var win32ProductDescriptions []win32Product + // Assuming the first product is the host... + if err := wmi.Query(wqlProduct, &win32ProductDescriptions); err != nil { + return err + } + if len(win32ProductDescriptions) > 0 { + i.Family = util.UNKNOWN + i.Name = *win32ProductDescriptions[0].Name + i.Vendor = *win32ProductDescriptions[0].Vendor + i.SerialNumber = *win32ProductDescriptions[0].IdentifyingNumber + i.UUID = *win32ProductDescriptions[0].UUID + i.SKU = *win32ProductDescriptions[0].SKUNumber + i.Version = *win32ProductDescriptions[0].Version + } + + return nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/topology/topology.go b/vendor/github.com/jaypipes/ghw/pkg/topology/topology.go new file mode 100644 index 000000000..a846584dc --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/topology/topology.go @@ -0,0 +1,128 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package topology + +import ( + "fmt" + "sort" + "strings" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/cpu" + "github.com/jaypipes/ghw/pkg/marshal" + "github.com/jaypipes/ghw/pkg/memory" + "github.com/jaypipes/ghw/pkg/option" +) + +// Architecture describes the overall hardware architecture. It can be either +// Symmetric Multi-Processor (SMP) or Non-Uniform Memory Access (NUMA) +type Architecture int + +const ( + // SMP is a Symmetric Multi-Processor system + ARCHITECTURE_SMP Architecture = iota + // NUMA is a Non-Uniform Memory Access system + ARCHITECTURE_NUMA +) + +var ( + architectureString = map[Architecture]string{ + ARCHITECTURE_SMP: "SMP", + ARCHITECTURE_NUMA: "NUMA", + } +) + +func (a Architecture) String() string { + return architectureString[a] +} + +// NOTE(jaypipes): since serialized output is as "official" as we're going to +// get, let's lowercase the string output when serializing, in order to +// "normalize" the expected serialized output +func (a Architecture) MarshalJSON() ([]byte, error) { + return []byte("\"" + strings.ToLower(a.String()) + "\""), nil +} + +// Node is an abstract construct representing a collection of processors and +// various levels of memory cache that those processors share. In a NUMA +// architecture, there are multiple NUMA nodes, abstracted here as multiple +// Node structs. In an SMP architecture, a single Node will be available in the +// Info struct and this single struct can be used to describe the levels of +// memory caching available to the single physical processor package's physical +// processor cores +type Node struct { + ID int `json:"id"` + Cores []*cpu.ProcessorCore `json:"cores"` + Caches []*memory.Cache `json:"caches"` + Distances []int `json:"distances"` +} + +func (n *Node) String() string { + return fmt.Sprintf( + "node #%d (%d cores)", + n.ID, + len(n.Cores), + ) +} + +// Info describes the system topology for the host hardware +type Info struct { + ctx *context.Context + Architecture Architecture `json:"architecture"` + Nodes []*Node `json:"nodes"` +} + +// New returns a pointer to an Info struct that contains information about the +// NUMA topology on the host system +func New(opts ...*option.Option) (*Info, error) { + return NewWithContext(context.New(opts...)) +} + +// NewWithContext returns a pointer to an Info struct that contains information about +// the NUMA topology on the host system. Use this function when you want to consume +// the topology package from another package (e.g. pci, gpu) +func NewWithContext(ctx *context.Context) (*Info, error) { + info := &Info{ctx: ctx} + if err := ctx.Do(info.load); err != nil { + return nil, err + } + for _, node := range info.Nodes { + sort.Sort(memory.SortByCacheLevelTypeFirstProcessor(node.Caches)) + } + return info, nil +} + +func (i *Info) String() string { + archStr := "SMP" + if i.Architecture == ARCHITECTURE_NUMA { + archStr = "NUMA" + } + res := fmt.Sprintf( + "topology %s (%d nodes)", + archStr, + len(i.Nodes), + ) + return res +} + +// simple private struct used to encapsulate topology information in a +// top-level "topology" YAML/JSON map/object key +type topologyPrinter struct { + Info *Info `json:"topology"` +} + +// YAMLString returns a string with the topology information formatted as YAML +// under a top-level "topology:" key +func (i *Info) YAMLString() string { + return marshal.SafeYAML(i.ctx, topologyPrinter{i}) +} + +// JSONString returns a string with the topology information formatted as JSON +// under a top-level "topology:" key +func (i *Info) JSONString(indent bool) string { + return marshal.SafeJSON(i.ctx, topologyPrinter{i}, indent) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/topology/topology_linux.go b/vendor/github.com/jaypipes/ghw/pkg/topology/topology_linux.go new file mode 100644 index 000000000..4c3bbb16b --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/topology/topology_linux.go @@ -0,0 +1,100 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package topology + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "strconv" + "strings" + + "github.com/jaypipes/ghw/pkg/context" + "github.com/jaypipes/ghw/pkg/cpu" + "github.com/jaypipes/ghw/pkg/linuxpath" + "github.com/jaypipes/ghw/pkg/memory" +) + +func (i *Info) load() error { + i.Nodes = topologyNodes(i.ctx) + if len(i.Nodes) == 1 { + i.Architecture = ARCHITECTURE_SMP + } else { + i.Architecture = ARCHITECTURE_NUMA + } + return nil +} + +func topologyNodes(ctx *context.Context) []*Node { + paths := linuxpath.New(ctx) + nodes := make([]*Node, 0) + + files, err := ioutil.ReadDir(paths.SysDevicesSystemNode) + if err != nil { + ctx.Warn("failed to determine nodes: %s\n", err) + return nodes + } + for _, file := range files { + filename := file.Name() + if !strings.HasPrefix(filename, "node") { + continue + } + node := &Node{} + nodeID, err := strconv.Atoi(filename[4:]) + if err != nil { + ctx.Warn("failed to determine node ID: %s\n", err) + return nodes + } + node.ID = nodeID + cores, err := cpu.CoresForNode(ctx, nodeID) + if err != nil { + ctx.Warn("failed to determine cores for node: %s\n", err) + return nodes + } + node.Cores = cores + caches, err := memory.CachesForNode(ctx, nodeID) + if err != nil { + ctx.Warn("failed to determine caches for node: %s\n", err) + return nodes + } + node.Caches = caches + + distances, err := distancesForNode(ctx, nodeID) + if err != nil { + ctx.Warn("failed to determine node distances for node: %s\n", err) + return nodes + } + node.Distances = distances + + nodes = append(nodes, node) + } + return nodes +} + +func distancesForNode(ctx *context.Context, nodeID int) ([]int, error) { + paths := linuxpath.New(ctx) + path := filepath.Join( + paths.SysDevicesSystemNode, + fmt.Sprintf("node%d", nodeID), + "distance", + ) + + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + items := strings.Fields(strings.TrimSpace(string(data))) + dists := make([]int, len(items), len(items)) // TODO: can a NUMA cell be offlined? + for idx, item := range items { + dist, err := strconv.Atoi(item) + if err != nil { + return dists, err + } + dists[idx] = dist + } + return dists, nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/topology/topology_stub.go b/vendor/github.com/jaypipes/ghw/pkg/topology/topology_stub.go new file mode 100644 index 000000000..b2b801f7b --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/topology/topology_stub.go @@ -0,0 +1,17 @@ +// +build !linux,!windows +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package topology + +import ( + "runtime" + + "github.com/pkg/errors" +) + +func (i *Info) load() error { + return errors.New("topologyFillInfo not implemented on " + runtime.GOOS) +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/topology/topology_windows.go b/vendor/github.com/jaypipes/ghw/pkg/topology/topology_windows.go new file mode 100644 index 000000000..3141ac99c --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/topology/topology_windows.go @@ -0,0 +1,156 @@ +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package topology + +import ( + "encoding/binary" + "fmt" + "syscall" + "unsafe" +) + +const ( + rcFailure = 0 + sizeofLogicalProcessorInfo = 32 + errInsufficientBuffer syscall.Errno = 122 + + relationProcessorCore = 0 + relationNUMANode = 1 + relationCache = 2 + relationProcessorPackage = 3 + relationGroup = 4 +) + +func (i *Info) load() error { + nodes, err := topologyNodes() + if err != nil { + return err + } + i.Nodes = nodes + if len(nodes) == 1 { + i.Architecture = ARCHITECTURE_SMP + } else { + i.Architecture = ARCHITECTURE_NUMA + } + return nil +} + +func topologyNodes() ([]*Node, error) { + nodes := make([]*Node, 0) + lpis, err := getWin32LogicalProcessorInfos() + if err != nil { + return nil, err + } + for _, lpi := range lpis { + switch lpi.relationship { + case relationNUMANode: + nodes = append(nodes, &Node{ + ID: lpi.numaNodeID(), + }) + case relationProcessorCore: + // TODO(jaypipes): associated LP to processor core + case relationProcessorPackage: + // ignore + case relationCache: + // TODO(jaypipes) handle cache layers + default: + return nil, fmt.Errorf("Unknown LOGICAL_PROCESSOR_RELATIONSHIP value: %d", lpi.relationship) + + } + } + return nodes, nil +} + +// This is the CACHE_DESCRIPTOR struct in the Win32 API +type cacheDescriptor struct { + level uint8 + associativity uint8 + lineSize uint16 + size uint32 + cacheType uint32 +} + +// This is the SYSTEM_LOGICAL_PROCESSOR_INFORMATION struct in the Win32 API +type logicalProcessorInfo struct { + processorMask uint64 + relationship uint64 + // The following dummyunion member is a representation of this part of + // the SYSTEM_LOGICAL_PROCESSOR_INFORMATION struct: + // + // union { + // struct { + // BYTE Flags; + // } ProcessorCore; + // struct { + // DWORD NodeNumber; + // } NumaNode; + // CACHE_DESCRIPTOR Cache; + // ULONGLONG Reserved[2]; + // } DUMMYUNIONNAME; + dummyunion [16]byte +} + +// numaNodeID returns the NUMA node's identifier from the logical processor +// information struct by grabbing the integer representation of the struct's +// NumaNode unioned data element +func (lpi *logicalProcessorInfo) numaNodeID() int { + if lpi.relationship != relationNUMANode { + return -1 + } + return int(binary.LittleEndian.Uint16(lpi.dummyunion[0:])) +} + +// ref: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformation +func getWin32LogicalProcessorInfos() ( + []*logicalProcessorInfo, + error, +) { + lpis := make([]*logicalProcessorInfo, 0) + win32api := syscall.NewLazyDLL("kernel32.dll") + glpi := win32api.NewProc("GetLogicalProcessorInformation") + + // The way the GetLogicalProcessorInformation (GLPI) Win32 API call + // works is wonky, but consistent with the Win32 API calling structure. + // Basically, you need to first call the GLPI API with a NUL pointerr + // and a pointer to an integer. That first call to the API should + // return ERROR_INSUFFICIENT_BUFFER, which is the indication that the + // supplied buffer pointer is NUL and needs to have memory allocated to + // it of an amount equal to the value of the integer pointer argument. + // Once the buffer is allocated this amount of space, the GLPI API call + // is again called. This time, the return value should be 0 and the + // buffer will have been set to an array of + // SYSTEM_LOGICAL_PROCESSOR_INFORMATION structs. + toAllocate := uint32(0) + // first, figure out how much we need + rc, _, win32err := glpi.Call(uintptr(0), uintptr(unsafe.Pointer(&toAllocate))) + if rc == rcFailure { + if win32err != errInsufficientBuffer { + return nil, fmt.Errorf("GetLogicalProcessorInformation Win32 API initial call failed to return ERROR_INSUFFICIENT_BUFFER") + } + } else { + // This shouldn't happen because buffer hasn't yet been allocated... + return nil, fmt.Errorf("GetLogicalProcessorInformation Win32 API initial call returned success instead of failure with ERROR_INSUFFICIENT_BUFFER") + } + + // OK, now we actually allocate a raw buffer to fill with some number + // of SYSTEM_LOGICAL_PROCESSOR_INFORMATION structs + b := make([]byte, toAllocate) + rc, _, win32err = glpi.Call(uintptr(unsafe.Pointer(&b[0])), uintptr(unsafe.Pointer(&toAllocate))) + if rc == rcFailure { + return nil, fmt.Errorf("GetLogicalProcessorInformation Win32 API call failed to set supplied buffer. Win32 system error: %s", win32err) + } + + for x := uint32(0); x < toAllocate; x += sizeofLogicalProcessorInfo { + lpiraw := b[x : x+sizeofLogicalProcessorInfo] + lpi := &logicalProcessorInfo{ + processorMask: binary.LittleEndian.Uint64(lpiraw[0:]), + relationship: binary.LittleEndian.Uint64(lpiraw[8:]), + } + copy(lpi.dummyunion[0:16], lpiraw[16:32]) + lpis = append(lpis, lpi) + } + return lpis, nil +} diff --git a/vendor/github.com/jaypipes/ghw/pkg/unitutil/unit.go b/vendor/github.com/jaypipes/ghw/pkg/unitutil/unit.go new file mode 100644 index 000000000..13fa7b5b4 --- /dev/null +++ b/vendor/github.com/jaypipes/ghw/pkg/unitutil/unit.go @@ -0,0 +1,37 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package unitutil + +var ( + KB int64 = 1024 + MB = KB * 1024 + GB = MB * 1024 + TB = GB * 1024 + PB = TB * 1024 + EB = PB * 1024 +) + +// AmountString returns a string representation of the amount with an amount +// suffix corresponding to the nearest kibibit. +// +// For example, AmountString(1022) == "1022). AmountString(1024) == "1KB", etc +func AmountString(size int64) (int64, string) { + switch { + case size < MB: + return KB, "KB" + case size < GB: + return MB, "MB" + case size < TB: + return GB, "GB" + case size < PB: + return TB, "TB" + case size < EB: + return PB, "PB" + default: + return EB, "EB" + } +} diff --git a/vendor/github.com/jaypipes/pcidb/.get-go-packages.sh b/vendor/github.com/jaypipes/pcidb/.get-go-packages.sh new file mode 100644 index 000000000..382782f8a --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/.get-go-packages.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +export GO_PACKAGES=$(go list ./... | grep -v /vendor/) diff --git a/vendor/github.com/jaypipes/pcidb/.gitignore b/vendor/github.com/jaypipes/pcidb/.gitignore new file mode 100644 index 000000000..cc292d34b --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/.gitignore @@ -0,0 +1,2 @@ +vendor/ +coverage*.* diff --git a/vendor/github.com/jaypipes/pcidb/.travis.yml b/vendor/github.com/jaypipes/pcidb/.travis.yml new file mode 100644 index 000000000..2598ce27a --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/.travis.yml @@ -0,0 +1,48 @@ +language: go +script: + - source ./.get-go-packages.sh + - go test -v $GO_PACKAGES +matrix: + include: + # On Go 1.10 and Go 1.11, use dep to ensure dependencies before running go + # test. + - os: windows + go: "1.10" + install: + - go get -u github.com/golang/dep/cmd/dep + - dep ensure -v + - os: linux + go: "1.10" + install: + - go get -u github.com/golang/dep/cmd/dep + - dep ensure -v + - os: osx + go: "1.10" + install: + - go get -u github.com/golang/dep/cmd/dep + - dep ensure -v + - os: windows + go: "1.11" + install: + - go get -u github.com/golang/dep/cmd/dep + - dep ensure -v + - os: linux + go: "1.11" + install: + - go get -u github.com/golang/dep/cmd/dep + - dep ensure -v + - os: osx + go: "1.11" + install: + - go get -u github.com/golang/dep/cmd/dep + - dep ensure -v + # On Go 1.12, use go modules to ensure dependencies instead of dep + - os: windows + go: "1.12" + env: GO111MODULE=on + - os: linux + go: "1.12" + env: GO111MODULE=on + - os: osx + go: "1.12" + env: GO111MODULE=on diff --git a/vendor/github.com/jaypipes/pcidb/COPYING b/vendor/github.com/jaypipes/pcidb/COPYING new file mode 100644 index 000000000..68c771a09 --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/COPYING @@ -0,0 +1,176 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/vendor/github.com/jaypipes/pcidb/Gopkg.lock b/vendor/github.com/jaypipes/pcidb/Gopkg.lock new file mode 100644 index 000000000..0030a4eff --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/Gopkg.lock @@ -0,0 +1,17 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:78bbb1ba5b7c3f2ed0ea1eab57bdd3859aec7e177811563edc41198a760b06af" + name = "github.com/mitchellh/go-homedir" + packages = ["."] + pruneopts = "UT" + revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4" + version = "v1.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = ["github.com/mitchellh/go-homedir"] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/jaypipes/pcidb/Gopkg.toml b/vendor/github.com/jaypipes/pcidb/Gopkg.toml new file mode 100644 index 000000000..53185c743 --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[prune] + go-tests = true + unused-packages = true + +[[constraint]] + name = "github.com/mitchellh/go-homedir" + version = "1.0.0" diff --git a/vendor/github.com/jaypipes/pcidb/LICENSE b/vendor/github.com/jaypipes/pcidb/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/jaypipes/pcidb/Makefile b/vendor/github.com/jaypipes/pcidb/Makefile new file mode 100644 index 000000000..e6b08dc8e --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/Makefile @@ -0,0 +1,46 @@ +VENDOR := vendor +PKGS := $(shell go list ./... | grep -v /$(VENDOR)/) +SRC = $(shell find . -type f -name '*.go' -not -path "*/$(VENDOR)/*") +BIN_DIR := $(GOPATH)/bin +DEP := $(BIN_DIR)/dep +GOMETALINTER := $(BIN_DIR)/gometalinter + +.PHONY: test +test: vet + go test $(PKGS) + +$(DEP): + go get -u github.com/golang/dep/cmd/dep + +.PHONY: dep +dep: $(DEP) + $(DEP) ensure + +$(GOMETALINTER): + go get -u github.com/alecthomas/gometalinter + $(GOMETALINTER) --install &> /dev/null + +.PHONY: lint +lint: $(GOMETALINTER) + $(GOMETALINTER) ./... --vendor + +.PHONY: fmt +fmt: + @gofmt -s -l -w $(SRC) + +.PHONY: fmtcheck +fmtcheck: + @bash -c "diff -u <(echo -n) <(gofmt -d $(SRC))" + +.PHONY: vet +vet: + go vet $(PKGS) + +.PHONY: cover +cover: + $(shell [ -e coverage.out ] && rm coverage.out) + @echo "mode: count" > coverage-all.out + @$(foreach pkg,$(PKGS),\ + go test -coverprofile=coverage.out -covermode=count $(pkg);\ + tail -n +2 coverage.out >> coverage-all.out;) + go tool cover -html=coverage-all.out -o=coverage-all.html diff --git a/vendor/github.com/jaypipes/pcidb/README.md b/vendor/github.com/jaypipes/pcidb/README.md new file mode 100644 index 000000000..fd2915b4e --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/README.md @@ -0,0 +1,413 @@ +# `pcidb` - the Golang PCI DB library [![Build Status](https://travis-ci.org/jaypipes/pcidb.svg?branch=master)](https://travis-ci.org/jaypipes/pcidb) + +`pcidb` is a small Golang library for programmatic querying of PCI vendor, +product and class information. + +We currently [test](https://travis-ci.org/jaypipes/pcidb/) `pcidb` on Linux, Windows and MacOSX. + +## Usage + +`pcidb` contains a PCI database inspection and querying facility that allows +developers to query for information about hardware device classes, vendor and +product information. + +The `pcidb.New()` function returns a `pcidb.PCIDB` struct or an error if the +PCI database could not be loaded. + +> `pcidb`'s default behaviour is to first search for pci-ids DB files on the +> local host system in well-known filesystem paths. If `pcidb` cannot find a +> pci-ids DB file on the local host system, it will then fetch a current +> pci-ids DB file from the network. You can disable this network-fetching +> behaviour with the `pcidb.WithDisableNetworkFetch()` function or set the +> `PCIDB_DISABLE_NETWORK_FETCH` to a non-0 value. + +The `pcidb.PCIDB` struct contains a number of fields that may be queried for +PCI information: + +* `pcidb.PCIDB.Classes` is a map, keyed by the PCI class ID (a hex-encoded + string) of pointers to `pcidb.Class` structs, one for each class of PCI + device known to `pcidb` +* `pcidb.PCIDB.Vendors` is a map, keyed by the PCI vendor ID (a hex-encoded + string) of pointers to `pcidb.Vendor` structs, one for each PCI vendor + known to `pcidb` +* `pcidb.PCIDB.Products` is a map, keyed by the PCI product ID* (a hex-encoded + string) of pointers to `pcidb.Product` structs, one for each PCI product + known to `pcidb` + +**NOTE**: PCI products are often referred to by their "device ID". We use +the term "product ID" in `pcidb` because it more accurately reflects what the +identifier is for: a specific product line produced by the vendor. + +### Overriding the root mountpoint `pcidb` uses + +The default root mountpoint that `pcidb` uses when looking for information +about the host system is `/`. So, for example, when looking up known PCI IDS DB +files on Linux, `pcidb` will attempt to discover a pciids DB file at +`/usr/share/misc/pci.ids`. If you are calling `pcidb` from a system that has an +alternate root mountpoint, you can either set the `PCIDB_CHROOT` environment +variable to that alternate path, or call the `pcidb.New()` function with the +`pcidb.WithChroot()` modifier. + +For example, if you are executing from within an application container that has +bind-mounted the root host filesystem to the mount point `/host`, you would set +`PCIDB_CHROOT` to `/host` so that pcidb can find files like +`/usr/share/misc/pci.ids` at `/host/usr/share/misc/pci.ids`. + +Alternately, you can use the `pcidb.WithChroot()` function like so: + +```go +pci := pcidb.New(pcidb.WithChroot("/host")) +``` + +### PCI device classes + +Let's take a look at the PCI device class information and how to query the PCI +database for class, subclass, and programming interface information. + +Each `pcidb.Class` struct contains the following fields: + +* `pcidb.Class.ID` is the hex-encoded string identifier for the device + class +* `pcidb.Class.Name` is the common name/description of the class +* `pcidb.Class.Subclasses` is an array of pointers to + `pcidb.Subclass` structs, one for each subclass in the device class + +Each `pcidb.Subclass` struct contains the following fields: + +* `pcidb.Subclass.ID` is the hex-encoded string identifier for the device + subclass +* `pcidb.Subclass.Name` is the common name/description of the subclass +* `pcidb.Subclass.ProgrammingInterfaces` is an array of pointers to + `pcidb.ProgrammingInterface` structs, one for each programming interface + for the device subclass + +Each `pcidb.ProgrammingInterface` struct contains the following fields: + +* `pcidb.ProgrammingInterface.ID` is the hex-encoded string identifier for + the programming interface +* `pcidb.ProgrammingInterface.Name` is the common name/description for the + programming interface + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/pcidb" +) + +func main() { + pci, err := pcidb.New() + if err != nil { + fmt.Printf("Error getting PCI info: %v", err) + } + + for _, devClass := range pci.Classes { + fmt.Printf(" Device class: %v ('%v')\n", devClass.Name, devClass.ID) + for _, devSubclass := range devClass.Subclasses { + fmt.Printf(" Device subclass: %v ('%v')\n", devSubclass.Name, devSubclass.ID) + for _, progIface := range devSubclass.ProgrammingInterfaces { + fmt.Printf(" Programming interface: %v ('%v')\n", progIface.Name, progIface.ID) + } + } + } +} +``` + +Example output from my personal workstation, snipped for brevity: + +``` +... + Device class: Serial bus controller ('0c') + Device subclass: FireWire (IEEE 1394) ('00') + Programming interface: Generic ('00') + Programming interface: OHCI ('10') + Device subclass: ACCESS Bus ('01') + Device subclass: SSA ('02') + Device subclass: USB controller ('03') + Programming interface: UHCI ('00') + Programming interface: OHCI ('10') + Programming interface: EHCI ('20') + Programming interface: XHCI ('30') + Programming interface: Unspecified ('80') + Programming interface: USB Device ('fe') + Device subclass: Fibre Channel ('04') + Device subclass: SMBus ('05') + Device subclass: InfiniBand ('06') + Device subclass: IPMI SMIC interface ('07') + Device subclass: SERCOS interface ('08') + Device subclass: CANBUS ('09') +... +``` + +### PCI vendors and products + +Let's take a look at the PCI vendor information and how to query the PCI +database for vendor information and the products a vendor supplies. + +Each `pcidb.Vendor` struct contains the following fields: + +* `pcidb.Vendor.ID` is the hex-encoded string identifier for the vendor +* `pcidb.Vendor.Name` is the common name/description of the vendor +* `pcidb.Vendor.Products` is an array of pointers to `pcidb.Product` + structs, one for each product supplied by the vendor + +Each `pcidb.Product` struct contains the following fields: + +* `pcidb.Product.VendorID` is the hex-encoded string identifier for the + product's vendor +* `pcidb.Product.ID` is the hex-encoded string identifier for the product +* `pcidb.Product.Name` is the common name/description of the subclass +* `pcidb.Product.Subsystems` is an array of pointers to + `pcidb.Product` structs, one for each "subsystem" (sometimes called + "sub-device" in PCI literature) for the product + +**NOTE**: A subsystem product may have a different vendor than its "parent" PCI +product. This is sometimes referred to as the "sub-vendor". + +Here's some example code that demonstrates listing the PCI vendors with the +most known products: + +```go +package main + +import ( + "fmt" + "sort" + + "github.com/jaypipes/pcidb" +) + +type ByCountProducts []*pcidb.Vendor + +func (v ByCountProducts) Len() int { + return len(v) +} + +func (v ByCountProducts) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} + +func (v ByCountProducts) Less(i, j int) bool { + return len(v[i].Products) > len(v[j].Products) +} + +func main() { + pci, err := pcidb.New() + if err != nil { + fmt.Printf("Error getting PCI info: %v", err) + } + + vendors := make([]*pcidb.Vendor, len(pci.Vendors)) + x := 0 + for _, vendor := range pci.Vendors { + vendors[x] = vendor + x++ + } + + sort.Sort(ByCountProducts(vendors)) + + fmt.Println("Top 5 vendors by product") + fmt.Println("====================================================") + for _, vendor := range vendors[0:5] { + fmt.Printf("%v ('%v') has %d products\n", vendor.Name, vendor.ID, len(vendor.Products)) + } +} +``` + +which yields (on my local workstation as of July 7th, 2018): + +``` +Top 5 vendors by product +==================================================== +Intel Corporation ('8086') has 3389 products +NVIDIA Corporation ('10de') has 1358 products +Advanced Micro Devices, Inc. [AMD/ATI] ('1002') has 886 products +National Instruments ('1093') has 601 products +Chelsio Communications Inc ('1425') has 525 products +``` + +The following is an example of querying the PCI product and subsystem +information to find the products which have the most number of subsystems that +have a different vendor than the top-level product. In other words, the two +products which have been re-sold or re-manufactured with the most number of +different companies. + +```go +package main + +import ( + "fmt" + "sort" + + "github.com/jaypipes/pcidb" +) + +type ByCountSeparateSubvendors []*pcidb.Product + +func (v ByCountSeparateSubvendors) Len() int { + return len(v) +} + +func (v ByCountSeparateSubvendors) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} + +func (v ByCountSeparateSubvendors) Less(i, j int) bool { + iVendor := v[i].VendorID + iSetSubvendors := make(map[string]bool, 0) + iNumDiffSubvendors := 0 + jVendor := v[j].VendorID + jSetSubvendors := make(map[string]bool, 0) + jNumDiffSubvendors := 0 + + for _, sub := range v[i].Subsystems { + if sub.VendorID != iVendor { + iSetSubvendors[sub.VendorID] = true + } + } + iNumDiffSubvendors = len(iSetSubvendors) + + for _, sub := range v[j].Subsystems { + if sub.VendorID != jVendor { + jSetSubvendors[sub.VendorID] = true + } + } + jNumDiffSubvendors = len(jSetSubvendors) + + return iNumDiffSubvendors > jNumDiffSubvendors +} + +func main() { + pci, err := pcidb.New() + if err != nil { + fmt.Printf("Error getting PCI info: %v", err) + } + + products := make([]*pcidb.Product, len(pci.Products)) + x := 0 + for _, product := range pci.Products { + products[x] = product + x++ + } + + sort.Sort(ByCountSeparateSubvendors(products)) + + fmt.Println("Top 2 products by # different subvendors") + fmt.Println("====================================================") + for _, product := range products[0:2] { + vendorID := product.VendorID + vendor := pci.Vendors[vendorID] + setSubvendors := make(map[string]bool, 0) + + for _, sub := range product.Subsystems { + if sub.VendorID != vendorID { + setSubvendors[sub.VendorID] = true + } + } + fmt.Printf("%v ('%v') from %v\n", product.Name, product.ID, vendor.Name) + fmt.Printf(" -> %d subsystems under the following different vendors:\n", len(setSubvendors)) + for subvendorID, _ := range setSubvendors { + subvendor, exists := pci.Vendors[subvendorID] + subvendorName := "Unknown subvendor" + if exists { + subvendorName = subvendor.Name + } + fmt.Printf(" - %v ('%v')\n", subvendorName, subvendorID) + } + } +} +``` + +which yields (on my local workstation as of July 7th, 2018): + +``` +Top 2 products by # different subvendors +==================================================== +RTL-8100/8101L/8139 PCI Fast Ethernet Adapter ('8139') from Realtek Semiconductor Co., Ltd. + -> 34 subsystems under the following different vendors: + - OVISLINK Corp. ('149c') + - EPoX Computer Co., Ltd. ('1695') + - Red Hat, Inc ('1af4') + - Mitac ('1071') + - Netgear ('1385') + - Micro-Star International Co., Ltd. [MSI] ('1462') + - Hangzhou Silan Microelectronics Co., Ltd. ('1904') + - Compex ('11f6') + - Edimax Computer Co. ('1432') + - KYE Systems Corporation ('1489') + - ZyXEL Communications Corporation ('187e') + - Acer Incorporated [ALI] ('1025') + - Matsushita Electric Industrial Co., Ltd. ('10f7') + - Ruby Tech Corp. ('146c') + - Belkin ('1799') + - Allied Telesis ('1259') + - Unex Technology Corp. ('1429') + - CIS Technology Inc ('1436') + - D-Link System Inc ('1186') + - Ambicom Inc ('1395') + - AOPEN Inc. ('a0a0') + - TTTech Computertechnik AG (Wrong ID) ('0357') + - Gigabyte Technology Co., Ltd ('1458') + - Packard Bell B.V. ('1631') + - Billionton Systems Inc ('14cb') + - Kingston Technologies ('2646') + - Accton Technology Corporation ('1113') + - Samsung Electronics Co Ltd ('144d') + - Biostar Microtech Int'l Corp ('1565') + - U.S. Robotics ('16ec') + - KTI ('8e2e') + - Hewlett-Packard Company ('103c') + - ASUSTeK Computer Inc. ('1043') + - Surecom Technology ('10bd') +Bt878 Video Capture ('036e') from Brooktree Corporation + -> 30 subsystems under the following different vendors: + - iTuner ('aa00') + - Nebula Electronics Ltd. ('0071') + - DViCO Corporation ('18ac') + - iTuner ('aa05') + - iTuner ('aa0d') + - LeadTek Research Inc. ('107d') + - Avermedia Technologies Inc ('1461') + - Chaintech Computer Co. Ltd ('270f') + - iTuner ('aa07') + - iTuner ('aa0a') + - Microtune, Inc. ('1851') + - iTuner ('aa01') + - iTuner ('aa04') + - iTuner ('aa06') + - iTuner ('aa0f') + - iTuner ('aa02') + - iTuner ('aa0b') + - Pinnacle Systems, Inc. (Wrong ID) ('bd11') + - Rockwell International ('127a') + - Askey Computer Corp. ('144f') + - Twinhan Technology Co. Ltd ('1822') + - Anritsu Corp. ('1852') + - iTuner ('aa08') + - Hauppauge computer works Inc. ('0070') + - Pinnacle Systems Inc. ('11bd') + - Conexant Systems, Inc. ('14f1') + - iTuner ('aa09') + - iTuner ('aa03') + - iTuner ('aa0c') + - iTuner ('aa0e') +``` + +## Developers + +Contributions to `pcidb` are welcomed! Fork the repo on GitHub and submit a pull +request with your proposed changes. Or, feel free to log an issue for a feature +request or bug report. + +### Running tests + +You can run unit tests easily using the `make test` command, like so: + + +``` +[jaypipes@uberbox pcidb]$ make test +go test github.com/jaypipes/pcidb +ok github.com/jaypipes/pcidb 0.045s +``` diff --git a/vendor/github.com/jaypipes/pcidb/context.go b/vendor/github.com/jaypipes/pcidb/context.go new file mode 100644 index 000000000..946124d9e --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/context.go @@ -0,0 +1,86 @@ +package pcidb + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + + homedir "github.com/mitchellh/go-homedir" +) + +// Concrete merged set of configuration switches that get passed to pcidb +// internal functions +type context struct { + chroot string + cacheOnly bool + cachePath string + path string + disableNetworkFetch bool + searchPaths []string +} + +func contextFromOptions(merged *WithOption) *context { + ctx := &context{ + chroot: *merged.Chroot, + cacheOnly: *merged.CacheOnly, + cachePath: getCachePath(), + disableNetworkFetch: *merged.DisableNetworkFetch, + path: *merged.Path, + searchPaths: make([]string, 0), + } + ctx.setSearchPaths() + return ctx +} + +func getCachePath() string { + hdir, err := homedir.Dir() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed getting homedir.Dir(): %v", err) + return "" + } + fp, err := homedir.Expand(filepath.Join(hdir, ".cache", "pci.ids")) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed expanding local cache path: %v", err) + return "" + } + return fp +} + +// Depending on the operating system, sets the context's searchPaths to a set +// of local filepaths to search for a pci.ids database file +func (ctx *context) setSearchPaths() { + // Look in direct path first, if set + if ctx.path != "" { + ctx.searchPaths = append(ctx.searchPaths, ctx.path) + return + } + // A set of filepaths we will first try to search for the pci-ids DB file + // on the local machine. If we fail to find one, we'll try pulling the + // latest pci-ids file from the network + ctx.searchPaths = append(ctx.searchPaths, ctx.cachePath) + if ctx.cacheOnly { + return + } + + rootPath := ctx.chroot + + if runtime.GOOS != "windows" { + ctx.searchPaths = append( + ctx.searchPaths, + filepath.Join(rootPath, "usr", "share", "hwdata", "pci.ids"), + ) + ctx.searchPaths = append( + ctx.searchPaths, + filepath.Join(rootPath, "usr", "share", "misc", "pci.ids"), + ) + ctx.searchPaths = append( + ctx.searchPaths, + filepath.Join(rootPath, "usr", "share", "hwdata", "pci.ids.gz"), + ) + ctx.searchPaths = append( + ctx.searchPaths, + filepath.Join(rootPath, "usr", "share", "misc", "pci.ids.gz"), + ) + } +} diff --git a/vendor/github.com/jaypipes/pcidb/discover.go b/vendor/github.com/jaypipes/pcidb/discover.go new file mode 100644 index 000000000..38f92a2a7 --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/discover.go @@ -0,0 +1,111 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package pcidb + +import ( + "bufio" + "compress/gzip" + "io" + "net/http" + "os" + "path/filepath" + "strings" +) + +const ( + PCIIDS_URI = "https://pci-ids.ucw.cz/v2.2/pci.ids.gz" + USER_AGENT = "golang-jaypipes-pcidb" +) + +func (db *PCIDB) load(ctx *context) error { + var foundPath string + for _, fp := range ctx.searchPaths { + if _, err := os.Stat(fp); err == nil { + foundPath = fp + break + } + } + if foundPath == "" { + if ctx.disableNetworkFetch { + return ERR_NO_DB + } + // OK, so we didn't find any host-local copy of the pci-ids DB file. Let's + // try fetching it from the network and storing it + if err := cacheDBFile(ctx.cachePath); err != nil { + return err + } + foundPath = ctx.cachePath + } + f, err := os.Open(foundPath) + if err != nil { + return err + } + defer f.Close() + + var scanner *bufio.Scanner + if strings.HasSuffix(foundPath, ".gz") { + var zipReader *gzip.Reader + if zipReader, err = gzip.NewReader(f); err != nil { + return err + } + defer zipReader.Close() + scanner = bufio.NewScanner(zipReader) + } else { + scanner = bufio.NewScanner(f) + } + + return parseDBFile(db, scanner) +} + +func ensureDir(fp string) error { + fpDir := filepath.Dir(fp) + if _, err := os.Stat(fpDir); os.IsNotExist(err) { + err = os.MkdirAll(fpDir, os.ModePerm) + if err != nil { + return err + } + } + return nil +} + +// Pulls down the latest copy of the pci-ids file from the network and stores +// it in the local host filesystem +func cacheDBFile(cacheFilePath string) error { + ensureDir(cacheFilePath) + + client := new(http.Client) + request, err := http.NewRequest("GET", PCIIDS_URI, nil) + if err != nil { + return err + } + request.Header.Set("User-Agent", USER_AGENT) + response, err := client.Do(request) + if err != nil { + return err + } + defer response.Body.Close() + f, err := os.Create(cacheFilePath) + if err != nil { + return err + } + defer func() { + if err != nil { + os.Remove(cacheFilePath) + } + }() + defer f.Close() + // write the gunzipped contents to our local cache file + zr, err := gzip.NewReader(response.Body) + if err != nil { + return err + } + defer zr.Close() + if _, err = io.Copy(f, zr); err != nil { + return err + } + return err +} diff --git a/vendor/github.com/jaypipes/pcidb/main.go b/vendor/github.com/jaypipes/pcidb/main.go new file mode 100644 index 000000000..3768bab2e --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/main.go @@ -0,0 +1,198 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package pcidb + +import ( + "fmt" + "os" + "strconv" +) + +var ( + ERR_NO_DB = fmt.Errorf("No pci-ids DB files found (and network fetch disabled)") + trueVar = true +) + +// ProgrammingInterface is the PCI programming interface for a class of PCI +// devices +type ProgrammingInterface struct { + // hex-encoded PCI_ID of the programming interface + ID string `json:"id"` + // common string name for the programming interface + Name string `json:"name"` +} + +// Subclass is a subdivision of a PCI class +type Subclass struct { + // hex-encoded PCI_ID for the device subclass + ID string `json:"id"` + // common string name for the subclass + Name string `json:"name"` + // any programming interfaces this subclass might have + ProgrammingInterfaces []*ProgrammingInterface `json:"programming_interfaces"` +} + +// Class is the PCI class +type Class struct { + // hex-encoded PCI_ID for the device class + ID string `json:"id"` + // common string name for the class + Name string `json:"name"` + // any subclasses belonging to this class + Subclasses []*Subclass `json:"subclasses"` +} + +// Product provides information about a PCI device model +// NOTE(jaypipes): In the hardware world, the PCI "device_id" is the identifier +// for the product/model +type Product struct { + // vendor ID for the product + VendorID string `json:"vendor_id"` + // hex-encoded PCI_ID for the product/model + ID string `json:"id"` + // common string name of the vendor + Name string `json:"name"` + // "subdevices" or "subsystems" for the product + Subsystems []*Product `json:"subsystems"` +} + +// Vendor provides information about a device vendor +type Vendor struct { + // hex-encoded PCI_ID for the vendor + ID string `json:"id"` + // common string name of the vendor + Name string `json:"name"` + // all top-level devices for the vendor + Products []*Product `json:"products"` +} + +type PCIDB struct { + // hash of class ID -> class information + Classes map[string]*Class `json:"classes"` + // hash of vendor ID -> vendor information + Vendors map[string]*Vendor `json:"vendors"` + // hash of vendor ID + product/device ID -> product information + Products map[string]*Product `json:"products"` +} + +// WithOption is used to represent optionally-configured settings +type WithOption struct { + // Chroot is the directory that pcidb uses when attempting to discover + // pciids DB files + Chroot *string + // CacheOnly is mostly just useful for testing. It essentially disables + // looking for any non ~/.cache/pci.ids filepaths (which is useful when we + // want to test the fetch-from-network code paths + CacheOnly *bool + // Disables the default behaviour of fetching a pci-ids from a known + // location on the network if no local pci-ids DB files can be found. + // Useful for secure environments or environments with no network + // connectivity. + DisableNetworkFetch *bool + // Path points to the absolute path of a pci.ids file in a non-standard + // location. + Path *string +} + +func WithChroot(dir string) *WithOption { + return &WithOption{Chroot: &dir} +} + +func WithCacheOnly() *WithOption { + return &WithOption{CacheOnly: &trueVar} +} + +func WithDirectPath(path string) *WithOption { + return &WithOption{Path: &path} +} + +func WithDisableNetworkFetch() *WithOption { + return &WithOption{DisableNetworkFetch: &trueVar} +} + +func mergeOptions(opts ...*WithOption) *WithOption { + // Grab options from the environs by default + defaultChroot := "/" + if val, exists := os.LookupEnv("PCIDB_CHROOT"); exists { + defaultChroot = val + } + path := "" + if val, exists := os.LookupEnv("PCIDB_PATH"); exists { + path = val + } + defaultCacheOnly := false + if val, exists := os.LookupEnv("PCIDB_CACHE_ONLY"); exists { + if parsed, err := strconv.ParseBool(val); err != nil { + fmt.Fprintf( + os.Stderr, + "Failed parsing a bool from PCIDB_CACHE_ONLY "+ + "environ value of %s", + val, + ) + } else if parsed { + defaultCacheOnly = parsed + } + } + defaultDisableNetworkFetch := false + if val, exists := os.LookupEnv("PCIDB_DISABLE_NETWORK_FETCH"); exists { + if parsed, err := strconv.ParseBool(val); err != nil { + fmt.Fprintf( + os.Stderr, + "Failed parsing a bool from PCIDB_DISABLE_NETWORK_FETCH "+ + "environ value of %s", + val, + ) + } else if parsed { + defaultDisableNetworkFetch = parsed + } + } + + merged := &WithOption{} + for _, opt := range opts { + if opt.Chroot != nil { + merged.Chroot = opt.Chroot + } + if opt.CacheOnly != nil { + merged.CacheOnly = opt.CacheOnly + } + if opt.DisableNetworkFetch != nil { + merged.DisableNetworkFetch = opt.DisableNetworkFetch + } + if opt.Path != nil { + merged.Path = opt.Path + } + } + // Set the default value if missing from merged + if merged.Chroot == nil { + merged.Chroot = &defaultChroot + } + if merged.CacheOnly == nil { + merged.CacheOnly = &defaultCacheOnly + } + if merged.DisableNetworkFetch == nil { + merged.DisableNetworkFetch = &defaultDisableNetworkFetch + } + if merged.Path == nil { + merged.Path = &path + } + return merged +} + +// New returns a pointer to a PCIDB struct which contains information you can +// use to query PCI vendor, product and class information. It accepts zero or +// more pointers to WithOption structs. If you want to modify the behaviour of +// pcidb, use one of the option modifiers when calling New. For example, to +// change the root directory that pcidb uses when discovering pciids DB files, +// call New(WithChroot("/my/root/override")) +func New(opts ...*WithOption) (*PCIDB, error) { + ctx := contextFromOptions(mergeOptions(opts...)) + db := &PCIDB{} + if err := db.load(ctx); err != nil { + return nil, err + } + return db, nil +} diff --git a/vendor/github.com/jaypipes/pcidb/parse.go b/vendor/github.com/jaypipes/pcidb/parse.go new file mode 100644 index 000000000..0fee5fe5e --- /dev/null +++ b/vendor/github.com/jaypipes/pcidb/parse.go @@ -0,0 +1,163 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package pcidb + +import ( + "bufio" + "strings" +) + +func parseDBFile(db *PCIDB, scanner *bufio.Scanner) error { + inClassBlock := false + db.Classes = make(map[string]*Class, 20) + db.Vendors = make(map[string]*Vendor, 200) + db.Products = make(map[string]*Product, 1000) + subclasses := make([]*Subclass, 0) + progIfaces := make([]*ProgrammingInterface, 0) + var curClass *Class + var curSubclass *Subclass + var curProgIface *ProgrammingInterface + vendorProducts := make([]*Product, 0) + var curVendor *Vendor + var curProduct *Product + var curSubsystem *Product + productSubsystems := make([]*Product, 0) + for scanner.Scan() { + line := scanner.Text() + // skip comments and blank lines + if line == "" || strings.HasPrefix(line, "#") { + continue + } + lineBytes := []rune(line) + + // Lines starting with an uppercase "C" indicate a PCI top-level class + // dbrmation block. These lines look like this: + // + // C 02 Network controller + if lineBytes[0] == 'C' { + if curClass != nil { + // finalize existing class because we found a new class block + curClass.Subclasses = subclasses + subclasses = make([]*Subclass, 0) + } + inClassBlock = true + classID := string(lineBytes[2:4]) + className := string(lineBytes[6:]) + curClass = &Class{ + ID: classID, + Name: className, + Subclasses: subclasses, + } + db.Classes[curClass.ID] = curClass + continue + } + + // Lines not beginning with an uppercase "C" or a TAB character + // indicate a top-level vendor dbrmation block. These lines look like + // this: + // + // 0a89 BREA Technologies Inc + if lineBytes[0] != '\t' { + if curVendor != nil { + // finalize existing vendor because we found a new vendor block + curVendor.Products = vendorProducts + vendorProducts = make([]*Product, 0) + } + inClassBlock = false + vendorID := string(lineBytes[0:4]) + vendorName := string(lineBytes[6:]) + curVendor = &Vendor{ + ID: vendorID, + Name: vendorName, + Products: vendorProducts, + } + db.Vendors[curVendor.ID] = curVendor + continue + } + + // Lines beginning with only a single TAB character are *either* a + // subclass OR are a device dbrmation block. If we're in a class + // block (i.e. the last parsed block header was for a PCI class), then + // we parse a subclass block. Otherwise, we parse a device dbrmation + // block. + // + // A subclass dbrmation block looks like this: + // + // \t00 Non-VGA unclassified device + // + // A device dbrmation block looks like this: + // + // \t0002 PCI to MCA Bridge + if len(lineBytes) > 1 && lineBytes[1] != '\t' { + if inClassBlock { + if curSubclass != nil { + // finalize existing subclass because we found a new subclass block + curSubclass.ProgrammingInterfaces = progIfaces + progIfaces = make([]*ProgrammingInterface, 0) + } + subclassID := string(lineBytes[1:3]) + subclassName := string(lineBytes[5:]) + curSubclass = &Subclass{ + ID: subclassID, + Name: subclassName, + ProgrammingInterfaces: progIfaces, + } + subclasses = append(subclasses, curSubclass) + } else { + if curProduct != nil { + // finalize existing product because we found a new product block + curProduct.Subsystems = productSubsystems + productSubsystems = make([]*Product, 0) + } + productID := string(lineBytes[1:5]) + productName := string(lineBytes[7:]) + productKey := curVendor.ID + productID + curProduct = &Product{ + VendorID: curVendor.ID, + ID: productID, + Name: productName, + } + vendorProducts = append(vendorProducts, curProduct) + db.Products[productKey] = curProduct + } + } else { + // Lines beginning with two TAB characters are *either* a subsystem + // (subdevice) OR are a programming interface for a PCI device + // subclass. If we're in a class block (i.e. the last parsed block + // header was for a PCI class), then we parse a programming + // interface block, otherwise we parse a subsystem block. + // + // A programming interface block looks like this: + // + // \t\t00 UHCI + // + // A subsystem block looks like this: + // + // \t\t0e11 4091 Smart Array 6i + if inClassBlock { + progIfaceID := string(lineBytes[2:4]) + progIfaceName := string(lineBytes[6:]) + curProgIface = &ProgrammingInterface{ + ID: progIfaceID, + Name: progIfaceName, + } + progIfaces = append(progIfaces, curProgIface) + } else { + vendorID := string(lineBytes[2:6]) + subsystemID := string(lineBytes[7:11]) + subsystemName := string(lineBytes[13:]) + curSubsystem = &Product{ + VendorID: vendorID, + ID: subsystemID, + Name: subsystemName, + } + productSubsystems = append(productSubsystems, curSubsystem) + } + } + } + return nil +} diff --git a/vendor/github.com/mitchellh/go-homedir/LICENSE b/vendor/github.com/mitchellh/go-homedir/LICENSE new file mode 100644 index 000000000..f9c841a51 --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/mitchellh/go-homedir/README.md b/vendor/github.com/mitchellh/go-homedir/README.md new file mode 100644 index 000000000..d70706d5b --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/README.md @@ -0,0 +1,14 @@ +# go-homedir + +This is a Go library for detecting the user's home directory without +the use of cgo, so the library can be used in cross-compilation environments. + +Usage is incredibly simple, just call `homedir.Dir()` to get the home directory +for a user, and `homedir.Expand()` to expand the `~` in a path to the home +directory. + +**Why not just use `os/user`?** The built-in `os/user` package requires +cgo on Darwin systems. This means that any Go code that uses that package +cannot cross compile. But 99% of the time the use for `os/user` is just to +retrieve the home directory, which we can do for the current user without +cgo. This library does that, enabling cross-compilation. diff --git a/vendor/github.com/mitchellh/go-homedir/homedir.go b/vendor/github.com/mitchellh/go-homedir/homedir.go new file mode 100644 index 000000000..25378537e --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/homedir.go @@ -0,0 +1,167 @@ +package homedir + +import ( + "bytes" + "errors" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" +) + +// DisableCache will disable caching of the home directory. Caching is enabled +// by default. +var DisableCache bool + +var homedirCache string +var cacheLock sync.RWMutex + +// Dir returns the home directory for the executing user. +// +// This uses an OS-specific method for discovering the home directory. +// An error is returned if a home directory cannot be detected. +func Dir() (string, error) { + if !DisableCache { + cacheLock.RLock() + cached := homedirCache + cacheLock.RUnlock() + if cached != "" { + return cached, nil + } + } + + cacheLock.Lock() + defer cacheLock.Unlock() + + var result string + var err error + if runtime.GOOS == "windows" { + result, err = dirWindows() + } else { + // Unix-like system, so just assume Unix + result, err = dirUnix() + } + + if err != nil { + return "", err + } + homedirCache = result + return result, nil +} + +// Expand expands the path to include the home directory if the path +// is prefixed with `~`. If it isn't prefixed with `~`, the path is +// returned as-is. +func Expand(path string) (string, error) { + if len(path) == 0 { + return path, nil + } + + if path[0] != '~' { + return path, nil + } + + if len(path) > 1 && path[1] != '/' && path[1] != '\\' { + return "", errors.New("cannot expand user-specific home dir") + } + + dir, err := Dir() + if err != nil { + return "", err + } + + return filepath.Join(dir, path[1:]), nil +} + +// Reset clears the cache, forcing the next call to Dir to re-detect +// the home directory. This generally never has to be called, but can be +// useful in tests if you're modifying the home directory via the HOME +// env var or something. +func Reset() { + cacheLock.Lock() + defer cacheLock.Unlock() + homedirCache = "" +} + +func dirUnix() (string, error) { + homeEnv := "HOME" + if runtime.GOOS == "plan9" { + // On plan9, env vars are lowercase. + homeEnv = "home" + } + + // First prefer the HOME environmental variable + if home := os.Getenv(homeEnv); home != "" { + return home, nil + } + + var stdout bytes.Buffer + + // If that fails, try OS specific commands + if runtime.GOOS == "darwin" { + cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`) + cmd.Stdout = &stdout + if err := cmd.Run(); err == nil { + result := strings.TrimSpace(stdout.String()) + if result != "" { + return result, nil + } + } + } else { + cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + // If the error is ErrNotFound, we ignore it. Otherwise, return it. + if err != exec.ErrNotFound { + return "", err + } + } else { + if passwd := strings.TrimSpace(stdout.String()); passwd != "" { + // username:password:uid:gid:gecos:home:shell + passwdParts := strings.SplitN(passwd, ":", 7) + if len(passwdParts) > 5 { + return passwdParts[5], nil + } + } + } + } + + // If all else fails, try the shell + stdout.Reset() + cmd := exec.Command("sh", "-c", "cd && pwd") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return "", err + } + + result := strings.TrimSpace(stdout.String()) + if result == "" { + return "", errors.New("blank output when reading home directory") + } + + return result, nil +} + +func dirWindows() (string, error) { + // First prefer the HOME environmental variable + if home := os.Getenv("HOME"); home != "" { + return home, nil + } + + // Prefer standard environment variable USERPROFILE + if home := os.Getenv("USERPROFILE"); home != "" { + return home, nil + } + + drive := os.Getenv("HOMEDRIVE") + path := os.Getenv("HOMEPATH") + home := drive + path + if drive == "" || path == "" { + return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank") + } + + return home, nil +} diff --git a/vendor/howett.net/plist/.gitlab-ci.yml b/vendor/howett.net/plist/.gitlab-ci.yml new file mode 100644 index 000000000..11d6dbf73 --- /dev/null +++ b/vendor/howett.net/plist/.gitlab-ci.yml @@ -0,0 +1,39 @@ +image: golang:alpine +stages: + - test + +variables: + GO_PACKAGE: "howett.net/plist" + +before_script: + - "mkdir -p $(dirname $GOPATH/src/$GO_PACKAGE)" + - "ln -s $(pwd) $GOPATH/src/$GO_PACKAGE" + - "cd $GOPATH/src/$GO_PACKAGE" + +.template:go-test: &template-go-test + stage: test + script: + - go test + +go-test-cover:latest: + stage: test + script: + - go test -v -cover + coverage: '/^coverage: \d+\.\d+/' + +go-test-appengine:latest: + stage: test + script: + - go test -tags appengine + +go-test:1.6: + <<: *template-go-test + image: golang:1.6-alpine + +go-test:1.4: + <<: *template-go-test + image: golang:1.4-alpine + +go-test:1.2: + <<: *template-go-test + image: golang:1.2 diff --git a/vendor/howett.net/plist/LICENSE b/vendor/howett.net/plist/LICENSE new file mode 100644 index 000000000..9f6012f32 --- /dev/null +++ b/vendor/howett.net/plist/LICENSE @@ -0,0 +1,58 @@ +Copyright (c) 2013, Dustin L. Howett. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. + +-------------------------------------------------------------------------------- +Parts of this package were made available under the license covering +the Go language and all attended core libraries. That license follows. +-------------------------------------------------------------------------------- + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/howett.net/plist/README.md b/vendor/howett.net/plist/README.md new file mode 100644 index 000000000..a13e29a38 --- /dev/null +++ b/vendor/howett.net/plist/README.md @@ -0,0 +1,21 @@ +# plist - A pure Go property list transcoder [![coverage report](https://gitlab.howett.net/go/plist/badges/master/coverage.svg)](https://gitlab.howett.net/go/plist/commits/master) +## INSTALL +``` +$ go get howett.net/plist +``` + +## FEATURES +* Supports encoding/decoding property lists (Apple XML, Apple Binary, OpenStep and GNUStep) from/to arbitrary Go types + +## USE +```go +package main +import ( + "howett.net/plist" + "os" +) +func main() { + encoder := plist.NewEncoder(os.Stdout) + encoder.Encode(map[string]string{"hello": "world"}) +} +``` diff --git a/vendor/howett.net/plist/bplist.go b/vendor/howett.net/plist/bplist.go new file mode 100644 index 000000000..962793a9f --- /dev/null +++ b/vendor/howett.net/plist/bplist.go @@ -0,0 +1,26 @@ +package plist + +type bplistTrailer struct { + Unused [5]uint8 + SortVersion uint8 + OffsetIntSize uint8 + ObjectRefSize uint8 + NumObjects uint64 + TopObject uint64 + OffsetTableOffset uint64 +} + +const ( + bpTagNull uint8 = 0x00 + bpTagBoolFalse = 0x08 + bpTagBoolTrue = 0x09 + bpTagInteger = 0x10 + bpTagReal = 0x20 + bpTagDate = 0x30 + bpTagData = 0x40 + bpTagASCIIString = 0x50 + bpTagUTF16String = 0x60 + bpTagUID = 0x80 + bpTagArray = 0xA0 + bpTagDictionary = 0xD0 +) diff --git a/vendor/howett.net/plist/bplist_generator.go b/vendor/howett.net/plist/bplist_generator.go new file mode 100644 index 000000000..09ab71b1f --- /dev/null +++ b/vendor/howett.net/plist/bplist_generator.go @@ -0,0 +1,303 @@ +package plist + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "time" + "unicode/utf16" +) + +func bplistMinimumIntSize(n uint64) int { + switch { + case n <= uint64(0xff): + return 1 + case n <= uint64(0xffff): + return 2 + case n <= uint64(0xffffffff): + return 4 + default: + return 8 + } +} + +func bplistValueShouldUnique(pval cfValue) bool { + switch pval.(type) { + case cfString, *cfNumber, *cfReal, cfDate, cfData: + return true + } + return false +} + +type bplistGenerator struct { + writer *countedWriter + objmap map[interface{}]uint64 // maps pValue.hash()es to object locations + objtable []cfValue + trailer bplistTrailer +} + +func (p *bplistGenerator) flattenPlistValue(pval cfValue) { + key := pval.hash() + if bplistValueShouldUnique(pval) { + if _, ok := p.objmap[key]; ok { + return + } + } + + p.objmap[key] = uint64(len(p.objtable)) + p.objtable = append(p.objtable, pval) + + switch pval := pval.(type) { + case *cfDictionary: + pval.sort() + for _, k := range pval.keys { + p.flattenPlistValue(cfString(k)) + } + for _, v := range pval.values { + p.flattenPlistValue(v) + } + case *cfArray: + for _, v := range pval.values { + p.flattenPlistValue(v) + } + } +} + +func (p *bplistGenerator) indexForPlistValue(pval cfValue) (uint64, bool) { + v, ok := p.objmap[pval.hash()] + return v, ok +} + +func (p *bplistGenerator) generateDocument(root cfValue) { + p.objtable = make([]cfValue, 0, 16) + p.objmap = make(map[interface{}]uint64) + p.flattenPlistValue(root) + + p.trailer.NumObjects = uint64(len(p.objtable)) + p.trailer.ObjectRefSize = uint8(bplistMinimumIntSize(p.trailer.NumObjects)) + + p.writer.Write([]byte("bplist00")) + + offtable := make([]uint64, p.trailer.NumObjects) + for i, pval := range p.objtable { + offtable[i] = uint64(p.writer.BytesWritten()) + p.writePlistValue(pval) + } + + p.trailer.OffsetIntSize = uint8(bplistMinimumIntSize(uint64(p.writer.BytesWritten()))) + p.trailer.TopObject = p.objmap[root.hash()] + p.trailer.OffsetTableOffset = uint64(p.writer.BytesWritten()) + + for _, offset := range offtable { + p.writeSizedInt(offset, int(p.trailer.OffsetIntSize)) + } + + binary.Write(p.writer, binary.BigEndian, p.trailer) +} + +func (p *bplistGenerator) writePlistValue(pval cfValue) { + if pval == nil { + return + } + + switch pval := pval.(type) { + case *cfDictionary: + p.writeDictionaryTag(pval) + case *cfArray: + p.writeArrayTag(pval.values) + case cfString: + p.writeStringTag(string(pval)) + case *cfNumber: + p.writeIntTag(pval.signed, pval.value) + case *cfReal: + if pval.wide { + p.writeRealTag(pval.value, 64) + } else { + p.writeRealTag(pval.value, 32) + } + case cfBoolean: + p.writeBoolTag(bool(pval)) + case cfData: + p.writeDataTag([]byte(pval)) + case cfDate: + p.writeDateTag(time.Time(pval)) + case cfUID: + p.writeUIDTag(UID(pval)) + default: + panic(fmt.Errorf("unknown plist type %t", pval)) + } +} + +func (p *bplistGenerator) writeSizedInt(n uint64, nbytes int) { + var val interface{} + switch nbytes { + case 1: + val = uint8(n) + case 2: + val = uint16(n) + case 4: + val = uint32(n) + case 8: + val = n + default: + panic(errors.New("illegal integer size")) + } + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeBoolTag(v bool) { + tag := uint8(bpTagBoolFalse) + if v { + tag = bpTagBoolTrue + } + binary.Write(p.writer, binary.BigEndian, tag) +} + +func (p *bplistGenerator) writeIntTag(signed bool, n uint64) { + var tag uint8 + var val interface{} + switch { + case n <= uint64(0xff): + val = uint8(n) + tag = bpTagInteger | 0x0 + case n <= uint64(0xffff): + val = uint16(n) + tag = bpTagInteger | 0x1 + case n <= uint64(0xffffffff): + val = uint32(n) + tag = bpTagInteger | 0x2 + case n > uint64(0x7fffffffffffffff) && !signed: + // 64-bit values are always *signed* in format 00. + // Any unsigned value that doesn't intersect with the signed + // range must be sign-extended and stored as a SInt128 + val = n + tag = bpTagInteger | 0x4 + default: + val = n + tag = bpTagInteger | 0x3 + } + + binary.Write(p.writer, binary.BigEndian, tag) + if tag&0xF == 0x4 { + // SInt128; in the absence of true 128-bit integers in Go, + // we'll just fake the top half. We only got here because + // we had an unsigned 64-bit int that didn't fit, + // so sign extend it with zeroes. + binary.Write(p.writer, binary.BigEndian, uint64(0)) + } + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeUIDTag(u UID) { + nbytes := bplistMinimumIntSize(uint64(u)) + tag := uint8(bpTagUID | (nbytes - 1)) + + binary.Write(p.writer, binary.BigEndian, tag) + p.writeSizedInt(uint64(u), nbytes) +} + +func (p *bplistGenerator) writeRealTag(n float64, bits int) { + var tag uint8 = bpTagReal | 0x3 + var val interface{} = n + if bits == 32 { + val = float32(n) + tag = bpTagReal | 0x2 + } + + binary.Write(p.writer, binary.BigEndian, tag) + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeDateTag(t time.Time) { + tag := uint8(bpTagDate) | 0x3 + val := float64(t.In(time.UTC).UnixNano()) / float64(time.Second) + val -= 978307200 // Adjust to Apple Epoch + + binary.Write(p.writer, binary.BigEndian, tag) + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeCountedTag(tag uint8, count uint64) { + marker := tag + if count >= 0xF { + marker |= 0xF + } else { + marker |= uint8(count) + } + + binary.Write(p.writer, binary.BigEndian, marker) + + if count >= 0xF { + p.writeIntTag(false, count) + } +} + +func (p *bplistGenerator) writeDataTag(data []byte) { + p.writeCountedTag(bpTagData, uint64(len(data))) + binary.Write(p.writer, binary.BigEndian, data) +} + +func (p *bplistGenerator) writeStringTag(str string) { + for _, r := range str { + if r > 0x7F { + utf16Runes := utf16.Encode([]rune(str)) + p.writeCountedTag(bpTagUTF16String, uint64(len(utf16Runes))) + binary.Write(p.writer, binary.BigEndian, utf16Runes) + return + } + } + + p.writeCountedTag(bpTagASCIIString, uint64(len(str))) + binary.Write(p.writer, binary.BigEndian, []byte(str)) +} + +func (p *bplistGenerator) writeDictionaryTag(dict *cfDictionary) { + // assumption: sorted already; flattenPlistValue did this. + cnt := len(dict.keys) + p.writeCountedTag(bpTagDictionary, uint64(cnt)) + vals := make([]uint64, cnt*2) + for i, k := range dict.keys { + // invariant: keys have already been "uniqued" (as PStrings) + keyIdx, ok := p.objmap[cfString(k).hash()] + if !ok { + panic(errors.New("failed to find key " + k + " in object map during serialization")) + } + vals[i] = keyIdx + } + + for i, v := range dict.values { + // invariant: values have already been "uniqued" + objIdx, ok := p.indexForPlistValue(v) + if !ok { + panic(errors.New("failed to find value in object map during serialization")) + } + vals[i+cnt] = objIdx + } + + for _, v := range vals { + p.writeSizedInt(v, int(p.trailer.ObjectRefSize)) + } +} + +func (p *bplistGenerator) writeArrayTag(arr []cfValue) { + p.writeCountedTag(bpTagArray, uint64(len(arr))) + for _, v := range arr { + objIdx, ok := p.indexForPlistValue(v) + if !ok { + panic(errors.New("failed to find value in object map during serialization")) + } + + p.writeSizedInt(objIdx, int(p.trailer.ObjectRefSize)) + } +} + +func (p *bplistGenerator) Indent(i string) { + // There's nothing to indent. +} + +func newBplistGenerator(w io.Writer) *bplistGenerator { + return &bplistGenerator{ + writer: &countedWriter{Writer: mustWriter{w}}, + } +} diff --git a/vendor/howett.net/plist/bplist_parser.go b/vendor/howett.net/plist/bplist_parser.go new file mode 100644 index 000000000..1825b570b --- /dev/null +++ b/vendor/howett.net/plist/bplist_parser.go @@ -0,0 +1,353 @@ +package plist + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "runtime" + "time" + "unicode/utf16" +) + +const ( + signedHighBits = 0xFFFFFFFFFFFFFFFF +) + +type offset uint64 + +type bplistParser struct { + buffer []byte + + reader io.ReadSeeker + version int + objects []cfValue // object ID to object + trailer bplistTrailer + trailerOffset uint64 + + containerStack []offset // slice of object offsets; manipulated during container deserialization +} + +func (p *bplistParser) validateDocumentTrailer() { + if p.trailer.OffsetTableOffset >= p.trailerOffset { + panic(fmt.Errorf("offset table beyond beginning of trailer (0x%x, trailer@0x%x)", p.trailer.OffsetTableOffset, p.trailerOffset)) + } + + if p.trailer.OffsetTableOffset < 9 { + panic(fmt.Errorf("offset table begins inside header (0x%x)", p.trailer.OffsetTableOffset)) + } + + if p.trailerOffset > (p.trailer.NumObjects*uint64(p.trailer.OffsetIntSize))+p.trailer.OffsetTableOffset { + panic(errors.New("garbage between offset table and trailer")) + } + + if p.trailer.OffsetTableOffset+(uint64(p.trailer.OffsetIntSize)*p.trailer.NumObjects) > p.trailerOffset { + panic(errors.New("offset table isn't long enough to address every object")) + } + + maxObjectRef := uint64(1) << (8 * p.trailer.ObjectRefSize) + if p.trailer.NumObjects > maxObjectRef { + panic(fmt.Errorf("more objects (%v) than object ref size (%v bytes) can support", p.trailer.NumObjects, p.trailer.ObjectRefSize)) + } + + if p.trailer.OffsetIntSize < uint8(8) && (uint64(1)<<(8*p.trailer.OffsetIntSize)) <= p.trailer.OffsetTableOffset { + panic(errors.New("offset size isn't big enough to address entire file")) + } + + if p.trailer.TopObject >= p.trailer.NumObjects { + panic(fmt.Errorf("top object #%d is out of range (only %d exist)", p.trailer.TopObject, p.trailer.NumObjects)) + } +} + +func (p *bplistParser) parseDocument() (pval cfValue, parseError error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + + parseError = plistParseError{"binary", r.(error)} + } + }() + + p.buffer, _ = ioutil.ReadAll(p.reader) + + l := len(p.buffer) + if l < 40 { + panic(errors.New("not enough data")) + } + + if !bytes.Equal(p.buffer[0:6], []byte{'b', 'p', 'l', 'i', 's', 't'}) { + panic(errors.New("incomprehensible magic")) + } + + p.version = int(((p.buffer[6] - '0') * 10) + (p.buffer[7] - '0')) + + if p.version > 1 { + panic(fmt.Errorf("unexpected version %d", p.version)) + } + + p.trailerOffset = uint64(l - 32) + p.trailer = bplistTrailer{ + SortVersion: p.buffer[p.trailerOffset+5], + OffsetIntSize: p.buffer[p.trailerOffset+6], + ObjectRefSize: p.buffer[p.trailerOffset+7], + NumObjects: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+8:]), + TopObject: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+16:]), + OffsetTableOffset: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+24:]), + } + + p.validateDocumentTrailer() + + // INVARIANTS: + // - Entire offset table is before trailer + // - Offset table begins after header + // - Offset table can address entire document + // - Object IDs are big enough to support the number of objects in this plist + // - Top object is in range + + p.objects = make([]cfValue, p.trailer.NumObjects) + + pval = p.objectAtIndex(p.trailer.TopObject) + return +} + +// parseSizedInteger returns a 128-bit integer as low64, high64 +func (p *bplistParser) parseSizedInteger(off offset, nbytes int) (lo uint64, hi uint64, newOffset offset) { + // Per comments in CoreFoundation, format version 00 requires that all + // 1, 2 or 4-byte integers be interpreted as unsigned. 8-byte integers are + // signed (always?) and therefore must be sign extended here. + // negative 1, 2, or 4-byte integers are always emitted as 64-bit. + switch nbytes { + case 1: + lo, hi = uint64(p.buffer[off]), 0 + case 2: + lo, hi = uint64(binary.BigEndian.Uint16(p.buffer[off:])), 0 + case 4: + lo, hi = uint64(binary.BigEndian.Uint32(p.buffer[off:])), 0 + case 8: + lo = binary.BigEndian.Uint64(p.buffer[off:]) + if p.buffer[off]&0x80 != 0 { + // sign extend if lo is signed + hi = signedHighBits + } + case 16: + lo, hi = binary.BigEndian.Uint64(p.buffer[off+8:]), binary.BigEndian.Uint64(p.buffer[off:]) + default: + panic(errors.New("illegal integer size")) + } + newOffset = off + offset(nbytes) + return +} + +func (p *bplistParser) parseObjectRefAtOffset(off offset) (uint64, offset) { + oid, _, next := p.parseSizedInteger(off, int(p.trailer.ObjectRefSize)) + return oid, next +} + +func (p *bplistParser) parseOffsetAtOffset(off offset) (offset, offset) { + parsedOffset, _, next := p.parseSizedInteger(off, int(p.trailer.OffsetIntSize)) + return offset(parsedOffset), next +} + +func (p *bplistParser) objectAtIndex(index uint64) cfValue { + if index >= p.trailer.NumObjects { + panic(fmt.Errorf("invalid object#%d (max %d)", index, p.trailer.NumObjects)) + } + + if pval := p.objects[index]; pval != nil { + return pval + } + + off, _ := p.parseOffsetAtOffset(offset(p.trailer.OffsetTableOffset + (index * uint64(p.trailer.OffsetIntSize)))) + if off > offset(p.trailer.OffsetTableOffset-1) { + panic(fmt.Errorf("object#%d starts beyond beginning of object table (0x%x, table@0x%x)", index, off, p.trailer.OffsetTableOffset)) + } + + pval := p.parseTagAtOffset(off) + p.objects[index] = pval + return pval + +} + +func (p *bplistParser) pushNestedObject(off offset) { + for _, v := range p.containerStack { + if v == off { + p.panicNestedObject(off) + } + } + p.containerStack = append(p.containerStack, off) +} + +func (p *bplistParser) panicNestedObject(off offset) { + ids := "" + for _, v := range p.containerStack { + ids += fmt.Sprintf("0x%x > ", v) + } + + // %s0x%d: ids above ends with " > " + panic(fmt.Errorf("self-referential collection@0x%x (%s0x%x) cannot be deserialized", off, ids, off)) +} + +func (p *bplistParser) popNestedObject() { + p.containerStack = p.containerStack[:len(p.containerStack)-1] +} + +func (p *bplistParser) parseTagAtOffset(off offset) cfValue { + tag := p.buffer[off] + + switch tag & 0xF0 { + case bpTagNull: + switch tag & 0x0F { + case bpTagBoolTrue, bpTagBoolFalse: + return cfBoolean(tag == bpTagBoolTrue) + } + case bpTagInteger: + lo, hi, _ := p.parseIntegerAtOffset(off) + return &cfNumber{ + signed: hi == signedHighBits, // a signed integer is stored as a 128-bit integer with the top 64 bits set + value: lo, + } + case bpTagReal: + nbytes := 1 << (tag & 0x0F) + switch nbytes { + case 4: + bits := binary.BigEndian.Uint32(p.buffer[off+1:]) + return &cfReal{wide: false, value: float64(math.Float32frombits(bits))} + case 8: + bits := binary.BigEndian.Uint64(p.buffer[off+1:]) + return &cfReal{wide: true, value: math.Float64frombits(bits)} + } + panic(errors.New("illegal float size")) + case bpTagDate: + bits := binary.BigEndian.Uint64(p.buffer[off+1:]) + val := math.Float64frombits(bits) + + // Apple Epoch is 20110101000000Z + // Adjust for UNIX Time + val += 978307200 + + sec, fsec := math.Modf(val) + time := time.Unix(int64(sec), int64(fsec*float64(time.Second))).In(time.UTC) + return cfDate(time) + case bpTagData: + data := p.parseDataAtOffset(off) + return cfData(data) + case bpTagASCIIString: + str := p.parseASCIIStringAtOffset(off) + return cfString(str) + case bpTagUTF16String: + str := p.parseUTF16StringAtOffset(off) + return cfString(str) + case bpTagUID: // Somehow different than int: low half is nbytes - 1 instead of log2(nbytes) + lo, _, _ := p.parseSizedInteger(off+1, int(tag&0xF)+1) + return cfUID(lo) + case bpTagDictionary: + return p.parseDictionaryAtOffset(off) + case bpTagArray: + return p.parseArrayAtOffset(off) + } + panic(fmt.Errorf("unexpected atom 0x%2.02x at offset 0x%x", tag, off)) +} + +func (p *bplistParser) parseIntegerAtOffset(off offset) (uint64, uint64, offset) { + tag := p.buffer[off] + return p.parseSizedInteger(off+1, 1<<(tag&0xF)) +} + +func (p *bplistParser) countForTagAtOffset(off offset) (uint64, offset) { + tag := p.buffer[off] + cnt := uint64(tag & 0x0F) + if cnt == 0xF { + cnt, _, off = p.parseIntegerAtOffset(off + 1) + return cnt, off + } + return cnt, off + 1 +} + +func (p *bplistParser) parseDataAtOffset(off offset) []byte { + len, start := p.countForTagAtOffset(off) + if start+offset(len) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("data@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start))) + } + return p.buffer[start : start+offset(len)] +} + +func (p *bplistParser) parseASCIIStringAtOffset(off offset) string { + len, start := p.countForTagAtOffset(off) + if start+offset(len) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("ascii string@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start))) + } + + return zeroCopy8BitString(p.buffer, int(start), int(len)) +} + +func (p *bplistParser) parseUTF16StringAtOffset(off offset) string { + len, start := p.countForTagAtOffset(off) + bytes := len * 2 + if start+offset(bytes) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("utf16 string@0x%x too long (%v bytes, max is %v)", off, bytes, p.trailer.OffsetTableOffset-uint64(start))) + } + + u16s := make([]uint16, len) + for i := offset(0); i < offset(len); i++ { + u16s[i] = binary.BigEndian.Uint16(p.buffer[start+(i*2):]) + } + runes := utf16.Decode(u16s) + return string(runes) +} + +func (p *bplistParser) parseObjectListAtOffset(off offset, count uint64) []cfValue { + if off+offset(count*uint64(p.trailer.ObjectRefSize)) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("list@0x%x length (%v) puts its end beyond the offset table at 0x%x", off, count, p.trailer.OffsetTableOffset)) + } + objects := make([]cfValue, count) + + next := off + var oid uint64 + for i := uint64(0); i < count; i++ { + oid, next = p.parseObjectRefAtOffset(next) + objects[i] = p.objectAtIndex(oid) + } + + return objects +} + +func (p *bplistParser) parseDictionaryAtOffset(off offset) *cfDictionary { + p.pushNestedObject(off) + defer p.popNestedObject() + + // a dictionary is an object list of [key key key val val val] + cnt, start := p.countForTagAtOffset(off) + objects := p.parseObjectListAtOffset(start, cnt*2) + + keys := make([]string, cnt) + for i := uint64(0); i < cnt; i++ { + if str, ok := objects[i].(cfString); ok { + keys[i] = string(str) + } else { + panic(fmt.Errorf("dictionary@0x%x contains non-string key at index %d", off, i)) + } + } + + return &cfDictionary{ + keys: keys, + values: objects[cnt:], + } +} + +func (p *bplistParser) parseArrayAtOffset(off offset) *cfArray { + p.pushNestedObject(off) + defer p.popNestedObject() + + // an array is just an object list + cnt, start := p.countForTagAtOffset(off) + return &cfArray{p.parseObjectListAtOffset(start, cnt)} +} + +func newBplistParser(r io.ReadSeeker) *bplistParser { + return &bplistParser{reader: r} +} diff --git a/vendor/howett.net/plist/decode.go b/vendor/howett.net/plist/decode.go new file mode 100644 index 000000000..4c6466771 --- /dev/null +++ b/vendor/howett.net/plist/decode.go @@ -0,0 +1,119 @@ +package plist + +import ( + "bytes" + "io" + "reflect" + "runtime" +) + +type parser interface { + parseDocument() (cfValue, error) +} + +// A Decoder reads a property list from an input stream. +type Decoder struct { + // the format of the most-recently-decoded property list + Format int + + reader io.ReadSeeker + lax bool +} + +// Decode works like Unmarshal, except it reads the decoder stream to find property list elements. +// +// After Decoding, the Decoder's Format field will be set to one of the plist format constants. +func (p *Decoder) Decode(v interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + + header := make([]byte, 6) + p.reader.Read(header) + p.reader.Seek(0, 0) + + var parser parser + var pval cfValue + if bytes.Equal(header, []byte("bplist")) { + parser = newBplistParser(p.reader) + pval, err = parser.parseDocument() + if err != nil { + // Had a bplist header, but still got an error: we have to die here. + return err + } + p.Format = BinaryFormat + } else { + parser = newXMLPlistParser(p.reader) + pval, err = parser.parseDocument() + if _, ok := err.(invalidPlistError); ok { + // Rewind: the XML parser might have exhausted the file. + p.reader.Seek(0, 0) + // We don't use parser here because we want the textPlistParser type + tp := newTextPlistParser(p.reader) + pval, err = tp.parseDocument() + if err != nil { + return err + } + p.Format = tp.format + if p.Format == OpenStepFormat { + // OpenStep property lists can only store strings, + // so we have to turn on lax mode here for the unmarshal step later. + p.lax = true + } + } else { + if err != nil { + return err + } + p.Format = XMLFormat + } + } + + p.unmarshal(pval, reflect.ValueOf(v)) + return +} + +// NewDecoder returns a Decoder that reads property list elements from a stream reader, r. +// NewDecoder requires a Seekable stream for the purposes of file type detection. +func NewDecoder(r io.ReadSeeker) *Decoder { + return &Decoder{Format: InvalidFormat, reader: r, lax: false} +} + +// Unmarshal parses a property list document and stores the result in the value pointed to by v. +// +// Unmarshal uses the inverse of the type encodings that Marshal uses, allocating heap-borne types as necessary. +// +// When given a nil pointer, Unmarshal allocates a new value for it to point to. +// +// To decode property list values into an interface value, Unmarshal decodes the property list into the concrete value contained +// in the interface value. If the interface value is nil, Unmarshal stores one of the following in the interface value: +// +// string, bool, uint64, float64 +// plist.UID for "CoreFoundation Keyed Archiver UIDs" (convertible to uint64) +// []byte, for plist data +// []interface{}, for plist arrays +// map[string]interface{}, for plist dictionaries +// +// If a property list value is not appropriate for a given value type, Unmarshal aborts immediately and returns an error. +// +// As Go does not support 128-bit types, and we don't want to pretend we're giving the user integer types (as opposed to +// secretly passing them structs), Unmarshal will drop the high 64 bits of any 128-bit integers encoded in binary property lists. +// (This is important because CoreFoundation serializes some large 64-bit values as 128-bit values with an empty high half.) +// +// When Unmarshal encounters an OpenStep property list, it will enter a relaxed parsing mode: OpenStep property lists can only store +// plain old data as strings, so we will attempt to recover integer, floating-point, boolean and date values wherever they are necessary. +// (for example, if Unmarshal attempts to unmarshal an OpenStep property list into a time.Time, it will try to parse the string it +// receives as a time.) +// +// Unmarshal returns the detected property list format and an error, if any. +func Unmarshal(data []byte, v interface{}) (format int, err error) { + r := bytes.NewReader(data) + dec := NewDecoder(r) + err = dec.Decode(v) + format = dec.Format + return +} diff --git a/vendor/howett.net/plist/doc.go b/vendor/howett.net/plist/doc.go new file mode 100644 index 000000000..457e60b60 --- /dev/null +++ b/vendor/howett.net/plist/doc.go @@ -0,0 +1,5 @@ +// Package plist implements encoding and decoding of Apple's "property list" format. +// Property lists come in three sorts: plain text (GNUStep and OpenStep), XML and binary. +// plist supports all of them. +// The mapping between property list and Go objects is described in the documentation for the Marshal and Unmarshal functions. +package plist diff --git a/vendor/howett.net/plist/encode.go b/vendor/howett.net/plist/encode.go new file mode 100644 index 000000000..f81309b58 --- /dev/null +++ b/vendor/howett.net/plist/encode.go @@ -0,0 +1,126 @@ +package plist + +import ( + "bytes" + "errors" + "io" + "reflect" + "runtime" +) + +type generator interface { + generateDocument(cfValue) + Indent(string) +} + +// An Encoder writes a property list to an output stream. +type Encoder struct { + writer io.Writer + format int + + indent string +} + +// Encode writes the property list encoding of v to the stream. +func (p *Encoder) Encode(v interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + + pval := p.marshal(reflect.ValueOf(v)) + if pval == nil { + panic(errors.New("plist: no root element to encode")) + } + + var g generator + switch p.format { + case XMLFormat: + g = newXMLPlistGenerator(p.writer) + case BinaryFormat, AutomaticFormat: + g = newBplistGenerator(p.writer) + case OpenStepFormat, GNUStepFormat: + g = newTextPlistGenerator(p.writer, p.format) + } + g.Indent(p.indent) + g.generateDocument(pval) + return +} + +// Indent turns on pretty-printing for the XML and Text property list formats. +// Each element begins on a new line and is preceded by one or more copies of indent according to its nesting depth. +func (p *Encoder) Indent(indent string) { + p.indent = indent +} + +// NewEncoder returns an Encoder that writes an XML property list to w. +func NewEncoder(w io.Writer) *Encoder { + return NewEncoderForFormat(w, XMLFormat) +} + +// NewEncoderForFormat returns an Encoder that writes a property list to w in the specified format. +// Pass AutomaticFormat to allow the library to choose the best encoding (currently BinaryFormat). +func NewEncoderForFormat(w io.Writer, format int) *Encoder { + return &Encoder{ + writer: w, + format: format, + } +} + +// NewBinaryEncoder returns an Encoder that writes a binary property list to w. +func NewBinaryEncoder(w io.Writer) *Encoder { + return NewEncoderForFormat(w, BinaryFormat) +} + +// Marshal returns the property list encoding of v in the specified format. +// +// Pass AutomaticFormat to allow the library to choose the best encoding (currently BinaryFormat). +// +// Marshal traverses the value v recursively. +// Any nil values encountered, other than the root, will be silently discarded as +// the property list format bears no representation for nil values. +// +// Strings, integers of varying size, floats and booleans are encoded unchanged. +// Strings bearing non-ASCII runes will be encoded differently depending upon the property list format: +// UTF-8 for XML property lists and UTF-16 for binary property lists. +// +// Slice and Array values are encoded as property list arrays, except for +// []byte values, which are encoded as data. +// +// Map values encode as dictionaries. The map's key type must be string; there is no provision for encoding non-string dictionary keys. +// +// Struct values are encoded as dictionaries, with only exported fields being serialized. Struct field encoding may be influenced with the use of tags. +// The tag format is: +// +// `plist:"[,flags...]"` +// +// The following flags are supported: +// +// omitempty Only include the field if it is not set to the zero value for its type. +// +// If the key is "-", the field is ignored. +// +// Anonymous struct fields are encoded as if their exported fields were exposed via the outer struct. +// +// Pointer values encode as the value pointed to. +// +// Channel, complex and function values cannot be encoded. Any attempt to do so causes Marshal to return an error. +func Marshal(v interface{}, format int) ([]byte, error) { + return MarshalIndent(v, format, "") +} + +// MarshalIndent works like Marshal, but each property list element +// begins on a new line and is preceded by one or more copies of indent according to its nesting depth. +func MarshalIndent(v interface{}, format int, indent string) ([]byte, error) { + buf := &bytes.Buffer{} + enc := NewEncoderForFormat(buf, format) + enc.Indent(indent) + if err := enc.Encode(v); err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/vendor/howett.net/plist/fuzz.go b/vendor/howett.net/plist/fuzz.go new file mode 100644 index 000000000..18a3b4b9e --- /dev/null +++ b/vendor/howett.net/plist/fuzz.go @@ -0,0 +1,17 @@ +// +build gofuzz + +package plist + +import ( + "bytes" +) + +func Fuzz(data []byte) int { + buf := bytes.NewReader(data) + + var obj interface{} + if err := NewDecoder(buf).Decode(&obj); err != nil { + return 0 + } + return 1 +} diff --git a/vendor/howett.net/plist/marshal.go b/vendor/howett.net/plist/marshal.go new file mode 100644 index 000000000..c461dfb54 --- /dev/null +++ b/vendor/howett.net/plist/marshal.go @@ -0,0 +1,186 @@ +package plist + +import ( + "encoding" + "reflect" + "time" +) + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +var ( + plistMarshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + timeType = reflect.TypeOf((*time.Time)(nil)).Elem() +) + +func implementsInterface(val reflect.Value, interfaceType reflect.Type) (interface{}, bool) { + if val.CanInterface() && val.Type().Implements(interfaceType) { + return val.Interface(), true + } + + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(interfaceType) { + return pv.Interface(), true + } + } + return nil, false +} + +func (p *Encoder) marshalPlistInterface(marshalable Marshaler) cfValue { + value, err := marshalable.MarshalPlist() + if err != nil { + panic(err) + } + return p.marshal(reflect.ValueOf(value)) +} + +// marshalTextInterface marshals a TextMarshaler to a plist string. +func (p *Encoder) marshalTextInterface(marshalable encoding.TextMarshaler) cfValue { + s, err := marshalable.MarshalText() + if err != nil { + panic(err) + } + return cfString(s) +} + +// marshalStruct marshals a reflected struct value to a plist dictionary +func (p *Encoder) marshalStruct(typ reflect.Type, val reflect.Value) cfValue { + tinfo, _ := getTypeInfo(typ) + + dict := &cfDictionary{ + keys: make([]string, 0, len(tinfo.fields)), + values: make([]cfValue, 0, len(tinfo.fields)), + } + for _, finfo := range tinfo.fields { + value := finfo.value(val) + if !value.IsValid() || finfo.omitEmpty && isEmptyValue(value) { + continue + } + dict.keys = append(dict.keys, finfo.name) + dict.values = append(dict.values, p.marshal(value)) + } + + return dict +} + +func (p *Encoder) marshalTime(val reflect.Value) cfValue { + time := val.Interface().(time.Time) + return cfDate(time) +} + +func (p *Encoder) marshal(val reflect.Value) cfValue { + if !val.IsValid() { + return nil + } + + if receiver, can := implementsInterface(val, plistMarshalerType); can { + return p.marshalPlistInterface(receiver.(Marshaler)) + } + + // time.Time implements TextMarshaler, but we need to store it in RFC3339 + if val.Type() == timeType { + return p.marshalTime(val) + } + if val.Kind() == reflect.Ptr || (val.Kind() == reflect.Interface && val.NumMethod() == 0) { + ival := val.Elem() + if ival.IsValid() && ival.Type() == timeType { + return p.marshalTime(ival) + } + } + + // Check for text marshaler. + if receiver, can := implementsInterface(val, textMarshalerType); can { + return p.marshalTextInterface(receiver.(encoding.TextMarshaler)) + } + + // Descend into pointers or interfaces + if val.Kind() == reflect.Ptr || (val.Kind() == reflect.Interface && val.NumMethod() == 0) { + val = val.Elem() + } + + // We got this far and still may have an invalid anything or nil ptr/interface + if !val.IsValid() || ((val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface) && val.IsNil()) { + return nil + } + + typ := val.Type() + + if typ == uidType { + return cfUID(val.Uint()) + } + + if val.Kind() == reflect.Struct { + return p.marshalStruct(typ, val) + } + + switch val.Kind() { + case reflect.String: + return cfString(val.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return &cfNumber{signed: true, value: uint64(val.Int())} + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return &cfNumber{signed: false, value: val.Uint()} + case reflect.Float32: + return &cfReal{wide: false, value: val.Float()} + case reflect.Float64: + return &cfReal{wide: true, value: val.Float()} + case reflect.Bool: + return cfBoolean(val.Bool()) + case reflect.Slice, reflect.Array: + if typ.Elem().Kind() == reflect.Uint8 { + bytes := []byte(nil) + if val.CanAddr() { + bytes = val.Bytes() + } else { + bytes = make([]byte, val.Len()) + reflect.Copy(reflect.ValueOf(bytes), val) + } + return cfData(bytes) + } else { + values := make([]cfValue, val.Len()) + for i, length := 0, val.Len(); i < length; i++ { + if subpval := p.marshal(val.Index(i)); subpval != nil { + values[i] = subpval + } + } + return &cfArray{values} + } + case reflect.Map: + if typ.Key().Kind() != reflect.String { + panic(&unknownTypeError{typ}) + } + + l := val.Len() + dict := &cfDictionary{ + keys: make([]string, 0, l), + values: make([]cfValue, 0, l), + } + for _, keyv := range val.MapKeys() { + if subpval := p.marshal(val.MapIndex(keyv)); subpval != nil { + dict.keys = append(dict.keys, keyv.String()) + dict.values = append(dict.values, subpval) + } + } + return dict + default: + panic(&unknownTypeError{typ}) + } +} diff --git a/vendor/howett.net/plist/must.go b/vendor/howett.net/plist/must.go new file mode 100644 index 000000000..2c2523d97 --- /dev/null +++ b/vendor/howett.net/plist/must.go @@ -0,0 +1,50 @@ +package plist + +import ( + "io" + "strconv" +) + +type mustWriter struct { + io.Writer +} + +func (w mustWriter) Write(p []byte) (int, error) { + n, err := w.Writer.Write(p) + if err != nil { + panic(err) + } + return n, nil +} + +func mustParseInt(str string, base, bits int) int64 { + i, err := strconv.ParseInt(str, base, bits) + if err != nil { + panic(err) + } + return i +} + +func mustParseUint(str string, base, bits int) uint64 { + i, err := strconv.ParseUint(str, base, bits) + if err != nil { + panic(err) + } + return i +} + +func mustParseFloat(str string, bits int) float64 { + i, err := strconv.ParseFloat(str, bits) + if err != nil { + panic(err) + } + return i +} + +func mustParseBool(str string) bool { + i, err := strconv.ParseBool(str) + if err != nil { + panic(err) + } + return i +} diff --git a/vendor/howett.net/plist/plist.go b/vendor/howett.net/plist/plist.go new file mode 100644 index 000000000..a4078b23e --- /dev/null +++ b/vendor/howett.net/plist/plist.go @@ -0,0 +1,85 @@ +package plist + +import ( + "reflect" +) + +// Property list format constants +const ( + // Used by Decoder to represent an invalid property list. + InvalidFormat int = 0 + + // Used to indicate total abandon with regards to Encoder's output format. + AutomaticFormat = 0 + + XMLFormat = 1 + BinaryFormat = 2 + OpenStepFormat = 3 + GNUStepFormat = 4 +) + +var FormatNames = map[int]string{ + InvalidFormat: "unknown/invalid", + XMLFormat: "XML", + BinaryFormat: "Binary", + OpenStepFormat: "OpenStep", + GNUStepFormat: "GNUStep", +} + +type unknownTypeError struct { + typ reflect.Type +} + +func (u *unknownTypeError) Error() string { + return "plist: can't marshal value of type " + u.typ.String() +} + +type invalidPlistError struct { + format string + err error +} + +func (e invalidPlistError) Error() string { + s := "plist: invalid " + e.format + " property list" + if e.err != nil { + s += ": " + e.err.Error() + } + return s +} + +type plistParseError struct { + format string + err error +} + +func (e plistParseError) Error() string { + s := "plist: error parsing " + e.format + " property list" + if e.err != nil { + s += ": " + e.err.Error() + } + return s +} + +// A UID represents a unique object identifier. UIDs are serialized in a manner distinct from +// that of integers. +// +// UIDs cannot be serialized in OpenStepFormat or GNUStepFormat property lists. +type UID uint64 + +// Marshaler is the interface implemented by types that can marshal themselves into valid +// property list objects. The returned value is marshaled in place of the original value +// implementing Marshaler +// +// If an error is returned by MarshalPlist, marshaling stops and the error is returned. +type Marshaler interface { + MarshalPlist() (interface{}, error) +} + +// Unmarshaler is the interface implemented by types that can unmarshal themselves from +// property list objects. The UnmarshalPlist method receives a function that may +// be called to unmarshal the original property list value into a field or variable. +// +// It is safe to call the unmarshal function more than once. +type Unmarshaler interface { + UnmarshalPlist(unmarshal func(interface{}) error) error +} diff --git a/vendor/howett.net/plist/plist_types.go b/vendor/howett.net/plist/plist_types.go new file mode 100644 index 000000000..954ef34e9 --- /dev/null +++ b/vendor/howett.net/plist/plist_types.go @@ -0,0 +1,139 @@ +package plist + +import ( + "hash/crc32" + "sort" + "time" +) + +type cfValue interface { + typeName() string + hash() interface{} +} + +type cfDictionary struct { + keys sort.StringSlice + values []cfValue +} + +func (*cfDictionary) typeName() string { + return "dictionary" +} + +func (p *cfDictionary) hash() interface{} { + return p +} + +func (p *cfDictionary) Len() int { + return len(p.keys) +} + +func (p *cfDictionary) Less(i, j int) bool { + return p.keys.Less(i, j) +} + +func (p *cfDictionary) Swap(i, j int) { + p.keys.Swap(i, j) + p.values[i], p.values[j] = p.values[j], p.values[i] +} + +func (p *cfDictionary) sort() { + sort.Sort(p) +} + +type cfArray struct { + values []cfValue +} + +func (*cfArray) typeName() string { + return "array" +} + +func (p *cfArray) hash() interface{} { + return p +} + +type cfString string + +func (cfString) typeName() string { + return "string" +} + +func (p cfString) hash() interface{} { + return string(p) +} + +type cfNumber struct { + signed bool + value uint64 +} + +func (*cfNumber) typeName() string { + return "integer" +} + +func (p *cfNumber) hash() interface{} { + if p.signed { + return int64(p.value) + } + return p.value +} + +type cfReal struct { + wide bool + value float64 +} + +func (cfReal) typeName() string { + return "real" +} + +func (p *cfReal) hash() interface{} { + if p.wide { + return p.value + } + return float32(p.value) +} + +type cfBoolean bool + +func (cfBoolean) typeName() string { + return "boolean" +} + +func (p cfBoolean) hash() interface{} { + return bool(p) +} + +type cfUID UID + +func (cfUID) typeName() string { + return "UID" +} + +func (p cfUID) hash() interface{} { + return p +} + +type cfData []byte + +func (cfData) typeName() string { + return "data" +} + +func (p cfData) hash() interface{} { + // Data are uniqued by their checksums. + // Todo: Look at calculating this only once and storing it somewhere; + // crc32 is fairly quick, however. + return crc32.ChecksumIEEE([]byte(p)) +} + +type cfDate time.Time + +func (cfDate) typeName() string { + return "date" +} + +func (p cfDate) hash() interface{} { + return time.Time(p) +} diff --git a/vendor/howett.net/plist/text_generator.go b/vendor/howett.net/plist/text_generator.go new file mode 100644 index 000000000..53078ba54 --- /dev/null +++ b/vendor/howett.net/plist/text_generator.go @@ -0,0 +1,226 @@ +package plist + +import ( + "encoding/hex" + "io" + "strconv" + "time" +) + +type textPlistGenerator struct { + writer io.Writer + format int + + quotableTable *characterSet + + indent string + depth int + + dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte +} + +var ( + textPlistTimeLayout = "2006-01-02 15:04:05 -0700" + padding = "0000" +) + +func (p *textPlistGenerator) generateDocument(pval cfValue) { + p.writePlistValue(pval) +} + +func (p *textPlistGenerator) plistQuotedString(str string) string { + if str == "" { + return `""` + } + s := "" + quot := false + for _, r := range str { + if r > 0xFF { + quot = true + s += `\U` + us := strconv.FormatInt(int64(r), 16) + s += padding[len(us):] + s += us + } else if r > 0x7F { + quot = true + s += `\` + us := strconv.FormatInt(int64(r), 8) + s += padding[1+len(us):] + s += us + } else { + c := uint8(r) + if p.quotableTable.ContainsByte(c) { + quot = true + } + + switch c { + case '\a': + s += `\a` + case '\b': + s += `\b` + case '\v': + s += `\v` + case '\f': + s += `\f` + case '\\': + s += `\\` + case '"': + s += `\"` + case '\t', '\r', '\n': + fallthrough + default: + s += string(c) + } + } + } + if quot { + s = `"` + s + `"` + } + return s +} + +func (p *textPlistGenerator) deltaIndent(depthDelta int) { + if depthDelta < 0 { + p.depth-- + } else if depthDelta > 0 { + p.depth++ + } +} + +func (p *textPlistGenerator) writeIndent() { + if len(p.indent) == 0 { + return + } + if len(p.indent) > 0 { + p.writer.Write([]byte("\n")) + for i := 0; i < p.depth; i++ { + io.WriteString(p.writer, p.indent) + } + } +} + +func (p *textPlistGenerator) writePlistValue(pval cfValue) { + if pval == nil { + return + } + + switch pval := pval.(type) { + case *cfDictionary: + pval.sort() + p.writer.Write([]byte(`{`)) + p.deltaIndent(1) + for i, k := range pval.keys { + p.writeIndent() + io.WriteString(p.writer, p.plistQuotedString(k)) + p.writer.Write(p.dictKvDelimiter) + p.writePlistValue(pval.values[i]) + p.writer.Write(p.dictEntryDelimiter) + } + p.deltaIndent(-1) + p.writeIndent() + p.writer.Write([]byte(`}`)) + case *cfArray: + p.writer.Write([]byte(`(`)) + p.deltaIndent(1) + for _, v := range pval.values { + p.writeIndent() + p.writePlistValue(v) + p.writer.Write(p.arrayDelimiter) + } + p.deltaIndent(-1) + p.writeIndent() + p.writer.Write([]byte(`)`)) + case cfString: + io.WriteString(p.writer, p.plistQuotedString(string(pval))) + case *cfNumber: + if p.format == GNUStepFormat { + p.writer.Write([]byte(`<*I`)) + } + if pval.signed { + io.WriteString(p.writer, strconv.FormatInt(int64(pval.value), 10)) + } else { + io.WriteString(p.writer, strconv.FormatUint(pval.value, 10)) + } + if p.format == GNUStepFormat { + p.writer.Write([]byte(`>`)) + } + case *cfReal: + if p.format == GNUStepFormat { + p.writer.Write([]byte(`<*R`)) + } + // GNUstep does not differentiate between 32/64-bit floats. + io.WriteString(p.writer, strconv.FormatFloat(pval.value, 'g', -1, 64)) + if p.format == GNUStepFormat { + p.writer.Write([]byte(`>`)) + } + case cfBoolean: + if p.format == GNUStepFormat { + if pval { + p.writer.Write([]byte(`<*BY>`)) + } else { + p.writer.Write([]byte(`<*BN>`)) + } + } else { + if pval { + p.writer.Write([]byte(`1`)) + } else { + p.writer.Write([]byte(`0`)) + } + } + case cfData: + var hexencoded [9]byte + var l int + var asc = 9 + hexencoded[8] = ' ' + + p.writer.Write([]byte(`<`)) + b := []byte(pval) + for i := 0; i < len(b); i += 4 { + l = i + 4 + if l >= len(b) { + l = len(b) + // We no longer need the space - or the rest of the buffer. + // (we used >= above to get this part without another conditional :P) + asc = (l - i) * 2 + } + // Fill the buffer (only up to 8 characters, to preserve the space we implicitly include + // at the end of every encode) + hex.Encode(hexencoded[:8], b[i:l]) + io.WriteString(p.writer, string(hexencoded[:asc])) + } + p.writer.Write([]byte(`>`)) + case cfDate: + if p.format == GNUStepFormat { + p.writer.Write([]byte(`<*D`)) + io.WriteString(p.writer, time.Time(pval).In(time.UTC).Format(textPlistTimeLayout)) + p.writer.Write([]byte(`>`)) + } else { + io.WriteString(p.writer, p.plistQuotedString(time.Time(pval).In(time.UTC).Format(textPlistTimeLayout))) + } + } +} + +func (p *textPlistGenerator) Indent(i string) { + p.indent = i + if i == "" { + p.dictKvDelimiter = []byte(`=`) + } else { + // For pretty-printing + p.dictKvDelimiter = []byte(` = `) + } +} + +func newTextPlistGenerator(w io.Writer, format int) *textPlistGenerator { + table := &osQuotable + if format == GNUStepFormat { + table = &gsQuotable + } + return &textPlistGenerator{ + writer: mustWriter{w}, + format: format, + quotableTable: table, + dictKvDelimiter: []byte(`=`), + arrayDelimiter: []byte(`,`), + dictEntryDelimiter: []byte(`;`), + } +} diff --git a/vendor/howett.net/plist/text_parser.go b/vendor/howett.net/plist/text_parser.go new file mode 100644 index 000000000..7e49d6f7d --- /dev/null +++ b/vendor/howett.net/plist/text_parser.go @@ -0,0 +1,515 @@ +package plist + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "runtime" + "strings" + "time" + "unicode/utf16" + "unicode/utf8" +) + +type textPlistParser struct { + reader io.Reader + format int + + input string + start int + pos int + width int +} + +func convertU16(buffer []byte, bo binary.ByteOrder) (string, error) { + if len(buffer)%2 != 0 { + return "", errors.New("truncated utf16") + } + + tmp := make([]uint16, len(buffer)/2) + for i := 0; i < len(buffer); i += 2 { + tmp[i/2] = bo.Uint16(buffer[i : i+2]) + } + return string(utf16.Decode(tmp)), nil +} + +func guessEncodingAndConvert(buffer []byte) (string, error) { + if len(buffer) >= 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF { + // UTF-8 BOM + return zeroCopy8BitString(buffer, 3, len(buffer)-3), nil + } else if len(buffer) >= 2 { + // UTF-16 guesses + + switch { + // stream is big-endian (BOM is FE FF or head is 00 XX) + case (buffer[0] == 0xFE && buffer[1] == 0xFF): + return convertU16(buffer[2:], binary.BigEndian) + case (buffer[0] == 0 && buffer[1] != 0): + return convertU16(buffer, binary.BigEndian) + + // stream is little-endian (BOM is FE FF or head is XX 00) + case (buffer[0] == 0xFF && buffer[1] == 0xFE): + return convertU16(buffer[2:], binary.LittleEndian) + case (buffer[0] != 0 && buffer[1] == 0): + return convertU16(buffer, binary.LittleEndian) + } + } + + // fallback: assume ASCII (not great!) + return zeroCopy8BitString(buffer, 0, len(buffer)), nil +} + +func (p *textPlistParser) parseDocument() (pval cfValue, parseError error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + // Wrap all non-invalid-plist errors. + parseError = plistParseError{"text", r.(error)} + } + }() + + buffer, err := ioutil.ReadAll(p.reader) + if err != nil { + panic(err) + } + + p.input, err = guessEncodingAndConvert(buffer) + if err != nil { + panic(err) + } + + val := p.parsePlistValue() + + p.skipWhitespaceAndComments() + if p.peek() != eof { + if _, ok := val.(cfString); !ok { + p.error("garbage after end of document") + } + + p.start = 0 + p.pos = 0 + val = p.parseDictionary(true) + } + + pval = val + + return +} + +const eof rune = -1 + +func (p *textPlistParser) error(e string, args ...interface{}) { + line := strings.Count(p.input[:p.pos], "\n") + char := p.pos - strings.LastIndex(p.input[:p.pos], "\n") - 1 + panic(fmt.Errorf("%s at line %d character %d", fmt.Sprintf(e, args...), line, char)) +} + +func (p *textPlistParser) next() rune { + if int(p.pos) >= len(p.input) { + p.width = 0 + return eof + } + r, w := utf8.DecodeRuneInString(p.input[p.pos:]) + p.width = w + p.pos += p.width + return r +} + +func (p *textPlistParser) backup() { + p.pos -= p.width +} + +func (p *textPlistParser) peek() rune { + r := p.next() + p.backup() + return r +} + +func (p *textPlistParser) emit() string { + s := p.input[p.start:p.pos] + p.start = p.pos + return s +} + +func (p *textPlistParser) ignore() { + p.start = p.pos +} + +func (p *textPlistParser) empty() bool { + return p.start == p.pos +} + +func (p *textPlistParser) scanUntil(ch rune) { + if x := strings.IndexRune(p.input[p.pos:], ch); x >= 0 { + p.pos += x + return + } + p.pos = len(p.input) +} + +func (p *textPlistParser) scanUntilAny(chs string) { + if x := strings.IndexAny(p.input[p.pos:], chs); x >= 0 { + p.pos += x + return + } + p.pos = len(p.input) +} + +func (p *textPlistParser) scanCharactersInSet(ch *characterSet) { + for ch.Contains(p.next()) { + } + p.backup() +} + +func (p *textPlistParser) scanCharactersNotInSet(ch *characterSet) { + var r rune + for { + r = p.next() + if r == eof || ch.Contains(r) { + break + } + } + p.backup() +} + +func (p *textPlistParser) skipWhitespaceAndComments() { + for { + p.scanCharactersInSet(&whitespace) + if strings.HasPrefix(p.input[p.pos:], "//") { + p.scanCharactersNotInSet(&newlineCharacterSet) + } else if strings.HasPrefix(p.input[p.pos:], "/*") { + if x := strings.Index(p.input[p.pos:], "*/"); x >= 0 { + p.pos += x + 2 // skip the */ as well + continue // consume more whitespace + } else { + p.error("unexpected eof in block comment") + } + } else { + break + } + } + p.ignore() +} + +func (p *textPlistParser) parseOctalDigits(max int) uint64 { + var val uint64 + + for i := 0; i < max; i++ { + r := p.next() + + if r >= '0' && r <= '7' { + val <<= 3 + val |= uint64((r - '0')) + } else { + p.backup() + break + } + } + return val +} + +func (p *textPlistParser) parseHexDigits(max int) uint64 { + var val uint64 + + for i := 0; i < max; i++ { + r := p.next() + + if r >= 'a' && r <= 'f' { + val <<= 4 + val |= 10 + uint64((r - 'a')) + } else if r >= 'A' && r <= 'F' { + val <<= 4 + val |= 10 + uint64((r - 'A')) + } else if r >= '0' && r <= '9' { + val <<= 4 + val |= uint64((r - '0')) + } else { + p.backup() + break + } + } + return val +} + +// the \ has already been consumed +func (p *textPlistParser) parseEscape() string { + var s string + switch p.next() { + case 'a': + s = "\a" + case 'b': + s = "\b" + case 'v': + s = "\v" + case 'f': + s = "\f" + case 't': + s = "\t" + case 'r': + s = "\r" + case 'n': + s = "\n" + case '\\': + s = `\` + case '"': + s = `"` + case 'x': + s = string(rune(p.parseHexDigits(2))) + case 'u', 'U': + s = string(rune(p.parseHexDigits(4))) + case '0', '1', '2', '3', '4', '5', '6', '7': + p.backup() // we've already consumed one of the digits + s = string(rune(p.parseOctalDigits(3))) + default: + p.backup() // everything else should be accepted + } + p.ignore() // skip the entire escape sequence + return s +} + +// the " has already been consumed +func (p *textPlistParser) parseQuotedString() cfString { + p.ignore() // ignore the " + + slowPath := false + s := "" + + for { + p.scanUntilAny(`"\`) + switch p.peek() { + case eof: + p.error("unexpected eof in quoted string") + case '"': + section := p.emit() + p.pos++ // skip " + if !slowPath { + return cfString(section) + } else { + s += section + return cfString(s) + } + case '\\': + slowPath = true + s += p.emit() + p.next() // consume \ + s += p.parseEscape() + } + } +} + +func (p *textPlistParser) parseUnquotedString() cfString { + p.scanCharactersNotInSet(&gsQuotable) + s := p.emit() + if s == "" { + p.error("invalid unquoted string (found an unquoted character that should be quoted?)") + } + + return cfString(s) +} + +// the { has already been consumed +func (p *textPlistParser) parseDictionary(ignoreEof bool) *cfDictionary { + //p.ignore() // ignore the { + var keypv cfValue + keys := make([]string, 0, 32) + values := make([]cfValue, 0, 32) +outer: + for { + p.skipWhitespaceAndComments() + + switch p.next() { + case eof: + if !ignoreEof { + p.error("unexpected eof in dictionary") + } + fallthrough + case '}': + break outer + case '"': + keypv = p.parseQuotedString() + default: + p.backup() + keypv = p.parseUnquotedString() + } + + // INVARIANT: key can't be nil; parseQuoted and parseUnquoted + // will panic out before they return nil. + + p.skipWhitespaceAndComments() + + var val cfValue + n := p.next() + if n == ';' { + val = keypv + } else if n == '=' { + // whitespace is consumed within + val = p.parsePlistValue() + + p.skipWhitespaceAndComments() + + if p.next() != ';' { + p.error("missing ; in dictionary") + } + } else { + p.error("missing = in dictionary") + } + + keys = append(keys, string(keypv.(cfString))) + values = append(values, val) + } + + return &cfDictionary{keys: keys, values: values} +} + +// the ( has already been consumed +func (p *textPlistParser) parseArray() *cfArray { + //p.ignore() // ignore the ( + values := make([]cfValue, 0, 32) +outer: + for { + p.skipWhitespaceAndComments() + + switch p.next() { + case eof: + p.error("unexpected eof in array") + case ')': + break outer // done here + case ',': + continue // restart; ,) is valid and we don't want to blow it + default: + p.backup() + } + + pval := p.parsePlistValue() // whitespace is consumed within + if str, ok := pval.(cfString); ok && string(str) == "" { + // Empty strings in arrays are apparently skipped? + // TODO: Figure out why this was implemented. + continue + } + values = append(values, pval) + } + return &cfArray{values} +} + +// the <* have already been consumed +func (p *textPlistParser) parseGNUStepValue() cfValue { + typ := p.next() + p.ignore() + p.scanUntil('>') + + if typ == eof || typ == '>' || p.empty() || p.peek() == eof { + p.error("invalid GNUStep extended value") + } + + v := p.emit() + p.next() // consume the > + + switch typ { + case 'I': + if v[0] == '-' { + n := mustParseInt(v, 10, 64) + return &cfNumber{signed: true, value: uint64(n)} + } else { + n := mustParseUint(v, 10, 64) + return &cfNumber{signed: false, value: n} + } + case 'R': + n := mustParseFloat(v, 64) + return &cfReal{wide: true, value: n} // TODO(DH) 32/64 + case 'B': + b := v[0] == 'Y' + return cfBoolean(b) + case 'D': + t, err := time.Parse(textPlistTimeLayout, v) + if err != nil { + p.error(err.Error()) + } + + return cfDate(t.In(time.UTC)) + } + p.error("invalid GNUStep type " + string(typ)) + return nil +} + +// The < has already been consumed +func (p *textPlistParser) parseHexData() cfData { + buf := make([]byte, 256) + i := 0 + c := 0 + + for { + r := p.next() + switch r { + case eof: + p.error("unexpected eof in data") + case '>': + if c&1 == 1 { + p.error("uneven number of hex digits in data") + } + p.ignore() + return cfData(buf[:i]) + case ' ', '\t', '\n', '\r', '\u2028', '\u2029': // more lax than apple here: skip spaces + continue + } + + buf[i] <<= 4 + if r >= 'a' && r <= 'f' { + buf[i] |= 10 + byte((r - 'a')) + } else if r >= 'A' && r <= 'F' { + buf[i] |= 10 + byte((r - 'A')) + } else if r >= '0' && r <= '9' { + buf[i] |= byte((r - '0')) + } else { + p.error("unexpected hex digit `%c'", r) + } + + c++ + if c&1 == 0 { + i++ + if i >= len(buf) { + realloc := make([]byte, len(buf)*2) + copy(realloc, buf) + buf = realloc + } + } + } +} + +func (p *textPlistParser) parsePlistValue() cfValue { + for { + p.skipWhitespaceAndComments() + + switch p.next() { + case eof: + return &cfDictionary{} + case '<': + if p.next() == '*' { + p.format = GNUStepFormat + return p.parseGNUStepValue() + } + + p.backup() + return p.parseHexData() + case '"': + return p.parseQuotedString() + case '{': + return p.parseDictionary(false) + case '(': + return p.parseArray() + default: + p.backup() + return p.parseUnquotedString() + } + } +} + +func newTextPlistParser(r io.Reader) *textPlistParser { + return &textPlistParser{ + reader: r, + format: OpenStepFormat, + } +} diff --git a/vendor/howett.net/plist/text_tables.go b/vendor/howett.net/plist/text_tables.go new file mode 100644 index 000000000..319c55c59 --- /dev/null +++ b/vendor/howett.net/plist/text_tables.go @@ -0,0 +1,43 @@ +package plist + +type characterSet [4]uint64 + +func (s *characterSet) Contains(ch rune) bool { + return ch >= 0 && ch <= 255 && s.ContainsByte(byte(ch)) +} + +func (s *characterSet) ContainsByte(ch byte) bool { + return (s[ch/64]&(1<<(ch%64)) > 0) +} + +// Bitmap of characters that must be inside a quoted string +// when written to an old-style property list +// Low bits represent lower characters, and each uint64 represents 64 characters. +var gsQuotable = characterSet{ + 0x78001385ffffffff, + 0xa800000138000000, + 0xffffffffffffffff, + 0xffffffffffffffff, +} + +// 7f instead of 3f in the top line: CFOldStylePlist.c says . is valid, but they quote it. +var osQuotable = characterSet{ + 0xf4007f6fffffffff, + 0xf8000001f8000001, + 0xffffffffffffffff, + 0xffffffffffffffff, +} + +var whitespace = characterSet{ + 0x0000000100003f00, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, +} + +var newlineCharacterSet = characterSet{ + 0x0000000000002400, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, +} diff --git a/vendor/howett.net/plist/typeinfo.go b/vendor/howett.net/plist/typeinfo.go new file mode 100644 index 000000000..f0b920f8a --- /dev/null +++ b/vendor/howett.net/plist/typeinfo.go @@ -0,0 +1,170 @@ +package plist + +import ( + "reflect" + "strings" + "sync" +) + +// typeInfo holds details for the plist representation of a type. +type typeInfo struct { + fields []fieldInfo +} + +// fieldInfo holds details for the plist representation of a single field. +type fieldInfo struct { + idx []int + name string + omitEmpty bool +} + +var tinfoMap = make(map[reflect.Type]*typeInfo) +var tinfoLock sync.RWMutex + +// getTypeInfo returns the typeInfo structure with details necessary +// for marshalling and unmarshalling typ. +func getTypeInfo(typ reflect.Type) (*typeInfo, error) { + tinfoLock.RLock() + tinfo, ok := tinfoMap[typ] + tinfoLock.RUnlock() + if ok { + return tinfo, nil + } + tinfo = &typeInfo{} + if typ.Kind() == reflect.Struct { + n := typ.NumField() + for i := 0; i < n; i++ { + f := typ.Field(i) + if f.PkgPath != "" || f.Tag.Get("plist") == "-" { + continue // Private field + } + + // For embedded structs, embed its fields. + if f.Anonymous { + t := f.Type + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.Struct { + inner, err := getTypeInfo(t) + if err != nil { + return nil, err + } + for _, finfo := range inner.fields { + finfo.idx = append([]int{i}, finfo.idx...) + if err := addFieldInfo(typ, tinfo, &finfo); err != nil { + return nil, err + } + } + continue + } + } + + finfo, err := structFieldInfo(typ, &f) + if err != nil { + return nil, err + } + + // Add the field if it doesn't conflict with other fields. + if err := addFieldInfo(typ, tinfo, finfo); err != nil { + return nil, err + } + } + } + tinfoLock.Lock() + tinfoMap[typ] = tinfo + tinfoLock.Unlock() + return tinfo, nil +} + +// structFieldInfo builds and returns a fieldInfo for f. +func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) { + finfo := &fieldInfo{idx: f.Index} + + // Split the tag from the xml namespace if necessary. + tag := f.Tag.Get("plist") + + // Parse flags. + tokens := strings.Split(tag, ",") + tag = tokens[0] + if len(tokens) > 1 { + tag = tokens[0] + for _, flag := range tokens[1:] { + switch flag { + case "omitempty": + finfo.omitEmpty = true + } + } + } + + if tag == "" { + // If the name part of the tag is completely empty, + // use the field name + finfo.name = f.Name + return finfo, nil + } + + finfo.name = tag + return finfo, nil +} + +// addFieldInfo adds finfo to tinfo.fields if there are no +// conflicts, or if conflicts arise from previous fields that were +// obtained from deeper embedded structures than finfo. In the latter +// case, the conflicting entries are dropped. +// A conflict occurs when the path (parent + name) to a field is +// itself a prefix of another path, or when two paths match exactly. +// It is okay for field paths to share a common, shorter prefix. +func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error { + var conflicts []int + // First, figure all conflicts. Most working code will have none. + for i := range tinfo.fields { + oldf := &tinfo.fields[i] + if newf.name == oldf.name { + conflicts = append(conflicts, i) + } + } + + // Without conflicts, add the new field and return. + if conflicts == nil { + tinfo.fields = append(tinfo.fields, *newf) + return nil + } + + // If any conflict is shallower, ignore the new field. + // This matches the Go field resolution on embedding. + for _, i := range conflicts { + if len(tinfo.fields[i].idx) < len(newf.idx) { + return nil + } + } + + // Otherwise, the new field is shallower, and thus takes precedence, + // so drop the conflicting fields from tinfo and append the new one. + for c := len(conflicts) - 1; c >= 0; c-- { + i := conflicts[c] + copy(tinfo.fields[i:], tinfo.fields[i+1:]) + tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] + } + tinfo.fields = append(tinfo.fields, *newf) + return nil +} + +// value returns v's field value corresponding to finfo. +// It's equivalent to v.FieldByIndex(finfo.idx), but initializes +// and dereferences pointers as necessary. +func (finfo *fieldInfo) value(v reflect.Value) reflect.Value { + for i, x := range finfo.idx { + if i > 0 { + t := v.Type() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} diff --git a/vendor/howett.net/plist/unmarshal.go b/vendor/howett.net/plist/unmarshal.go new file mode 100644 index 000000000..06f5d6fc3 --- /dev/null +++ b/vendor/howett.net/plist/unmarshal.go @@ -0,0 +1,317 @@ +package plist + +import ( + "encoding" + "fmt" + "reflect" + "runtime" + "time" +) + +type incompatibleDecodeTypeError struct { + dest reflect.Type + src string // type name (from cfValue) +} + +func (u *incompatibleDecodeTypeError) Error() string { + return fmt.Sprintf("plist: type mismatch: tried to decode plist type `%v' into value of type `%v'", u.src, u.dest) +} + +var ( + plistUnmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() + textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + uidType = reflect.TypeOf(UID(0)) +) + +func isEmptyInterface(v reflect.Value) bool { + return v.Kind() == reflect.Interface && v.NumMethod() == 0 +} + +func (p *Decoder) unmarshalPlistInterface(pval cfValue, unmarshalable Unmarshaler) { + err := unmarshalable.UnmarshalPlist(func(i interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + p.unmarshal(pval, reflect.ValueOf(i)) + return + }) + + if err != nil { + panic(err) + } +} + +func (p *Decoder) unmarshalTextInterface(pval cfString, unmarshalable encoding.TextUnmarshaler) { + err := unmarshalable.UnmarshalText([]byte(pval)) + if err != nil { + panic(err) + } +} + +func (p *Decoder) unmarshalTime(pval cfDate, val reflect.Value) { + val.Set(reflect.ValueOf(time.Time(pval))) +} + +func (p *Decoder) unmarshalLaxString(s string, val reflect.Value) { + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i := mustParseInt(s, 10, 64) + val.SetInt(i) + return + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + i := mustParseUint(s, 10, 64) + val.SetUint(i) + return + case reflect.Float32, reflect.Float64: + f := mustParseFloat(s, 64) + val.SetFloat(f) + return + case reflect.Bool: + b := mustParseBool(s) + val.SetBool(b) + return + case reflect.Struct: + if val.Type() == timeType { + t, err := time.Parse(textPlistTimeLayout, s) + if err != nil { + panic(err) + } + val.Set(reflect.ValueOf(t.In(time.UTC))) + return + } + fallthrough + default: + panic(&incompatibleDecodeTypeError{val.Type(), "string"}) + } +} + +func (p *Decoder) unmarshal(pval cfValue, val reflect.Value) { + if pval == nil { + return + } + + if val.Kind() == reflect.Ptr { + if val.IsNil() { + val.Set(reflect.New(val.Type().Elem())) + } + val = val.Elem() + } + + if isEmptyInterface(val) { + v := p.valueInterface(pval) + val.Set(reflect.ValueOf(v)) + return + } + + incompatibleTypeError := &incompatibleDecodeTypeError{val.Type(), pval.typeName()} + + // time.Time implements TextMarshaler, but we need to parse it as RFC3339 + if date, ok := pval.(cfDate); ok { + if val.Type() == timeType { + p.unmarshalTime(date, val) + return + } + panic(incompatibleTypeError) + } + + if receiver, can := implementsInterface(val, plistUnmarshalerType); can { + p.unmarshalPlistInterface(pval, receiver.(Unmarshaler)) + return + } + + if val.Type() != timeType { + if receiver, can := implementsInterface(val, textUnmarshalerType); can { + if str, ok := pval.(cfString); ok { + p.unmarshalTextInterface(str, receiver.(encoding.TextUnmarshaler)) + } else { + panic(incompatibleTypeError) + } + return + } + } + + typ := val.Type() + + switch pval := pval.(type) { + case cfString: + if val.Kind() == reflect.String { + val.SetString(string(pval)) + return + } + if p.lax { + p.unmarshalLaxString(string(pval), val) + return + } + + panic(incompatibleTypeError) + case *cfNumber: + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + val.SetInt(int64(pval.value)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + val.SetUint(pval.value) + default: + panic(incompatibleTypeError) + } + case *cfReal: + if val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 { + // TODO: Consider warning on a downcast (storing a 64-bit value in a 32-bit reflect) + val.SetFloat(pval.value) + } else { + panic(incompatibleTypeError) + } + case cfBoolean: + if val.Kind() == reflect.Bool { + val.SetBool(bool(pval)) + } else { + panic(incompatibleTypeError) + } + case cfData: + if val.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 { + val.SetBytes([]byte(pval)) + } else { + panic(incompatibleTypeError) + } + case cfUID: + if val.Type() == uidType { + val.SetUint(uint64(pval)) + } else { + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + val.SetInt(int64(pval)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + val.SetUint(uint64(pval)) + default: + panic(incompatibleTypeError) + } + } + case *cfArray: + p.unmarshalArray(pval, val) + case *cfDictionary: + p.unmarshalDictionary(pval, val) + } +} + +func (p *Decoder) unmarshalArray(a *cfArray, val reflect.Value) { + var n int + if val.Kind() == reflect.Slice { + // Slice of element values. + // Grow slice. + cnt := len(a.values) + val.Len() + if cnt >= val.Cap() { + ncap := 2 * cnt + if ncap < 4 { + ncap = 4 + } + new := reflect.MakeSlice(val.Type(), val.Len(), ncap) + reflect.Copy(new, val) + val.Set(new) + } + n = val.Len() + val.SetLen(cnt) + } else if val.Kind() == reflect.Array { + if len(a.values) > val.Cap() { + panic(fmt.Errorf("plist: attempted to unmarshal %d values into an array of size %d", len(a.values), val.Cap())) + } + } else { + panic(&incompatibleDecodeTypeError{val.Type(), a.typeName()}) + } + + // Recur to read element into slice. + for _, sval := range a.values { + p.unmarshal(sval, val.Index(n)) + n++ + } + return +} + +func (p *Decoder) unmarshalDictionary(dict *cfDictionary, val reflect.Value) { + typ := val.Type() + switch val.Kind() { + case reflect.Struct: + tinfo, err := getTypeInfo(typ) + if err != nil { + panic(err) + } + + entries := make(map[string]cfValue, len(dict.keys)) + for i, k := range dict.keys { + sval := dict.values[i] + entries[k] = sval + } + + for _, finfo := range tinfo.fields { + p.unmarshal(entries[finfo.name], finfo.value(val)) + } + case reflect.Map: + if val.IsNil() { + val.Set(reflect.MakeMap(typ)) + } + + for i, k := range dict.keys { + sval := dict.values[i] + + keyv := reflect.ValueOf(k).Convert(typ.Key()) + mapElem := reflect.New(typ.Elem()).Elem() + + p.unmarshal(sval, mapElem) + val.SetMapIndex(keyv, mapElem) + } + default: + panic(&incompatibleDecodeTypeError{typ, dict.typeName()}) + } +} + +/* *Interface is modelled after encoding/json */ +func (p *Decoder) valueInterface(pval cfValue) interface{} { + switch pval := pval.(type) { + case cfString: + return string(pval) + case *cfNumber: + if pval.signed { + return int64(pval.value) + } + return pval.value + case *cfReal: + if pval.wide { + return pval.value + } else { + return float32(pval.value) + } + case cfBoolean: + return bool(pval) + case *cfArray: + return p.arrayInterface(pval) + case *cfDictionary: + return p.dictionaryInterface(pval) + case cfData: + return []byte(pval) + case cfDate: + return time.Time(pval) + case cfUID: + return UID(pval) + } + return nil +} + +func (p *Decoder) arrayInterface(a *cfArray) []interface{} { + out := make([]interface{}, len(a.values)) + for i, subv := range a.values { + out[i] = p.valueInterface(subv) + } + return out +} + +func (p *Decoder) dictionaryInterface(dict *cfDictionary) map[string]interface{} { + out := make(map[string]interface{}) + for i, k := range dict.keys { + subv := dict.values[i] + out[k] = p.valueInterface(subv) + } + return out +} diff --git a/vendor/howett.net/plist/util.go b/vendor/howett.net/plist/util.go new file mode 100644 index 000000000..d4e437a4f --- /dev/null +++ b/vendor/howett.net/plist/util.go @@ -0,0 +1,25 @@ +package plist + +import "io" + +type countedWriter struct { + io.Writer + nbytes int +} + +func (w *countedWriter) Write(p []byte) (int, error) { + n, err := w.Writer.Write(p) + w.nbytes += n + return n, err +} + +func (w *countedWriter) BytesWritten() int { + return w.nbytes +} + +func unsignedGetBase(s string) (string, int) { + if len(s) > 1 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { + return s[2:], 16 + } + return s, 10 +} diff --git a/vendor/howett.net/plist/xml_generator.go b/vendor/howett.net/plist/xml_generator.go new file mode 100644 index 000000000..0b59ed7f2 --- /dev/null +++ b/vendor/howett.net/plist/xml_generator.go @@ -0,0 +1,185 @@ +package plist + +import ( + "bufio" + "encoding/base64" + "encoding/xml" + "io" + "math" + "strconv" + "time" +) + +const ( + xmlHEADER string = `` + "\n" + xmlDOCTYPE = `` + "\n" + xmlArrayTag = "array" + xmlDataTag = "data" + xmlDateTag = "date" + xmlDictTag = "dict" + xmlFalseTag = "false" + xmlIntegerTag = "integer" + xmlKeyTag = "key" + xmlPlistTag = "plist" + xmlRealTag = "real" + xmlStringTag = "string" + xmlTrueTag = "true" + + // magic value used in the XML encoding of UIDs + // (stored as a dictionary mapping CF$UID->integer) + xmlCFUIDMagic = "CF$UID" +) + +func formatXMLFloat(f float64) string { + switch { + case math.IsInf(f, 1): + return "inf" + case math.IsInf(f, -1): + return "-inf" + case math.IsNaN(f): + return "nan" + } + return strconv.FormatFloat(f, 'g', -1, 64) +} + +type xmlPlistGenerator struct { + *bufio.Writer + + indent string + depth int + putNewline bool +} + +func (p *xmlPlistGenerator) generateDocument(root cfValue) { + p.WriteString(xmlHEADER) + p.WriteString(xmlDOCTYPE) + + p.openTag(`plist version="1.0"`) + p.writePlistValue(root) + p.closeTag(xmlPlistTag) + p.Flush() +} + +func (p *xmlPlistGenerator) openTag(n string) { + p.writeIndent(1) + p.WriteByte('<') + p.WriteString(n) + p.WriteByte('>') +} + +func (p *xmlPlistGenerator) closeTag(n string) { + p.writeIndent(-1) + p.WriteString("') +} + +func (p *xmlPlistGenerator) element(n string, v string) { + p.writeIndent(0) + if len(v) == 0 { + p.WriteByte('<') + p.WriteString(n) + p.WriteString("/>") + } else { + p.WriteByte('<') + p.WriteString(n) + p.WriteByte('>') + + err := xml.EscapeText(p.Writer, []byte(v)) + if err != nil { + panic(err) + } + + p.WriteString("') + } +} + +func (p *xmlPlistGenerator) writeDictionary(dict *cfDictionary) { + dict.sort() + p.openTag(xmlDictTag) + for i, k := range dict.keys { + p.element(xmlKeyTag, k) + p.writePlistValue(dict.values[i]) + } + p.closeTag(xmlDictTag) +} + +func (p *xmlPlistGenerator) writeArray(a *cfArray) { + p.openTag(xmlArrayTag) + for _, v := range a.values { + p.writePlistValue(v) + } + p.closeTag(xmlArrayTag) +} + +func (p *xmlPlistGenerator) writePlistValue(pval cfValue) { + if pval == nil { + return + } + + switch pval := pval.(type) { + case cfString: + p.element(xmlStringTag, string(pval)) + case *cfNumber: + if pval.signed { + p.element(xmlIntegerTag, strconv.FormatInt(int64(pval.value), 10)) + } else { + p.element(xmlIntegerTag, strconv.FormatUint(pval.value, 10)) + } + case *cfReal: + p.element(xmlRealTag, formatXMLFloat(pval.value)) + case cfBoolean: + if bool(pval) { + p.element(xmlTrueTag, "") + } else { + p.element(xmlFalseTag, "") + } + case cfData: + p.element(xmlDataTag, base64.StdEncoding.EncodeToString([]byte(pval))) + case cfDate: + p.element(xmlDateTag, time.Time(pval).In(time.UTC).Format(time.RFC3339)) + case *cfDictionary: + p.writeDictionary(pval) + case *cfArray: + p.writeArray(pval) + case cfUID: + p.openTag(xmlDictTag) + p.element(xmlKeyTag, xmlCFUIDMagic) + p.element(xmlIntegerTag, strconv.FormatUint(uint64(pval), 10)) + p.closeTag(xmlDictTag) + } +} + +func (p *xmlPlistGenerator) writeIndent(delta int) { + if len(p.indent) == 0 { + return + } + + if delta < 0 { + p.depth-- + } + + if p.putNewline { + // from encoding/xml/marshal.go; it seems to be intended + // to suppress the first newline. + p.WriteByte('\n') + } else { + p.putNewline = true + } + for i := 0; i < p.depth; i++ { + p.WriteString(p.indent) + } + if delta > 0 { + p.depth++ + } +} + +func (p *xmlPlistGenerator) Indent(i string) { + p.indent = i +} + +func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator { + return &xmlPlistGenerator{Writer: bufio.NewWriter(w)} +} diff --git a/vendor/howett.net/plist/xml_parser.go b/vendor/howett.net/plist/xml_parser.go new file mode 100644 index 000000000..8d8cfd19b --- /dev/null +++ b/vendor/howett.net/plist/xml_parser.go @@ -0,0 +1,216 @@ +package plist + +import ( + "encoding/base64" + "encoding/xml" + "errors" + "fmt" + "io" + "runtime" + "strings" + "time" +) + +type xmlPlistParser struct { + reader io.Reader + xmlDecoder *xml.Decoder + whitespaceReplacer *strings.Replacer + ntags int +} + +func (p *xmlPlistParser) parseDocument() (pval cfValue, parseError error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + if _, ok := r.(invalidPlistError); ok { + parseError = r.(error) + } else { + // Wrap all non-invalid-plist errors. + parseError = plistParseError{"XML", r.(error)} + } + } + }() + for { + if token, err := p.xmlDecoder.Token(); err == nil { + if element, ok := token.(xml.StartElement); ok { + pval = p.parseXMLElement(element) + if p.ntags == 0 { + panic(invalidPlistError{"XML", errors.New("no elements encountered")}) + } + return + } + } else { + // The first XML parse turned out to be invalid: + // we do not have an XML property list. + panic(invalidPlistError{"XML", err}) + } + } +} + +func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) cfValue { + var charData xml.CharData + switch element.Name.Local { + case "plist": + p.ntags++ + for { + token, err := p.xmlDecoder.Token() + if err != nil { + panic(err) + } + + if el, ok := token.(xml.EndElement); ok && el.Name.Local == "plist" { + break + } + + if el, ok := token.(xml.StartElement); ok { + return p.parseXMLElement(el) + } + } + return nil + case "string": + p.ntags++ + err := p.xmlDecoder.DecodeElement(&charData, &element) + if err != nil { + panic(err) + } + + return cfString(charData) + case "integer": + p.ntags++ + err := p.xmlDecoder.DecodeElement(&charData, &element) + if err != nil { + panic(err) + } + + s := string(charData) + if len(s) == 0 { + panic(errors.New("invalid empty ")) + } + + if s[0] == '-' { + s, base := unsignedGetBase(s[1:]) + n := mustParseInt("-"+s, base, 64) + return &cfNumber{signed: true, value: uint64(n)} + } else { + s, base := unsignedGetBase(s) + n := mustParseUint(s, base, 64) + return &cfNumber{signed: false, value: n} + } + case "real": + p.ntags++ + err := p.xmlDecoder.DecodeElement(&charData, &element) + if err != nil { + panic(err) + } + + n := mustParseFloat(string(charData), 64) + return &cfReal{wide: true, value: n} + case "true", "false": + p.ntags++ + p.xmlDecoder.Skip() + + b := element.Name.Local == "true" + return cfBoolean(b) + case "date": + p.ntags++ + err := p.xmlDecoder.DecodeElement(&charData, &element) + if err != nil { + panic(err) + } + + t, err := time.ParseInLocation(time.RFC3339, string(charData), time.UTC) + if err != nil { + panic(err) + } + + return cfDate(t) + case "data": + p.ntags++ + err := p.xmlDecoder.DecodeElement(&charData, &element) + if err != nil { + panic(err) + } + + str := p.whitespaceReplacer.Replace(string(charData)) + + l := base64.StdEncoding.DecodedLen(len(str)) + bytes := make([]uint8, l) + l, err = base64.StdEncoding.Decode(bytes, []byte(str)) + if err != nil { + panic(err) + } + + return cfData(bytes[:l]) + case "dict": + p.ntags++ + var key *string + keys := make([]string, 0, 32) + values := make([]cfValue, 0, 32) + for { + token, err := p.xmlDecoder.Token() + if err != nil { + panic(err) + } + + if el, ok := token.(xml.EndElement); ok && el.Name.Local == "dict" { + if key != nil { + panic(errors.New("missing value in dictionary")) + } + break + } + + if el, ok := token.(xml.StartElement); ok { + if el.Name.Local == "key" { + var k string + p.xmlDecoder.DecodeElement(&k, &el) + key = &k + } else { + if key == nil { + panic(errors.New("missing key in dictionary")) + } + keys = append(keys, *key) + values = append(values, p.parseXMLElement(el)) + key = nil + } + } + } + + if len(keys) == 1 && keys[0] == "CF$UID" && len(values) == 1 { + if integer, ok := values[0].(*cfNumber); ok { + return cfUID(integer.value) + } + } + + return &cfDictionary{keys: keys, values: values} + case "array": + p.ntags++ + values := make([]cfValue, 0, 10) + for { + token, err := p.xmlDecoder.Token() + if err != nil { + panic(err) + } + + if el, ok := token.(xml.EndElement); ok && el.Name.Local == "array" { + break + } + + if el, ok := token.(xml.StartElement); ok { + values = append(values, p.parseXMLElement(el)) + } + } + return &cfArray{values} + } + err := fmt.Errorf("encountered unknown element %s", element.Name.Local) + if p.ntags == 0 { + // If out first XML tag is invalid, it might be an openstep data element, ala or <0101> + panic(invalidPlistError{"XML", err}) + } + panic(err) +} + +func newXMLPlistParser(r io.Reader) *xmlPlistParser { + return &xmlPlistParser{r, xml.NewDecoder(r), strings.NewReplacer("\t", "", "\n", "", " ", "", "\r", ""), 0} +} diff --git a/vendor/howett.net/plist/zerocopy.go b/vendor/howett.net/plist/zerocopy.go new file mode 100644 index 000000000..999f401b7 --- /dev/null +++ b/vendor/howett.net/plist/zerocopy.go @@ -0,0 +1,20 @@ +// +build !appengine + +package plist + +import ( + "reflect" + "unsafe" +) + +func zeroCopy8BitString(buf []byte, off int, len int) string { + if len == 0 { + return "" + } + + var s string + hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) + hdr.Data = uintptr(unsafe.Pointer(&buf[off])) + hdr.Len = len + return s +} diff --git a/vendor/howett.net/plist/zerocopy_appengine.go b/vendor/howett.net/plist/zerocopy_appengine.go new file mode 100644 index 000000000..dbd9a1acf --- /dev/null +++ b/vendor/howett.net/plist/zerocopy_appengine.go @@ -0,0 +1,7 @@ +// +build appengine + +package plist + +func zeroCopy8BitString(buf []byte, off int, len int) string { + return string(buf[off : off+len]) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index fb9a58fac..0677e280d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -109,15 +109,30 @@ github.com/inconshreveable/mousetrap github.com/james-barrow/golang-ipc # github.com/jaypipes/ghw v0.8.0 ## explicit; go 1.12 +github.com/jaypipes/ghw github.com/jaypipes/ghw/pkg/baseboard +github.com/jaypipes/ghw/pkg/bios +github.com/jaypipes/ghw/pkg/block +github.com/jaypipes/ghw/pkg/chassis github.com/jaypipes/ghw/pkg/context +github.com/jaypipes/ghw/pkg/cpu +github.com/jaypipes/ghw/pkg/gpu github.com/jaypipes/ghw/pkg/linuxdmi github.com/jaypipes/ghw/pkg/linuxpath github.com/jaypipes/ghw/pkg/marshal +github.com/jaypipes/ghw/pkg/memory +github.com/jaypipes/ghw/pkg/net github.com/jaypipes/ghw/pkg/option +github.com/jaypipes/ghw/pkg/pci github.com/jaypipes/ghw/pkg/pci/address +github.com/jaypipes/ghw/pkg/product github.com/jaypipes/ghw/pkg/snapshot +github.com/jaypipes/ghw/pkg/topology +github.com/jaypipes/ghw/pkg/unitutil github.com/jaypipes/ghw/pkg/util +# github.com/jaypipes/pcidb v0.6.0 +## explicit; go 1.11 +github.com/jaypipes/pcidb # github.com/json-iterator/go v1.1.12 ## explicit; go 1.12 github.com/json-iterator/go @@ -156,6 +171,9 @@ github.com/mholt/archiver/v3 # github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db ## explicit github.com/mitchellh/colorstring +# github.com/mitchellh/go-homedir v1.1.0 +## explicit +github.com/mitchellh/go-homedir # github.com/mmcloughlin/avo v0.0.0-20200523190732-4439b6b2c061 ## explicit; go 1.11 github.com/mmcloughlin/avo/attr @@ -382,6 +400,9 @@ gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ## explicit gopkg.in/yaml.v3 +# howett.net/plist v0.0.0-20181124034731-591f970eefbb +## explicit +howett.net/plist # nhooyr.io/websocket v1.8.2 ## explicit; go 1.13 nhooyr.io/websocket From 1bb294ec96e95c4709667df88fc586b1c16123a0 Mon Sep 17 00:00:00 2001 From: MohammadReza Palide Date: Thu, 30 Dec 2021 16:47:13 +0330 Subject: [PATCH 3/5] add log for achive host info --- pkg/visor/visor.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 3c7261461..44feb57c7 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -282,6 +282,8 @@ func (v *Visor) HostKeeper(skybianBuildVersion string) { keeperInfo.Model = model keeperInfo.SerialNumber = serialNumber + logger.WithField("Info", keeperInfo).Info("Host information achieved.") + client, err := httpauth.NewClient(context.Background(), v.conf.HostKeeper, v.conf.PK, v.conf.SK, &http.Client{}, v.MasterLogger()) if err != nil { logger.Errorf("Host Keeper httpauth: %v", err) From 54a7ffa004f4573ed962a1b8a705d20710dbc478 Mon Sep 17 00:00:00 2001 From: MohammadReza Palide Date: Thu, 30 Dec 2021 16:47:56 +0330 Subject: [PATCH 4/5] use UUID instead serial number in product --- pkg/visor/visor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 44feb57c7..58b4fd470 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -274,7 +274,7 @@ func (v *Visor) HostKeeper(skybianBuildVersion string) { return } model = productInfo.Vendor - serialNumber = productInfo.SerialNumber + serialNumber = productInfo.UUID } } From f8c71b7273df4d6f5187af6dc0991970872296ba Mon Sep 17 00:00:00 2001 From: MohammadReza Palide Date: Thu, 30 Dec 2021 16:49:22 +0330 Subject: [PATCH 5/5] use unknown verb instead blank on check model and serial number --- pkg/visor/visor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 58b4fd470..502cb45a3 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -267,7 +267,7 @@ func (v *Visor) HostKeeper(skybianBuildVersion string) { } model = baseboardInfo.Vendor serialNumber = baseboardInfo.SerialNumber - if model == "" || serialNumber == "" { + if model == "unknown" || serialNumber == "unknown" { productInfo, err := product.New(ghw.WithDisableWarnings()) if err != nil { logger.Errorf("Error during get information of host due to %v", err)