blob: e2d236e38027d4a2dec201b93665b3f355333acb [file] [log] [blame]
// INCITS 502 Revision 19 / SPC-5 R19
package scsi
import (
"errors"
"fmt"
"os"
"syscall"
"time"
)
// Device is a handle for a SCSI device
type Device struct {
fd syscall.Conn
}
// NewFromFd creates a new SCSI device handle from a system handle.
func NewFromFd(fd syscall.Conn) (*Device, error) {
d := &Device{fd: fd}
// There is no good way to validate that a file descriptor indeed points to
// a SCSI device. For future compatibility let this return an error so that
// code is already prepared to handle it.
return d, nil
}
// Open creates a new SCSI device handle from a device path (like /dev/sda).
func Open(path string) (*Device, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("unable to open path: %w", err)
}
return NewFromFd(f)
}
// Close closes the SCSI device handle if opened by Open()
func (d *Device) Close() error {
if f, ok := d.fd.(*os.File); ok {
return f.Close()
} else {
return errors.New("unable to close device not opened via Open, please close it yourself")
}
}
type DataTransferDirection uint8
const (
DataTransferNone DataTransferDirection = iota
DataTransferToDevice
DataTransferFromDevice
DataTransferBidirectional
)
type OperationCode uint8
const (
InquiryOp OperationCode = 0x12
ReadDefectDataOp OperationCode = 0x37
LogSenseOp OperationCode = 0x4d
)
// CommandDataBuffer represents a command
type CommandDataBuffer struct {
// OperationCode contains the code of the command to be called
OperationCode OperationCode
// Request contains the OperationCode-specific request parameters
Request []byte
// ServiceAction can (for certain CDB encodings) contain an additional
// qualification for the OperationCode.
ServiceAction *uint8
// Control contains common CDB metadata
Control uint8
// DataTransferDirection contains the direction(s) of the data transfer(s)
// to be made.
DataTransferDirection DataTransferDirection
// Data contains the data to be transferred. If data needs to be received
// from the device, a buffer needs to be provided here.
Data []byte
// Timeout can contain an optional timeout (0 = no timeout) for the command
Timeout time.Duration
}
// Bytes returns the raw CDB to be sent to the device
func (c *CommandDataBuffer) Bytes() ([]byte, error) {
// Table 24
switch {
case c.OperationCode < 0x20:
// Use CDB6 as defined in Table 3
if c.ServiceAction != nil {
return nil, errors.New("ServiceAction field not available in CDB6")
}
if len(c.Request) != 4 {
return nil, fmt.Errorf("CDB6 request size is %d bytes, needs to be 4 bytes without LengthField", len(c.Request))
}
outBuf := make([]byte, 6)
outBuf[0] = uint8(c.OperationCode)
copy(outBuf[1:5], c.Request)
outBuf[5] = c.Control
return outBuf, nil
case c.OperationCode < 0x60:
// Use CDB10 as defined in Table 5
if len(c.Request) != 8 {
return nil, fmt.Errorf("CDB10 request size is %d bytes, needs to be 4 bytes", len(c.Request))
}
outBuf := make([]byte, 10)
outBuf[0] = uint8(c.OperationCode)
copy(outBuf[1:9], c.Request)
if c.ServiceAction != nil {
outBuf[1] |= *c.ServiceAction & 0b11111
}
outBuf[9] = c.Control
return outBuf, nil
case c.OperationCode < 0x7e:
return nil, errors.New("OperationCode is reserved")
case c.OperationCode == 0x7e:
// Use variable extended
return nil, errors.New("variable extended CDBs are unimplemented")
case c.OperationCode == 0x7f:
// Use variable
return nil, errors.New("variable CDBs are unimplemented")
case c.OperationCode < 0xa0:
// Use CDB16 as defined in Table 13
if len(c.Request) != 14 {
return nil, fmt.Errorf("CDB16 request size is %d bytes, needs to be 14 bytes", len(c.Request))
}
outBuf := make([]byte, 16)
outBuf[0] = uint8(c.OperationCode)
copy(outBuf[1:15], c.Request)
if c.ServiceAction != nil {
outBuf[1] |= *c.ServiceAction & 0b11111
}
outBuf[15] = c.Control
return outBuf, nil
case c.OperationCode < 0xc0:
// Use CDB12 as defined in Table 7
if len(c.Request) != 10 {
return nil, fmt.Errorf("CDB12 request size is %d bytes, needs to be 10 bytes", len(c.Request))
}
outBuf := make([]byte, 12)
outBuf[0] = uint8(c.OperationCode)
copy(outBuf[1:11], c.Request)
if c.ServiceAction != nil {
outBuf[1] |= *c.ServiceAction & 0b11111
}
outBuf[11] = c.Control
return outBuf, nil
default:
return nil, errors.New("unable to encode CDB for given OperationCode")
}
}
// SenseKey represents the top-level status code of a SCSI sense response.
type SenseKey uint8
const (
NoSense SenseKey = 0x0
RecoveredError SenseKey = 0x1
NotReady SenseKey = 0x2
MediumError SenseKey = 0x3
HardwareError SenseKey = 0x4
IllegalRequest SenseKey = 0x5
UnitAttention SenseKey = 0x6
DataProtect SenseKey = 0x7
BlankCheck SenseKey = 0x8
VendorSpecific SenseKey = 0x9
CopyAborted SenseKey = 0xa
AbortedCommand SenseKey = 0xb
VolumeOverflow SenseKey = 0xd
Miscompare SenseKey = 0xe
Completed SenseKey = 0xf
)
var senseKeyDesc = map[SenseKey]string{
NoSense: "no sense information",
RecoveredError: "recovered error",
NotReady: "not ready",
MediumError: "medium error",
HardwareError: "hardware error",
IllegalRequest: "illegal request",
UnitAttention: "unit attention",
DataProtect: "data protected",
BlankCheck: "blank check failed",
VendorSpecific: "vendor-specific error",
CopyAborted: "third-party copy aborted",
AbortedCommand: "command aborted",
VolumeOverflow: "volume overflow",
Miscompare: "miscompare",
Completed: "completed",
}
func (s SenseKey) String() string {
if str, ok := senseKeyDesc[s]; ok {
return str
}
return fmt.Sprintf("sense key %xh", uint8(s))
}
// AdditionalSenseCode contains the additional sense key and qualifier in one
// 16-bit value. The high 8 bits are the sense key, the bottom 8 bits the
// qualifier.
type AdditionalSenseCode uint16
// ASK returns the raw Additional Sense Key
func (a AdditionalSenseCode) ASK() uint8 {
return uint8(a >> 8)
}
// ASKQ returns the raw Additional Sense Key Qualifier
func (a AdditionalSenseCode) ASKQ() uint8 {
return uint8(a & 0xFF)
}
// IsKey checks if the ASK portion of a is the same as the ASK portion of b.
func (a AdditionalSenseCode) IsKey(b AdditionalSenseCode) bool {
return a.ASK() == b.ASK()
}
// String returns the textual representation of this ASK
func (s AdditionalSenseCode) String() string {
if str, ok := additionalSenseCodeDesc[s]; ok {
return str
}
return fmt.Sprintf("unknown additional sense code %xh %xh", s.ASK(), s.ASKQ())
}
// FixedError is one type of error returned by a SCSI CHECK_CONDITION.
// See also Table 48 in the standard.
type FixedError struct {
Deferred bool
SenseKey SenseKey
Information uint32
CommandSpecificInformation uint32
AdditionalSenseCode AdditionalSenseCode
}
func (e FixedError) Error() string {
if e.AdditionalSenseCode == 0 {
return fmt.Sprintf("%v", e.SenseKey)
}
return fmt.Sprintf("%v: %v", e.SenseKey, e.AdditionalSenseCode)
}
// UnknownError is a type of error returned by SCSI which is not understood by this
// library. This can be a vendor-specific or future error.
type UnknownError struct {
RawSenseData []byte
}
func (e *UnknownError) Error() string {
return fmt.Sprintf("unknown SCSI error, raw sense data follows: %x", e.RawSenseData)
}