| // 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) | 
 | } |