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