(v0.1.1) Automated packaging of release by Packagr

This commit is contained in:
packagr-io-beta
2020-08-21 06:31:48 +00:00
parent 7a10a7ee63
commit b6ca94f786
1303 changed files with 620126 additions and 109 deletions
+3
View File
@@ -0,0 +1,3 @@
#! /bin/sh
export GO_PACKAGES=$(go list ./... | grep -v /vendor/)
+2
View File
@@ -0,0 +1,2 @@
vendor/
coverage*.*
+43
View File
@@ -0,0 +1,43 @@
language: go
# 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
matrix:
include:
# On Go 1.10 and Go 1.11, use dep to ensure dependencies before running go
# test.
- os: linux
go: "1.10"
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
# On Go >=1.12, use go modules to ensure dependencies instead of dep
- os: linux
go: "1.12"
env: GO111MODULE=on
- os: linux
go: "1.13"
env: GO111MODULE=on
- os: linux
go: "1.14.x"
env: GO111MODULE=on
# Tests that ghw builds on MacOSX (even though there is currently only
# support for block devices)
#- os: osx
# go: "1.12"
# env: GO111MODULE=on
#- os: osx
# go: "1.13"
# env: GO111MODULE=on
+176
View File
@@ -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.
+26
View File
@@ -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
+87
View File
@@ -0,0 +1,87 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22"
name = "github.com/ghodss/yaml"
packages = ["."]
pruneopts = ""
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
version = "v1.0.0"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = ""
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:49ab40aecc2c09b5eb0a277716f17958c502dc634d31eff3c345821320b1af31"
name = "github.com/jaypipes/pcidb"
packages = ["."]
pruneopts = ""
revision = "4d488f78747b8369c9f5bb93462241a30d178059"
version = "0.4"
[[projects]]
digest = "1:096a8a9182648da3d00ff243b88407838902b6703fc12657f76890e08d1899bf"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
pruneopts = ""
revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4"
version = "v1.0.0"
[[projects]]
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = ""
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:a1403cc8a94b8d7956ee5e9694badef0e7b051af289caad1cf668331e3ffa4f6"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = ""
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
version = "v0.0.3"
[[projects]]
digest = "1:0a52bcb568386d98f4894575d53ce3e456f56471de6897bb8b9de13c33d9340e"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = ""
revision = "9a97c102cda95a86cec2345a6f09f55a939babf5"
version = "v1.0.2"
[[projects]]
digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = ""
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
version = "v2.2.2"
[[projects]]
digest = "1:e43acd1190dde7223727f1f8aeac537156d33bf87776d5637e672e1c6b354be4"
name = "howett.net/plist"
packages = ["."]
pruneopts = ""
revision = "591f970eefbbeb04d7b37f334a0c4c3256e32876"
source = "github.com/DHowett/go-plist"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/ghodss/yaml",
"github.com/jaypipes/pcidb",
"github.com/pkg/errors",
"github.com/spf13/cobra",
"howett.net/plist",
]
solver-name = "gps-cdcl"
solver-version = 1
+28
View File
@@ -0,0 +1,28 @@
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
[[constraint]]
name = "github.com/jaypipes/pcidb"
version = "0.4"
[[constraint]]
name = "github.com/spf13/cobra"
version = "0.0.3"
[[constraint]]
name = "howett.net/plist"
source = "github.com/DHowett/go-plist"
# pinning a sha since as yet no release has ever been made
revision = "591f970eefbbeb04d7b37f334a0c4c3256e32876"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
[[constraint]]
name = "github.com/ghodss/yaml"
version = "1.0.0"
[[override]]
name = "gopkg.in/yaml.v2"
version = "2.2.2"
+47
View File
@@ -0,0 +1,47 @@
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:
@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
+1168
View File
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
//
// 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 "strings"
// 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
}
+73
View File
@@ -0,0 +1,73 @@
//
// 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"
// BaseboardInfo defines baseboard release information
type BaseboardInfo struct {
AssetTag string `json:"asset_tag"`
SerialNumber string `json:"serial_number"`
Vendor string `json:"vendor"`
Version string `json:"version"`
}
func (i *BaseboardInfo) String() string {
vendorStr := ""
if i.Vendor != "" {
vendorStr = " vendor=" + i.Vendor
}
serialStr := ""
if i.SerialNumber != "" && i.SerialNumber != UNKNOWN {
serialStr = " serial=" + i.SerialNumber
}
versionStr := ""
if i.Version != "" {
versionStr = " version=" + i.Version
}
res := fmt.Sprintf(
"baseboard%s%s%s",
vendorStr,
serialStr,
versionStr,
)
return res
}
// Baseboard returns a pointer to a BaseboardInfo struct containing information
// about the host's baseboard
func Baseboard(opts ...*WithOption) (*BaseboardInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &BaseboardInfo{}
if err := ctx.baseboardFillInfo(info); err != nil {
return nil, err
}
return info, nil
}
// simple private struct used to encapsulate baseboard information in a top-level
// "baseboard" YAML/JSON map/object key
type baseboardPrinter struct {
Info *BaseboardInfo `json:"baseboard"`
}
// YAMLString returns a string with the baseboard information formatted as YAML
// under a top-level "dmi:" key
func (info *BaseboardInfo) YAMLString() string {
return safeYAML(baseboardPrinter{info})
}
// JSONString returns a string with the baseboard information formatted as JSON
// under a top-level "baseboard:" key
func (info *BaseboardInfo) JSONString(indent bool) string {
return safeJSON(baseboardPrinter{info}, indent)
}
+15
View File
@@ -0,0 +1,15 @@
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//
package ghw
func (ctx *context) baseboardFillInfo(info *BaseboardInfo) error {
info.AssetTag = ctx.dmiItem("board_asset_tag")
info.SerialNumber = ctx.dmiItem("board_serial")
info.Vendor = ctx.dmiItem("board_vendor")
info.Version = ctx.dmiItem("board_version")
return nil
}
+17
View File
@@ -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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) baseboardFillInfo(info *BaseboardInfo) error {
return errors.New("baseboardFillInfo not implemented on " + runtime.GOOS)
}
+33
View File
@@ -0,0 +1,33 @@
// 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/StackExchange/wmi"
const wqlBaseboard = "SELECT Manufacturer, SerialNumber, Tag, Version FROM Win32_BaseBoard"
type win32Baseboard struct {
Manufacturer *string
SerialNumber *string
Tag *string
Version *string
}
func (ctx *context) baseboardFillInfo(info *BaseboardInfo) error {
// Getting data from WMI
var win32BaseboardDescriptions []win32Baseboard
if err := wmi.Query(wqlBaseboard, &win32BaseboardDescriptions); err != nil {
return err
}
if len(win32BaseboardDescriptions) > 0 {
info.AssetTag = *win32BaseboardDescriptions[0].Tag
info.SerialNumber = *win32BaseboardDescriptions[0].SerialNumber
info.Vendor = *win32BaseboardDescriptions[0].Manufacturer
info.Version = *win32BaseboardDescriptions[0].Version
}
return nil
}
+72
View File
@@ -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 ghw
import "fmt"
// BIOSInfo defines BIOS release information
type BIOSInfo struct {
Vendor string `json:"vendor"`
Version string `json:"version"`
Date string `json:"date"`
}
func (i *BIOSInfo) String() string {
vendorStr := ""
if i.Vendor != "" {
vendorStr = " vendor=" + i.Vendor
}
versionStr := ""
if i.Version != "" {
versionStr = " version=" + i.Version
}
dateStr := ""
if i.Date != "" && i.Date != UNKNOWN {
dateStr = " date=" + i.Date
}
res := fmt.Sprintf(
"bios%s%s%s",
vendorStr,
versionStr,
dateStr,
)
return res
}
// BIOS returns a pointer to a BIOSInfo struct containing information
// about the host's BIOS
func BIOS(opts ...*WithOption) (*BIOSInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &BIOSInfo{}
if err := ctx.biosFillInfo(info); 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 *BIOSInfo `json:"bios"`
}
// YAMLString returns a string with the BIOS information formatted as YAML
// under a top-level "dmi:" key
func (info *BIOSInfo) YAMLString() string {
return safeYAML(biosPrinter{info})
}
// JSONString returns a string with the BIOS information formatted as JSON
// under a top-level "bios:" key
func (info *BIOSInfo) JSONString(indent bool) string {
return safeJSON(biosPrinter{info}, indent)
}
+14
View File
@@ -0,0 +1,14 @@
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//
package ghw
func (ctx *context) biosFillInfo(info *BIOSInfo) error {
info.Vendor = ctx.dmiItem("bios_vendor")
info.Version = ctx.dmiItem("bios_version")
info.Date = ctx.dmiItem("bios_date")
return nil
}
+17
View File
@@ -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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) biosFillInfo(info *BIOSInfo) error {
return errors.New("biosFillInfo not implemented on " + runtime.GOOS)
}
+30
View File
@@ -0,0 +1,30 @@
// 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/StackExchange/wmi"
const wqlBIOS = "SELECT InstallDate, Manufacturer, Version FROM CIM_BIOSElement"
type win32BIOS struct {
InstallDate *string
Manufacturer *string
Version *string
}
func (ctx *context) biosFillInfo(info *BIOSInfo) error {
// Getting data from WMI
var win32BIOSDescriptions []win32BIOS
if err := wmi.Query(wqlBIOS, &win32BIOSDescriptions); err != nil {
return err
}
if len(win32BIOSDescriptions) > 0 {
info.Vendor = *win32BIOSDescriptions[0].Manufacturer
info.Version = *win32BIOSDescriptions[0].Version
info.Date = *win32BIOSDescriptions[0].InstallDate
}
return nil
}
+248
View File
@@ -0,0 +1,248 @@
//
// 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"
"math"
"strings"
)
// 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"`
// NOTE(jaypipes): BusType is DEPRECATED. Use the DriveType and
// StorageController fields instead
BusType BusType `json:"-"`
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"`
}
// BlockInfo describes all disk drives and partitions in the host system.
type BlockInfo struct {
// TODO(jaypipes): Deprecate this field and replace with TotalSizeBytes
TotalPhysicalBytes uint64 `json:"total_size_bytes"`
Disks []*Disk `json:"disks"`
Partitions []*Partition `json:"-"`
}
// Block returns a BlockInfo struct that describes the block storage resources
// of the host system.
func Block(opts ...*WithOption) (*BlockInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &BlockInfo{}
if err := ctx.blockFillInfo(info); err != nil {
return nil, err
}
return info, nil
}
func (i *BlockInfo) String() string {
tpbs := UNKNOWN
if i.TotalPhysicalBytes > 0 {
tpb := i.TotalPhysicalBytes
unit, unitStr := unitWithString(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 := UNKNOWN
if d.SizeBytes > 0 {
size := d.SizeBytes
unit, unitStr := unitWithString(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 != UNKNOWN {
model = " model=" + d.Model
}
serial := ""
if d.SerialNumber != UNKNOWN {
serial = " serial=" + d.SerialNumber
}
wwn := ""
if d.WWN != 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 := UNKNOWN
if p.SizeBytes > 0 {
size := p.SizeBytes
unit, unitStr := unitWithString(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 *BlockInfo `json:"block" yaml:"block"`
}
// YAMLString returns a string with the block information formatted as YAML
// under a top-level "block:" key
func (i *BlockInfo) YAMLString() string {
return safeYAML(blockPrinter{i})
}
// JSONString returns a string with the block information formatted as JSON
// under a top-level "block:" key
func (i *BlockInfo) JSONString(indent bool) string {
return safeJSON(blockPrinter{i}, indent)
}
+297
View File
@@ -0,0 +1,297 @@
// 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"
"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 (ctx *context) 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 (ctx *context) 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 (ctx *context) 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 (ctx *context) 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 := ctx.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,
}, nil
}
// driveTypeFromPlist looks at the supplied property list struct and attempts to
// determine the disk type
func (ctx *context) 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 (ctx *context) 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
}
// busTypeFromPlist looks at the supplied property list struct and attempts to
// determine the bus type in use for the device
func (ctx *context) busTypeFromPlist(
infoPlist *diskUtilInfoPlist,
) BusType {
// TODO(jaypipes): Find out if Macs support any bus other than
// PCIe... it doesn't seem like they do
return BUS_TYPE_PCI
}
func (ctx *context) blockFillInfo(info *BlockInfo) error {
listPlist, err := ctx.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 := ctx.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 := ctx.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: ctx.driveTypeFromPlist(infoPlist),
IsRemovable: infoPlist.Removable,
StorageController: ctx.storageControllerFromPlist(infoPlist),
BusType: ctx.busTypeFromPlist(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 := ctx.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 := ctx.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
}
+652
View File
@@ -0,0 +1,652 @@
// 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 (
"bufio"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
const (
sectorSize = 512
)
func (ctx *context) blockFillInfo(info *BlockInfo) error {
info.Disks = ctx.disks()
var tpb uint64
for _, d := range info.Disks {
tpb += d.SizeBytes
}
info.TotalPhysicalBytes = tpb
return nil
}
// DiskPhysicalBlockSizeBytes has been deprecated in 0.2. Please use the
// Disk.PhysicalBlockSizeBytes attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskPhysicalBlockSizeBytes(disk string) uint64 {
msg := `
The DiskPhysicalBlockSizeBytes() function has been DEPRECATED and will be
removed in the 1.0 release of ghw. Please use the Disk.PhysicalBlockSizeBytes
attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskPhysicalBlockSizeBytes(disk)
}
func (ctx *context) diskPhysicalBlockSizeBytes(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(ctx.pathSysBlock(), 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
}
// DiskSizeBytes has been deprecated in 0.2. Please use the Disk.SizeBytes
// attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskSizeBytes(disk string) uint64 {
msg := `
The DiskSizeBytes() function has been DEPRECATED and will be
removed in the 1.0 release of ghw. Please use the Disk.SizeBytes attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskSizeBytes(disk)
}
func (ctx *context) diskSizeBytes(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(ctx.pathSysBlock(), 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
}
// DiskNUMANodeID has been deprecated in 0.2. Please use the Disk.NUMANodeID
// attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskNUMANodeID(disk string) int {
msg := `
The DiskNUMANodeID() function has been DEPRECATED and will be
removed in the 1.0 release of ghw. Please use the Disk.NUMANodeID attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskNUMANodeID(disk)
}
func (ctx *context) diskNUMANodeID(disk string) int {
link, err := os.Readlink(filepath.Join(ctx.pathSysBlock(), disk))
if err != nil {
return -1
}
for partial := link; strings.HasPrefix(partial, "../devices/"); partial = filepath.Base(partial) {
if nodeContents, err := ioutil.ReadFile(filepath.Join(ctx.pathSysBlock(), partial, "numa_node")); err != nil {
if nodeInt, err := strconv.Atoi(string(nodeContents)); err != nil {
return nodeInt
}
}
}
return -1
}
// DiskVendor has been deprecated in 0.2. Please use the Disk.Vendor attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskVendor(disk string) string {
msg := `
The DiskVendor() function has been DEPRECATED and will be
removed in the 1.0 release of ghw. Please use the Disk.Vendor attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskVendor(disk)
}
func (ctx *context) diskVendor(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(ctx.pathSysBlock(), disk, "device", "vendor")
contents, err := ioutil.ReadFile(path)
if err != nil {
return UNKNOWN
}
return strings.TrimSpace(string(contents))
}
func (ctx *context) udevInfo(disk string) (map[string]string, error) {
// Get device major:minor numbers
devNo, err := ioutil.ReadFile(filepath.Join(ctx.pathSysBlock(), 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(ctx.pathRunUdevData(), 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
}
// DiskModel has been deprecated in 0.2. Please use the Disk.Model attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskModel(disk string) string {
msg := `
The DiskModel() function has been DEPRECATED and will be removed in the 1.0
release of ghw. Please use the Disk.Model attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskModel(disk)
}
func (ctx *context) diskModel(disk string) string {
info, err := ctx.udevInfo(disk)
if err != nil {
return UNKNOWN
}
if model, ok := info["ID_MODEL"]; ok {
return model
}
return UNKNOWN
}
// DiskSerialNumber has been deprecated in 0.2. Please use the Disk.SerialNumber attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskSerialNumber(disk string) string {
msg := `
The DiskSerialNumber() function has been DEPRECATED and will be removed in the
1.0 release of ghw. Please use the Disk.SerialNumber attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskSerialNumber(disk)
}
func (ctx *context) diskSerialNumber(disk string) string {
info, err := ctx.udevInfo(disk)
if err != nil {
return 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.
if serial, ok := info["ID_SERIAL_SHORT"]; ok {
return serial
}
return UNKNOWN
}
// DiskBusPath has been deprecated in 0.2. Please use the Disk.BusPath attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskBusPath(disk string) string {
msg := `
The DiskBusPath() function has been DEPRECATED and will be removed in the 1.0
release of ghw. Please use the Disk.BusPath attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskBusPath(disk)
}
func (ctx *context) diskBusPath(disk string) string {
info, err := ctx.udevInfo(disk)
if err != nil {
return 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 UNKNOWN
}
// DiskWWN has been deprecated in 0.2. Please use the Disk.WWN attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskWWN(disk string) string {
msg := `
The DiskWWN() function has been DEPRECATED and will be removed in the 1.0
release of ghw. Please use the Disk.WWN attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskWWN(disk)
}
func (ctx *context) diskWWN(disk string) string {
info, err := ctx.udevInfo(disk)
if err != nil {
return 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 UNKNOWN
}
// DiskPartitions has been deprecated in 0.2. Please use the Disk.Partitions attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskPartitions(disk string) []*Partition {
msg := `
The DiskPartitions() function has been DEPRECATED and will be removed in the
1.0 release of ghw. Please use the Disk.Partitions attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskPartitions(disk)
}
// 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 (ctx *context) diskPartitions(disk string) []*Partition {
out := make([]*Partition, 0)
path := filepath.Join(ctx.pathSysBlock(), disk)
files, err := ioutil.ReadDir(path)
if err != nil {
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 := ctx.partitionSizeBytes(disk, fname)
mp, pt, ro := ctx.partitionInfo(fname)
p := &Partition{
Name: fname,
SizeBytes: size,
MountPoint: mp,
Type: pt,
IsReadOnly: ro,
}
out = append(out, p)
}
return out
}
func (ctx *context) diskIsRemovable(disk string) bool {
path := filepath.Join(ctx.pathSysBlock(), disk, "removable")
contents, err := ioutil.ReadFile(path)
if err != nil {
return false
}
removable := strings.TrimSpace(string(contents))
if removable == "1" {
return true
}
return false
}
// Disks has been deprecated in 0.2. Please use the BlockInfo.Disks attribute.
// TODO(jaypipes): Remove in 1.0.
func Disks() []*Disk {
msg := `
The Disks() function has been DEPRECATED and will be removed in the
1.0 release of ghw. Please use the BlockInfo.Disks attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.disks()
}
func (ctx *context) disks() []*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(ctx.pathSysBlock())
if err != nil {
return nil
}
for _, file := range files {
dname := file.Name()
if strings.HasPrefix(dname, "loop") {
continue
}
driveType, storageController, busType := diskTypes(dname)
// TODO(jaypipes): Move this into diskTypes() once abstracting
// diskIsRotational for ease of unit testing
if !ctx.diskIsRotational(dname) {
driveType = DRIVE_TYPE_SSD
}
size := ctx.diskSizeBytes(dname)
pbs := ctx.diskPhysicalBlockSizeBytes(dname)
busPath := ctx.diskBusPath(dname)
node := ctx.diskNUMANodeID(dname)
vendor := ctx.diskVendor(dname)
model := ctx.diskModel(dname)
serialNo := ctx.diskSerialNumber(dname)
wwn := ctx.diskWWN(dname)
removable := ctx.diskIsRemovable(dname)
d := &Disk{
Name: dname,
SizeBytes: size,
PhysicalBlockSizeBytes: pbs,
DriveType: driveType,
IsRemovable: removable,
StorageController: storageController,
BusType: busType,
BusPath: busPath,
NUMANodeID: node,
Vendor: vendor,
Model: model,
SerialNumber: serialNo,
WWN: wwn,
}
parts := ctx.diskPartitions(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,
BusType,
) {
// The conditionals below which set the controller and drive type are
// based on information listed here:
// https://en.wikipedia.org/wiki/Device_file
busType := BUS_TYPE_UNKNOWN
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
busType = BUS_TYPE_SCSI
storageController = STORAGE_CONTROLLER_SCSI
} else if strings.HasPrefix(dname, "hd") {
driveType = DRIVE_TYPE_HDD
busType = BUS_TYPE_IDE
storageController = STORAGE_CONTROLLER_IDE
} else if strings.HasPrefix(dname, "vd") {
driveType = DRIVE_TYPE_HDD
busType = BUS_TYPE_VIRTIO
storageController = STORAGE_CONTROLLER_VIRTIO
} else if strings.HasPrefix(dname, "nvme") {
driveType = DRIVE_TYPE_SSD
busType = BUS_TYPE_NVME
storageController = STORAGE_CONTROLLER_NVME
} else if strings.HasPrefix(dname, "sr") {
driveType = DRIVE_TYPE_ODD
busType = BUS_TYPE_SCSI
storageController = STORAGE_CONTROLLER_SCSI
} else if strings.HasPrefix(dname, "xvd") {
driveType = DRIVE_TYPE_HDD
busType = BUS_TYPE_SCSI
storageController = STORAGE_CONTROLLER_SCSI
} else if strings.HasPrefix(dname, "mmc") {
driveType = DRIVE_TYPE_SSD
busType = BUS_TYPE_UNKNOWN
storageController = STORAGE_CONTROLLER_MMC
}
return driveType, storageController, busType
}
func (ctx *context) diskIsRotational(devName string) bool {
path := filepath.Join(ctx.pathSysBlock(), devName, "queue", "rotational")
contents := safeIntFromFile(path)
return contents == 1
}
// PartitionSizeBytes has been deprecated in 0.2. Please use the
// Partition.SizeBytes attribute. TODO(jaypipes): Remove in 1.0.
func PartitionSizeBytes(part string) uint64 {
msg := `
The PartitionSizeBytes() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition.SizeBytes attribute.
`
warn(msg)
ctx := contextFromEnv()
disk := strings.TrimPrefix(part, "/dev")
if len(disk) < 3 {
warn("PartitionSizeBytes: unknown disk %s, returning 0\n", disk)
return 0
}
switch disk[0:2] {
case "fd":
case "sd":
case "hd":
case "vd":
case "sr":
case "mm":
disk = disk[0:3]
case "xv":
disk = disk[0:4]
case "nv":
// NOTE(jaypipes): I'm putting this regex here instead of a const
// because this function is the only thing that uses it and I'm
// deprecating this function in 1.0
// nvme0n1p2
var regexNVMeDev = regexp.MustCompile(`^nvme\d+n\d+$`)
matches := regexNVMeDev.FindSubmatch([]byte(disk))
if len(matches) < 1 {
warn("PartitionSizeBytes: unknown disk %s, returning 0\n", disk)
return 0
}
disk = string(matches[0])
}
return ctx.partitionSizeBytes(disk, part)
}
// 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 (ctx *context) partitionSizeBytes(disk string, part string) uint64 {
path := filepath.Join(ctx.pathSysBlock(), 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
}
// PartitionInfo has been deprecated in 0.2. Please use the Partition struct.
// TODO(jaypipes): Remove in 1.0.
func PartitionInfo(part string) (string, string, bool) {
msg := `
The PartitionInfo() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition struct.
`
warn(msg)
ctx := contextFromEnv()
return ctx.partitionInfo(part)
}
// Given a full or short partition name, returns the mount point, the type of
// the partition and whether it's readonly
func (ctx *context) partitionInfo(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(ctx.pathEtcMtab())
if err != nil {
return "", "", true
}
defer 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
}
// PartitionMountPoint has been deprecated in 0.2. Please use the
// Partition.MountPoint attribute. TODO(jaypipes): Remove in 1.0.
func PartitionMountPoint(part string) string {
msg := `
The PartitionMountPoint() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition.MountPoint attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.partitionMountPoint(part)
}
func (ctx *context) partitionMountPoint(part string) string {
mp, _, _ := ctx.partitionInfo(part)
return mp
}
// PartitionType has been deprecated in 0.2. Please use the
// Partition.Type attribute. TODO(jaypipes): Remove in 1.0.
func PartitionType(part string) string {
msg := `
The PartitionType() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition.Type attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.partitionType(part)
}
func (ctx *context) partitionType(part string) string {
_, pt, _ := ctx.partitionInfo(part)
return pt
}
// PartitionIsReadOnly has been deprecated in 0.2. Please use the
// Partition.IsReadOnly attribute. TODO(jaypipes): Remove in 1.0.
func PartitionIsReadOnly(part string) bool {
msg := `
The PartitionIsReadOnly() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition.IsReadOnly attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.partitionIsReadOnly(part)
}
func (ctx *context) partitionIsReadOnly(part string) bool {
_, _, ro := ctx.partitionInfo(part)
return ro
}
+267
View File
@@ -0,0 +1,267 @@
// +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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) blockFillInfo(info *BlockInfo) error {
return errors.New("blockFillInfo not implemented on " + runtime.GOOS)
}
// DiskPhysicalBlockSizeBytes has been deprecated in 0.2. Please use the
// Disk.PhysicalBlockSizeBytes attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskPhysicalBlockSizeBytes(disk string) uint64 {
msg := `
The DiskPhysicalBlockSizeBytes() function has been DEPRECATED and will be
removed in the 1.0 release of ghw. Please use the Disk.PhysicalBlockSizeBytes
attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskPhysicalBlockSizeBytes(disk)
}
func (ctx *context) diskPhysicalBlockSizeBytes(disk string) uint64 {
return 0
}
// DiskSizeBytes has been deprecated in 0.2. Please use the Disk.SizeBytes
// attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskSizeBytes(disk string) uint64 {
msg := `
The DiskSizeBytes() function has been DEPRECATED and will be
removed in the 1.0 release of ghw. Please use the Disk.SizeBytes attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskSizeBytes(disk)
}
func (ctx *context) diskSizeBytes(disk string) uint64 {
return 0
}
// DiskNUMANodeID has been deprecated in 0.2. Please use the Disk.NUMANodeID
// attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskNUMANodeID(disk string) int {
msg := `
The DiskNUMANodeID() function has been DEPRECATED and will be
removed in the 1.0 release of ghw. Please use the Disk.NUMANodeID attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskNUMANodeID(disk)
}
func (ctx *context) diskNUMANodeID(disk string) int {
return -1
}
// DiskVendor has been deprecated in 0.2. Please use the Disk.Vendor attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskVendor(disk string) string {
msg := `
The DiskVendor() function has been DEPRECATED and will be
removed in the 1.0 release of ghw. Please use the Disk.Vendor attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskVendor(disk)
}
func (ctx *context) diskVendor(disk string) string {
return UNKNOWN
}
// DiskModel has been deprecated in 0.2. Please use the Disk.Model attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskModel(disk string) string {
msg := `
The DiskModel() function has been DEPRECATED and will be removed in the 1.0
release of ghw. Please use the Disk.Model attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskModel(disk)
}
func (ctx *context) diskModel(disk string) string {
return UNKNOWN
}
// DiskSerialNumber has been deprecated in 0.2. Please use the Disk.SerialNumber attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskSerialNumber(disk string) string {
msg := `
The DiskSerialNumber() function has been DEPRECATED and will be removed in the
1.0 release of ghw. Please use the Disk.SerialNumber attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskSerialNumber(disk)
}
func (ctx *context) diskSerialNumber(disk string) string {
return UNKNOWN
}
// DiskBusPath has been deprecated in 0.2. Please use the Disk.BusPath attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskBusPath(disk string) string {
msg := `
The DiskBusPath() function has been DEPRECATED and will be removed in the 1.0
release of ghw. Please use the Disk.BusPath attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskBusPath(disk)
}
func (ctx *context) diskBusPath(disk string) string {
return UNKNOWN
}
// DiskWWN has been deprecated in 0.2. Please use the Disk.WWN attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskWWN(disk string) string {
msg := `
The DiskWWN() function has been DEPRECATED and will be removed in the 1.0
release of ghw. Please use the Disk.WWN attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskWWN(disk)
}
func (ctx *context) diskWWN(disk string) string {
return UNKNOWN
}
// DiskPartitions has been deprecated in 0.2. Please use the Disk.Partitions attribute.
// TODO(jaypipes): Remove in 1.0.
func DiskPartitions(disk string) []*Partition {
msg := `
The DiskPartitions() function has been DEPRECATED and will be removed in the
1.0 release of ghw. Please use the Disk.Partitions attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.diskPartitions(disk)
}
func (ctx *context) diskPartitions(disk string) []*Partition {
return nil
}
// Disks has been deprecated in 0.2. Please use the BlockInfo.Disks attribute.
// TODO(jaypipes): Remove in 1.0.
func Disks() []*Disk {
msg := `
The Disks() function has been DEPRECATED and will be removed in the
1.0 release of ghw. Please use the BlockInfo.Disks attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.disks()
}
func (ctx *context) disks() []*Disk {
return nil
}
// PartitionSizeBytes has been deprecated in 0.2. Please use the
// Partition.SizeBytes attribute. TODO(jaypipes): Remove in 1.0.
func PartitionSizeBytes(part string) uint64 {
msg := `
The PartitionSizeBytes() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition.SizeBytes attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.partitionSizeBytes(part)
}
func (ctx *context) partitionSizeBytes(part string) uint64 {
return 0
}
// PartitionInfo has been deprecated in 0.2. Please use the Partition struct.
// TODO(jaypipes): Remove in 1.0.
func PartitionInfo(part string) (string, string, bool) {
msg := `
The PartitionInfo() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition struct.
`
warn(msg)
ctx := contextFromEnv()
return ctx.partitionInfo(part)
}
// Given a full or short partition name, returns the mount point, the type of
// the partition and whether it's readonly
func (ctx *context) partitionInfo(part string) (string, string, bool) {
// full name, short name, read-only
return "", "", true
}
// PartitionMountPoint has been deprecated in 0.2. Please use the
// Partition.MountPoint attribute. TODO(jaypipes): Remove in 1.0.
func PartitionMountPoint(part string) string {
msg := `
The PartitionMountPoint() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition.MountPoint attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.partitionMountPoint(part)
}
func (ctx *context) partitionMountPoint(part string) string {
mp, _, _ := ctx.partitionInfo(part)
return mp
}
// PartitionType has been deprecated in 0.2. Please use the
// Partition.Type attribute. TODO(jaypipes): Remove in 1.0.
func PartitionType(part string) string {
msg := `
The PartitionType() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition.Type attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.partitionType(part)
}
func (ctx *context) partitionType(part string) string {
_, pt, _ := ctx.partitionInfo(part)
return pt
}
// PartitionIsReadOnly has been deprecated in 0.2. Please use the
// Partition.IsReadOnly attribute. TODO(jaypipes): Remove in 1.0.
func PartitionIsReadOnly(part string) bool {
msg := `
The PartitionIsReadOnly() function has been DEPRECATED and will be removed in
the 1.0 release of ghw. Please use the Partition.IsReadOnly attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.partitionIsReadOnly(part)
}
func (ctx *context) partitionIsReadOnly(part string) bool {
_, _, ro := ctx.partitionInfo(part)
return ro
}
+232
View File
@@ -0,0 +1,232 @@
// 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 (
"strings"
"github.com/StackExchange/wmi"
)
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 (ctx *context) blockFillInfo(info *BlockInfo) 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),
BusType: toBusType(*diskdrive.InterfaceType),
BusPath: UNKNOWN, // TODO: add information
NUMANodeID: -1,
Vendor: strings.TrimSpace(*diskdrive.Manufacturer),
Model: strings.TrimSpace(*diskdrive.Caption),
SerialNumber: strings.TrimSpace(*diskdrive.SerialNumber),
WWN: 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),
}
disk.Partitions = append(disk.Partitions, p)
break
}
}
}
}
}
disks = append(disks, disk)
}
info.Disks = disks
var tpb uint64
for _, d := range info.Disks {
tpb += d.SizeBytes
}
info.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 toBusType(interfaceType string) BusType {
var busType BusType
switch interfaceType {
case "SCSI":
busType = BUS_TYPE_SCSI
case "IDE":
busType = BUS_TYPE_IDE
default:
busType = BUS_TYPE_UNKNOWN
}
return busType
}
// 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
}
+42
View File
@@ -0,0 +1,42 @@
//
// 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 "strings"
type BusType int
const (
BUS_TYPE_UNKNOWN BusType = iota
BUS_TYPE_IDE
BUS_TYPE_PCI
BUS_TYPE_SCSI
BUS_TYPE_NVME
BUS_TYPE_VIRTIO
)
var (
busTypeString = map[BusType]string{
BUS_TYPE_UNKNOWN: "Unknown",
BUS_TYPE_IDE: "IDE",
BUS_TYPE_PCI: "PCI",
BUS_TYPE_SCSI: "SCSI",
BUS_TYPE_NVME: "NVMe",
BUS_TYPE_VIRTIO: "Virtio",
}
)
func (bt BusType) String() string {
return busTypeString[bt]
}
// 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 (bt BusType) MarshalJSON() ([]byte, error) {
return []byte("\"" + strings.ToLower(bt.String()) + "\""), nil
}
+117
View File
@@ -0,0 +1,117 @@
//
// 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"
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",
}
)
// ChassisInfo defines chassis release information
type ChassisInfo struct {
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 *ChassisInfo) String() string {
vendorStr := ""
if i.Vendor != "" {
vendorStr = " vendor=" + i.Vendor
}
serialStr := ""
if i.SerialNumber != "" && i.SerialNumber != 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
}
// Chassis returns a pointer to a ChassisInfo struct containing information
// about the host's chassis
func Chassis(opts ...*WithOption) (*ChassisInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &ChassisInfo{}
if err := ctx.chassisFillInfo(info); 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 *ChassisInfo `json:"chassis"`
}
// YAMLString returns a string with the chassis information formatted as YAML
// under a top-level "dmi:" key
func (info *ChassisInfo) YAMLString() string {
return safeYAML(chassisPrinter{info})
}
// JSONString returns a string with the chassis information formatted as JSON
// under a top-level "chassis:" key
func (info *ChassisInfo) JSONString(indent bool) string {
return safeJSON(chassisPrinter{info}, indent)
}
+21
View File
@@ -0,0 +1,21 @@
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//
package ghw
func (ctx *context) chassisFillInfo(info *ChassisInfo) error {
info.AssetTag = ctx.dmiItem("chassis_asset_tag")
info.SerialNumber = ctx.dmiItem("chassis_serial")
info.Type = ctx.dmiItem("chassis_type")
typeDesc, found := chassisTypeDescriptions[info.Type]
if !found {
typeDesc = UNKNOWN
}
info.TypeDescription = typeDesc
info.Vendor = ctx.dmiItem("chassis_vendor")
info.Version = ctx.dmiItem("chassis_version")
return nil
}
+17
View File
@@ -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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) chassisFillInfo(info *ChassisInfo) error {
return errors.New("chassisFillInfo not implemented on " + runtime.GOOS)
}
+41
View File
@@ -0,0 +1,41 @@
// 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/StackExchange/wmi"
)
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 (ctx *context) chassisFillInfo(info *ChassisInfo) error {
// Getting data from WMI
var win32ChassisDescriptions []win32Chassis
if err := wmi.Query(wqlChassis, &win32ChassisDescriptions); err != nil {
return err
}
if len(win32ChassisDescriptions) > 0 {
info.AssetTag = *win32ChassisDescriptions[0].Tag
info.SerialNumber = *win32ChassisDescriptions[0].SerialNumber
info.Type = UNKNOWN // TODO:
info.TypeDescription = *win32ChassisDescriptions[0].Model
info.Vendor = *win32ChassisDescriptions[0].Manufacturer
info.Version = *win32ChassisDescriptions[0].Version
}
return nil
}
+171
View File
@@ -0,0 +1,171 @@
//
// 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"
)
// 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 {
// TODO(jaypipes): Deprecated in 0.2, remove in 1.0
Id int `json:"-"`
// 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 {
// TODO(jaypipes): Deprecated in 0.2, remove in 1.0
Id int `json:"-"`
// 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,
)
}
// CPUInfo describes all central processing unit (CPU) functionality on a host.
// Returned by the `ghw.CPU()` function.
type CPUInfo struct {
// 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"`
}
// CPU returns a CPUInfo struct that contains information about the CPUs on the
// host system
func CPU(opts ...*WithOption) (*CPUInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &CPUInfo{}
if err := ctx.cpuFillInfo(info); err != nil {
return nil, err
}
return info, nil
}
// String returns a short string indicating a summary of CPU information
func (i *CPUInfo) 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 *CPUInfo `json:"cpu"`
}
// YAMLString returns a string with the cpu information formatted as YAML
// under a top-level "cpu:" key
func (i *CPUInfo) YAMLString() string {
return safeYAML(cpuPrinter{i})
}
// JSONString returns a string with the cpu information formatted as JSON
// under a top-level "cpu:" key
func (i *CPUInfo) JSONString(indent bool) string {
return safeJSON(cpuPrinter{i}, indent)
}
+224
View File
@@ -0,0 +1,224 @@
// 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 (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
func (ctx *context) cpuFillInfo(info *CPUInfo) error {
info.Processors = ctx.processorsGet()
var totCores uint32
var totThreads uint32
for _, p := range info.Processors {
totCores += p.NumCores
totThreads += p.NumThreads
}
info.TotalCores = totCores
info.TotalThreads = totThreads
return nil
}
// Processors has been DEPRECATED in 0.2 and will be REMOVED in 1.0. Please use
// the CPUInfo.Processors attribute.
// TODO(jaypipes): Remove in 1.0
func Processors() []*Processor {
ctx := contextFromEnv()
return ctx.processorsGet()
}
func (ctx *context) processorsGet() []*Processor {
procs := make([]*Processor, 0)
r, err := os.Open(ctx.pathProcCpuinfo())
if err != nil {
return nil
}
defer 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 (ctx *context) coresForNode(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.
path := filepath.Join(
ctx.pathSysDevicesSystemNode(),
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{
// TODO(jaypipes): Deprecated in 0.2, remove in 1.0
Id: coreID,
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 := safeIntFromFile(coreIDPath)
core := findCoreByID(coreID)
core.LogicalProcessors = append(
core.LogicalProcessors,
procID,
)
}
for _, c := range cores {
c.NumThreads = uint32(len(c.LogicalProcessors))
}
return cores, nil
}
+29
View File
@@ -0,0 +1,29 @@
// +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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) cpuFillInfo(info *CPUInfo) error {
return errors.New("cpuFillInfo not implemented on " + runtime.GOOS)
}
// Processors has been DEPRECATED in 0.2 and will be REMOVED in 1.0. Please use
// the CPUInfo.Processors attribute.
// TODO(jaypipes): Remove in 1.0
func Processors() []*Processor {
return nil
}
// TODO: remove
func (ctx *context) coresForNode(nodeID int) ([]*ProcessorCore, error) {
return nil, errors.New("coresForNode not implemented on " + runtime.GOOS)
}
+55
View File
@@ -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 ghw
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 (ctx *context) cpuFillInfo(info *CPUInfo) error {
// Getting info from WMI
var win32descriptions []win32Processor
if err := wmi.Query(wmqlProcessor, &win32descriptions); err != nil {
return err
}
// Converting into standard structures
info.Processors = ctx.processorsGet(win32descriptions)
var totCores uint32
var totThreads uint32
for _, p := range info.Processors {
totCores += p.NumCores
totThreads += p.NumThreads
}
info.TotalCores = totCores
info.TotalThreads = totThreads
return nil
}
func (ctx *context) processorsGet(win32descriptions []win32Processor) []*Processor {
var procs []*Processor
// Converting into standard structures
for index, description := range win32descriptions {
p := &Processor{
Id: index, // TODO: how to get a decent "Physical ID" to use ?
Model: *description.Name,
Vendor: *description.Manufacturer,
NumCores: description.NumberOfCores,
NumThreads: description.NumberOfLogicalProcessors,
}
procs = append(procs, p)
}
return procs
}
+24
View File
@@ -0,0 +1,24 @@
// 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 (
"io/ioutil"
"path/filepath"
"strings"
)
func (ctx *context) dmiItem(value string) string {
path := filepath.Join(ctx.pathSysClassDMI(), "id", value)
b, err := ioutil.ReadFile(path)
if err != nil {
warn("Unable to read %s: %s\n", value, err)
return UNKNOWN
}
return strings.TrimSpace(string(b))
}
+17
View File
@@ -0,0 +1,17 @@
// +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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) dmiItem(value string) error {
return errors.New("dmiItem not implemented on " + runtime.GOOS)
}
+314
View File
@@ -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
+16
View File
@@ -0,0 +1,16 @@
module github.com/jaypipes/ghw
go 1.12
require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d
github.com/ghodss/yaml v1.0.0
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jaypipes/pcidb v0.5.0
github.com/pkg/errors v0.8.0
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.2 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
howett.net/plist v0.0.0-20181124034731-591f970eefbb
)
+32
View File
@@ -0,0 +1,32 @@
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jaypipes/pcidb v0.5.0 h1:4W5gZ+G7QxydevI8/MmmKdnIPJpURqJ2JNXTzfLxF5c=
github.com/jaypipes/pcidb v0.5.0/go.mod h1:L2RGk04sfRhp5wvHO0gfRAMoLY/F3PKv/nwJeVoho0o=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
+89
View File
@@ -0,0 +1,89 @@
//
// 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"
)
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 *PCIDevice `json:"pci"`
// Topology node that the graphics card is affined to. Will be nil if the
// architecture is not NUMA.
Node *TopologyNode `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 GPUInfo struct {
GraphicsCards []*GraphicsCard `json:"cards"`
}
func GPU(opts ...*WithOption) (*GPUInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &GPUInfo{}
if err := ctx.gpuFillInfo(info); err != nil {
return nil, err
}
return info, nil
}
func (i *GPUInfo) 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 *GPUInfo `json:"gpu"`
}
// YAMLString returns a string with the gpu information formatted as YAML
// under a top-level "gpu:" key
func (i *GPUInfo) YAMLString() string {
return safeYAML(gpuPrinter{i})
}
// JSONString returns a string with the gpu information formatted as JSON
// under a top-level "gpu:" key
func (i *GPUInfo) JSONString(indent bool) string {
return safeJSON(gpuPrinter{i}, indent)
}
+142
View File
@@ -0,0 +1,142 @@
// 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 (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
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 (ctx *context) gpuFillInfo(info *GPUInfo) 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-<INTERFACE_TYPE>-<DISPLAY_ID>. 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.
links, err := ioutil.ReadDir(ctx.pathSysClassDrm())
if err != nil {
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(ctx.pathSysClassDrm(), 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)
}
ctx.gpuFillNUMANodes(cards)
ctx.gpuFillPCIDevice(cards)
info.GraphicsCards = cards
return nil
}
// Loops through each GraphicsCard struct and attempts to fill the DeviceInfo
// attribute with PCI device information
func (ctx *context) gpuFillPCIDevice(cards []*GraphicsCard) {
pci, err := PCI()
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 (ctx *context) gpuFillNUMANodes(cards []*GraphicsCard) {
topo := &TopologyInfo{}
if err := ctx.topologyFillInfo(topo); err != nil {
for _, card := range cards {
if topo.Architecture != 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(
ctx.pathSysClassDrm(),
"card"+cardIndexStr,
"device",
"numa_node",
)
nodeIdx := safeIntFromFile(fpath)
if nodeIdx == -1 {
continue
}
for _, node := range topo.Nodes {
if nodeIdx == int(node.Id) {
card.Node = node
}
}
}
}
+17
View File
@@ -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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) gpuFillInfo(info *GPUInfo) error {
return errors.New("gpuFillInfo not implemented on " + runtime.GOOS)
}
+128
View File
@@ -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 ghw
import (
"strings"
"github.com/StackExchange/wmi"
"github.com/jaypipes/pcidb"
)
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 (ctx *context) gpuFillInfo(info *GPUInfo) 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: ctx.GetDevice(description.PNPDeviceID, win32PnPDescriptions),
}
cards = append(cards, card)
}
info.GraphicsCards = cards
return nil
}
func (ctx *context) GetDevice(id string, entities []win32PnPEntity) *PCIDevice {
// 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 = &PCIDevice{
Address: queryAddress,
Vendor: &pcidb.Vendor{
ID: UNKNOWN,
Name: UNKNOWN,
Products: []*pcidb.Product{},
},
Subsystem: &pcidb.Product{
ID: UNKNOWN,
Name: UNKNOWN,
Subsystems: []*pcidb.Product{},
},
Product: &pcidb.Product{
ID: UNKNOWN,
Name: UNKNOWN,
Subsystems: []*pcidb.Product{},
},
Class: &pcidb.Class{
ID: UNKNOWN,
Name: UNKNOWN,
Subclasses: []*pcidb.Subclass{},
},
Subclass: &pcidb.Subclass{
ID: UNKNOWN,
Name: UNKNOWN,
ProgrammingInterfaces: []*pcidb.ProgrammingInterface{},
},
ProgrammingInterface: &pcidb.ProgrammingInterface{
ID: UNKNOWN,
Name: 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
}
+125
View File
@@ -0,0 +1,125 @@
//
// 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"
const (
UNKNOWN = "unknown"
)
// Concrete merged set of configuration switches that act as an execution
// context when calling internal discovery methods
type context struct {
chroot string
}
// HostInfo is a wrapper struct containing information about the host system's
// memory, block storage, CPU, etc
type HostInfo struct {
Memory *MemoryInfo `json:"memory"`
Block *BlockInfo `json:"block"`
CPU *CPUInfo `json:"cpu"`
Topology *TopologyInfo `json:"topology"`
Network *NetworkInfo `json:"network"`
GPU *GPUInfo `json:"gpu"`
Chassis *ChassisInfo `json:"chassis"`
BIOS *BIOSInfo `json:"bios"`
Baseboard *BaseboardInfo `json:"baseboard"`
Product *ProductInfo `json:"product"`
}
// 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) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
mem := &MemoryInfo{}
if err := ctx.memFillInfo(mem); err != nil {
return nil, err
}
block := &BlockInfo{}
if err := ctx.blockFillInfo(block); err != nil {
return nil, err
}
cpu := &CPUInfo{}
if err := ctx.cpuFillInfo(cpu); err != nil {
return nil, err
}
topology := &TopologyInfo{}
if err := ctx.topologyFillInfo(topology); err != nil {
return nil, err
}
net := &NetworkInfo{}
if err := ctx.netFillInfo(net); err != nil {
return nil, err
}
gpu := &GPUInfo{}
if err := ctx.gpuFillInfo(gpu); err != nil {
return nil, err
}
chassis := &ChassisInfo{}
if err := ctx.chassisFillInfo(chassis); err != nil {
return nil, err
}
bios := &BIOSInfo{}
if err := ctx.biosFillInfo(bios); err != nil {
return nil, err
}
baseboard := &BaseboardInfo{}
if err := ctx.baseboardFillInfo(baseboard); err != nil {
return nil, err
}
product := &ProductInfo{}
if err := ctx.productFillInfo(product); err != nil {
return nil, err
}
return &HostInfo{
CPU: cpu,
Memory: mem,
Block: block,
Topology: topology,
Network: net,
GPU: gpu,
Chassis: chassis,
BIOS: bios,
Baseboard: baseboard,
Product: product,
}, 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",
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(),
)
}
// YAMLString returns a string with the host information formatted as YAML
// under a top-level "host:" key
func (i *HostInfo) YAMLString() string {
return safeYAML(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 safeJSON(i, indent)
}
+46
View File
@@ -0,0 +1,46 @@
//
// 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 (
"encoding/json"
"github.com/ghodss/yaml"
)
// safeYAML returns a string after marshalling the supplied parameter into YAML
func safeYAML(p interface{}) string {
b, err := json.Marshal(p)
if err != nil {
warn("error marshalling JSON: %s", err)
return ""
}
yb, err := yaml.JSONToYAML(b)
if err != nil {
warn("error converting JSON to YAML: %s", err)
return ""
}
return string(yb)
}
// safeJSON returns a string after marshalling the supplied parameter into
// JSON. Accepts an optional argument to trigger pretty/indented formatting of
// the JSON string
func safeJSON(p interface{}, indent bool) string {
var b []byte
var err error
if !indent {
b, err = json.Marshal(p)
} else {
b, err = json.MarshalIndent(&p, "", " ")
}
if err != nil {
warn("error marshalling JSON: %s", err)
return ""
}
return string(b)
}
+76
View File
@@ -0,0 +1,76 @@
//
// 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"
"math"
)
type MemoryModule struct {
Label string `json:"label"`
Location string `json:"location"`
SerialNumber string `json:"serial_number"`
SizeBytes int64 `json:"size_bytes"`
Vendor string `json:"vendor"`
}
type MemoryInfo struct {
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 []*MemoryModule `json:"modules"`
}
func Memory(opts ...*WithOption) (*MemoryInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &MemoryInfo{}
if err := ctx.memFillInfo(info); err != nil {
return nil, err
}
return info, nil
}
func (i *MemoryInfo) String() string {
tpbs := UNKNOWN
if i.TotalPhysicalBytes > 0 {
tpb := i.TotalPhysicalBytes
unit, unitStr := unitWithString(tpb)
tpb = int64(math.Ceil(float64(i.TotalPhysicalBytes) / float64(unit)))
tpbs = fmt.Sprintf("%d%s", tpb, unitStr)
}
tubs := UNKNOWN
if i.TotalUsableBytes > 0 {
tub := i.TotalUsableBytes
unit, unitStr := unitWithString(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 *MemoryInfo `json:"memory"`
}
// YAMLString returns a string with the memory information formatted as YAML
// under a top-level "memory:" key
func (i *MemoryInfo) YAMLString() string {
return safeYAML(memoryPrinter{i})
}
// JSONString returns a string with the memory information formatted as JSON
// under a top-level "memory:" key
func (i *MemoryInfo) JSONString(indent bool) string {
return safeJSON(memoryPrinter{i}, indent)
}
+99
View File
@@ -0,0 +1,99 @@
//
// 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"
"strconv"
"strings"
)
type MemoryCacheType int
const (
MEMORY_CACHE_TYPE_UNIFIED MemoryCacheType = iota
MEMORY_CACHE_TYPE_INSTRUCTION
MEMORY_CACHE_TYPE_DATA
)
var (
memoryCacheTypeString = map[MemoryCacheType]string{
MEMORY_CACHE_TYPE_UNIFIED: "Unified",
MEMORY_CACHE_TYPE_INSTRUCTION: "Instruction",
MEMORY_CACHE_TYPE_DATA: "Data",
}
)
func (a MemoryCacheType) 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 MemoryCacheType) MarshalJSON() ([]byte, error) {
return []byte("\"" + strings.ToLower(a.String()) + "\""), nil
}
type SortByMemoryCacheLevelTypeFirstProcessor []*MemoryCache
func (a SortByMemoryCacheLevelTypeFirstProcessor) Len() int { return len(a) }
func (a SortByMemoryCacheLevelTypeFirstProcessor) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a SortByMemoryCacheLevelTypeFirstProcessor) 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 MemoryCache struct {
Level uint8 `json:"level"`
Type MemoryCacheType `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 *MemoryCache) String() string {
sizeKb := c.SizeBytes / uint64(KB)
typeStr := ""
if c.Type == MEMORY_CACHE_TYPE_INSTRUCTION {
typeStr = "i"
} else if c.Type == MEMORY_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,
)
}
+204
View File
@@ -0,0 +1,204 @@
// 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"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
)
func (ctx *context) cachesForNode(nodeID int) ([]*MemoryCache, 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.
path := filepath.Join(
ctx.pathSysDevicesSystemNode(),
fmt.Sprintf("node%d", nodeID),
)
caches := make(map[string]*MemoryCache)
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); os.IsNotExist(err) {
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 MemoryCache object for each
// unique combination of level, type and processor map
level := ctx.memoryCacheLevel(nodeID, lpID, cacheIndex)
cacheType := ctx.memoryCacheType(nodeID, lpID, cacheIndex)
sharedCpuMap := ctx.memoryCacheSharedCPUMap(nodeID, lpID, cacheIndex)
cacheKey := fmt.Sprintf("%d-%d-%s", level, cacheType, sharedCpuMap)
cache, exists := caches[cacheKey]
if !exists {
size := ctx.memoryCacheSize(nodeID, lpID, level)
cache = &MemoryCache{
Level: uint8(level),
Type: cacheType,
SizeBytes: uint64(size) * uint64(KB),
LogicalProcessors: make([]uint32, 0),
}
caches[cacheKey] = cache
}
cache.LogicalProcessors = append(
cache.LogicalProcessors,
uint32(lpID),
)
}
}
cacheVals := make([]*MemoryCache, 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 (ctx *context) pathNodeCPU(nodeID int, lpID int) string {
return filepath.Join(
ctx.pathSysDevicesSystemNode(),
fmt.Sprintf("node%d", nodeID),
fmt.Sprintf("cpu%d", lpID),
)
}
func (ctx *context) pathNodeCPUCache(nodeID int, lpID int) string {
return filepath.Join(
ctx.pathNodeCPU(nodeID, lpID),
"cache",
)
}
func (ctx *context) pathNodeCPUCacheIndex(nodeID int, lpID int, cacheIndex int) string {
return filepath.Join(
ctx.pathNodeCPUCache(nodeID, lpID),
fmt.Sprintf("index%d", cacheIndex),
)
}
func (ctx *context) memoryCacheLevel(nodeID int, lpID int, cacheIndex int) int {
levelPath := filepath.Join(
ctx.pathNodeCPUCacheIndex(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 (ctx *context) memoryCacheSize(nodeID int, lpID int, cacheIndex int) int {
sizePath := filepath.Join(
ctx.pathNodeCPUCacheIndex(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 (ctx *context) memoryCacheType(nodeID int, lpID int, cacheIndex int) MemoryCacheType {
typePath := filepath.Join(
ctx.pathNodeCPUCacheIndex(nodeID, lpID, cacheIndex),
"type",
)
cacheTypeContents, err := ioutil.ReadFile(typePath)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", err)
return MEMORY_CACHE_TYPE_UNIFIED
}
switch string(cacheTypeContents[:len(cacheTypeContents)-1]) {
case "Data":
return MEMORY_CACHE_TYPE_DATA
case "Instruction":
return MEMORY_CACHE_TYPE_INSTRUCTION
default:
return MEMORY_CACHE_TYPE_UNIFIED
}
}
func (ctx *context) memoryCacheSharedCPUMap(nodeID int, lpID int, cacheIndex int) string {
scpuPath := filepath.Join(
ctx.pathNodeCPUCacheIndex(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])
}
+183
View File
@@ -0,0 +1,183 @@
// 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 (
"bufio"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
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 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 (ctx *context) memFillInfo(info *MemoryInfo) error {
tub := ctx.memTotalUsableBytes()
if tub < 1 {
return fmt.Errorf("Could not determine total usable bytes of memory")
}
info.TotalUsableBytes = tub
tpb := ctx.memTotalPhysicalBytes()
info.TotalPhysicalBytes = tpb
if tpb < 1 {
warn(_WARN_CANNOT_DETERMINE_PHYSICAL_MEMORY)
info.TotalPhysicalBytes = tub
}
info.SupportedPageSizes = ctx.memSupportedPageSizes()
return nil
}
func (ctx *context) memTotalPhysicalBytes() 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 := ctx.pathVarLog()
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 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 (ctx *context) memTotalUsableBytes() 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 := ctx.pathProcMeminfo()
r, err := os.Open(filePath)
if err != nil {
return -1
}
defer 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(KB)
}
return int64(value)
}
return -1
}
func (ctx *context) memSupportedPageSizes() []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 := ctx.pathSysKernelMMHugepages()
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(KB)))
}
return out
}
+17
View File
@@ -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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) memFillInfo(info *MemoryInfo) error {
return errors.New("memFillInfo not implemented on " + runtime.GOOS)
}
+70
View File
@@ -0,0 +1,70 @@
//
// 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/StackExchange/wmi"
)
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 (ctx *context) memFillInfo(info *MemoryInfo) 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
info.Modules = make([]*MemoryModule, 0, len(win32MemDescriptions))
for _, description := range win32MemDescriptions {
totalPhysicalBytes += *description.Capacity
info.Modules = append(info.Modules, &MemoryModule{
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(KB)
}
info.TotalUsableBytes = int64(totalUsableBytes)
info.TotalPhysicalBytes = int64(totalPhysicalBytes)
return nil
}
+79
View File
@@ -0,0 +1,79 @@
//
// 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"
)
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"`
// TODO(jaypipes): Add PCI field for accessing PCI device information
// PCI *PCIDevice `json:"pci"`
}
func (n *NIC) String() string {
isVirtualStr := ""
if n.IsVirtual {
isVirtualStr = " (virtual)"
}
return fmt.Sprintf(
"%s%s",
n.Name,
isVirtualStr,
)
}
type NetworkInfo struct {
NICs []*NIC `json:"nics"`
}
func Network(opts ...*WithOption) (*NetworkInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &NetworkInfo{}
if err := ctx.netFillInfo(info); err != nil {
return nil, err
}
return info, nil
}
func (i *NetworkInfo) 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 *NetworkInfo `json:"network"`
}
// YAMLString returns a string with the net information formatted as YAML
// under a top-level "net:" key
func (i *NetworkInfo) YAMLString() string {
return safeYAML(netPrinter{i})
}
// JSONString returns a string with the net information formatted as JSON
// under a top-level "net:" key
func (i *NetworkInfo) JSONString(indent bool) string {
return safeJSON(netPrinter{i}, indent)
}
+172
View File
@@ -0,0 +1,172 @@
// 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 (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
)
const (
_WARN_ETHTOOL_NOT_INSTALLED = `ethtool not installed. Cannot grab NIC capabilities`
)
func (ctx *context) netFillInfo(info *NetworkInfo) error {
info.NICs = ctx.nics()
return nil
}
// NICS has been deprecated in 0.2. Please use the NetworkInfo.NICs attribute.
// TODO(jaypipes): Remove in 1.0.
func NICs() []*NIC {
msg := `
The NICs() function has been DEPRECATED and will be removed in the 1.0 release
of ghw. Please use the NetworkInfo.NICs attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.nics()
}
func (ctx *context) nics() []*NIC {
nics := make([]*NIC, 0)
files, err := ioutil.ReadDir(ctx.pathSysClassNet())
if err != nil {
return nics
}
etInstalled := ethtoolInstalled()
if !etInstalled {
warn(_WARN_ETHTOOL_NOT_INSTALLED)
}
for _, file := range files {
filename := file.Name()
// Ignore loopback...
if filename == "lo" {
continue
}
netPath := filepath.Join(ctx.pathSysClassNet(), filename)
dest, _ := os.Readlink(netPath)
isVirtual := false
if strings.Contains(dest, "devices/virtual/net") {
isVirtual = true
}
nic := &NIC{
Name: filename,
IsVirtual: isVirtual,
}
mac := ctx.netDeviceMacAddress(filename)
nic.MacAddress = mac
if etInstalled {
nic.Capabilities = ctx.netDeviceCapabilities(filename)
} else {
nic.Capabilities = []*NICCapability{}
}
nics = append(nics, nic)
}
return nics
}
func (ctx *context) netDeviceMacAddress(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(ctx.pathSysClassNet(), dev, "addr_assign_type")
contents, err := ioutil.ReadFile(aatPath)
if err != nil {
return ""
}
if strings.TrimSpace(string(contents)) != "0" {
return ""
}
addrPath := filepath.Join(ctx.pathSysClassNet(), 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 (ctx *context) netDeviceCapabilities(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)
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,
}
}
+33
View File
@@ -0,0 +1,33 @@
// +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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) netFillInfo(info *NetworkInfo) error {
return errors.New("netFillInfo not implemented on " + runtime.GOOS)
}
// NICS has been deprecated in 0.2. Please use the NetworkInfo.NICs attribute.
// TODO(jaypipes): Remove in 1.0.
func NICs() []*NIC {
msg := `
The NICs() function has been DEPRECATED and will be removed in the 1.0 release
of ghw. Please use the NetworkInfo.NICs attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.nics()
}
func (ctx *context) nics() []*NIC {
return nil
}
+65
View File
@@ -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 ghw
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 (ctx *context) netFillInfo(info *NetworkInfo) error {
// Getting info from WMI
var win32NetDescriptions []win32NetworkAdapter
if err := wmi.Query(wqlNetworkAdapter, &win32NetDescriptions); err != nil {
return err
}
info.NICs = ctx.nics(win32NetDescriptions)
return nil
}
func (ctx *context) nics(win32NetDescriptions []win32NetworkAdapter) []*NIC {
// Converting into standard structures
nics := make([]*NIC, 0)
for _, nicDescription := range win32NetDescriptions {
nic := &NIC{
Name: ctx.netDeviceName(nicDescription),
MacAddress: *nicDescription.MACAddress,
IsVirtual: false,
Capabilities: []*NICCapability{},
}
// Appenging NIC to NICs
nics = append(nics, nic)
}
return nics
}
func (ctx *context) netDeviceName(description win32NetworkAdapter) string {
var name string
if strings.TrimSpace(*description.NetConnectionID) != "" {
name = *description.NetConnectionID + " - " + *description.Description
} else {
name = *description.Description
}
return name
}
+59
View File
@@ -0,0 +1,59 @@
package ghw
import "os"
const (
defaultChroot = "/"
envKeyChroot = "GHW_CHROOT"
)
// optDefaultChroot returns the value of the GHW_CHROOT environs variable or
// the default value of "/" if not set
func envOrDefaultChroot() string {
// Grab options from the environs by default
if val, exists := os.LookupEnv(envKeyChroot); exists {
return val
}
return defaultChroot
}
// WithOption is used to represent optionally-configured settings. Each field
// is a pointer to some concrete value so that we can tell when something has
// been set or left unset.
type WithOption struct {
// To facilitate querying of sysfs filesystems that are bind-mounted to a
// non-default root mountpoint, we allow users to set the GHW_CHROOT environ
// vairable to an alternate mountpoint. For instance, assume that the user of
// ghw is a Golang binary being executed from an application container that has
// certain host filesystems bind-mounted into the container at /host. The user
// would ensure the GHW_CHROOT environ variable is set to "/host" and ghw will
// build its paths from that location instead of /
Chroot *string
}
func WithChroot(dir string) *WithOption {
return &WithOption{Chroot: &dir}
}
func mergeOptions(opts ...*WithOption) *WithOption {
merged := &WithOption{}
for _, opt := range opts {
if opt.Chroot != nil {
merged.Chroot = opt.Chroot
}
}
// Set the default value if missing from mergeOpts
if merged.Chroot == nil {
chroot := envOrDefaultChroot()
merged.Chroot = &chroot
}
return merged
}
// contextFromEnv returns a pointer to a context struct that has been populated
// from the environs or default options values
func contextFromEnv() *context {
return &context{
chroot: envOrDefaultChroot(),
}
}
+58
View File
@@ -0,0 +1,58 @@
// 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 (
"path/filepath"
)
func (ctx *context) pathVarLog() string {
return filepath.Join(ctx.chroot, "var", "log")
}
func (ctx *context) pathProcMeminfo() string {
return filepath.Join(ctx.chroot, "proc", "meminfo")
}
func (ctx *context) pathSysKernelMMHugepages() string {
return filepath.Join(ctx.chroot, "sys", "kernel", "mm", "hugepages")
}
func (ctx *context) pathProcCpuinfo() string {
return filepath.Join(ctx.chroot, "proc", "cpuinfo")
}
func (ctx *context) pathEtcMtab() string {
return filepath.Join(ctx.chroot, "etc", "mtab")
}
func (ctx *context) pathSysBlock() string {
return filepath.Join(ctx.chroot, "sys", "block")
}
func (ctx *context) pathSysDevicesSystemNode() string {
return filepath.Join(ctx.chroot, "sys", "devices", "system", "node")
}
func (ctx *context) pathSysBusPciDevices() string {
return filepath.Join(ctx.chroot, "sys", "bus", "pci", "devices")
}
func (ctx *context) pathSysClassDrm() string {
return filepath.Join(ctx.chroot, "sys", "class", "drm")
}
func (ctx *context) pathSysClassDMI() string {
return filepath.Join(ctx.chroot, "sys", "class", "dmi")
}
func (ctx *context) pathSysClassNet() string {
return filepath.Join(ctx.chroot, "sys", "class", "net")
}
func (ctx *context) pathRunUdevData() string {
return filepath.Join(ctx.chroot, "run", "udev", "data")
}
+177
View File
@@ -0,0 +1,177 @@
//
// 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 (
"bytes"
"fmt"
"regexp"
"strings"
"github.com/jaypipes/pcidb"
)
var (
regexPCIAddress *regexp.Regexp = regexp.MustCompile(
`^(([0-9a-f]{0,4}):)?([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`,
)
)
type PCIDevice struct {
// The PCI address of the device
Address string `json:"address"`
Vendor *pcidb.Vendor `json:"vendor"`
Product *pcidb.Product `json:"product"`
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"`
}
// NOTE(jaypipes) PCIDevice 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 (pd *PCIDevice) MarshalJSON() ([]byte, error) {
b := bytes.NewBufferString("{")
b.WriteString(fmt.Sprintf("\"address\":\"%s\"", pd.Address))
b.WriteString(",\"vendor\": {")
b.WriteString(
fmt.Sprintf(
"\"id\":\"%s\",\"name\":\"%s\"",
pd.Vendor.ID,
pd.Vendor.Name,
),
)
b.WriteString("},")
b.WriteString("\"product\": {")
b.WriteString(
fmt.Sprintf(
"\"id\":\"%s\",\"name\":\"%s\"",
pd.Product.ID,
pd.Product.Name,
),
)
b.WriteString("},")
b.WriteString("\"subsystem\": {")
b.WriteString(
fmt.Sprintf(
"\"id\":\"%s\",\"name\":\"%s\"",
pd.Subsystem.ID,
pd.Subsystem.Name,
),
)
b.WriteString("},")
b.WriteString("\"class\": {")
b.WriteString(
fmt.Sprintf(
"\"id\":\"%s\",\"name\":\"%s\"",
pd.Class.ID,
pd.Class.Name,
),
)
b.WriteString("},")
b.WriteString("\"subclass\": {")
b.WriteString(
fmt.Sprintf(
"\"id\":\"%s\",\"name\":\"%s\"",
pd.Subclass.ID,
pd.Subclass.Name,
),
)
b.WriteString("},")
b.WriteString("\"programming_interface\": {")
b.WriteString(
fmt.Sprintf(
"\"id\":\"%s\",\"name\":\"%s\"",
pd.ProgrammingInterface.ID,
pd.ProgrammingInterface.Name,
),
)
b.WriteString("}")
b.WriteString("}")
return b.Bytes(), nil
}
func (di *PCIDevice) String() string {
vendorName := UNKNOWN
if di.Vendor != nil {
vendorName = di.Vendor.Name
}
productName := UNKNOWN
if di.Product != nil {
productName = di.Product.Name
}
className := UNKNOWN
if di.Class != nil {
className = di.Class.Name
}
return fmt.Sprintf(
"%s -> class: '%s' vendor: '%s' product: '%s'",
di.Address,
className,
vendorName,
productName,
)
}
type PCIInfo struct {
ctx *context
// hash of class ID -> class information
Classes map[string]*pcidb.Class
// hash of vendor ID -> vendor information
Vendors map[string]*pcidb.Vendor
// hash of vendor ID + product/device ID -> product information
Products map[string]*pcidb.Product
}
type PCIAddress struct {
Domain string
Bus string
Slot string
Function string
}
// Given a string address, returns a complete PCIAddress struct, filled in with
// domain, bus, slot and function components. The address string may either
// be in $BUS:$SLOT.$FUNCTION (BSF) format or it can be a full PCI address
// that includes the 4-digit $DOMAIN information as well:
// $DOMAIN:$BUS:$SLOT.$FUNCTION.
//
// Returns "" if the address string wasn't a valid PCI address.
func PCIAddressFromString(address string) *PCIAddress {
addrLowered := strings.ToLower(address)
matches := regexPCIAddress.FindStringSubmatch(addrLowered)
if len(matches) == 6 {
dom := "0000"
if matches[1] != "" {
dom = matches[2]
}
return &PCIAddress{
Domain: dom,
Bus: matches[3],
Slot: matches[4],
Function: matches[5],
}
}
return nil
}
func PCI(opts ...*WithOption) (*PCIInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &PCIInfo{}
if err := ctx.pciFillInfo(info); err != nil {
return nil, err
}
return info, nil
}
+293
View File
@@ -0,0 +1,293 @@
// 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"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/jaypipes/pcidb"
)
func (ctx *context) pciFillInfo(info *PCIInfo) error {
db, err := pcidb.New(pcidb.WithChroot(ctx.chroot))
if err != nil {
return err
}
info.Classes = db.Classes
info.Vendors = db.Vendors
info.Products = db.Products
info.ctx = ctx
return nil
}
func (ctx *context) getPCIDeviceModaliasPath(address string) string {
pciAddr := PCIAddressFromString(address)
if pciAddr == nil {
return ""
}
return filepath.Join(
ctx.pathSysBusPciDevices(),
pciAddr.Domain+":"+pciAddr.Bus+":"+pciAddr.Slot+"."+pciAddr.Function,
"modalias",
)
}
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
}
// 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(string(data[9:13]))
productID := strings.ToLower(string(data[18:22]))
subvendorID := strings.ToLower(string(data[28:32]))
subproductID := strings.ToLower(string(data[38:42]))
classID := string(data[44:46])
subclassID := string(data[48:50])
progIfaceID := string(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 *PCIInfo, vendorID string) *pcidb.Vendor {
vendor := info.Vendors[vendorID]
if vendor == nil {
return &pcidb.Vendor{
ID: vendorID,
Name: 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 *PCIInfo,
vendorID string,
productID string,
) *pcidb.Product {
product := info.Products[vendorID+productID]
if product == nil {
return &pcidb.Product{
ID: productID,
Name: 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 *PCIInfo,
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: 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 *PCIInfo, classID string) *pcidb.Class {
class := info.Classes[classID]
if class == nil {
return &pcidb.Class{
ID: classID,
Name: 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 *PCIInfo,
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: 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 *PCIInfo,
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: UNKNOWN,
}
}
// GetDevice returns a pointer to a PCIDevice struct that describes the PCI
// device at the requested address. If no such device could be found, returns
// nil
func (info *PCIInfo) GetDevice(address string) *PCIDevice {
fp := info.ctx.getPCIDeviceModaliasPath(address)
if fp == "" {
return nil
}
modaliasInfo := parseModaliasFile(fp)
if modaliasInfo == nil {
return nil
}
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 &PCIDevice{
Address: address,
Vendor: vendor,
Subsystem: subsystem,
Product: product,
Class: class,
Subclass: subclass,
ProgrammingInterface: progIface,
}
}
// ListDevices returns a list of pointers to PCIDevice structs present on the
// host system
func (info *PCIInfo) ListDevices() []*PCIDevice {
devs := make([]*PCIDevice, 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 *PCIDevice matching the
// address and append to the returned array.
links, err := ioutil.ReadDir(info.ctx.pathSysBusPciDevices())
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: failed to read /sys/bus/pci/devices")
return nil
}
var dev *PCIDevice
for _, link := range links {
addr := link.Name()
dev = info.GetDevice(addr)
if dev == nil {
_, _ = fmt.Fprintf(os.Stderr, "error: failed to get device information for PCI address %s\n", addr)
} else {
devs = append(devs, dev)
}
}
return devs
}
+30
View File
@@ -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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) pciFillInfo(info *PCIInfo) error {
return errors.New("pciFillInfo not implemented on " + runtime.GOOS)
}
// GetDevice returns a pointer to a PCIDevice struct that describes the PCI
// device at the requested address. If no such device could be found, returns
// nil
func (info *PCIInfo) GetDevice(address string) *PCIDevice {
return nil
}
// ListDevices returns a list of pointers to PCIDevice structs present on the
// host system
func (info *PCIInfo) ListDevices() []*PCIDevice {
return nil
}
+95
View File
@@ -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 ghw
import "fmt"
// ProductInfo defines product information
type ProductInfo struct {
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 *ProductInfo) 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 != UNKNOWN {
serialStr = " serial=" + i.SerialNumber
}
uuidStr := ""
if i.UUID != "" && i.UUID != 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
}
// Product returns a pointer to a ProductInfo struct containing information
// about the host's product
func Product(opts ...*WithOption) (*ProductInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &ProductInfo{}
if err := ctx.productFillInfo(info); 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 *ProductInfo `json:"product"`
}
// YAMLString returns a string with the product information formatted as YAML
// under a top-level "dmi:" key
func (info *ProductInfo) YAMLString() string {
return safeYAML(productPrinter{info})
}
// JSONString returns a string with the product information formatted as JSON
// under a top-level "product:" key
func (info *ProductInfo) JSONString(indent bool) string {
return safeJSON(productPrinter{info}, indent)
}
+19
View File
@@ -0,0 +1,19 @@
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//
package ghw
func (ctx *context) productFillInfo(info *ProductInfo) error {
info.Family = ctx.dmiItem("product_family")
info.Name = ctx.dmiItem("product_name")
info.Vendor = ctx.dmiItem("sys_vendor")
info.SerialNumber = ctx.dmiItem("product_serial")
info.UUID = ctx.dmiItem("product_uuid")
info.SKU = ctx.dmiItem("product_sku")
info.Version = ctx.dmiItem("product_version")
return nil
}
+17
View File
@@ -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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) productFillInfo(info *ProductInfo) error {
return errors.New("productFillInfo not implemented on " + runtime.GOOS)
}
+43
View File
@@ -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 ghw
import (
"github.com/StackExchange/wmi"
)
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 (ctx *context) productFillInfo(info *ProductInfo) 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 {
info.Family = UNKNOWN
info.Name = *win32ProductDescriptions[0].Name
info.Vendor = *win32ProductDescriptions[0].Vendor
info.SerialNumber = *win32ProductDescriptions[0].IdentifyingNumber
info.UUID = *win32ProductDescriptions[0].UUID
info.SKU = *win32ProductDescriptions[0].SKUNumber
info.Version = *win32ProductDescriptions[0].Version
}
return nil
}
+89
View File
@@ -0,0 +1,89 @@
//
// 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"
"sort"
)
// TopologyNode 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 TopologyNode structs. In an SMP architecture, a single TopologyNode
// will be available in the TopologyInfo 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 TopologyNode struct {
// TODO(jaypipes): Deprecated in 0.2, remove in 1.0
Id int `json:"-"`
ID int `json:"id"`
Cores []*ProcessorCore `json:"cores"`
Caches []*MemoryCache `json:"caches"`
}
func (n *TopologyNode) String() string {
return fmt.Sprintf(
"node #%d (%d cores)",
n.ID,
len(n.Cores),
)
}
// TopologyInfo describes the system topology for the host hardware
type TopologyInfo struct {
Architecture Architecture `json:"architecture"`
Nodes []*TopologyNode `json:"nodes"`
}
// Topology returns a TopologyInfo struct that describes the system topology of
// the host hardware
func Topology(opts ...*WithOption) (*TopologyInfo, error) {
mergeOpts := mergeOptions(opts...)
ctx := &context{
chroot: *mergeOpts.Chroot,
}
info := &TopologyInfo{}
if err := ctx.topologyFillInfo(info); err != nil {
return nil, err
}
for _, node := range info.Nodes {
sort.Sort(SortByMemoryCacheLevelTypeFirstProcessor(node.Caches))
}
return info, nil
}
func (i *TopologyInfo) 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 *TopologyInfo `json:"topology"`
}
// YAMLString returns a string with the topology information formatted as YAML
// under a top-level "topology:" key
func (i *TopologyInfo) YAMLString() string {
return safeYAML(topologyPrinter{i})
}
// JSONString returns a string with the topology information formatted as JSON
// under a top-level "topology:" key
func (i *TopologyInfo) JSONString(indent bool) string {
return safeJSON(topologyPrinter{i}, indent)
}
+72
View File
@@ -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 ghw
import (
"io/ioutil"
"strconv"
"strings"
)
func (ctx *context) topologyFillInfo(info *TopologyInfo) error {
info.Nodes = ctx.topologyNodes()
if len(info.Nodes) == 1 {
info.Architecture = ARCHITECTURE_SMP
} else {
info.Architecture = ARCHITECTURE_NUMA
}
return nil
}
// TopologyNodes has been deprecated in 0.2. Please use the TopologyInfo.Nodes
// attribute.
// TODO(jaypipes): Remove in 1.0.
func TopologyNodes() ([]*TopologyNode, error) {
msg := `
The TopologyNodes() function has been DEPRECATED and will be removed in the 1.0
release of ghw. Please use the TopologyInfo.Nodes attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.topologyNodes(), nil
}
func (ctx *context) topologyNodes() []*TopologyNode {
nodes := make([]*TopologyNode, 0)
files, err := ioutil.ReadDir(ctx.pathSysDevicesSystemNode())
if err != nil {
warn("failed to determine nodes: %s\n", err)
return nodes
}
for _, file := range files {
filename := file.Name()
if !strings.HasPrefix(filename, "node") {
continue
}
node := &TopologyNode{}
nodeID, err := strconv.Atoi(filename[4:])
if err != nil {
warn("failed to determine node ID: %s\n", err)
return nodes
}
node.ID = nodeID
cores, err := ctx.coresForNode(nodeID)
if err != nil {
warn("failed to determine cores for node: %s\n", err)
return nodes
}
node.Cores = cores
caches, err := ctx.cachesForNode(nodeID)
if err != nil {
warn("failed to determine caches for node: %s\n", err)
return nodes
}
node.Caches = caches
nodes = append(nodes, node)
}
return nodes
}
+34
View File
@@ -0,0 +1,34 @@
// +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 ghw
import (
"runtime"
"github.com/pkg/errors"
)
func (ctx *context) topologyFillInfo(info *TopologyInfo) error {
return errors.New("topologyFillInfo not implemented on " + runtime.GOOS)
}
// TopologyNodes has been deprecated in 0.2. Please use the TopologyInfo.Nodes
// attribute.
// TODO(jaypipes): Remove in 1.0.
func TopologyNodes() ([]*TopologyNode, error) {
msg := `
The TopologyNodes() function has been DEPRECATED and will be removed in the 1.0
release of ghw. Please use the TopologyInfo.Nodes attribute.
`
warn(msg)
ctx := contextFromEnv()
return ctx.topologyNodes()
}
func (ctx *context) topologyNodes() ([]*TopologyNode, error) {
return nil, errors.New("Don't know how to get topology on " + runtime.GOOS)
}
+33
View File
@@ -0,0 +1,33 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//
package ghw
var (
KB int64 = 1024
MB = KB * 1024
GB = MB * 1024
TB = GB * 1024
PB = TB * 1024
EB = PB * 1024
)
func unitWithString(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"
}
}
+58
View File
@@ -0,0 +1,58 @@
//
// 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"
"io/ioutil"
"os"
"strconv"
"strings"
)
const (
disableWarningsEnv = "GHW_DISABLE_WARNINGS"
)
type closer interface {
Close() error
}
func safeClose(c closer) {
err := c.Close()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to close: %s", err)
}
}
func warn(msg string, args ...interface{}) {
if _, ok := os.LookupEnv(disableWarningsEnv); ok {
return
}
_, _ = fmt.Fprint(os.Stderr, "WARNING: ")
_, _ = fmt.Fprintf(os.Stderr, msg, args...)
}
// Reads a supplied filepath and converts the contents to an integer. Returns
// -1 if there were file permissions or existence errors or if the contents
// could not be successfully converted to an integer. In any error, a warning
// message is printed to STDERR and -1 is returned.
func safeIntFromFile(path string) int {
msg := "failed to read int from file: %s\n"
buf, err := ioutil.ReadFile(path)
if err != nil {
warn(msg, err)
return -1
}
contents := strings.TrimSpace(string(buf))
res, err := strconv.Atoi(contents)
if err != nil {
warn(msg, err)
return -1
}
return res
}
+3
View File
@@ -0,0 +1,3 @@
#! /bin/sh
export GO_PACKAGES=$(go list ./... | grep -v /vendor/)
+2
View File
@@ -0,0 +1,2 @@
vendor/
coverage*.*
+48
View File
@@ -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
+176
View File
@@ -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.
+17
View File
@@ -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
+34
View File
@@ -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"
+201
View File
@@ -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.
+46
View File
@@ -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
+413
View File
@@ -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
```
+71
View File
@@ -0,0 +1,71 @@
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
disableNetworkFetch bool
searchPaths []string
}
func contextFromOptions(merged *WithOption) *context {
ctx := &context{
chroot: *merged.Chroot,
cacheOnly: *merged.CacheOnly,
cachePath: getCachePath(),
disableNetworkFetch: *merged.DisableNetworkFetch,
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() {
// 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"),
)
}
}
+86
View File
@@ -0,0 +1,86 @@
//
// 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"
)
const (
PCIIDS_URI = "https://pci-ids.ucw.cz/v2.2/pci.ids.gz"
)
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()
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)
response, err := http.Get(PCIIDS_URI)
if err != nil {
return err
}
defer response.Body.Close()
f, err := os.Create(cacheFilePath)
if err != nil {
return err
}
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
}
+5
View File
@@ -0,0 +1,5 @@
module github.com/jaypipes/pcidb
go 1.11
require github.com/mitchellh/go-homedir v1.0.0
+2
View File
@@ -0,0 +1,2 @@
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+181
View File
@@ -0,0 +1,181 @@
//
// 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
}
func WithChroot(dir string) *WithOption {
return &WithOption{Chroot: &dir}
}
func WithCacheOnly() *WithOption {
return &WithOption{CacheOnly: &trueVar}
}
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
}
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
}
}
// 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
}
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
}
+163
View File
@@ -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
}