| //go:build linux |
| |
| package scsi |
| |
| import ( |
| "encoding/binary" |
| "errors" |
| "fmt" |
| "math" |
| "runtime" |
| "unsafe" |
| |
| "golang.org/x/sys/unix" |
| ) |
| |
| // RawCommand issues a raw command against the device. |
| func (d *Device) RawCommand(c *CommandDataBuffer) error { |
| cdb, err := c.Bytes() |
| if err != nil { |
| return fmt.Errorf("error encoding CDB: %w", err) |
| } |
| conn, err := d.fd.SyscallConn() |
| if err != nil { |
| return fmt.Errorf("unable to get RawConn: %w", err) |
| } |
| var dxferDir int32 |
| switch c.DataTransferDirection { |
| case DataTransferNone: |
| dxferDir = SG_DXFER_NONE |
| case DataTransferFromDevice: |
| dxferDir = SG_DXFER_FROM_DEV |
| case DataTransferToDevice: |
| dxferDir = SG_DXFER_TO_DEV |
| case DataTransferBidirectional: |
| dxferDir = SG_DXFER_TO_FROM_DEV |
| default: |
| return errors.New("invalid DataTransferDirection") |
| } |
| var timeout uint32 |
| if c.Timeout.Milliseconds() > math.MaxUint32 { |
| timeout = math.MaxUint32 |
| } |
| if len(c.Data) > math.MaxUint32 { |
| return errors.New("payload larger than 2^32 bytes, unable to issue") |
| } |
| if len(cdb) > math.MaxUint8 { |
| return errors.New("CDB larger than 2^8 bytes, unable to issue") |
| } |
| var senseBuf [32]byte |
| cmdRaw := sgIOHdr{ |
| Interface_id: 'S', |
| Dxfer_direction: dxferDir, |
| Dxfer_len: uint32(len(c.Data)), |
| Dxferp: uintptr(unsafe.Pointer(&c.Data[0])), |
| Cmd_len: uint8(len(cdb)), |
| Cmdp: uintptr(unsafe.Pointer(&cdb[0])), |
| Mx_sb_len: uint8(len(senseBuf)), |
| Sbp: uintptr(unsafe.Pointer(&senseBuf[0])), |
| Timeout: timeout, |
| } |
| var errno unix.Errno |
| err = conn.Control(func(fd uintptr) { |
| _, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, SG_IO, uintptr(unsafe.Pointer(&cmdRaw))) |
| }) |
| runtime.KeepAlive(cmdRaw) |
| runtime.KeepAlive(c.Data) |
| runtime.KeepAlive(senseBuf) |
| runtime.KeepAlive(cdb) |
| if err != nil { |
| return fmt.Errorf("unable to get fd: %w", err) |
| } |
| if errno != 0 { |
| return errno |
| } |
| if cmdRaw.Masked_status != 0 { |
| if senseBuf[0] == 0x70 || senseBuf[0] == 0x71 { |
| err := &FixedError{ |
| Deferred: senseBuf[0] == 0x71, |
| SenseKey: SenseKey(senseBuf[2] & 0b1111), |
| Information: binary.BigEndian.Uint32(senseBuf[3:7]), |
| } |
| length := int(senseBuf[7]) |
| if length >= 4 { |
| err.CommandSpecificInformation = binary.BigEndian.Uint32(senseBuf[8:12]) |
| if length >= 6 { |
| err.AdditionalSenseCode = AdditionalSenseCode(uint16(senseBuf[12])<<8 | uint16(senseBuf[13])) |
| } |
| } |
| return err |
| } |
| return &UnknownError{ |
| RawSenseData: senseBuf[:], |
| } |
| } |
| return nil |
| } |