This repository has been archived by the owner on Jan 30, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 26
/
cmd_handler.go
382 lines (343 loc) · 9.66 KB
/
cmd_handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
package tcmu
import (
"bytes"
"encoding/binary"
"io"
"github.com/coreos/go-tcmu/scsi"
"github.com/prometheus/common/log"
)
// SCSICmdHandler is a simple request/response handler for SCSI commands coming to TCMU.
// A SCSI error is reported as an SCSIResponse with an error bit set, while returning a Go error is for flagrant, process-ending errors (OOM, perhaps).
type SCSICmdHandler interface {
HandleCommand(cmd *SCSICmd) (SCSIResponse, error)
}
type ReadWriterAtCmdHandler struct {
RW ReadWriterAt
Inq *InquiryInfo
}
// InquiryInfo holds the general vendor information for the emulated SCSI Device. Fields used from this will be padded or trunacted to meet the spec.
type InquiryInfo struct {
VendorID string
ProductID string
ProductRev string
}
var defaultInquiry = InquiryInfo{
VendorID: "go-tcmu",
ProductID: "TCMU Device",
ProductRev: "0001",
}
func (h ReadWriterAtCmdHandler) HandleCommand(cmd *SCSICmd) (SCSIResponse, error) {
switch cmd.Command() {
case scsi.Inquiry:
if h.Inq == nil {
h.Inq = &defaultInquiry
}
return EmulateInquiry(cmd, h.Inq)
case scsi.TestUnitReady:
return EmulateTestUnitReady(cmd)
case scsi.ServiceActionIn16:
return EmulateServiceActionIn(cmd)
case scsi.ModeSense, scsi.ModeSense10:
return EmulateModeSense(cmd, false)
case scsi.ModeSelect, scsi.ModeSelect10:
return EmulateModeSelect(cmd, false)
case scsi.Read6, scsi.Read10, scsi.Read12, scsi.Read16:
return EmulateRead(cmd, h.RW)
case scsi.Write6, scsi.Write10, scsi.Write12, scsi.Write16:
return EmulateWrite(cmd, h.RW)
default:
log.Debugf("Ignore unknown SCSI command 0x%x\n", cmd.Command())
}
return cmd.NotHandled(), nil
}
func EmulateInquiry(cmd *SCSICmd, inq *InquiryInfo) (SCSIResponse, error) {
if (cmd.GetCDB(1) & 0x01) == 0 {
if cmd.GetCDB(2) == 0x00 {
return EmulateStdInquiry(cmd, inq)
}
return cmd.IllegalRequest(), nil
}
return EmulateEvpdInquiry(cmd, inq)
}
func FixedString(s string, length int) []byte {
p := []byte(s)
l := len(p)
if l >= length {
return p[:length]
}
sp := bytes.Repeat([]byte{' '}, length-l)
return append(p, sp...)
}
func EmulateStdInquiry(cmd *SCSICmd, inq *InquiryInfo) (SCSIResponse, error) {
buf := make([]byte, 36)
buf[2] = 0x05 // SPC-3
buf[3] = 0x02 // response data format
buf[7] = 0x02 // CmdQue
vendorID := FixedString(inq.VendorID, 8)
copy(buf[8:16], vendorID)
productID := FixedString(inq.ProductID, 16)
copy(buf[16:32], productID)
productRev := FixedString(inq.ProductRev, 4)
copy(buf[32:36], productRev)
buf[4] = 31 // Set additional length to 31
_, err := cmd.Write(buf)
if err != nil {
return SCSIResponse{}, err
}
return cmd.Ok(), nil
}
func EmulateEvpdInquiry(cmd *SCSICmd, inq *InquiryInfo) (SCSIResponse, error) {
vpdType := cmd.GetCDB(2)
log.Debugf("SCSI EVPD Inquiry 0x%x\n", vpdType)
switch vpdType {
case 0x0: // Supported VPD pages
// The absolute minimum.
data := make([]byte, 6)
// We support 0x00 and 0x83 only
data[3] = 2
data[4] = 0x00
data[5] = 0x83
cmd.Write(data)
return cmd.Ok(), nil
case 0x83: // Device identification
used := 4
data := make([]byte, 512)
data[1] = 0x83
wwn := []byte("") // TODO(barakmich): Report WWN. See tcmu_get_wwn;
// 1/3: T10 Vendor id
ptr := data[used:]
ptr[0] = 2 // code set: ASCII
ptr[1] = 1 // identifier: T10 vendor id
copy(ptr[4:], FixedString(inq.VendorID, 8))
n := copy(ptr[12:], wwn)
ptr[3] = byte(8 + n + 1)
used += int(ptr[3]) + 4
// 2/3: NAA binary // TODO(barakmich): Emulate given a real WWN
ptr = data[used:]
ptr[0] = 1 // code set: binary
ptr[1] = 3 // identifier: NAA
ptr[3] = 16 // body length for naa registered extended format
// Set type 6 and use OpenFabrics IEEE Company ID: 00 14 05
ptr[4] = 0x60
ptr[5] = 0x01
ptr[6] = 0x40
ptr[7] = 0x50
next := true
i := 7
for _, x := range wwn {
if i >= 20 {
break
}
v, ok := charToHex(x)
if !ok {
continue
}
if next {
next = false
ptr[i] |= v
i++
} else {
next = true
ptr[i] = (v << 4)
}
}
used += 20
// 3/3: Vendor specific
ptr = data[used:]
ptr[0] = 2 // code set: ASCII
ptr[1] = 0 // identifier: vendor-specific
cfgString := cmd.Device().GetDevConfig()
n = copy(ptr[4:], []byte(cfgString))
ptr[3] = byte(n + 1)
used += n + 1 + 4
order := binary.BigEndian
order.PutUint16(data[2:4], uint16(used-4))
cmd.Write(data[:used])
return cmd.Ok(), nil
default:
return cmd.IllegalRequest(), nil
}
}
func EmulateTestUnitReady(cmd *SCSICmd) (SCSIResponse, error) {
return cmd.Ok(), nil
}
func EmulateServiceActionIn(cmd *SCSICmd) (SCSIResponse, error) {
if cmd.GetCDB(1) == scsi.ReadCapacity16 {
return EmulateReadCapacity16(cmd)
}
return cmd.NotHandled(), nil
}
func EmulateReadCapacity16(cmd *SCSICmd) (SCSIResponse, error) {
buf := make([]byte, 32)
order := binary.BigEndian
// This is in LBAs, and the "index of the last LBA", so minus 1. Friggin spec.
order.PutUint64(buf[0:8], uint64(cmd.Device().Sizes().VolumeSize/cmd.Device().Sizes().BlockSize)-1)
// This is in BlockSize
order.PutUint32(buf[8:12], uint32(cmd.Device().Sizes().BlockSize))
// All the rest is 0
cmd.Write(buf)
return cmd.Ok(), nil
}
func charToHex(c byte) (byte, bool) {
if c >= '0' && c <= '9' {
return c - '0', true
}
if c >= 'a' && c <= 'f' {
return c - 'a' + 10, true
}
if c >= 'A' && c <= 'F' {
return c - 'A' + 10, true
}
return 0x00, false
}
func CachingModePage(w io.Writer, wce bool) {
buf := make([]byte, 20)
buf[0] = 0x08 // caching mode page
buf[1] = 0x12 // page length (20, forced)
if wce {
buf[2] = buf[2] | 0x04
}
w.Write(buf)
}
// EmulateModeSense responds to a static Mode Sense command. `wce` enables or diables
// the SCSI "Write Cache Enabled" flag.
func EmulateModeSense(cmd *SCSICmd, wce bool) (SCSIResponse, error) {
pgs := &bytes.Buffer{}
outlen := int(cmd.XferLen())
page := cmd.GetCDB(2)
if page == 0x3f || page == 0x08 {
CachingModePage(pgs, wce)
}
scsiCmd := cmd.Command()
dsp := byte(0x10) // Support DPO/FUA
pgdata := pgs.Bytes()
var hdr []byte
if scsiCmd == scsi.ModeSense {
// MODE_SENSE_6
hdr = make([]byte, 4)
hdr[0] = byte(len(pgdata) + 3)
hdr[1] = 0x00 // Device type
hdr[2] = dsp
} else {
// MODE_SENSE_10
hdr = make([]byte, 8)
order := binary.BigEndian
order.PutUint16(hdr, uint16(len(pgdata)+6))
hdr[2] = 0x00 // Device type
hdr[3] = dsp
}
data := append(hdr, pgdata...)
if outlen < len(data) {
data = data[:outlen]
}
cmd.Write(data)
return cmd.Ok(), nil
}
// EmulateModeSelect checks that the only mode selected is the static one returned from
// EmulateModeSense. `wce` should match the Write Cache Enabled of the EmulateModeSense call.
func EmulateModeSelect(cmd *SCSICmd, wce bool) (SCSIResponse, error) {
selectTen := (cmd.GetCDB(0) == scsi.ModeSelect10)
page := cmd.GetCDB(2) & 0x3f
subpage := cmd.GetCDB(3)
allocLen := cmd.XferLen()
hdrLen := 4
if selectTen {
hdrLen = 8
}
inBuf := make([]byte, 512)
gotSense := false
if allocLen == 0 {
return cmd.Ok(), nil
}
n, err := cmd.Read(inBuf)
if err != nil {
return SCSIResponse{}, err
}
if n >= len(inBuf) {
return cmd.CheckCondition(scsi.SenseIllegalRequest, scsi.AscParameterListLengthError), nil
}
cdbone := cmd.GetCDB(1)
if cdbone&0x10 == 0 || cdbone&0x01 != 0 {
return cmd.IllegalRequest(), nil
}
pgs := &bytes.Buffer{}
// TODO(barakmich): select over handlers. Today we have one.
if page == 0x08 && subpage == 0 {
CachingModePage(pgs, wce)
gotSense = true
}
if !gotSense {
return cmd.IllegalRequest(), nil
}
b := pgs.Bytes()
if int(allocLen) < (hdrLen + len(b)) {
return cmd.CheckCondition(scsi.SenseIllegalRequest, scsi.AscParameterListLengthError), nil
}
/* Verify what was selected is identical to what sense returns, since we
don't support actually setting anything. */
if !bytes.Equal(inBuf[hdrLen:len(b)], b) {
log.Errorf("not equal for some reason: %#v %#v", inBuf[hdrLen:len(b)], b)
return cmd.CheckCondition(scsi.SenseIllegalRequest, scsi.AscInvalidFieldInParameterList), nil
}
return cmd.Ok(), nil
}
func EmulateRead(cmd *SCSICmd, r io.ReaderAt) (SCSIResponse, error) {
offset := cmd.LBA() * uint64(cmd.Device().Sizes().BlockSize)
length := int(cmd.XferLen() * uint32(cmd.Device().Sizes().BlockSize))
if cmd.Buf == nil {
cmd.Buf = make([]byte, length)
}
if len(cmd.Buf) < int(length) {
//realloc
cmd.Buf = make([]byte, length)
}
n, err := r.ReadAt(cmd.Buf[:length], int64(offset))
if n < length {
log.Errorln("read/read failed: unable to copy enough")
return cmd.MediumError(), nil
}
if err != nil {
log.Errorln("read/read failed: error:", err)
return cmd.MediumError(), nil
}
n, err = cmd.Write(cmd.Buf[:length])
if n < length {
log.Errorln("read/write failed: unable to copy enough")
return cmd.MediumError(), nil
}
if err != nil {
log.Errorln("read/write failed: error:", err)
return cmd.MediumError(), nil
}
return cmd.Ok(), nil
}
func EmulateWrite(cmd *SCSICmd, r io.WriterAt) (SCSIResponse, error) {
offset := cmd.LBA() * uint64(cmd.Device().Sizes().BlockSize)
length := int(cmd.XferLen() * uint32(cmd.Device().Sizes().BlockSize))
if cmd.Buf == nil {
cmd.Buf = make([]byte, length)
}
if len(cmd.Buf) < int(length) {
//realloc
cmd.Buf = make([]byte, length)
}
n, err := cmd.Read(cmd.Buf[:int(length)])
if n < length {
log.Errorln("write/read failed: unable to copy enough")
return cmd.MediumError(), nil
}
if err != nil {
log.Errorln("write/read failed: error:", err)
return cmd.MediumError(), nil
}
n, err = r.WriteAt(cmd.Buf[:length], int64(offset))
if n < length {
log.Errorln("write/write failed: unable to copy enough")
return cmd.MediumError(), nil
}
if err != nil {
log.Errorln("write/write failed: error:", err)
return cmd.MediumError(), nil
}
return cmd.Ok(), nil
}