blob: e2d236e38027d4a2dec201b93665b3f355333acb [file] [log] [blame]
Lorenz Brunbffdda82023-01-10 05:00:36 +00001// INCITS 502 Revision 19 / SPC-5 R19
2package scsi
3
4import (
5 "errors"
6 "fmt"
7 "os"
8 "syscall"
9 "time"
10)
11
12// Device is a handle for a SCSI device
13type Device struct {
14 fd syscall.Conn
15}
16
17// NewFromFd creates a new SCSI device handle from a system handle.
18func NewFromFd(fd syscall.Conn) (*Device, error) {
19 d := &Device{fd: fd}
20 // There is no good way to validate that a file descriptor indeed points to
21 // a SCSI device. For future compatibility let this return an error so that
22 // code is already prepared to handle it.
23 return d, nil
24}
25
26// Open creates a new SCSI device handle from a device path (like /dev/sda).
27func Open(path string) (*Device, error) {
28 f, err := os.Open(path)
29 if err != nil {
30 return nil, fmt.Errorf("unable to open path: %w", err)
31 }
32 return NewFromFd(f)
33}
34
35// Close closes the SCSI device handle if opened by Open()
36func (d *Device) Close() error {
37 if f, ok := d.fd.(*os.File); ok {
38 return f.Close()
39 } else {
40 return errors.New("unable to close device not opened via Open, please close it yourself")
41 }
42}
43
44type DataTransferDirection uint8
45
46const (
47 DataTransferNone DataTransferDirection = iota
48 DataTransferToDevice
49 DataTransferFromDevice
50 DataTransferBidirectional
51)
52
53type OperationCode uint8
54
55const (
56 InquiryOp OperationCode = 0x12
57 ReadDefectDataOp OperationCode = 0x37
58 LogSenseOp OperationCode = 0x4d
59)
60
61// CommandDataBuffer represents a command
62type CommandDataBuffer struct {
63 // OperationCode contains the code of the command to be called
64 OperationCode OperationCode
65 // Request contains the OperationCode-specific request parameters
66 Request []byte
67 // ServiceAction can (for certain CDB encodings) contain an additional
68 // qualification for the OperationCode.
69 ServiceAction *uint8
70 // Control contains common CDB metadata
71 Control uint8
72 // DataTransferDirection contains the direction(s) of the data transfer(s)
73 // to be made.
74 DataTransferDirection DataTransferDirection
75 // Data contains the data to be transferred. If data needs to be received
76 // from the device, a buffer needs to be provided here.
77 Data []byte
78 // Timeout can contain an optional timeout (0 = no timeout) for the command
79 Timeout time.Duration
80}
81
82// Bytes returns the raw CDB to be sent to the device
83func (c *CommandDataBuffer) Bytes() ([]byte, error) {
84 // Table 24
85 switch {
86 case c.OperationCode < 0x20:
87 // Use CDB6 as defined in Table 3
88 if c.ServiceAction != nil {
89 return nil, errors.New("ServiceAction field not available in CDB6")
90 }
91 if len(c.Request) != 4 {
92 return nil, fmt.Errorf("CDB6 request size is %d bytes, needs to be 4 bytes without LengthField", len(c.Request))
93 }
94
95 outBuf := make([]byte, 6)
96 outBuf[0] = uint8(c.OperationCode)
97
98 copy(outBuf[1:5], c.Request)
99 outBuf[5] = c.Control
100 return outBuf, nil
101 case c.OperationCode < 0x60:
102 // Use CDB10 as defined in Table 5
103 if len(c.Request) != 8 {
104 return nil, fmt.Errorf("CDB10 request size is %d bytes, needs to be 4 bytes", len(c.Request))
105 }
106
107 outBuf := make([]byte, 10)
108 outBuf[0] = uint8(c.OperationCode)
109 copy(outBuf[1:9], c.Request)
110 if c.ServiceAction != nil {
111 outBuf[1] |= *c.ServiceAction & 0b11111
112 }
113 outBuf[9] = c.Control
114 return outBuf, nil
115 case c.OperationCode < 0x7e:
116 return nil, errors.New("OperationCode is reserved")
117 case c.OperationCode == 0x7e:
118 // Use variable extended
119 return nil, errors.New("variable extended CDBs are unimplemented")
120 case c.OperationCode == 0x7f:
121 // Use variable
122 return nil, errors.New("variable CDBs are unimplemented")
123 case c.OperationCode < 0xa0:
124 // Use CDB16 as defined in Table 13
125 if len(c.Request) != 14 {
126 return nil, fmt.Errorf("CDB16 request size is %d bytes, needs to be 14 bytes", len(c.Request))
127 }
128
129 outBuf := make([]byte, 16)
130 outBuf[0] = uint8(c.OperationCode)
131 copy(outBuf[1:15], c.Request)
132 if c.ServiceAction != nil {
133 outBuf[1] |= *c.ServiceAction & 0b11111
134 }
135 outBuf[15] = c.Control
136 return outBuf, nil
137 case c.OperationCode < 0xc0:
138 // Use CDB12 as defined in Table 7
139 if len(c.Request) != 10 {
140 return nil, fmt.Errorf("CDB12 request size is %d bytes, needs to be 10 bytes", len(c.Request))
141 }
142
143 outBuf := make([]byte, 12)
144 outBuf[0] = uint8(c.OperationCode)
145 copy(outBuf[1:11], c.Request)
146 if c.ServiceAction != nil {
147 outBuf[1] |= *c.ServiceAction & 0b11111
148 }
149 outBuf[11] = c.Control
150 return outBuf, nil
151 default:
152 return nil, errors.New("unable to encode CDB for given OperationCode")
153 }
154}
155
156// SenseKey represents the top-level status code of a SCSI sense response.
157type SenseKey uint8
158
159const (
160 NoSense SenseKey = 0x0
161 RecoveredError SenseKey = 0x1
162 NotReady SenseKey = 0x2
163 MediumError SenseKey = 0x3
164 HardwareError SenseKey = 0x4
165 IllegalRequest SenseKey = 0x5
166 UnitAttention SenseKey = 0x6
167 DataProtect SenseKey = 0x7
168 BlankCheck SenseKey = 0x8
169 VendorSpecific SenseKey = 0x9
170 CopyAborted SenseKey = 0xa
171 AbortedCommand SenseKey = 0xb
172 VolumeOverflow SenseKey = 0xd
173 Miscompare SenseKey = 0xe
174 Completed SenseKey = 0xf
175)
176
177var senseKeyDesc = map[SenseKey]string{
178 NoSense: "no sense information",
179 RecoveredError: "recovered error",
180 NotReady: "not ready",
181 MediumError: "medium error",
182 HardwareError: "hardware error",
183 IllegalRequest: "illegal request",
184 UnitAttention: "unit attention",
185 DataProtect: "data protected",
186 BlankCheck: "blank check failed",
187 VendorSpecific: "vendor-specific error",
188 CopyAborted: "third-party copy aborted",
189 AbortedCommand: "command aborted",
190 VolumeOverflow: "volume overflow",
191 Miscompare: "miscompare",
192 Completed: "completed",
193}
194
195func (s SenseKey) String() string {
196 if str, ok := senseKeyDesc[s]; ok {
197 return str
198 }
199 return fmt.Sprintf("sense key %xh", uint8(s))
200}
201
202// AdditionalSenseCode contains the additional sense key and qualifier in one
203// 16-bit value. The high 8 bits are the sense key, the bottom 8 bits the
204// qualifier.
205type AdditionalSenseCode uint16
206
207// ASK returns the raw Additional Sense Key
208func (a AdditionalSenseCode) ASK() uint8 {
209 return uint8(a >> 8)
210}
211
212// ASKQ returns the raw Additional Sense Key Qualifier
213func (a AdditionalSenseCode) ASKQ() uint8 {
214 return uint8(a & 0xFF)
215}
216
217// IsKey checks if the ASK portion of a is the same as the ASK portion of b.
218func (a AdditionalSenseCode) IsKey(b AdditionalSenseCode) bool {
219 return a.ASK() == b.ASK()
220}
221
222// String returns the textual representation of this ASK
223func (s AdditionalSenseCode) String() string {
224 if str, ok := additionalSenseCodeDesc[s]; ok {
225 return str
226 }
227 return fmt.Sprintf("unknown additional sense code %xh %xh", s.ASK(), s.ASKQ())
228}
229
230// FixedError is one type of error returned by a SCSI CHECK_CONDITION.
231// See also Table 48 in the standard.
232type FixedError struct {
233 Deferred bool
234 SenseKey SenseKey
235 Information uint32
236 CommandSpecificInformation uint32
237 AdditionalSenseCode AdditionalSenseCode
238}
239
240func (e FixedError) Error() string {
241 if e.AdditionalSenseCode == 0 {
242 return fmt.Sprintf("%v", e.SenseKey)
243 }
244 return fmt.Sprintf("%v: %v", e.SenseKey, e.AdditionalSenseCode)
245
246}
247
248// UnknownError is a type of error returned by SCSI which is not understood by this
249// library. This can be a vendor-specific or future error.
250type UnknownError struct {
251 RawSenseData []byte
252}
253
254func (e *UnknownError) Error() string {
255 return fmt.Sprintf("unknown SCSI error, raw sense data follows: %x", e.RawSenseData)
256}