pax_global_header00006660000000000000000000000064141413607730014520gustar00rootroot0000000000000052 comment=71fb6da4301f9c6e42883514e0463330e833b05a golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/000077500000000000000000000000001414136077300231525ustar00rootroot00000000000000golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/.travis.yml000066400000000000000000000006131414136077300252630ustar00rootroot00000000000000language: go go: - 1.x os: - linux sudo: required before_install: - go get github.com/golang/lint/golint - go get honnef.co/go/tools/cmd/staticcheck - go get -d ./... script: - ./scripts/licensecheck.sh - go build -tags=gofuzz ./... - go vet ./... - staticcheck ./... - golint -set_exit_status - go test -v -race ./... - go test -c ./smbios - sudo ./smbios.test -test.vgolang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/AUTHORS000066400000000000000000000002701414136077300242210ustar00rootroot00000000000000Maintainer ---------- DigitalOcean, Inc Original Authors ---------------- Matt Layher Contributors ------------ Christopher Dudley golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/CONTRIBUTING.md000066400000000000000000000022601414136077300254030ustar00rootroot00000000000000Contributing ============ The `go-smbios` project makes use of the [GitHub Flow](https://guides.github.com/introduction/flow/) for contributions. If you'd like to contribute to the project, please [open an issue](https://github.com/digitalocean/go-smbios/issues/new) or find an [existing issue](https://github.com/digitalocean/go-smbios/issues) that you'd like to take on. This ensures that efforts are not duplicated, and that a new feature aligns with the focus of the rest of the repository. Once your suggestion has been submitted and discussed, please be sure that your code meets the following criteria: - code is completely `gofmt`'d - new features or codepaths have appropriate test coverage - `go test ./...` passes - `go vet ./...` passes - `staticcheck ./...` passes - `golint ./...` returns no warnings, including documentation comment warnings In addition, if this is your first time contributing to the `go-smbios` project, add your name and email address to the [AUTHORS](https://github.com/digitalocean/go-smbios/blob/master/AUTHORS) file under the "Contributors" section using the format: `First Last `. Finally, submit a pull request for review!golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/LICENSE.md000066400000000000000000000243611414136077300245640ustar00rootroot00000000000000Apache License ============== _Version 2.0, January 2004_ _<>_ ### 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. golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/README.md000066400000000000000000000056411414136077300244370ustar00rootroot00000000000000go-smbios [![Build Status](https://travis-ci.org/digitalocean/go-smbios.svg?branch=master)](https://travis-ci.org/digitalocean/go-smbios) [![GoDoc](https://godoc.org/github.com/digitalocean/go-smbios/smbios?status.svg)](https://godoc.org/github.com/digitalocean/go-smbios/smbios) [![Go Report Card](https://goreportcard.com/badge/github.com/digitalocean/go-smbios)](https://goreportcard.com/report/github.com/digitalocean/go-smbios) ========= Package `smbios` provides detection and access to System Management BIOS (SMBIOS) and Desktop Management Interface (DMI) data and structures. Apache 2.0 Licensed. Introduction ------------ [SMBIOS](https://en.wikipedia.org/wiki/System_Management_BIOS) is a standard mechanism for fetching BIOS and hardware information from within an operating system. It shares some similarities with the older DMI standard, and the two are often confused. To install this package, run: ``` $ go get github.com/digitalocean/go-smbios/smbios ``` This package is based on the [SMBIOS 3.1.1 specification](https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf), but should work with SMBIOS 2.0 and above. In general, it's up to hardware manufacturers to properly populate SMBIOS data, and many structures may not be populated correctly or at all; especially on consumer hardware. The `smbios.Structure` types created by this package can be decoded using the structure information in the SMBIOS specification. In the future, some common structures may be parsed and readily available by using this package. Supported operating systems and their SMBIOS retrieval mechanisms include: - DragonFlyBSD (/dev/mem) - FreeBSD (/dev/mem) - Linux (sysfs and /dev/mem) - NetBSD (/dev/mem) - OpenBSD (/dev/mem) - Solaris (/dev/mem) - Windows (GetSystemFirmwareTable) At this time, macOS is not supported, as it does not expose SMBIOS information in the same way as the supported operating systems. Pull requests are welcome to add support for additional operating systems. Example ------- See `cmd/lssmbios` for a runnable example. Note that retrieving SMBIOS information is a privileged operation. On Linux, you may invoke the binary as root directly, or apply the `CAP_DAC_OVERRIDE` capability to enable reading the information without superuser access. Here's the gist of it: ```go // Find SMBIOS data in operating system-specific location. rc, ep, err := smbios.Stream() if err != nil { log.Fatalf("failed to open stream: %v", err) } // Be sure to close the stream! defer rc.Close() // Decode SMBIOS structures from the stream. d := smbios.NewDecoder(rc) ss, err := d.Decode() if err != nil { log.Fatalf("failed to decode structures: %v", err) } // Determine SMBIOS version and table location from entry point. major, minor, rev := ep.Version() addr, size := ep.Table() fmt.Printf("SMBIOS %d.%d.%d - table: address: %#x, size: %d\n", major, minor, rev, addr, size) for _, s := range ss { fmt.Println(s) } ``` golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/cmd/000077500000000000000000000000001414136077300237155ustar00rootroot00000000000000golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/cmd/lsdimms/000077500000000000000000000000001414136077300253655ustar00rootroot00000000000000golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/cmd/lsdimms/main.go000066400000000000000000000045661414136077300266530ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. // Command lsdimms lists memory DIMM information from SMBIOS. package main import ( "encoding/binary" "fmt" "log" "github.com/digitalocean/go-smbios/smbios" ) func main() { // Find SMBIOS data in operating system-specific location. rc, ep, err := smbios.Stream() if err != nil { log.Fatalf("failed to open stream: %v", err) } // Be sure to close the stream! defer rc.Close() // Decode SMBIOS structures from the stream. d := smbios.NewDecoder(rc) ss, err := d.Decode() if err != nil { log.Fatalf("failed to decode structures: %v", err) } major, minor, rev := ep.Version() fmt.Printf("SMBIOS %d.%d.%d\n", major, minor, rev) for _, s := range ss { // Only look at memory devices. if s.Header.Type != 17 { continue } // Code based on: https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf. // TODO: this should go in a new package in go-smbios for parsing specific structures. // Only parse the DIMM size. dimmSize := int(binary.LittleEndian.Uint16(s.Formatted[8:10])) if dimmSize == 0 { fmt.Printf("[% 3s] empty\n", s.Strings[0]) continue } //If the DIMM size is 32GB or greater, we need to parse the extended field. // Spec says 0x7fff in regular size field means we should parse the extended. if dimmSize == 0x7fff { dimmSize = int(binary.LittleEndian.Uint32(s.Formatted[24:28])) } // The granularity in which the value is specified // depends on the setting of the most-significant bit (bit // 15). If the bit is 0, the value is specified in megabyte // units; if the bit is 1, the value is specified in kilobyte // units. // // Little endian MSB for uint16 is in second byte. unit := "KB" if s.Formatted[9]&0x80 == 0 { unit = "MB" } fmt.Printf("[% 3s] DIMM: %d %s\n", s.Strings[0], dimmSize, unit) } } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/cmd/lssmbios/000077500000000000000000000000001414136077300255505ustar00rootroot00000000000000golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/cmd/lssmbios/main.go000066400000000000000000000025731414136077300270320ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. // Command lssmbios accesses and displays SMBIOS data. package main import ( "fmt" "log" "github.com/digitalocean/go-smbios/smbios" ) func main() { // Find SMBIOS data in operating system-specific location. rc, ep, err := smbios.Stream() if err != nil { log.Fatalf("failed to open stream: %v", err) } // Be sure to close the stream! defer rc.Close() // Decode SMBIOS structures from the stream. d := smbios.NewDecoder(rc) ss, err := d.Decode() if err != nil { log.Fatalf("failed to decode structures: %v", err) } // Determine SMBIOS version and table location from entry point. major, minor, rev := ep.Version() addr, size := ep.Table() fmt.Printf("SMBIOS %d.%d.%d - table: address: %#x, size: %d\n", major, minor, rev, addr, size) for _, s := range ss { fmt.Println(s) } } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/scripts/000077500000000000000000000000001414136077300246415ustar00rootroot00000000000000golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/scripts/license.txt000066400000000000000000000011211414136077300270170ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/scripts/licensecheck.sh000077500000000000000000000005561414136077300276260ustar00rootroot00000000000000#!/bin/bash # Verify that the correct license block is present in all Go source # files. EXPECTED=$(cat ./scripts/license.txt) # Scan each Go source file for license. EXIT=0 GOFILES=$(find . -name "*.go") for FILE in $GOFILES; do BLOCK=$(head -n 14 $FILE) if [ "$BLOCK" != "$EXPECTED" ]; then echo "file missing license: $FILE" EXIT=1 fi done exit $EXIT golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/000077500000000000000000000000001414136077300244465ustar00rootroot00000000000000golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/decoder.go000066400000000000000000000126661414136077300264150ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios import ( "bufio" "bytes" "encoding/binary" "io" ) const ( // headerLen is the length of the Header structure. headerLen = 4 // typeEndOfTable indicates the end of a stream of Structures. typeEndOfTable = 127 ) var ( // Byte slices used to help parsing string-sets. null = []byte{0x00} endStringSet = []byte{0x00, 0x00} ) // A Decoder decodes Structures from a stream. type Decoder struct { br *bufio.Reader b []byte } // Stream locates and opens a stream of SMBIOS data and the SMBIOS entry // point from an operating system-specific location. The stream must be // closed after decoding to free its resources. // // If no suitable location is found, an error is returned. func Stream() (io.ReadCloser, EntryPoint, error) { rc, ep, err := stream() if err != nil { return nil, nil, err } // The io.ReadCloser from stream could be any one of a number of types // depending on the source of the SMBIOS stream information. // // To prevent the caller from potentially tampering with something dangerous // like mmap'd memory by using a type assertion, we make the io.ReadCloser // into an opaque and unexported type to prevent type assertion. return &opaqueReadCloser{rc: rc}, ep, nil } // NewDecoder creates a Decoder which decodes Structures from the input stream. func NewDecoder(r io.Reader) *Decoder { return &Decoder{ br: bufio.NewReader(r), b: make([]byte, 1024), } } // Decode decodes Structures from the Decoder's stream until an End-of-table // structure is found. func (d *Decoder) Decode() ([]*Structure, error) { var ss []*Structure for { s, err := d.next() if err != nil { return nil, err } // End-of-table structure indicates end of stream. ss = append(ss, s) if s.Header.Type == typeEndOfTable { break } } return ss, nil } // next decodes the next Structure from the stream. func (d *Decoder) next() (*Structure, error) { h, err := d.parseHeader() if err != nil { return nil, err } // Length of formatted section is length specified by header, minus // the length of the header itself. l := int(h.Length) - headerLen fb, err := d.parseFormatted(l) if err != nil { return nil, err } ss, err := d.parseStrings() if err != nil { return nil, err } return &Structure{ Header: *h, Formatted: fb, Strings: ss, }, nil } // parseHeader parses a Structure's Header from the stream. func (d *Decoder) parseHeader() (*Header, error) { if _, err := io.ReadFull(d.br, d.b[:headerLen]); err != nil { return nil, err } return &Header{ Type: d.b[0], Length: d.b[1], Handle: binary.LittleEndian.Uint16(d.b[2:4]), }, nil } // parseFormatted parses a Structure's formatted data from the stream. func (d *Decoder) parseFormatted(l int) ([]byte, error) { // Guard against malformed input length. if l < 0 { return nil, io.ErrUnexpectedEOF } if l == 0 { // No formatted data. return nil, nil } if _, err := io.ReadFull(d.br, d.b[:l]); err != nil { return nil, err } // Make a copy to free up the internal buffer. fb := make([]byte, len(d.b[:l])) copy(fb, d.b[:l]) return fb, nil } // parseStrings parses a Structure's strings from the stream, if they // are present. func (d *Decoder) parseStrings() ([]string, error) { term, err := d.br.Peek(2) if err != nil { return nil, err } // If no string-set present, discard delimeter and end parsing. if bytes.Equal(term, endStringSet) { if _, err := d.br.Discard(2); err != nil { return nil, err } return nil, nil } var ss []string for { s, more, err := d.parseString() if err != nil { return nil, err } // When final string is received, end parse loop. ss = append(ss, s) if !more { break } } return ss, nil } // parseString parses a single string from the stream, and returns if // any more strings are present. func (d *Decoder) parseString() (str string, more bool, err error) { // We initially read bytes because it's more efficient to manipulate bytes // and allocate a string once we're all done. // // Strings are null-terminated. raw, err := d.br.ReadBytes(0x00) if err != nil { return "", false, err } b := bytes.TrimRight(raw, "\x00") peek, err := d.br.Peek(1) if err != nil { return "", false, err } if !bytes.Equal(peek, null) { // Next byte isn't null; more strings to come. return string(b), true, nil } // If two null bytes appear in a row, end of string-set. // Discard the null and indicate no more strings. if _, err := d.br.Discard(1); err != nil { return "", false, err } return string(b), false, nil } var _ io.ReadCloser = &opaqueReadCloser{} // An opaqueReadCloser masks the type of the underlying io.ReadCloser to // prevent type assertions. type opaqueReadCloser struct { rc io.ReadCloser } func (rc *opaqueReadCloser) Read(b []byte) (int, error) { return rc.rc.Read(b) } func (rc *opaqueReadCloser) Close() error { return rc.rc.Close() } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/decoder_test.go000066400000000000000000000073431414136077300274500ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios_test import ( "bytes" "testing" "github.com/digitalocean/go-smbios/smbios" "github.com/google/go-cmp/cmp" ) func TestDecoder(t *testing.T) { tests := []struct { name string b []byte ss []*smbios.Structure ok bool }{ { name: "short header", b: []byte{0x00}, }, { name: "length too short", b: []byte{0x00, 0x00, 0x00, 0x00}, }, { name: "length too long", b: []byte{0x00, 0xff, 0x00, 0x00}, }, { name: "string not terminated", b: []byte{ 0x01, 0x04, 0x01, 0x00, 'a', 'b', 'c', 'd', }, }, { name: "no end of table", b: []byte{ 0x01, 0x04, 0x01, 0x00, 0x00, 0x00, }, }, { name: "bad second message", b: []byte{ 0x01, 0x0c, 0x02, 0x00, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 'd', 'e', 'a', 'd', 'b', 'e', 'e', 'f', 0x00, 0x00, 0xff, }, }, { name: "OK, one, no format, no strings", b: []byte{ 127, 0x04, 0x01, 0x00, 0x00, 0x00, }, ss: []*smbios.Structure{{ Header: smbios.Header{ Type: 127, Length: 4, Handle: 1, }, }}, ok: true, }, { name: "OK, one, format, no strings", b: []byte{ 127, 0x06, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, }, ss: []*smbios.Structure{{ Header: smbios.Header{ Type: 127, Length: 6, Handle: 1, }, Formatted: []byte{0x01, 0x02}, }}, ok: true, }, { name: "OK, one, format, strings", b: []byte{ 127, 0x06, 0x01, 0x00, 0x01, 0x02, 'a', 'b', 'c', 'd', 0x00, '1', '2', '3', '4', 0x00, 0x00, }, ss: []*smbios.Structure{{ Header: smbios.Header{ Type: 127, Length: 6, Handle: 1, }, Formatted: []byte{0x01, 0x02}, Strings: []string{"abcd", "1234"}, }}, ok: true, }, { name: "OK, multiple", b: []byte{ 0x00, 0x05, 0x01, 0x00, 0xff, 0x00, 0x00, 0x01, 0x0c, 0x02, 0x00, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 'd', 'e', 'a', 'd', 'b', 'e', 'e', 'f', 0x00, 0x00, 127, 0x06, 0x03, 0x00, 0x01, 0x02, 'a', 'b', 'c', 'd', 0x00, '1', '2', '3', '4', 0x00, 0x00, }, ss: []*smbios.Structure{ { Header: smbios.Header{ Type: 0, Length: 5, Handle: 1, }, Formatted: []byte{0xff}, }, { Header: smbios.Header{ Type: 1, Length: 12, Handle: 2, }, Formatted: []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}, Strings: []string{"deadbeef"}, }, { Header: smbios.Header{ Type: 127, Length: 6, Handle: 3, }, Formatted: []byte{0x01, 0x02}, Strings: []string{"abcd", "1234"}, }, }, ok: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := smbios.NewDecoder(bytes.NewReader(tt.b)) ss, err := d.Decode() if tt.ok && err != nil { t.Fatalf("unexpected error: %v", err) } if !tt.ok && err == nil { t.Fatalf("expected an error, but none occurred: %v", err) } if diff := cmp.Diff(tt.ss, ss); diff != "" { t.Fatalf("unexpected structures (-want +got):\n%s", diff) } }) } } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/doc.go000066400000000000000000000013631414136077300255450ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. // Package smbios provides detection and access to System Management BIOS (SMBIOS) // and Desktop Management Interface (DMI) data and structures. package smbios golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/entrypoint.go000066400000000000000000000174601414136077300272200ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios import ( "bytes" "encoding/binary" "fmt" "io" "io/ioutil" ) // Anchor strings used to detect entry points. var ( // Used when searching for an entry point in memory. magicPrefix = []byte("_SM") // Used to determine specific entry point types. magic32 = []byte("_SM_") magic64 = []byte("_SM3_") magicDMI = []byte("_DMI_") ) // An EntryPoint is an SMBIOS entry point. EntryPoints contain various // properties about SMBIOS. // // Use a type assertion to access detailed EntryPoint information. type EntryPoint interface { // Table returns the memory address and maximum size of the SMBIOS table. Table() (address, size int) // Version returns the system's SMBIOS version. Version() (major, minor, revision int) } // ParseEntryPoint parses an EntryPoint from the input stream. func ParseEntryPoint(r io.Reader) (EntryPoint, error) { // Prevent unbounded reads since this structure should be small. b, err := ioutil.ReadAll(io.LimitReader(r, 64)) if err != nil { return nil, err } if l := len(b); l < 4 { return nil, fmt.Errorf("too few bytes for SMBIOS entry point magic: %d", l) } switch { case bytes.HasPrefix(b, magic32): return parse32(b) case bytes.HasPrefix(b, magic64): return parse64(b) } return nil, fmt.Errorf("unrecognized SMBIOS entry point magic: %v", b[0:4]) } var _ EntryPoint = &EntryPoint32Bit{} // EntryPoint32Bit is the SMBIOS 32-bit Entry Point structure, used starting // in SMBIOS 2.1. type EntryPoint32Bit struct { Anchor string Checksum uint8 Length uint8 Major uint8 Minor uint8 MaxStructureSize uint16 EntryPointRevision uint8 FormattedArea [5]byte IntermediateAnchor string IntermediateChecksum uint8 StructureTableLength uint16 StructureTableAddress uint32 NumberStructures uint16 BCDRevision uint8 } // Table implements EntryPoint. func (e *EntryPoint32Bit) Table() (address, size int) { return int(e.StructureTableAddress), int(e.StructureTableLength) } // Version implements EntryPoint. func (e *EntryPoint32Bit) Version() (major, minor, revision int) { return int(e.Major), int(e.Minor), 0 } // parse32 parses an EntryPoint32Bit from b. func parse32(b []byte) (*EntryPoint32Bit, error) { l := len(b) // Correct minimum length as of SMBIOS 3.1.1. const expLen = 31 if l < expLen { return nil, fmt.Errorf("expected SMBIOS 32-bit entry point minimum length of at least %d, but got: %d", expLen, l) } // Allow more data in the buffer than the actual length, for when the // entry point is being read from system memory. length := b[5] if l < int(length) { return nil, fmt.Errorf("expected SMBIOS 32-bit entry point actual length of at least %d, but got: %d", length, l) } // Look for intermediate anchor with DMI magic. iAnchor := b[16:21] if !bytes.Equal(iAnchor, magicDMI) { return nil, fmt.Errorf("incorrect DMI magic in SMBIOS 32-bit entry point: %v", iAnchor) } // Entry point checksum occurs at index 4, compute and verify it. const epChkIndex = 4 epChk := b[epChkIndex] if err := checksum(epChk, epChkIndex, b[:length]); err != nil { return nil, err } // Since we already computed the checksum for the outer entry point, // no real need to compute it for the intermediate entry point. ep := &EntryPoint32Bit{ Anchor: string(b[0:4]), Checksum: epChk, Length: length, Major: b[6], Minor: b[7], MaxStructureSize: binary.LittleEndian.Uint16(b[8:10]), EntryPointRevision: b[10], IntermediateAnchor: string(iAnchor), IntermediateChecksum: b[21], StructureTableLength: binary.LittleEndian.Uint16(b[22:24]), StructureTableAddress: binary.LittleEndian.Uint32(b[24:28]), NumberStructures: binary.LittleEndian.Uint16(b[28:30]), BCDRevision: b[30], } copy(ep.FormattedArea[:], b[10:15]) return ep, nil } var _ EntryPoint = &EntryPoint64Bit{} // EntryPoint64Bit is the SMBIOS 64-bit Entry Point structure, used starting // in SMBIOS 3.0. type EntryPoint64Bit struct { Anchor string Checksum uint8 Length uint8 Major uint8 Minor uint8 Revision uint8 EntryPointRevision uint8 Reserved uint8 StructureTableMaxSize uint32 StructureTableAddress uint64 } // Table implements EntryPoint. func (e *EntryPoint64Bit) Table() (address, size int) { return int(e.StructureTableAddress), int(e.StructureTableMaxSize) } // Version implements EntryPoint. func (e *EntryPoint64Bit) Version() (major, minor, revision int) { return int(e.Major), int(e.Minor), int(e.Revision) } const ( // expLen64 is the expected minimum length of a 64-bit entry point. // Correct minimum length as of SMBIOS 3.1.1. expLen64 = 24 // chkIndex64 is the index of the checksum byte in a 64-bit entry point. chkIndex64 = 5 ) // parse64 parses an EntryPoint64Bit from b. func parse64(b []byte) (*EntryPoint64Bit, error) { l := len(b) // Ensure expected minimum length. if l < expLen64 { return nil, fmt.Errorf("expected SMBIOS 64-bit entry point minimum length of at least %d, but got: %d", expLen64, l) } // Allow more data in the buffer than the actual length, for when the // entry point is being read from system memory. length := b[6] if l < int(length) { return nil, fmt.Errorf("expected SMBIOS 64-bit entry point actual length of at least %d, but got: %d", length, l) } // Checksum occurs at index 5, compute and verify it. chk := b[chkIndex64] if err := checksum(chk, chkIndex64, b); err != nil { return nil, err } return &EntryPoint64Bit{ Anchor: string(b[0:5]), Checksum: chk, Length: length, Major: b[7], Minor: b[8], Revision: b[9], EntryPointRevision: b[10], Reserved: b[11], StructureTableMaxSize: binary.LittleEndian.Uint32(b[12:16]), StructureTableAddress: binary.LittleEndian.Uint64(b[16:24]), }, nil } // checksum computes the checksum of b using the starting value of start, and // skipping the checksum byte which occurs at index chkIndex. // // checksum assumes that b has already had its bounds checked. func checksum(start uint8, chkIndex int, b []byte) error { chk := start for i := range b { // Checksum computation does not include index of checksum byte. if i == chkIndex { continue } chk += b[i] } if chk != 0 { return fmt.Errorf("invalid entry point checksum %#02x from initial checksum %#02x", chk, start) } return nil } // WindowsEntryPoint contains SMBIOS Table entry point data returned from // GetSystemFirmwareTable. As raw access to the underlying memory is not given, // the full breadth of information is not available. type WindowsEntryPoint struct { Size uint32 MajorVersion byte MinorVersion byte Revision byte } // Table implements EntryPoint. The returned address will always be 0, as it // is not returned by GetSystemFirmwareTable. func (e *WindowsEntryPoint) Table() (address, size int) { return 0, int(e.Size) } // Version implements EntryPoint. func (e *WindowsEntryPoint) Version() (major, minor, revision int) { return int(e.MajorVersion), int(e.MinorVersion), int(e.Revision) } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/entrypoint_test.go000066400000000000000000000146571414136077300302640ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios_test import ( "bytes" "testing" "github.com/digitalocean/go-smbios/smbios" "github.com/google/go-cmp/cmp" ) func TestParseEntryPoint(t *testing.T) { tests := []struct { name string b []byte ep smbios.EntryPoint major, minor, revision int addr, size int ok bool }{ { name: "short magic", b: []byte{0x00}, }, { name: "unknown magic", b: []byte{0xff, 0xff, 0xff, 0xff}, }, { name: "32, short entry point", b: []byte{ '_', 'S', 'M', '_', }, }, { name: "32, bad length", b: []byte{ '_', 'S', 'M', '_', 0x00, 0xff, // 255 length 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, '_', 'F', 'O', 'O', '_', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, }, { name: "32, bad intermediate anchor", b: []byte{ '_', 'S', 'M', '_', 0x00, 31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, '_', 'F', 'O', 'O', '_', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, }, { name: "32, bad checksum", b: []byte{ '_', 'S', 'M', '_', 0x00, // 0 checksum 31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, '_', 'D', 'M', 'I', '_', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, }, { name: "32, OK", b: []byte{ '_', 'S', 'M', '_', 0xa4, 0x1f, 0x2, 0x8, 0xd4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, '_', 'D', 'M', 'I', '_', 0x95, 0x5f, 0xf, 0x0, 0x90, 0xf0, 0x7a, 0x43, 0x0, 0x28, }, ep: &smbios.EntryPoint32Bit{ Anchor: "_SM_", Checksum: 0xa4, Length: 0x1f, Major: 0x02, Minor: 0x08, MaxStructureSize: 0x01d4, IntermediateAnchor: "_DMI_", IntermediateChecksum: 0x95, StructureTableLength: 0x0f5f, StructureTableAddress: 0x7af09000, NumberStructures: 0x43, BCDRevision: 0x28, }, major: 2, minor: 8, revision: 0, addr: 0x7af09000, size: 0x0f5f, ok: true, }, { name: "32, OK, trailing data", b: []byte{ '_', 'S', 'M', '_', 0xa4, 0x20, 0x2, 0x8, 0xd4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, '_', 'D', 'M', 'I', '_', 0x95, 0x5f, 0xf, 0x0, 0x90, 0xf0, 0x7a, 0x43, 0x0, 0x28, 0xff, }, ep: &smbios.EntryPoint32Bit{ Anchor: "_SM_", Checksum: 0xa4, Length: 0x20, Major: 0x02, Minor: 0x08, MaxStructureSize: 0x01d4, IntermediateAnchor: "_DMI_", IntermediateChecksum: 0x95, StructureTableLength: 0x0f5f, StructureTableAddress: 0x7af09000, NumberStructures: 0x43, BCDRevision: 0x28, }, major: 2, minor: 8, revision: 0, addr: 0x7af09000, size: 0x0f5f, ok: true, }, { name: "64, short entry point", b: []byte{ '_', 'S', 'M', '3', '_', }, }, { name: "64, bad length", b: []byte{ '_', 'S', 'M', '3', '_', 0x00, 0xff, // 255 length 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, }, { name: "64, bad checksum", b: []byte{ '_', 'S', 'M', '3', '_', 0x00, // 0 checksum 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, }, { name: "64, OK", b: []byte{ '_', 'S', 'M', '3', '_', 0x86, 0x18, 0x3, 0x0, 0x0, 0x1, 0x0, 0x53, 0x9, 0x0, 0x0, 0xb0, 0xb3, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, }, ep: &smbios.EntryPoint64Bit{ Anchor: "_SM3_", Checksum: 0x86, Length: 0x18, Major: 0x03, EntryPointRevision: 0x01, StructureTableMaxSize: 0x0953, StructureTableAddress: 0x0eb3b0, }, major: 3, minor: 0, revision: 0, addr: 0x0eb3b0, size: 0x0953, ok: true, }, { name: "64, OK, trailing data", b: []byte{ '_', 'S', 'M', '3', '_', 0x86, 0x19, 0x3, 0x0, 0x0, 0x1, 0x0, 0x53, 0x9, 0x0, 0x0, 0xb0, 0xb3, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, }, ep: &smbios.EntryPoint64Bit{ Anchor: "_SM3_", Checksum: 0x86, Length: 0x19, Major: 0x03, EntryPointRevision: 0x01, StructureTableMaxSize: 0x0953, StructureTableAddress: 0x0eb3b0, }, major: 3, minor: 0, revision: 0, addr: 0x0eb3b0, size: 0x0953, ok: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ep, err := smbios.ParseEntryPoint(bytes.NewReader(tt.b)) if tt.ok && err != nil { t.Fatalf("unexpected error: %v", err) } if !tt.ok && err == nil { t.Fatalf("expected an error, but none occurred: %v", err) } if !tt.ok { // Don't bother doing comparison if entry point is invalid. t.Logf("OK error: %v", err) return } if diff := cmp.Diff(tt.ep, ep); diff != "" { t.Fatalf("unexpected entry point (-want +got):\n%s", diff) } major, minor, revision := ep.Version() wantVersion := []int{tt.major, tt.minor, tt.revision} gotVersion := []int{major, minor, revision} if diff := cmp.Diff(wantVersion, gotVersion); diff != "" { t.Fatalf("unexpected SMBIOS version (-want +got):\n%s", diff) } addr, size := ep.Table() wantTable := []int{tt.addr, tt.size} gotTable := []int{addr, size} if diff := cmp.Diff(wantTable, gotTable); diff != "" { t.Fatalf("unexpected SMBIOS table info (-want +got):\n%s", diff) } }) } } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/fuzz.go000066400000000000000000000015211414136077300257720ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. //+build gofuzz package smbios import ( "bytes" ) func Fuzz(data []byte) int { return fuzzDecoder(data) } func fuzzDecoder(data []byte) int { d := NewDecoder(bytes.NewReader(data)) if _, err := d.Decode(); err != nil { return 0 } return 1 } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/stream_integration_test.go000066400000000000000000000037101414136077300317330ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios_test import ( "os" "runtime" "testing" "github.com/digitalocean/go-smbios/smbios" ) func TestStreamIntegration(t *testing.T) { if goos := runtime.GOOS; goos != "linux" { t.Skipf("skipping on non-Linux platform: %q", goos) } rc, ep, err := smbios.Stream() if err != nil { if os.IsPermission(err) { t.Skipf("skipping, permission denied while reading SMBIOS stream: %v", err) } return } defer rc.Close() d := smbios.NewDecoder(rc) ss, err := d.Decode() if err != nil { t.Fatalf("failed to decode structures: %v", err) } major, minor, rev := ep.Version() addr, size := ep.Table() // Assume SMBIOS version 2+, assume non-zero table address and size. if major < 2 { t.Fatalf("unexpected major version: %d", major) } if addr == 0 { t.Fatal("expected non-zero table address") } if size == 0 { t.Fatal("expected non-zero table size") } // Show some info in the test output. t.Logf("SMBIOS %d.%d.%d - table: address: %#x, size: %d\n", major, minor, rev, addr, size) // Assume we find BIOS and end of table types. var foundBIOS, foundEOT bool for _, s := range ss { switch s.Header.Type { case 0: foundBIOS = true t.Logf("BIOS: %#v", s) case 127: foundEOT = true t.Logf(" EOT: %#v", s) } } if !foundBIOS { t.Fatal("did not find BIOS information") } if !foundEOT { t.Fatal("did not find end of table") } } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/stream_linux.go000066400000000000000000000033001414136077300275030ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. //+build linux package smbios import ( "io" "os" ) const ( // sysfs locations for SMBIOS information. sysfsDMI = "/sys/firmware/dmi/tables/DMI" sysfsEntryPoint = "/sys/firmware/dmi/tables/smbios_entry_point" ) // stream opens the SMBIOS entry point and an SMBIOS structure stream. func stream() (io.ReadCloser, EntryPoint, error) { // First, check for the sysfs location present in modern kernels. _, err := os.Stat(sysfsEntryPoint) switch { case err == nil: return sysfsStream(sysfsEntryPoint, sysfsDMI) case os.IsNotExist(err): // Fall back to the standard UNIX-like system method. return devMemStream() default: return nil, nil, err } } // sysfsStream reads the SMBIOS entry point and structure stream from // two files; usually the modern sysfs locations. func sysfsStream(entryPoint, dmi string) (io.ReadCloser, EntryPoint, error) { epf, err := os.Open(entryPoint) if err != nil { return nil, nil, err } defer epf.Close() ep, err := ParseEntryPoint(epf) if err != nil { return nil, nil, err } sf, err := os.Open(dmi) if err != nil { return nil, nil, err } return sf, ep, nil } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/stream_memory.go000066400000000000000000000066371414136077300276740ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios import ( "bytes" "errors" "io" "io/ioutil" "os" ) const ( // devMem is the UNIX-like system memory device location used to // find SMBIOS information. devMem = "/dev/mem" // SMBIOS specification indicates that the entry point should exist // between these two memory addresses. startAddr = 0x000f0000 endAddr = 0x000fffff ) // memoryStream reads the SMBIOS entry point and structure stream from // an io.ReadSeeker (usually system memory). // // memoryStream is an entry point for tests. func memoryStream(rs io.ReadSeeker, startAddr, endAddr int) (io.ReadCloser, EntryPoint, error) { // Try to find the entry point. addr, err := findEntryPoint(rs, startAddr, endAddr) if err != nil { return nil, nil, err } // Found it; seek to the location of the entry point. if _, err := rs.Seek(int64(addr), io.SeekStart); err != nil { return nil, nil, err } // Read the entry point and determine where the SMBIOS table is. ep, err := ParseEntryPoint(rs) if err != nil { return nil, nil, err } // Seek to the start of the SMBIOS table. tableAddr, tableSize := ep.Table() if _, err := rs.Seek(int64(tableAddr), io.SeekStart); err != nil { return nil, nil, err } // Make a copy of the memory so we don't return a handle to system memory // to the caller. out := make([]byte, tableSize) if _, err := io.ReadFull(rs, out); err != nil { return nil, nil, err } return ioutil.NopCloser(bytes.NewReader(out)), ep, nil } // findEntryPoint attempts to locate the entry point structure in the io.ReadSeeker // using the start and end bound as hints for its location. func findEntryPoint(rs io.ReadSeeker, start, end int) (int, error) { // Begin searching at the start bound. if _, err := rs.Seek(int64(start), io.SeekStart); err != nil { return 0, err } // Iterate one "paragraph" of memory at a time until we either find the entry point // or reach the end bound. const paragraph = 16 b := make([]byte, paragraph) var ( addr int found bool ) for addr = start; addr < end; addr += paragraph { if _, err := io.ReadFull(rs, b); err != nil { return 0, err } // Both the 32-bit and 64-bit entry point have a similar prefix. if bytes.HasPrefix(b, magicPrefix) { found = true break } } if !found { return 0, errors.New("no SMBIOS entry point found in memory") } // Return the exact memory location of the entry point. return addr, nil } // devMemStream reads the SMBIOS entry point and structure stream from // the UNIX-like system /dev/mem device. // // This is UNIX-like system specific, but since it doesn't employ any system // calls or OS-dependent constants, it remains in this file for simplicity. func devMemStream() (io.ReadCloser, EntryPoint, error) { mem, err := os.Open(devMem) if err != nil { return nil, nil, err } defer mem.Close() return memoryStream(mem, startAddr, endAddr) } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/stream_memory_test.go000066400000000000000000000105221414136077300307170ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios import ( "bytes" "encoding/binary" "fmt" "math" "testing" "github.com/google/go-cmp/cmp" ) func Test_memoryStream(t *testing.T) { tests := []struct { name string b []byte ss []*Structure ok bool }{ { name: "empty", b: nil, }, { name: "magic before first paragraph", b: makeMemory( []byte{'_', 'S', 'M', '_'}, nil, nil, ), }, { name: "magic after last paragraph", b: makeMemory( nil, nil, []byte{'_', 'S', 'M', '_'}, ), }, { name: "64, OK", b: func() []byte { // Just enough information to point to an address // that contains the structure stream. const addr = 0x00f0 epb := mustMarshalEntryPoint(&EntryPoint64Bit{ StructureTableMaxSize: 512, StructureTableAddress: addr, }) // Place entry point in searchable range. b := makeMemory( nil, epb, nil, ) // Structure stream, placed starting at the address // specified in entry point. stream := []byte{ 0x00, 0x05, 0x01, 0x00, 0xff, 0x00, 0x00, 0x01, 0x0c, 0x02, 0x00, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 'd', 'e', 'a', 'd', 'b', 'e', 'e', 'f', 0x00, 0x00, 127, 0x06, 0x03, 0x00, 0x01, 0x02, 'a', 'b', 'c', 'd', 0x00, '1', '2', '3', '4', 0x00, 0x00, } copy(b[addr:], stream) return b }(), ss: []*Structure{ { Header: Header{ Type: 0, Length: 5, Handle: 1, }, Formatted: []byte{0xff}, }, { Header: Header{ Type: 1, Length: 12, Handle: 2, }, Formatted: []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}, Strings: []string{"deadbeef"}, }, { Header: Header{ Type: 127, Length: 6, Handle: 3, }, Formatted: []byte{0x01, 0x02}, Strings: []string{"abcd", "1234"}, }, }, ok: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rs := bytes.NewReader(tt.b) rc, _, err := memoryStream(rs, start, end) if tt.ok && err != nil { t.Fatalf("unexpected error: %v", err) } if !tt.ok && err == nil { t.Fatalf("expected an error, but none occurred: %v", err) } if !tt.ok { // Don't bother doing comparison if entry point is invalid. t.Logf("OK error: %v", err) return } defer rc.Close() ss, err := NewDecoder(rc).Decode() if err != nil { t.Fatalf("failed to decode structures: %v", err) } if diff := cmp.Diff(tt.ss, ss); diff != "" { t.Fatalf("unexpected structures (-want +got):\n%s", diff) } }) } } // Memory addresses used to start and stop searching for entry points. const ( start = 0x0010 end = 0xfff0 ) func makeMemory(before, in, after []byte) []byte { b := make([]byte, math.MaxUint16) copy(b[0x0000:start], before) copy(b[start:0xfff0], in) copy(b[end:0xffff], after) return b } func mustMarshalEntryPoint(ep EntryPoint) []byte { switch x := ep.(type) { case *EntryPoint64Bit: return marshal64(x) default: // TODO(mdlayher): expand with 32-bit entry point. panic(fmt.Sprintf("entry point marshaling not implemented for %T", ep)) } } func marshal64(ep *EntryPoint64Bit) []byte { b := make([]byte, expLen64) copy(b[0:5], magic64) b[6] = expLen64 b[7] = ep.Major b[8] = ep.Minor b[9] = ep.Revision b[10] = ep.EntryPointRevision b[11] = ep.Reserved binary.LittleEndian.PutUint32(b[12:16], ep.StructureTableMaxSize) binary.LittleEndian.PutUint64(b[16:24], ep.StructureTableAddress) var chk uint8 for i := range b { // Explicitly skip the checksum byte for computation. if i == chkIndex64 { continue } chk += b[i] } // Produce the correct checksum for the entry point. b[5] = uint8(256 - int(chk)) return b } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/stream_others.go000066400000000000000000000016251414136077300276600ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. //+build !dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows package smbios import ( "fmt" "io" "runtime" ) // stream is not implemented for unsupported platforms. func stream() (io.ReadCloser, EntryPoint, error) { return nil, nil, fmt.Errorf("opening SMBIOS stream not implemented on %q", runtime.GOOS) } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/stream_unix.go000066400000000000000000000017661414136077300273450ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. //+build dragonfly freebsd netbsd openbsd solaris // Linux intentionally omitted because it has an alternative method that // is used before attempting /dev/mem access. See stream_linux.go. package smbios import ( "io" ) // stream opens the SMBIOS entry point and an SMBIOS structure stream. func stream() (io.ReadCloser, EntryPoint, error) { // Use the standard UNIX-like system method. return devMemStream() } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/stream_windows.go000066400000000000000000000126221414136077300300450ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios import ( "bytes" "encoding/binary" "errors" "fmt" "io" "io/ioutil" "syscall" "unsafe" ) // firmwareTableProviderSigRSMB is the identifier for the raw SMBIOS firmware table // provider. // It is equal to the ASCII characters 'RSMB' packed into a uint32. // In the C++ example code in the MSDN documentation, this is specified using // multi-byte character literals, which are automatically coerced to an integer by // the C++ compiler. const firmwareTableProviderSigRSMB uint32 = 0x52534d42 // smbiosDataHeaderSize is size of the "header" (non-variable) part of the // RawSMBIOSData struct. This serves as both the offset to the actual // SMBIOS table data, and the minimum possible size of a valid RawSMBIOSDATA // struct (with a table length of 0). const rawSMBIOSDataHeaderSize = 8 var ( libKernel32 = syscall.NewLazyDLL("kernel32.dll") // MSDN Documentation for GetSystemFirmwareTable: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724379(v=vs.85).aspx procGetSystemFirmwareTable = libKernel32.NewProc("GetSystemFirmwareTable") ) // nativeEndian returns the native byte order of this system. func nativeEndian() binary.ByteOrder { // Determine endianness by interpreting a uint16 as a byte slice. v := uint16(1) b := *(*[2]byte)(unsafe.Pointer(&v)) if b[0] == 1 { return binary.LittleEndian } return binary.BigEndian } // windowsStream parses the data returned from GetSystemFirmwareTable('RSMB',...) // and returns a stream and entrypoint that can be used to decode the system's // SMBIOS table. // // When calling GetSystemFirmwareTable with FirmwareTableProviderSignature = "RSMB", // Windows will write a RawSMBIOSData struct into the output buffer. // Thus, `buf` is expected to contain a valid RawSMBIOSData structure. // // From windows.h: // // struct RawSMBIOSData { // BYTE Used20CallingMethod; // BYTE SMBIOSMajorVersion; // BYTE SMBIOSMinorVersion; // BYTE DMIRevision; // DWORD Length; // BYTE SMBIOSTableData[]; // } // // Note: a DWORD is equivalent to a uint32 // See: https://msdn.microsoft.com/en-us/library/cc230318.aspx func windowsStream(buf []byte) (io.ReadCloser, EntryPoint, error) { bufLen := uint32(len(buf)) // Do an additional check to make sure the actual amount written is sane. if bufLen < rawSMBIOSDataHeaderSize { return nil, nil, fmt.Errorf("GetSystemFirmwareTable wrote less data than expected: wrote %d bytes, expected at least 8 bytes", bufLen) } tableSize := nativeEndian().Uint32(buf[4:8]) if rawSMBIOSDataHeaderSize+tableSize > bufLen { return nil, nil, errors.New("reported SMBIOS table size exceeds buffer") } entryPoint := &WindowsEntryPoint{ MajorVersion: buf[1], MinorVersion: buf[2], Revision: buf[3], Size: tableSize, } tableBuff := buf[rawSMBIOSDataHeaderSize : rawSMBIOSDataHeaderSize+tableSize] return ioutil.NopCloser(bytes.NewReader(tableBuff)), entryPoint, nil } func stream() (io.ReadCloser, EntryPoint, error) { // Call first with empty buffer to get size. r1, _, err := procGetSystemFirmwareTable.Call( uintptr(firmwareTableProviderSigRSMB), // FirmwareTableProviderSignature = 'RSMB' 0, // FirmwareTableID = 0 0, // pFirmwareTableBuffer = NULL 0, // BufferSize = 0 ) // LazyProc.Call will always return err != nil, so we need to check the primary // return value (r1) to determine whether or not an error occurred. // In this case, r1 should contain the size of the needed buffer, so it will only // be 0 if the function call failed for some reason. // // Godoc for LazyProc.Call: // https://golang.org/pkg/syscall/?GOOS=windows&GOARCH=amd64#LazyProc.Call if r1 == 0 { return nil, nil, fmt.Errorf("failed to determine size of buffer needed: %v", err) } if r1 < rawSMBIOSDataHeaderSize { return nil, nil, fmt.Errorf("reported buffer size smaller than expected: reported %d, expected >= 8", r1) } bufferSize := uint32(r1) buffer := make([]byte, bufferSize) r1, _, err = procGetSystemFirmwareTable.Call( uintptr(firmwareTableProviderSigRSMB), // FirmwareTableProviderSignature = 'RSMB' 0, // FirmwareTableID = 0 uintptr(unsafe.Pointer(&buffer[0])), // pFirmwareTableBuffer = &buffer uintptr(bufferSize), // BufferSize = bufferSize ) bytesWritten := uint32(r1) // Check for the two possible failure cases documented in API: if bytesWritten > bufferSize { return nil, nil, fmt.Errorf("buffer size was too small, somehow: have %d bytes, Windows wanted %d bytes", bufferSize, bytesWritten) } if bytesWritten == 0 { return nil, nil, fmt.Errorf("failed to read SMBIOS data: %v", err) } // At this point, bytesWritten <= bufferSize, which means the call succeeded as // per the MSDN documentation. return windowsStream(buffer[:bytesWritten]) } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/stream_windows_test.go000066400000000000000000000105421414136077300311030ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios import ( "bytes" "encoding/hex" "io/ioutil" "testing" ) // makeRawSMBIOSData creates a buffer with a valid RawSMBIOSData struct with the // given version and stream information. func makeRawSMBIOSData(major, minor, revision byte, stream []byte) []byte { buffer := make([]byte, rawSMBIOSDataHeaderSize+len(stream)) buffer[0] = 0 buffer[1] = major buffer[2] = minor buffer[3] = revision nativeEndian().PutUint32(buffer[4:8], uint32(len(stream))) copy(buffer[8:], stream) return buffer } func Test_windowsStream(t *testing.T) { const major = byte(2) const minor = byte(4) const revision = byte(1) // Note: buffer will be automatically created from the stream if it is not // explicitly set to a non-nil value. This prevents having to duplicate the // stream data in the struct definitions below for large test cases. // // Unlike in Test_memoryStream, we're not worrying about the actual decoding // of the structures here. All we care about is whether or not windowsStream // gives us back the stream data we expect. Whether or not that is valid can // be tested separately. tests := []struct { name string buffer []byte stream []byte ok bool }{ { name: "empty buffer", buffer: []byte{}, // purposefully not nil }, { name: "short buffer", buffer: []byte{0, 1, 2, 3, 4, 5, 6}, // only 7 bytes }, { name: "valid header, empty table", stream: nil, ok: true, }, { name: "length too large", buffer: func() []byte { buf := []byte{ 0, 2, 4, 1, // version 0, 0, 0, 0, // length placeholder 1, 2, 3, 4, // stream } nativeEndian().PutUint32(buf[4:8], 5) return buf }(), }, { name: "valid header and stream", stream: []byte{ 0x00, 0x05, 0x01, 0x00, 0xff, 0x00, 0x00, 0x01, 0x0c, 0x02, 0x00, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 'd', 'e', 'a', 'd', 'b', 'e', 'e', 'f', 0x00, 0x00, 127, 0x06, 0x03, 0x00, 0x01, 0x02, 'a', 'b', 'c', 'd', 0x00, '1', '2', '3', '4', 0x00, 0x00, }, ok: true, }, { name: "buffer larger than needed", buffer: func() []byte { buf := makeRawSMBIOSData(major, minor, revision, []byte{1, 2, 3, 4}) buf = append(buf, 5, 6, 7, 8) return buf }(), stream: []byte{1, 2, 3, 4}, ok: true, }, } for _, tt := range tests { // Make buffer if not set explicitly if tt.buffer == nil { tt.buffer = makeRawSMBIOSData(major, minor, revision, tt.stream) } t.Run(tt.name, func(t *testing.T) { rc, ep, err := windowsStream(tt.buffer) if tt.ok && err != nil { t.Fatalf("unexpected error: %v", err) } if !tt.ok && err == nil { t.Fatalf("expected an error, but none occurred: %v", err) } if !tt.ok { // Don't bother doing comparison if entry point is invalid t.Logf("OK error: %v", err) return } defer rc.Close() _, streamSize := ep.Table() if streamSize != len(tt.stream) { t.Fatalf("bad stream size: got %d, wanted %d", streamSize, len(tt.stream)) } maj, min, rev := ep.Version() if maj != int(major) { t.Fatalf("bad major version: got %d, wanted %d", maj, major) } if min != int(minor) { t.Fatalf("bad minor version: got %d, wanted %d", min, minor) } if rev != int(revision) { t.Fatalf("bad revision: got %d, wanted %d", rev, revision) } streamData, err := ioutil.ReadAll(rc) if err != nil { t.Fatalf("failed to read stream: %v", err) } if len(streamData) != len(tt.stream) { t.Fatalf("bad stream data: got %d bytes, wanted %d", len(streamData), len(tt.stream)) } if bytes.Compare(tt.stream, streamData) != 0 { t.Fatalf( "stream data different:\nwant: %s\ngot : %s", hex.EncodeToString(tt.stream), hex.EncodeToString(streamData), ) } }) } } golang-github-digitalocean-go-smbios-0.0~git20180907.390a4f4/smbios/structure.go000066400000000000000000000015031414136077300270340ustar00rootroot00000000000000// Copyright 2017-2018 DigitalOcean. // // 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. package smbios // A Header is a Structure's header. type Header struct { Type uint8 Length uint8 Handle uint16 } // A Structure is an SMBIOS structure. type Structure struct { Header Header Formatted []byte Strings []string }