Skip to content

Commit

Permalink
Fix non-linux builds
Browse files Browse the repository at this point in the history
Adding build tags for linux and added stubs for other operating systems. This should ensure that code using it will always compile, but only work on linux.

I am unsure if FreeBSD and friends are expected to work, but for now it is "linux or nothing".
  • Loading branch information
klauspost authored and anatol committed Feb 18, 2022
1 parent 11da420 commit 71e2e3c
Show file tree
Hide file tree
Showing 17 changed files with 617 additions and 498 deletions.
File renamed without changes.
19 changes: 19 additions & 0 deletions examples/other_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// go:build !linux
//go:build !linux
// +build !linux

package test

import (
"testing"

"github.com/anatol/smart.go"
"github.com/stretchr/testify/require"
)

func TestOpen(t *testing.T) {
path := "/dev/nvme0n1"

_, err := smart.Open(path)
require.ErrorIs(t, err, smart.ErrOSUnsupported)
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (
)

func TestWithQemu(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip()
}
t.Parallel()

wd, err := os.Getwd()
Expand Down
File renamed without changes.
128 changes: 0 additions & 128 deletions nvme.go
Original file line number Diff line number Diff line change
@@ -1,43 +1,5 @@
package smart

import (
"bytes"
"encoding/binary"
"fmt"
"unsafe"

"golang.org/x/sys/unix"
)

// https:// nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2.0b-2021.12.18-Ratified.pdf
// https:// nvmexpress.org/wp-content/uploads/NVM-Express-NVM-Command-Set-Specification-1.0b-2021.12.18-Ratified.pdf

// include/uapi/linux/nvme_ioctl.h

var nvmeIoctlAdmin64Cmd = iowr('N', 0x47, unsafe.Sizeof(nvmePassthruCmd64{}))

type nvmePassthruCmd64 struct {
opcode uint8
flags uint8
_ uint16
nsid uint32
cdw2 uint32
cdw3 uint32
metadata uint64
addr uint64
metadataLen uint32
dataLen uint32
cdw10 uint32
cdw11 uint32
cdw12 uint32
cdw13 uint32
cdw14 uint32
cdw15 uint32
timeoutMs uint32
_ uint32
result uint64
}

// Uint128 is a stopgap until https:// github.com/golang/go/issues/9455 is implemented
type Uint128 struct {
// uint128 is represented as pair of uint64. Val[0] represents lower part of the uint128 value.
Expand Down Expand Up @@ -1233,93 +1195,3 @@ type NVMeDevice struct {
func (d *NVMeDevice) Type() string {
return "nvme"
}

func OpenNVMe(name string) (*NVMeDevice, error) {
fd, err := unix.Open(name, unix.O_RDWR, 0600)
if err != nil {
return nil, err
}

dev := NVMeDevice{
fd: fd,
}
return &dev, nil
}

func (d *NVMeDevice) Close() error {
return unix.Close(d.fd)
}

func (d *NVMeDevice) Identify() (*NvmeIdentController, []NvmeIdentNamespace, error) {
buf := make([]byte, 4096)
if err := nvmeReadIdentify(d.fd, 0, 1, buf); err != nil {
return nil, nil, err
}
var controller NvmeIdentController
if err := binary.Read(bytes.NewBuffer(buf), binary.LittleEndian, &controller); err != nil {
return nil, nil, err
}

var ns []NvmeIdentNamespace
// QEMU has 256 namespaces for some reason, TODO: clarify
for i := 0; i < int(controller.Nn); i++ {
buf2 := make([]byte, 4096)
var n NvmeIdentNamespace
if err := nvmeReadIdentify(d.fd, uint32(i+1), 0, buf2); err != nil {
return nil, nil, err
}
if err := binary.Read(bytes.NewBuffer(buf2), binary.LittleEndian, &n); err != nil {
return nil, nil, err
}
if n.Nsze == 0 {
continue
}

ns = append(ns, n)
}

return &controller, ns, nil
}

func (d *NVMeDevice) ReadSMART() (*NvmeSMARTLog, error) {
buf3 := make([]byte, 512)
if err := nvmeReadLogPage(d.fd, nvmeLogSmartInformation, buf3); err != nil {
return nil, err
}
var sl NvmeSMARTLog
if err := binary.Read(bytes.NewBuffer(buf3), binary.LittleEndian, &sl); err != nil {
return nil, err
}

return &sl, nil
}

func nvmeReadLogPage(fd int, logID uint8, buf []byte) error {
bufLen := len(buf)

if (bufLen < 4) || (bufLen > 0x4000) || (bufLen%4 != 0) {
return fmt.Errorf("invalid buffer size")
}

cmd := nvmePassthruCmd64{
opcode: nvmeAdminGetLogPage,
nsid: 0xffffffff, // controller-level SMART info
addr: uint64(uintptr(unsafe.Pointer(&buf[0]))),
dataLen: uint32(bufLen),
cdw10: uint32(logID) | (((uint32(bufLen) / 4) - 1) << 16),
}

return ioctl(uintptr(fd), nvmeIoctlAdmin64Cmd, uintptr(unsafe.Pointer(&cmd)))
}

func nvmeReadIdentify(fd int, nsid, cns uint32, data []byte) error {
cmd := nvmePassthruCmd64{
opcode: nvmeAdminIdentify,
nsid: nsid,
addr: uint64(uintptr(unsafe.Pointer(&data[0]))),
dataLen: uint32(len(data)),
cdw10: cns,
}

return ioctl(uintptr(fd), nvmeIoctlAdmin64Cmd, uintptr(unsafe.Pointer(&cmd)))
}
129 changes: 129 additions & 0 deletions nvme_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package smart

import (
"bytes"
"encoding/binary"
"fmt"
"unsafe"

"golang.org/x/sys/unix"
)

// https:// nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-2.0b-2021.12.18-Ratified.pdf
// https:// nvmexpress.org/wp-content/uploads/NVM-Express-NVM-Command-Set-Specification-1.0b-2021.12.18-Ratified.pdf

// include/uapi/linux/nvme_ioctl.h

var nvmeIoctlAdmin64Cmd = iowr('N', 0x47, unsafe.Sizeof(nvmePassthruCmd64{}))

type nvmePassthruCmd64 struct {
opcode uint8
flags uint8
_ uint16
nsid uint32
cdw2 uint32
cdw3 uint32
metadata uint64
addr uint64
metadataLen uint32
dataLen uint32
cdw10 uint32
cdw11 uint32
cdw12 uint32
cdw13 uint32
cdw14 uint32
cdw15 uint32
timeoutMs uint32
_ uint32
result uint64
}

func OpenNVMe(name string) (*NVMeDevice, error) {
fd, err := unix.Open(name, unix.O_RDWR, 0600)
if err != nil {
return nil, err
}

dev := NVMeDevice{
fd: fd,
}
return &dev, nil
}

func (d *NVMeDevice) Close() error {
return unix.Close(d.fd)
}

func (d *NVMeDevice) Identify() (*NvmeIdentController, []NvmeIdentNamespace, error) {
buf := make([]byte, 4096)
if err := nvmeReadIdentify(d.fd, 0, 1, buf); err != nil {
return nil, nil, err
}
var controller NvmeIdentController
if err := binary.Read(bytes.NewBuffer(buf), binary.LittleEndian, &controller); err != nil {
return nil, nil, err
}

var ns []NvmeIdentNamespace
// QEMU has 256 namespaces for some reason, TODO: clarify
for i := 0; i < int(controller.Nn); i++ {
buf2 := make([]byte, 4096)
var n NvmeIdentNamespace
if err := nvmeReadIdentify(d.fd, uint32(i+1), 0, buf2); err != nil {
return nil, nil, err
}
if err := binary.Read(bytes.NewBuffer(buf2), binary.LittleEndian, &n); err != nil {
return nil, nil, err
}
if n.Nsze == 0 {
continue
}

ns = append(ns, n)
}

return &controller, ns, nil
}

func (d *NVMeDevice) ReadSMART() (*NvmeSMARTLog, error) {
buf3 := make([]byte, 512)
if err := nvmeReadLogPage(d.fd, nvmeLogSmartInformation, buf3); err != nil {
return nil, err
}
var sl NvmeSMARTLog
if err := binary.Read(bytes.NewBuffer(buf3), binary.LittleEndian, &sl); err != nil {
return nil, err
}

return &sl, nil
}

func nvmeReadLogPage(fd int, logID uint8, buf []byte) error {
bufLen := len(buf)

if (bufLen < 4) || (bufLen > 0x4000) || (bufLen%4 != 0) {
return fmt.Errorf("invalid buffer size")
}

cmd := nvmePassthruCmd64{
opcode: nvmeAdminGetLogPage,
nsid: 0xffffffff, // controller-level SMART info
addr: uint64(uintptr(unsafe.Pointer(&buf[0]))),
dataLen: uint32(bufLen),
cdw10: uint32(logID) | (((uint32(bufLen) / 4) - 1) << 16),
}

return ioctl(uintptr(fd), nvmeIoctlAdmin64Cmd, uintptr(unsafe.Pointer(&cmd)))
}

func nvmeReadIdentify(fd int, nsid, cns uint32, data []byte) error {
cmd := nvmePassthruCmd64{
opcode: nvmeAdminIdentify,
nsid: nsid,
addr: uint64(uintptr(unsafe.Pointer(&data[0]))),
dataLen: uint32(len(data)),
cdw10: cns,
}

return ioctl(uintptr(fd), nvmeIoctlAdmin64Cmd, uintptr(unsafe.Pointer(&cmd)))
}
21 changes: 21 additions & 0 deletions nvme_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// go:build !linux
//go:build !linux
// +build !linux

package smart

func OpenNVMe(name string) (*NVMeDevice, error) {
return nil, ErrOSUnsupported
}

func (d *NVMeDevice) Close() error {
return ErrOSUnsupported
}

func (d *NVMeDevice) Identify() (*NvmeIdentController, []NvmeIdentNamespace, error) {
return nil, nil, ErrOSUnsupported
}

func (d *NVMeDevice) ReadSMART() (*NvmeSMARTLog, error) {
return nil, ErrOSUnsupported
}
Loading

0 comments on commit 71e2e3c

Please sign in to comment.