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