| package scsi | 
 |  | 
 | import ( | 
 | 	"bytes" | 
 | 	"encoding/binary" | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"math" | 
 | ) | 
 |  | 
 | // Inquiry queries the device for various metadata about its identity and | 
 | // supported features. | 
 | func (d Device) Inquiry() (*InquiryData, error) { | 
 | 	data := make([]byte, 96) | 
 | 	var req [4]byte | 
 | 	binary.BigEndian.PutUint16(req[2:4], uint16(len(data))) | 
 | 	if err := d.RawCommand(&CommandDataBuffer{ | 
 | 		OperationCode:         InquiryOp, | 
 | 		Request:               req[:], | 
 | 		Data:                  data, | 
 | 		DataTransferDirection: DataTransferFromDevice, | 
 | 	}); err != nil { | 
 | 		return nil, fmt.Errorf("error during INQUIRY: %w", err) | 
 | 	} | 
 | 	resLen := int64(data[4]) + 5 | 
 | 	// Use LimitReader to not have to deal with out-of-bounds slices | 
 | 	rawReader := io.LimitReader(bytes.NewReader(data), resLen) | 
 | 	var raw inquiryDataRaw | 
 | 	if err := binary.Read(rawReader, binary.BigEndian, &raw); err != nil { | 
 | 		if err == io.ErrUnexpectedEOF { | 
 | 			return nil, fmt.Errorf("response to INQUIRY is smaller than %d bytes, very old or broken device", binary.Size(raw)) | 
 | 		} | 
 | 		panic(err) // Read from memory, shouldn't be possible to hit | 
 | 	} | 
 |  | 
 | 	var res InquiryData | 
 | 	res.PeriperalQualifier = (raw.PeripheralData >> 5) & 0b111 | 
 | 	res.PeripheralDeviceType = DeviceType(raw.PeripheralData & 0b11111) | 
 | 	res.RemovableMedium = (raw.Flags1 & 1 << 0) != 0 | 
 | 	res.LogicalUnitConglomerate = (raw.Flags1 & 1 << 1) != 0 | 
 | 	res.CommandSetVersion = Version(raw.Version) | 
 | 	res.NormalACASupported = (raw.Flags2 & 1 << 5) != 0 | 
 | 	res.HistoricalSupport = (raw.Flags2 & 1 << 4) != 0 | 
 | 	res.ResponseDataFormat = raw.Flags2 & 0b1111 | 
 | 	res.SCCSupported = (raw.Flags3 & 1 << 7) != 0 | 
 | 	res.TargetPortGroupSupport = (raw.Flags3 >> 4) & 0b11 | 
 | 	res.ThirdPartyCopySupport = (raw.Flags3 & 1 << 3) != 0 | 
 | 	res.HasProtectionInfo = (raw.Flags3 & 1 << 0) != 0 | 
 | 	res.HasEnclosureServices = (raw.Flags4 & 1 << 6) != 0 | 
 | 	res.VendorFeature1 = (raw.Flags4 & 1 << 5) != 0 | 
 | 	res.HasMultipleSCSIPorts = (raw.Flags4 & 1 << 4) != 0 | 
 | 	res.CmdQueue = (raw.Flags5 & 1 << 1) != 0 | 
 | 	res.VendorFeature2 = (raw.Flags5 & 1 << 0) != 0 | 
 | 	res.Vendor = string(bytes.TrimRight(raw.Vendor[:], " ")) | 
 | 	res.Product = string(bytes.TrimRight(raw.Product[:], " ")) | 
 | 	res.ProductRevisionLevel = string(bytes.TrimRight(raw.ProductRevisionLevel[:], " ")) | 
 |  | 
 | 	// Read rest conditionally, as it might not be present on every device | 
 | 	var vendorSpecific bytes.Buffer | 
 | 	_, err := io.CopyN(&vendorSpecific, rawReader, 20) | 
 | 	res.VendorSpecific = vendorSpecific.Bytes() | 
 | 	if err == io.EOF { | 
 | 		return &res, nil | 
 | 	} | 
 | 	if err != nil { | 
 | 		panic(err) // Mem2Mem copy, can't really happen | 
 | 	} | 
 | 	var padding [2]byte | 
 | 	if _, err := io.ReadFull(rawReader, padding[:]); err != nil { | 
 | 		if err == io.ErrUnexpectedEOF { | 
 | 			return &res, nil | 
 | 		} | 
 | 	} | 
 | 	for i := 0; i < 8; i++ { | 
 | 		var versionDesc uint16 | 
 | 		if err := binary.Read(rawReader, binary.BigEndian, &versionDesc); err != nil { | 
 | 			if err == io.EOF || err == io.ErrUnexpectedEOF { | 
 | 				return &res, nil | 
 | 			} | 
 | 		} | 
 | 		res.VersionDescriptors = append(res.VersionDescriptors, versionDesc) | 
 | 	} | 
 |  | 
 | 	return &res, nil | 
 | } | 
 |  | 
 | // Table 148, only first 36 mandatory bytes | 
 | type inquiryDataRaw struct { | 
 | 	PeripheralData       uint8 | 
 | 	Flags1               uint8 | 
 | 	Version              uint8 | 
 | 	Flags2               uint8 | 
 | 	AdditionalLength     uint8 // n-4 | 
 | 	Flags3               uint8 | 
 | 	Flags4               uint8 | 
 | 	Flags5               uint8 | 
 | 	Vendor               [8]byte | 
 | 	Product              [16]byte | 
 | 	ProductRevisionLevel [4]byte | 
 | } | 
 |  | 
 | // DeviceType represents a SCSI peripheral device type, which | 
 | // can be used to determine the command set to use to control | 
 | // the device. See Table 150 in the standard. | 
 | type DeviceType uint8 | 
 |  | 
 | const ( | 
 | 	TypeBlockDevice              DeviceType = 0x00 | 
 | 	TypeSequentialAccessDevice   DeviceType = 0x01 | 
 | 	TypeProcessor                DeviceType = 0x03 | 
 | 	TypeOpticalDrive             DeviceType = 0x05 | 
 | 	TypeOpticalMemory            DeviceType = 0x07 | 
 | 	TypeMediaChanger             DeviceType = 0x08 | 
 | 	TypeArrayController          DeviceType = 0x0c | 
 | 	TypeEncloseServices          DeviceType = 0x0d | 
 | 	TypeOpticalCardRWDevice      DeviceType = 0x0f | 
 | 	TypeObjectStorageDevice      DeviceType = 0x11 | 
 | 	TypeAutomationDriveInterface DeviceType = 0x12 | 
 | 	TypeZonedBlockDevice         DeviceType = 0x14 | 
 | 	TypeUnknownDevice            DeviceType = 0x1f | 
 | ) | 
 |  | 
 | var deviceTypeDesc = map[DeviceType]string{ | 
 | 	TypeBlockDevice:              "Block Device", | 
 | 	TypeSequentialAccessDevice:   "Sequential Access Device", | 
 | 	TypeProcessor:                "Processor", | 
 | 	TypeOpticalDrive:             "Optical Drive", | 
 | 	TypeOpticalMemory:            "Optical Memory", | 
 | 	TypeMediaChanger:             "Media Changer", | 
 | 	TypeArrayController:          "Array Controller", | 
 | 	TypeEncloseServices:          "Enclosure Services", | 
 | 	TypeOpticalCardRWDevice:      "Optical Card reader/writer device", | 
 | 	TypeObjectStorageDevice:      "Object-based Storage Device", | 
 | 	TypeAutomationDriveInterface: "Automation/Drive Interface", | 
 | 	TypeZonedBlockDevice:         "Zoned Block Device", | 
 | 	TypeUnknownDevice:            "Unknown or no device", | 
 | } | 
 |  | 
 | func (d DeviceType) String() string { | 
 | 	if str, ok := deviceTypeDesc[d]; ok { | 
 | 		return str | 
 | 	} | 
 | 	return fmt.Sprintf("unknown device type %xh", uint8(d)) | 
 | } | 
 |  | 
 | // Version represents a specific standardized version of the SCSI | 
 | // primary command set (SPC). The enum values are sorted, so | 
 | // for example version >= SPC3 is true for SPC-3 and all later | 
 | // standards. See table 151 in the standard. | 
 | type Version uint8 | 
 |  | 
 | const ( | 
 | 	SPC1 = 0x03 | 
 | 	SPC2 = 0x04 | 
 | 	SPC3 = 0x05 | 
 | 	SPC4 = 0x06 | 
 | 	SPC5 = 0x07 | 
 | ) | 
 |  | 
 | var versionDesc = map[Version]string{ | 
 | 	SPC1: "SPC-1 (INCITS 301-1997)", | 
 | 	SPC2: "SPC-2 (INCITS 351-2001)", | 
 | 	SPC3: "SPC-3 (INCITS 408-2005)", | 
 | 	SPC4: "SPC-4 (INCITS 513-2015)", | 
 | 	SPC5: "SPC-5 (INCITS 502-2019)", | 
 | } | 
 |  | 
 | func (v Version) String() string { | 
 | 	if str, ok := versionDesc[v]; ok { | 
 | 		return str | 
 | 	} | 
 | 	return fmt.Sprintf("unknown version %xh", uint8(v)) | 
 | } | 
 |  | 
 | // InquiryData contains data returned by the INQUIRY command. | 
 | type InquiryData struct { | 
 | 	PeriperalQualifier      uint8 | 
 | 	PeripheralDeviceType    DeviceType | 
 | 	RemovableMedium         bool | 
 | 	LogicalUnitConglomerate bool | 
 | 	CommandSetVersion       Version | 
 | 	NormalACASupported      bool | 
 | 	HistoricalSupport       bool | 
 | 	ResponseDataFormat      uint8 | 
 | 	SCCSupported            bool | 
 | 	TargetPortGroupSupport  uint8 | 
 | 	ThirdPartyCopySupport   bool | 
 | 	HasProtectionInfo       bool | 
 | 	HasEnclosureServices    bool | 
 | 	VendorFeature1          bool | 
 | 	HasMultipleSCSIPorts    bool | 
 | 	CmdQueue                bool | 
 | 	VendorFeature2          bool | 
 | 	Vendor                  string | 
 | 	Product                 string | 
 | 	ProductRevisionLevel    string | 
 | 	VendorSpecific          []byte | 
 | 	VersionDescriptors      []uint16 | 
 | } | 
 |  | 
 | // Table 498 | 
 | type VPDPageCode uint8 | 
 |  | 
 | const ( | 
 | 	SupportedVPDs                      VPDPageCode = 0x00 | 
 | 	UnitSerialNumberVPD                VPDPageCode = 0x80 | 
 | 	DeviceIdentificationVPD            VPDPageCode = 0x83 | 
 | 	SoftwareInterfaceIdentificationVPD VPDPageCode = 0x84 | 
 | 	ManagementNetworkAddressesVPD      VPDPageCode = 0x85 | 
 | 	ExtendedINQUIRYDataVPD             VPDPageCode = 0x86 | 
 | 	ModePagePolicyVPD                  VPDPageCode = 0x87 | 
 | 	SCSIPortsVPD                       VPDPageCode = 0x88 | 
 | 	ATAInformationVPD                  VPDPageCode = 0x89 | 
 | 	PowerConditionVPD                  VPDPageCode = 0x8a | 
 | 	DeviceConstituentsVPD              VPDPageCode = 0x8b | 
 | ) | 
 |  | 
 | var vpdPageCodeDesc = map[VPDPageCode]string{ | 
 | 	SupportedVPDs:                      "Supported VPD Pages", | 
 | 	UnitSerialNumberVPD:                "Unit Serial Number", | 
 | 	DeviceIdentificationVPD:            "Device Identification", | 
 | 	SoftwareInterfaceIdentificationVPD: "Software Interface Identification", | 
 | 	ManagementNetworkAddressesVPD:      "Management Network Addresses", | 
 | 	ExtendedINQUIRYDataVPD:             "Extended INQUIRY Data", | 
 | 	ModePagePolicyVPD:                  "Mode Page Policy", | 
 | 	SCSIPortsVPD:                       "SCSI Ports", | 
 | 	ATAInformationVPD:                  "ATA Information", | 
 | 	PowerConditionVPD:                  "Power Condition", | 
 | 	DeviceConstituentsVPD:              "Device Constituents", | 
 | } | 
 |  | 
 | func (v VPDPageCode) String() string { | 
 | 	if str, ok := vpdPageCodeDesc[v]; ok { | 
 | 		return str | 
 | 	} | 
 | 	return fmt.Sprintf("Page %xh", uint8(v)) | 
 | } | 
 |  | 
 | // InquiryVPD requests a specified Vital Product Description Page from the | 
 | // device. If the size of the page is known in advance, initialSize should be | 
 | // set to a non-zero value to make the query more efficient. | 
 | func (d *Device) InquiryVPD(pageCode VPDPageCode, initialSize uint16) ([]byte, error) { | 
 | 	var bufferSize uint16 = 254 | 
 | 	if initialSize > 0 { | 
 | 		bufferSize = initialSize | 
 | 	} | 
 | 	for { | 
 | 		data := make([]byte, bufferSize) | 
 | 		var req [4]byte | 
 | 		req[0] = 0b1 // Enable Vital Product Data | 
 | 		req[1] = uint8(pageCode) | 
 | 		binary.BigEndian.PutUint16(req[2:4], uint16(len(data))) | 
 | 		if err := d.RawCommand(&CommandDataBuffer{ | 
 | 			OperationCode:         InquiryOp, | 
 | 			Request:               req[:], | 
 | 			Data:                  data, | 
 | 			DataTransferDirection: DataTransferFromDevice, | 
 | 		}); err != nil { | 
 | 			return nil, fmt.Errorf("error during INQUIRY VPD: %w", err) | 
 | 		} | 
 | 		if data[1] != uint8(pageCode) { | 
 | 			return nil, fmt.Errorf("requested VPD page %x, got %x", pageCode, data[1]) | 
 | 		} | 
 | 		pageLength := binary.BigEndian.Uint16(data[2:4]) | 
 | 		if pageLength > math.MaxUint16-4 { | 
 | 			// Guard against uint16 overflows, this cannot be requested anyway | 
 | 			return nil, fmt.Errorf("device VPD page is too long (%d bytes)", pageLength) | 
 | 		} | 
 | 		if pageLength > uint16(len(data)-4) { | 
 | 			bufferSize = pageLength + 4 | 
 | 			continue | 
 | 		} | 
 | 		return data[4 : pageLength+4], nil | 
 | 	} | 
 | } | 
 |  | 
 | // SupportedVPDPages returns the list of supported vital product data pages | 
 | // supported by the device. | 
 | func (d *Device) SupportedVPDPages() (map[VPDPageCode]bool, error) { | 
 | 	res, err := d.InquiryVPD(SupportedVPDs, 0) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	supportedPages := make(map[VPDPageCode]bool) | 
 | 	for _, p := range res { | 
 | 		supportedPages[VPDPageCode(p)] = true | 
 | 	} | 
 | 	return supportedPages, nil | 
 | } | 
 |  | 
 | // UnitSerialNumber returns the serial number of the device. Only available if | 
 | // UnitSerialNumberVPD is a supported VPD page. | 
 | func (d *Device) UnitSerialNumber() (string, error) { | 
 | 	serial, err := d.InquiryVPD(UnitSerialNumberVPD, 0) | 
 | 	if err != nil { | 
 | 		return "", err | 
 | 	} | 
 | 	return string(bytes.Trim(serial, " \x00")), nil | 
 | } |