- Fix nil pointer dereference in BindISCSISession when existSess is nil - Fix reversed logic in SPCLuOffline/SPCLuOnline (Online flag was swapped) - Use negotiated MaxXmitDataSegmentLength for response PDU segmentation (issue #41) - Fix debug log incorrectly using Warn level in SBCGetLbaStatus Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1521 lines
39 KiB
Go
1521 lines
39 KiB
Go
/*
|
|
Copyright 2017 The GoStor Authors All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// SCSI primary command processing
|
|
package scsi
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gostor/gotgt/pkg/api"
|
|
"github.com/gostor/gotgt/pkg/util"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
EnableMultipath = true
|
|
)
|
|
|
|
func SPCIllegalOp(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_OP_CODE)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCLuOffline(lu *api.SCSILu) error {
|
|
lu.Attrs.Online = false
|
|
return nil
|
|
}
|
|
|
|
func SPCLuOnline(lu *api.SCSILu) error {
|
|
if luPreventRemoval(lu) {
|
|
return fmt.Errorf("lu prevent removal")
|
|
}
|
|
|
|
lu.Attrs.Online = true
|
|
return nil
|
|
}
|
|
|
|
func InquiryPage0x00(host int, cmd *api.SCSICommand) (*bytes.Buffer, uint16) {
|
|
var (
|
|
buf = &bytes.Buffer{}
|
|
descBuf = &bytes.Buffer{}
|
|
data []byte = []byte{}
|
|
pageLength uint16 = 0
|
|
)
|
|
|
|
descBuf.WriteByte(0x00)
|
|
descBuf.WriteByte(0x80)
|
|
descBuf.WriteByte(0x83)
|
|
descBuf.WriteByte(0xB0)
|
|
descBuf.WriteByte(0xB2)
|
|
/*
|
|
TODO:
|
|
descBuf.WriteByte(0x86)
|
|
*/
|
|
|
|
data = descBuf.Bytes()
|
|
pageLength = uint16(len(data))
|
|
|
|
//byte 0
|
|
if cmd.Device.Attrs.Online {
|
|
buf.WriteByte(PQ_DEVICE_CONNECTED | byte(cmd.Device.Attrs.DeviceType))
|
|
} else {
|
|
buf.WriteByte(PQ_DEVICE_NOT_CONNECT | byte(cmd.Device.Attrs.DeviceType))
|
|
}
|
|
//byte 1
|
|
//PAGE CODE
|
|
buf.WriteByte(0x00)
|
|
//PAGE LENGTH
|
|
binary.Write(buf, binary.BigEndian, pageLength)
|
|
buf.Write(data)
|
|
return buf, pageLength
|
|
}
|
|
|
|
func InquiryPage0x80(host int, cmd *api.SCSICommand) (*bytes.Buffer, uint16) {
|
|
var (
|
|
buf = &bytes.Buffer{}
|
|
pageLength uint16 = 36
|
|
scsisn = make([]byte, pageLength)
|
|
)
|
|
|
|
//byte 0
|
|
if cmd.Device.Attrs.Online {
|
|
buf.WriteByte(PQ_DEVICE_CONNECTED | byte(cmd.Device.Attrs.DeviceType))
|
|
} else {
|
|
buf.WriteByte(PQ_DEVICE_NOT_CONNECT | byte(cmd.Device.Attrs.DeviceType))
|
|
}
|
|
//byte 1: PAGE CODE
|
|
buf.WriteByte(0x80)
|
|
//byte 2-3: PAGE LENGTH
|
|
buf.WriteByte(uint8(pageLength >> 8))
|
|
buf.WriteByte(uint8(pageLength & 0xff))
|
|
copy(scsisn, []byte(fmt.Sprintf("gotgt-%-36v", cmd.Device.UUID)))
|
|
buf.Write(scsisn)
|
|
return buf, pageLength
|
|
}
|
|
|
|
func InquiryPage0x83(host int, cmd *api.SCSICommand) (*bytes.Buffer, uint16) {
|
|
var (
|
|
buf = &bytes.Buffer{}
|
|
descBuf = &bytes.Buffer{}
|
|
data []byte = []byte{}
|
|
portName []byte
|
|
pageLength uint16 = 0
|
|
portID uint16 = cmd.RelTargetPortID
|
|
portGroup uint16 = FindTargetGroup(cmd.Target, portID)
|
|
targetPort *api.SCSITargetPort = FindTargetPort(cmd.Target, portID)
|
|
)
|
|
|
|
//DESCRIPTOR 1 TARGET NAME
|
|
descBuf.WriteByte((PIV_ISCSI << 4) | INQ_CODE_ASCII)
|
|
descBuf.WriteByte(0x80 | (ASS_TGT_PORT << 4) | DESG_VENDOR)
|
|
descBuf.WriteByte(0x00)
|
|
//length
|
|
descBuf.WriteByte(byte(len([]byte(cmd.Target.Name))))
|
|
//target name
|
|
descBuf.Write([]byte(cmd.Target.Name))
|
|
|
|
//DESCRIPTOR 2 NNA Locally
|
|
descBuf.WriteByte((PIV_ISCSI << 4) | INQ_CODE_BIN)
|
|
descBuf.WriteByte(0x80 | (ASS_LU << 4) | DESG_NAA)
|
|
descBuf.WriteByte(0x00)
|
|
//length
|
|
descBuf.WriteByte(0x08)
|
|
//NNA
|
|
binary.Write(descBuf, binary.BigEndian, (cmd.Device.UUID | (uint64(NAA_LOCAL) << 60)))
|
|
|
|
//TODO: Target Port Group(0x05), Relative Target port identifier(0x04)
|
|
|
|
//DESCRIPTOR 3 TPG
|
|
descBuf.WriteByte((PIV_ISCSI << 4) | INQ_CODE_BIN)
|
|
descBuf.WriteByte(0x80 | (ASS_TGT_PORT << 4) | DESG_TGT_PORT_GRP)
|
|
descBuf.WriteByte(0x00)
|
|
//length
|
|
descBuf.WriteByte(0x04)
|
|
//TPG
|
|
descBuf.WriteByte(0x00)
|
|
descBuf.WriteByte(0x00)
|
|
binary.Write(descBuf, binary.BigEndian, portGroup)
|
|
|
|
//DESCRIPTOR 4 Relative Target Port ID
|
|
descBuf.WriteByte((PIV_ISCSI << 4) | INQ_CODE_BIN)
|
|
descBuf.WriteByte(0x80 | (ASS_TGT_PORT << 4) | DESG_REL_TGT_PORT)
|
|
descBuf.WriteByte(0x00)
|
|
//length
|
|
descBuf.WriteByte(0x04)
|
|
//RTPGI
|
|
descBuf.WriteByte(0x00)
|
|
descBuf.WriteByte(0x00)
|
|
binary.Write(descBuf, binary.BigEndian, portID)
|
|
|
|
//DESCRIPTOR 5 SCSI Name,Port
|
|
portName = util.StringToByte(targetPort.TargetPortName, 4, 256)
|
|
descBuf.WriteByte((PIV_ISCSI << 4) | INQ_CODE_UTF8)
|
|
descBuf.WriteByte(0x80 | (ASS_TGT_PORT << 4) | DESG_SCSI)
|
|
descBuf.WriteByte(0x00)
|
|
//length
|
|
descBuf.WriteByte(byte(len(portName)))
|
|
//RTPGI
|
|
descBuf.Write(portName)
|
|
|
|
data = descBuf.Bytes()
|
|
pageLength = uint16(len(data))
|
|
|
|
//byte 0
|
|
if cmd.Device.Attrs.Online {
|
|
buf.WriteByte(PQ_DEVICE_CONNECTED | byte(cmd.Device.Attrs.DeviceType))
|
|
} else {
|
|
buf.WriteByte(PQ_DEVICE_NOT_CONNECT | byte(cmd.Device.Attrs.DeviceType))
|
|
}
|
|
//byte 1
|
|
//PAGE CODE
|
|
buf.WriteByte(0x83)
|
|
//PAGE LENGTH
|
|
binary.Write(buf, binary.BigEndian, pageLength)
|
|
buf.Write(data)
|
|
return buf, pageLength
|
|
}
|
|
|
|
func InquiryPage0xB0(host int, cmd *api.SCSICommand) (*bytes.Buffer, uint16) {
|
|
var (
|
|
buf = &bytes.Buffer{}
|
|
pageLength uint16 = 0x3C
|
|
maxUnmapLbaCount uint32 = 0
|
|
maxUnmapBlockDescriptorCount uint32 = 0
|
|
)
|
|
|
|
if cmd.Device.Attrs.ThinProvisioning {
|
|
maxUnmapLbaCount = 0xFFFFFFFF
|
|
maxUnmapBlockDescriptorCount = 16
|
|
}
|
|
|
|
//byte 0
|
|
if cmd.Device.Attrs.Online {
|
|
buf.WriteByte(PQ_DEVICE_CONNECTED | byte(cmd.Device.Attrs.DeviceType))
|
|
} else {
|
|
buf.WriteByte(PQ_DEVICE_NOT_CONNECT | byte(cmd.Device.Attrs.DeviceType))
|
|
}
|
|
//PAGE CODE
|
|
buf.WriteByte(0xB0)
|
|
//PAGE LENGTH
|
|
binary.Write(buf, binary.BigEndian, pageLength)
|
|
|
|
buf.Write(make([]byte, 16))
|
|
|
|
//MAXIMUM UNMAP LBA COUNT
|
|
binary.Write(buf, binary.BigEndian, maxUnmapLbaCount)
|
|
|
|
//MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT
|
|
binary.Write(buf, binary.BigEndian, maxUnmapBlockDescriptorCount)
|
|
|
|
buf.Write(make([]byte, 36))
|
|
return buf, pageLength
|
|
}
|
|
|
|
func InquiryPage0xB1(host int, cmd *api.SCSICommand) (*bytes.Buffer, uint16) {
|
|
var (
|
|
buf = &bytes.Buffer{}
|
|
pageLength uint16 = 0x3C // 60 bytes
|
|
)
|
|
|
|
//byte 0
|
|
if cmd.Device.Attrs.Online {
|
|
buf.WriteByte(PQ_DEVICE_CONNECTED | byte(cmd.Device.Attrs.DeviceType))
|
|
} else {
|
|
buf.WriteByte(PQ_DEVICE_NOT_CONNECT | byte(cmd.Device.Attrs.DeviceType))
|
|
}
|
|
//PAGE CODE
|
|
buf.WriteByte(0xB1)
|
|
//PAGE LENGTH
|
|
binary.Write(buf, binary.BigEndian, pageLength)
|
|
|
|
// MEDIA ROTATION RATE (bytes 4-5)
|
|
// 0x0001 = Non-rotating medium (SSD)
|
|
binary.Write(buf, binary.BigEndian, uint16(0x0001))
|
|
|
|
// Reserved bytes (6-63)
|
|
buf.Write(make([]byte, 58))
|
|
|
|
return buf, pageLength
|
|
}
|
|
|
|
func InquiryPage0xB2(host int, cmd *api.SCSICommand) (*bytes.Buffer, uint16) {
|
|
var (
|
|
buf = &bytes.Buffer{}
|
|
pageLength uint16 = 0x4
|
|
)
|
|
|
|
//byte 0
|
|
if cmd.Device.Attrs.Online {
|
|
buf.WriteByte(PQ_DEVICE_CONNECTED | byte(cmd.Device.Attrs.DeviceType))
|
|
} else {
|
|
buf.WriteByte(PQ_DEVICE_NOT_CONNECT | byte(cmd.Device.Attrs.DeviceType))
|
|
}
|
|
//PAGE CODE
|
|
buf.WriteByte(0xB2)
|
|
|
|
//PAGE LENGTH
|
|
binary.Write(buf, binary.BigEndian, pageLength)
|
|
|
|
var lbpu byte
|
|
if cmd.Device.Attrs.ThinProvisioning {
|
|
lbpu = 1 << 7
|
|
}
|
|
|
|
// THRESHOLD EXPONENT
|
|
buf.WriteByte(0)
|
|
|
|
// LBPU | LBPWS | LBPWS10 | LBPRZ | ANC_SUP | DP
|
|
buf.WriteByte(lbpu)
|
|
|
|
// MINIMUM PERCENTAGE | PROVISIONING TYPE
|
|
buf.WriteByte(0)
|
|
|
|
// THRESHOLD PERCENTAGE
|
|
buf.WriteByte(0)
|
|
|
|
return buf, pageLength
|
|
}
|
|
|
|
/*
|
|
* SPCInquiry Implements SCSI INQUIRY command
|
|
* The INQUIRY command requests the device server to return information regarding the logical unit and SCSI target device.
|
|
*
|
|
* Reference : SPC4r11
|
|
* 6.6 - INQUIRY
|
|
*/
|
|
func SPCInquiry(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
allocationLength uint16
|
|
additionLength byte
|
|
buf = &bytes.Buffer{}
|
|
data []byte = []byte{}
|
|
addBuf = &bytes.Buffer{}
|
|
addBufData []byte = []byte{}
|
|
//b byte = 0x75
|
|
scb []byte = cmd.SCB
|
|
pcode byte = scb[2]
|
|
evpd bool = false
|
|
|
|
vendorID = make([]byte, 8)
|
|
productID = make([]byte, 16)
|
|
productRev = make([]byte, 4)
|
|
)
|
|
|
|
allocationLength = util.GetUnalignedUint16(scb[3:5])
|
|
|
|
if scb[1]&0x01 > 0 {
|
|
evpd = true
|
|
}
|
|
|
|
if cmd.Device == nil {
|
|
goto sense
|
|
}
|
|
|
|
if evpd {
|
|
switch pcode {
|
|
case 0x00:
|
|
buf, _ = InquiryPage0x00(host, cmd)
|
|
case 0x80:
|
|
buf, _ = InquiryPage0x80(host, cmd)
|
|
case 0x83:
|
|
buf, _ = InquiryPage0x83(host, cmd)
|
|
case 0xB0:
|
|
buf, _ = InquiryPage0xB0(host, cmd)
|
|
case 0xB1:
|
|
buf, _ = InquiryPage0xB1(host, cmd)
|
|
case 0xB2:
|
|
buf, _ = InquiryPage0xB2(host, cmd)
|
|
default:
|
|
goto sense
|
|
}
|
|
data = buf.Bytes()
|
|
if int(allocationLength) < len(data) {
|
|
copy(cmd.InSDBBuffer.Buffer, data[0:allocationLength])
|
|
cmd.InSDBBuffer.Resid = uint32(len(data))
|
|
} else {
|
|
copy(cmd.InSDBBuffer.Buffer, data[0:])
|
|
}
|
|
} else {
|
|
if pcode != 0 {
|
|
goto sense
|
|
}
|
|
//byte 5
|
|
//SCCS(0) AAC(0) TPGS(0) 3PC(0) PROTECT(0)
|
|
if EnableMultipath {
|
|
addBuf.WriteByte(INQUIRY_TPGS_IMPLICIT)
|
|
} else {
|
|
addBuf.WriteByte(INQUIRY_TPGS_NO)
|
|
}
|
|
//byte 6
|
|
//ENCSERV(0) VS(0) MULTIP(0) ADDR16(0)
|
|
addBuf.WriteByte(0x00)
|
|
//byte 7
|
|
//WBUS16(0) SYNC(0) CMDQUE(1) VS1(0)
|
|
addBuf.WriteByte(INQUIRY_CMDQUE)
|
|
|
|
copy(vendorID, []byte(fmt.Sprintf("%-8s", cmd.Device.Attrs.VendorID)))
|
|
addBuf.Write(vendorID)
|
|
|
|
copy(productID, []byte(fmt.Sprintf("%-16s", cmd.Device.Attrs.ProductID)))
|
|
addBuf.Write(productID)
|
|
|
|
copy(productRev, []byte(fmt.Sprintf("%-4s", cmd.Device.Attrs.ProductRev)))
|
|
addBuf.Write(productRev)
|
|
//Vendor specific(20 bytes)
|
|
for i := 0; i < 20; i++ {
|
|
addBuf.WriteByte(0x00)
|
|
}
|
|
//byte 56
|
|
addBuf.WriteByte(0x00)
|
|
//byte 57
|
|
addBuf.WriteByte(0x00)
|
|
//VERSION DESCRIPTOR 1 ~ 8
|
|
binary.Write(addBuf, binary.BigEndian, cmd.Device.Attrs.VersionDesction)
|
|
|
|
addBufData = addBuf.Bytes()
|
|
additionLength = byte(len(addBufData))
|
|
|
|
//Write header
|
|
//byte 0
|
|
//PERIPHERAL QUALIFIER, PERIPHERAL DEVICE TYPE
|
|
if cmd.Device.Attrs.Online {
|
|
buf.WriteByte(PQ_DEVICE_CONNECTED | byte(cmd.Device.Attrs.DeviceType))
|
|
} else {
|
|
buf.WriteByte(PQ_DEVICE_NOT_CONNECT | byte(cmd.Device.Attrs.DeviceType))
|
|
}
|
|
// byte 1
|
|
// RMB(0) LU_CONG(0)
|
|
buf.WriteByte(0x00)
|
|
// byte 2
|
|
// VERSION
|
|
buf.WriteByte(VERSION_WITHDRAW_SPC3)
|
|
// byte 3
|
|
// Reserved, Reserved, NORMACA, HISUP, RESPONSE DATA FORMAT
|
|
buf.WriteByte(INQUIRY_HISUP | INQUIRY_STANDARD_FORMAT)
|
|
// byte 4
|
|
// ADDITIONAL LENGTH
|
|
buf.WriteByte(additionLength)
|
|
|
|
buf.Write(addBufData)
|
|
data = buf.Bytes()
|
|
if allocationLength < uint16(additionLength) {
|
|
cmd.InSDBBuffer.Resid = uint32(allocationLength)
|
|
copy(cmd.InSDBBuffer.Buffer, data[0:allocationLength])
|
|
} else {
|
|
cmd.InSDBBuffer.Resid = uint32(len(data))
|
|
copy(cmd.InSDBBuffer.Buffer, data[0:])
|
|
}
|
|
}
|
|
|
|
return api.SAMStatGood
|
|
sense:
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
/*
|
|
* SPCReportLuns Implements SCSI REPORT LUNS command
|
|
* The REPORT LUNS command requests the device server to return the peripheral Device
|
|
* logical unit inventory accessible to the I_T nexus.
|
|
*
|
|
* Reference : SPC4r11
|
|
* 6.33 - REPORT LUNS
|
|
*/
|
|
func SPCReportLuns(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
remainLength uint32
|
|
availLength uint32 = 0
|
|
allocationLength uint32
|
|
buf *bytes.Buffer = &bytes.Buffer{}
|
|
)
|
|
// Get Allocation Length
|
|
allocationLength = util.GetUnalignedUint32(cmd.SCB[6:10])
|
|
if allocationLength < 16 {
|
|
log.Warn("goto sense, allocationLength < 16")
|
|
goto sense
|
|
}
|
|
|
|
remainLength = allocationLength
|
|
if _, ok := cmd.Target.Devices[0]; !ok {
|
|
availLength = 8 * uint32(len(cmd.Target.Devices)+1)
|
|
} else {
|
|
availLength = 8 * uint32(len(cmd.Target.Devices))
|
|
}
|
|
|
|
// LUN list length
|
|
buf.Write(util.MarshalUint32(availLength))
|
|
cmd.InSDBBuffer.Resid = allocationLength
|
|
|
|
// Skip through to byte 4, Reserved
|
|
for i := 0; i < 4; i++ {
|
|
buf.WriteByte(0x00)
|
|
}
|
|
|
|
//For LUN0
|
|
if _, ok := cmd.Target.Devices[0]; !ok {
|
|
buf.Write(util.MarshalUint64(0))
|
|
remainLength -= 8
|
|
}
|
|
|
|
for lun := range cmd.Target.Devices {
|
|
if remainLength > 0 {
|
|
if lun > 0xff {
|
|
lun = (0x01 << 30) | (0x3fff&lun)<<16
|
|
} else {
|
|
lun = (0x3fff & lun) << 16
|
|
}
|
|
lun = uint64(lun << 32)
|
|
|
|
buf.Write(util.MarshalUint64(lun))
|
|
remainLength -= 8
|
|
}
|
|
}
|
|
|
|
copy(cmd.InSDBBuffer.Buffer, buf.Bytes())
|
|
return api.SAMStatGood
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCStartStop(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
pwrcnd, loej, start byte
|
|
)
|
|
if err := deviceReserve(cmd); err != nil {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
scb := cmd.SCB
|
|
pwrcnd = scb[4] & 0xf0
|
|
if pwrcnd != 0 {
|
|
return api.SAMStatGood
|
|
}
|
|
|
|
loej = scb[4] & 0x02
|
|
start = scb[4] & 0x01
|
|
|
|
if loej != 0 && start == 0 && cmd.Device.Attrs.Removable {
|
|
if luPreventRemoval(cmd.Device) {
|
|
if cmd.Device.Attrs.Online {
|
|
// online == media is present
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_MEDIUM_REMOVAL_PREVENTED)
|
|
} else {
|
|
// !online == media is not present
|
|
BuildSenseData(cmd, NOT_READY, ASC_MEDIUM_REMOVAL_PREVENTED)
|
|
}
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
SPCLuOffline(cmd.Device)
|
|
}
|
|
if loej != 0 && start != 0 && cmd.Device.Attrs.Removable {
|
|
SPCLuOnline(cmd.Device)
|
|
}
|
|
|
|
return api.SAMStatGood
|
|
}
|
|
|
|
/*
|
|
* SPCTestUnit Implements SCSI TEST UNIT READY command
|
|
* The TEST UNIT READY command requests the device server to indicate whether the logical unit is ready.
|
|
*
|
|
* Reference : SPC4r11
|
|
* 6.47 - TEST UNIT READY
|
|
*/
|
|
func SPCTestUnit(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
/*
|
|
if err := deviceReserve(cmd); err != nil {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
*/
|
|
if cmd.Device.Attrs.Online {
|
|
return api.SAMStatGood
|
|
}
|
|
if cmd.Device.Attrs.Removable {
|
|
BuildSenseData(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT)
|
|
} else {
|
|
BuildSenseData(cmd, NOT_READY, ASC_BECOMING_READY)
|
|
}
|
|
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCPreventAllowMediaRemoval(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
if err := deviceReserve(cmd); err != nil {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
// PREVENT_MASK = 0x03
|
|
cmd.ITNexusLuInfo.Prevent = int(cmd.SCB[4] & 0x03)
|
|
return api.SAMStatGood
|
|
}
|
|
|
|
/*
|
|
* SPCModeSense Implement SCSI MODE SENSE(6) and MODE SENSE(10) command
|
|
* The MODE SENSE command requests the device server to return the specified medium,
|
|
* logical unit, or peripheral device parameters.
|
|
*
|
|
* Reference : SPC4r11
|
|
* 6.11 - MODE SENSE(6)
|
|
* 6.12 - MODE SENSE(10)
|
|
*/
|
|
func SPCModeSense(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
scb = cmd.SCB
|
|
mode6 = (scb[0] == 0x1a)
|
|
dbd = scb[1] & 0x8 // Disable Block Descriptors
|
|
pcode = scb[2] & 0x3f
|
|
pctrl = (scb[2] & 0xc0) >> 6
|
|
subpcode = scb[3]
|
|
blkDesctionLen = 0
|
|
key = ILLEGAL_REQUEST
|
|
asc = ASC_INVALID_FIELD_IN_CDB
|
|
data []byte
|
|
allocLen uint32
|
|
)
|
|
|
|
if dbd == 0 {
|
|
blkDesctionLen = 8
|
|
}
|
|
if pctrl == 3 {
|
|
asc = ASC_SAVING_PARMS_UNSUP
|
|
goto sense
|
|
}
|
|
if mode6 {
|
|
allocLen = uint32(scb[4])
|
|
// set header (4 bytes)
|
|
// byte 0: Mode Data Length
|
|
// byte 1: Medium Type
|
|
// byte 2: Device-Specific Parameter (DPOFUA=bit4)
|
|
// byte 3: Block Descriptor Length
|
|
data = append(data, 0x00) // Mode Data Length (filled later)
|
|
data = append(data, 0x00) // Medium Type
|
|
data = append(data, 0x10) // Device-Specific Parameter (DPOFUA=1)
|
|
data = append(data, 0x00) // Block Descriptor Length (filled later)
|
|
} else {
|
|
allocLen = uint32(util.GetUnalignedUint16(scb[7:9]))
|
|
// set header (8 bytes)
|
|
// byte 0-1: Mode Data Length
|
|
// byte 2: Medium Type
|
|
// byte 3: Device-Specific Parameter (DPOFUA=bit4)
|
|
// byte 4-5: Reserved
|
|
// byte 6-7: Block Descriptor Length
|
|
data = append(data, 0x00) // Mode Data Length (MSB, filled later)
|
|
data = append(data, 0x00) // Mode Data Length (LSB, filled later)
|
|
data = append(data, 0x00) // Medium Type
|
|
data = append(data, 0x10) // Device-Specific Parameter (DPOFUA=1)
|
|
data = append(data, 0x00) // Reserved
|
|
data = append(data, 0x00) // Reserved
|
|
data = append(data, 0x00) // Block Descriptor Length (MSB, filled later)
|
|
data = append(data, 0x00) // Block Descriptor Length (LSB, filled later)
|
|
}
|
|
if dbd == 0 {
|
|
data = append(data, cmd.Device.ModeBlockDescriptor...)
|
|
}
|
|
if pcode == 0x3f {
|
|
for _, pg := range cmd.Device.ModePages {
|
|
if pg.SubPageCode == 0 {
|
|
data = append(data, pg.PageCode)
|
|
data = append(data, byte(pg.Size))
|
|
} else {
|
|
data = append(data, pg.PageCode|0x40)
|
|
data = append(data, pg.SubPageCode)
|
|
data = append(data, byte((pg.Size>>8)&0xff))
|
|
data = append(data, byte(pg.Size&0xff))
|
|
}
|
|
if pctrl == 1 {
|
|
data = append(data, pg.Data[pg.Size:]...)
|
|
} else {
|
|
data = append(data, pg.Data[:pg.Size]...)
|
|
}
|
|
}
|
|
} else {
|
|
var pg *api.ModePage
|
|
for _, p := range cmd.Device.ModePages {
|
|
if p.PageCode == pcode && p.SubPageCode == subpcode {
|
|
pg = &p
|
|
break
|
|
}
|
|
}
|
|
if pg == nil {
|
|
goto sense
|
|
}
|
|
if pg.SubPageCode == 0 {
|
|
data = append(data, pg.PageCode)
|
|
data = append(data, byte(pg.Size))
|
|
if pctrl == 1 {
|
|
data = append(data, pg.Data[pg.Size:]...)
|
|
} else {
|
|
data = append(data, pg.Data[:pg.Size]...)
|
|
}
|
|
} else {
|
|
data = append(data, pg.PageCode|0x40)
|
|
data = append(data, pg.SubPageCode)
|
|
data = append(data, byte((pg.Size>>8)&0xff))
|
|
data = append(data, byte(pg.Size&0xff))
|
|
if pctrl == 1 {
|
|
data = append(data, pg.Data[pg.Size:]...)
|
|
} else {
|
|
data = append(data, pg.Data[:pg.Size]...)
|
|
}
|
|
}
|
|
}
|
|
if mode6 {
|
|
data[0] = uint8(len(data) - 1)
|
|
data[3] = uint8(blkDesctionLen)
|
|
} else {
|
|
data[0] = uint8((len(data) - 2) >> 8)
|
|
data[1] = uint8(len(data) - 2)
|
|
data[6] = uint8(blkDesctionLen >> 8)
|
|
data[7] = uint8(blkDesctionLen)
|
|
}
|
|
if rlen := uint32(len(data)); rlen > allocLen {
|
|
cmd.InSDBBuffer.Resid = allocLen
|
|
}
|
|
copy(cmd.InSDBBuffer.Buffer, data)
|
|
return api.SAMStatGood
|
|
sense:
|
|
BuildSenseData(cmd, key, asc)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
/*
|
|
* SPCSendDiagnostics Implements SCSI SEND DIAGNOSTIC command
|
|
* The SEND DIAGNOSTIC command requests the device server to perform diagnostic operations
|
|
* on the SCSI target device, on the logical unit, or on both.
|
|
*
|
|
* Reference : SPC4r11
|
|
* 6.42 - SEND DIAGNOSTIC
|
|
*/
|
|
func SPCSendDiagnostics(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
// we only support SELF-TEST==1
|
|
if cmd.SCB[1]&0x04 == 0 {
|
|
goto sense
|
|
}
|
|
|
|
return api.SAMStatGood
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func getSCSICmdSize(opcode api.SCSICommandType) byte {
|
|
var scsi_command_size = []byte{6, 10, 10, 12, 16, 12, 10, 10}
|
|
|
|
return scsi_command_size[(byte(opcode)>>5)&7]
|
|
}
|
|
|
|
func reportOpcodesAll(cmd *api.SCSICommand, rctd int) error {
|
|
var (
|
|
data = []byte{0x00, 0x00, 0x00, 0x00}
|
|
)
|
|
for _, i := range []api.SCSICommandType{api.TEST_UNIT_READY, api.WRITE_6, api.INQUIRY, api.READ_CAPACITY, api.WRITE_10, api.WRITE_16, api.REPORT_LUNS, api.WRITE_12, api.UNMAP} {
|
|
data = append(data, byte(i))
|
|
// reserved
|
|
data = append(data, 0x00)
|
|
// service action
|
|
data = append(data, 0x00)
|
|
data = append(data, 0x00)
|
|
// reserved
|
|
data = append(data, 0x00)
|
|
// flags: no service action, possibly timeout desc
|
|
if rctd != 0 {
|
|
data = append(data, 0x08)
|
|
} else {
|
|
data = append(data, 0x08)
|
|
}
|
|
// cdb length
|
|
length := getSCSICmdSize(i)
|
|
data = append(data, 0)
|
|
data = append(data, length&0xff)
|
|
// timeout descriptor (if rctd is set) - 12 bytes (all zeros)
|
|
if rctd != 0 {
|
|
// length == 0x0a
|
|
data[1] = 0x0a
|
|
for n := 0; n < 12; n++ {
|
|
data = append(data, 0x00)
|
|
}
|
|
}
|
|
}
|
|
copy(cmd.InSDBBuffer.Buffer, util.MarshalUint32(uint32(len(data)-4)))
|
|
copy(cmd.InSDBBuffer.Buffer[4:], data[4:])
|
|
return nil
|
|
}
|
|
|
|
func reportOpcodeOne(cmd *api.SCSICommand, rctd int, opcode byte, rsa uint16, serviceAction bool) error {
|
|
var data = []byte{0x00, 0x00, 0x00, 0x00}
|
|
|
|
// Support common opcodes that are tested by libiscsi
|
|
switch api.SCSICommandType(opcode) {
|
|
case api.READ_6, api.READ_10, api.READ_12, api.READ_16,
|
|
api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16,
|
|
api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16,
|
|
api.INQUIRY, api.TEST_UNIT_READY, api.READ_CAPACITY,
|
|
api.VERIFY_10, api.VERIFY_12, api.VERIFY_16:
|
|
// For RCTD=0, libiscsi expects:
|
|
// data[0:4]: list length
|
|
// data[4:20]: CDB usage data (16 bytes)
|
|
// libiscsi reads ctdp from data[1], cdb_length from data[2:4]
|
|
// and copies data[4:4+cdb_length] to cdb_usage_data
|
|
//
|
|
// So we need to format data as:
|
|
// data[4]: opcode (CDB usage data byte 0)
|
|
// data[5]: byte 1 with DPO/FUA bits
|
|
// data[6:20]: remaining CDB usage data bytes
|
|
|
|
// CDB usage data (16 bytes) - describes the CDB format
|
|
cdbUsageData := make([]byte, 16)
|
|
cdbUsageData[0] = opcode // byte 0: opcode
|
|
// byte 1: RDPROTECT(7-5) | DPO(4) | FUA(3) | ...
|
|
// Set DPO(0x10) | FUA(0x08) = 0x18 for READ/WRITE/VERIFY/WRITE_VERIFY
|
|
if opcode == 0x28 || opcode == 0x2A || opcode == 0x2F || // READ10, WRITE10, VERIFY10
|
|
opcode == 0xA8 || opcode == 0xAA || opcode == 0xAF || // READ12, WRITE12, VERIFY12
|
|
opcode == 0x88 || opcode == 0x8A || opcode == 0x8F || // READ16, WRITE16, VERIFY16
|
|
opcode == 0x2E || opcode == 0xAE || opcode == 0x8E { // WRITE_VERIFY, WRITE_VERIFY_12, WRITE_VERIFY_16
|
|
cdbUsageData[1] = 0x18 // DPO | FUA
|
|
}
|
|
data = append(data, cdbUsageData...)
|
|
|
|
// timeout descriptor (if rctd is set) - 12 bytes (all zeros)
|
|
if rctd != 0 {
|
|
for n := 0; n < 12; n++ {
|
|
data = append(data, 0x00)
|
|
}
|
|
}
|
|
default:
|
|
return fmt.Errorf("opcode: %02xh not supported in report one", opcode)
|
|
}
|
|
|
|
// Update list length (total bytes after the length field)
|
|
copy(cmd.InSDBBuffer.Buffer, util.MarshalUint32(uint32(len(data)-4)))
|
|
copy(cmd.InSDBBuffer.Buffer[4:], data[4:])
|
|
return nil
|
|
}
|
|
|
|
func SPCReportSupportedOperationCodes(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
scb := cmd.SCB
|
|
reporting_options := scb[2] & 0x07
|
|
opcode := scb[3]
|
|
rctd := int(scb[2] & 0x80)
|
|
rsa := util.GetUnalignedUint16(scb[4:])
|
|
switch reporting_options {
|
|
case 0x00: /* report all */
|
|
log.Debugf("Service Action: report all")
|
|
err := reportOpcodesAll(cmd, rctd)
|
|
if err != nil {
|
|
log.Error(err)
|
|
goto sense
|
|
}
|
|
case 0x01: /* report one no service action*/
|
|
log.Debugf("Service Action: report one no service action")
|
|
err := reportOpcodeOne(cmd, rctd, opcode, rsa, false)
|
|
if err != nil {
|
|
log.Error(err)
|
|
goto sense
|
|
}
|
|
case 0x02: /* report one service action */
|
|
log.Debugf("Service Action: report one service action")
|
|
err := reportOpcodeOne(cmd, rctd, opcode, rsa, true)
|
|
if err != nil {
|
|
log.Error(err)
|
|
goto sense
|
|
}
|
|
default:
|
|
goto sense
|
|
}
|
|
return api.SAMStatGood
|
|
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
// This is useful for the various commands using the SERVICE ACTION format.
|
|
func SPCServiceAction(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
|
|
scb := cmd.SCB
|
|
opcode := int(scb[0])
|
|
action := uint8(scb[1] & 0x1F)
|
|
serviceAction := cmd.Device.DeviceProtocol.PerformServiceAction(opcode, action)
|
|
if serviceAction != nil {
|
|
fnop := serviceAction.(*SCSIServiceAction)
|
|
return fnop.CommandPerformFunc(host, cmd)
|
|
}
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCPRReadKeys(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
addBuf []byte = []byte{}
|
|
allocationLength uint16
|
|
additionLength uint32
|
|
l int = 0
|
|
)
|
|
tgtName := cmd.Target.Name
|
|
devUUID := cmd.Device.UUID
|
|
scsiResOp := GetSCSIReservationOperator()
|
|
PRGeneration, _ := scsiResOp.GetPRGeneration(tgtName, devUUID)
|
|
resList := scsiResOp.GetReservationList(tgtName, devUUID)
|
|
length, _ := SCSICDBBufXLength(cmd.SCB)
|
|
allocationLength = uint16(length)
|
|
if allocationLength < 8 {
|
|
goto sense
|
|
}
|
|
|
|
for _, res := range resList {
|
|
addBuf = append(addBuf, util.MarshalUint64(res.Key)...)
|
|
}
|
|
additionLength = uint32(len(addBuf))
|
|
|
|
l += copy(cmd.InSDBBuffer.Buffer[l:], util.MarshalUint32(PRGeneration))
|
|
l += copy(cmd.InSDBBuffer.Buffer[l:], util.MarshalUint32(additionLength))
|
|
l += copy(cmd.InSDBBuffer.Buffer[l:], addBuf)
|
|
|
|
cmd.InSDBBuffer.Resid = uint32(additionLength)
|
|
return api.SAMStatGood
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCPRReadReservation(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
buf = &bytes.Buffer{}
|
|
data []byte = []byte{}
|
|
addBuf = &bytes.Buffer{}
|
|
allocationLength uint16
|
|
additionLength uint32
|
|
)
|
|
tgtName := cmd.Target.Name
|
|
devUUID := cmd.Device.UUID
|
|
scsiResOp := GetSCSIReservationOperator()
|
|
PRGeneration, _ := scsiResOp.GetPRGeneration(tgtName, devUUID)
|
|
curRes := scsiResOp.GetCurrentReservation(tgtName, devUUID)
|
|
|
|
length, _ := SCSICDBBufXLength(cmd.SCB)
|
|
allocationLength = uint16(length)
|
|
if allocationLength < 8 {
|
|
goto sense
|
|
}
|
|
|
|
if curRes == nil {
|
|
additionLength = 0
|
|
} else {
|
|
addBuf.Write(util.MarshalUint64(curRes.Key))
|
|
//Obsolete
|
|
addBuf.WriteByte(0x00)
|
|
addBuf.WriteByte(0x00)
|
|
addBuf.WriteByte(0x00)
|
|
addBuf.WriteByte(0x00)
|
|
//Reserved
|
|
addBuf.WriteByte(0x00)
|
|
//SCOPE and TYPE
|
|
scope_type := (curRes.Scope << 4) | curRes.Type
|
|
addBuf.WriteByte(scope_type)
|
|
additionLength = uint32(0x10)
|
|
}
|
|
|
|
buf.Write(util.MarshalUint32(PRGeneration))
|
|
buf.Write(util.MarshalUint32(additionLength))
|
|
buf.Write(addBuf.Bytes())
|
|
data = buf.Bytes()
|
|
if allocationLength < uint16(additionLength) {
|
|
copy(cmd.InSDBBuffer.Buffer, data[0:allocationLength])
|
|
} else {
|
|
copy(cmd.InSDBBuffer.Buffer, data)
|
|
}
|
|
|
|
cmd.InSDBBuffer.Resid = uint32(additionLength)
|
|
return api.SAMStatGood
|
|
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCPRReportCapabilities(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
availLength uint32 = 8
|
|
actualLength uint32 = 0
|
|
)
|
|
allocationLength := uint32(util.GetUnalignedUint16(cmd.SCB[7:9]))
|
|
if allocationLength < 8 {
|
|
goto sense
|
|
}
|
|
if cmd.InSDBBuffer.Length < allocationLength {
|
|
goto sense
|
|
}
|
|
copy(cmd.InSDBBuffer.Buffer, util.MarshalUint16(uint16(8)))
|
|
// Persistent Reservation Type Mask format
|
|
// Type Mask Valid (TMV)
|
|
cmd.InSDBBuffer.Buffer[3] |= 0x80
|
|
// PR_TYPE_EXCLUSIVE_ACCESS_ALLREG
|
|
cmd.InSDBBuffer.Buffer[4] |= 0x80
|
|
// PR_TYPE_EXCLUSIVE_ACCESS_REGONLY
|
|
cmd.InSDBBuffer.Buffer[4] |= 0x40
|
|
// PR_TYPE_WRITE_EXCLUSIVE_REGONLY
|
|
cmd.InSDBBuffer.Buffer[4] |= 0x20
|
|
// PR_TYPE_EXCLUSIVE_ACCESS
|
|
cmd.InSDBBuffer.Buffer[4] |= 0x08
|
|
// PR_TYPE_WRITE_EXCLUSIVE
|
|
cmd.InSDBBuffer.Buffer[4] |= 0x02
|
|
// PR_TYPE_EXCLUSIVE_ACCESS_ALLREG
|
|
cmd.InSDBBuffer.Buffer[5] |= 0x01
|
|
|
|
actualLength = availLength
|
|
cmd.InSDBBuffer.Resid = uint32(actualLength)
|
|
return api.SAMStatGood
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func reservationCheck(host int, cmd *api.SCSICommand) bool {
|
|
var (
|
|
paramLen uint32
|
|
buf []byte = cmd.OutSDBBuffer.Buffer
|
|
)
|
|
length, _ := SCSICDBBufXLength(cmd.SCB)
|
|
paramLen = uint32(length)
|
|
if paramLen != 24 {
|
|
return false
|
|
}
|
|
spec_i_pt := uint8(buf[20] & 0x08)
|
|
all_tg_pt := uint8(buf[20] & 0x04)
|
|
aptpl := uint8(buf[20] & 0x01)
|
|
/* Currently, We don't support these flags */
|
|
if (spec_i_pt | all_tg_pt | aptpl) > 0 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func SPCPRRegister(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
buf []byte = cmd.OutSDBBuffer.Buffer
|
|
scb []byte = cmd.SCB
|
|
ignoreKey bool = false
|
|
ok bool = false
|
|
resKey uint64
|
|
sAResKey uint64
|
|
)
|
|
|
|
tgtName := cmd.Target.Name
|
|
devUUID := cmd.Device.UUID
|
|
scsiResOp := GetSCSIReservationOperator()
|
|
res := scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID)
|
|
|
|
if scb[1] == PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY {
|
|
ignoreKey = true
|
|
}
|
|
ok = reservationCheck(host, cmd)
|
|
if !ok {
|
|
goto sense
|
|
}
|
|
resKey = util.GetUnalignedUint64(buf[0:8])
|
|
sAResKey = util.GetUnalignedUint64(buf[8:16])
|
|
|
|
if res != nil {
|
|
if ignoreKey || resKey == res.Key {
|
|
if sAResKey != 0 {
|
|
res.Key = sAResKey
|
|
} else {
|
|
scsiResOp.DeleteAndRemoveReservation(tgtName, devUUID, res)
|
|
}
|
|
} else {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
} else {
|
|
if ignoreKey || resKey == 0 {
|
|
if sAResKey != 0 {
|
|
newRes := &api.SCSIReservation{
|
|
ID: uuid.New(),
|
|
Key: sAResKey,
|
|
ITNexusID: cmd.ITNexusID,
|
|
}
|
|
scsiResOp.AddReservation(tgtName, devUUID, newRes)
|
|
}
|
|
} else {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
}
|
|
scsiResOp.IncPRGeneration(tgtName, devUUID)
|
|
scsiResOp.Save(tgtName, devUUID)
|
|
return api.SAMStatGood
|
|
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCPRReserve(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
scb []byte = cmd.SCB
|
|
curRes *api.SCSIReservation
|
|
res *api.SCSIReservation
|
|
ok bool = false
|
|
resScope uint8
|
|
resType uint8
|
|
)
|
|
|
|
tgtName := cmd.Target.Name
|
|
devUUID := cmd.Device.UUID
|
|
scsiResOp := GetSCSIReservationOperator()
|
|
|
|
ok = reservationCheck(host, cmd)
|
|
if !ok {
|
|
goto sense
|
|
}
|
|
|
|
resScope = scb[2] & 0xf0 >> 4
|
|
resType = scb[2] & 0x0f
|
|
|
|
switch resType {
|
|
case PR_TYPE_WRITE_EXCLUSIVE_REGONLY,
|
|
PR_TYPE_EXCLUSIVE_ACCESS_REGONLY,
|
|
PR_TYPE_WRITE_EXCLUSIVE_ALLREG,
|
|
PR_TYPE_EXCLUSIVE_ACCESS_ALLREG:
|
|
break
|
|
default:
|
|
goto sense
|
|
}
|
|
if resScope != PR_LU_SCOPE {
|
|
goto sense
|
|
}
|
|
|
|
res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID)
|
|
if res == nil {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID)
|
|
if curRes != nil {
|
|
if !scsiResOp.IsCurrentReservation(tgtName, devUUID, res) {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
if curRes.Type != resType ||
|
|
curRes.Scope != resScope {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
}
|
|
res.Scope = resScope
|
|
res.Type = resType
|
|
scsiResOp.SetCurrentReservation(tgtName, devUUID, res)
|
|
scsiResOp.Save(tgtName, devUUID)
|
|
return api.SAMStatGood
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCPRRelease(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
buf []byte = cmd.OutSDBBuffer.Buffer
|
|
scb []byte = cmd.SCB
|
|
curRes *api.SCSIReservation
|
|
res *api.SCSIReservation
|
|
resList []*api.SCSIReservation
|
|
ok bool = false
|
|
resKey uint64
|
|
resScope uint8
|
|
resType uint8
|
|
)
|
|
|
|
tgtName := cmd.Target.Name
|
|
devUUID := cmd.Device.UUID
|
|
scsiResOp := GetSCSIReservationOperator()
|
|
|
|
ok = reservationCheck(host, cmd)
|
|
if !ok {
|
|
goto sense
|
|
}
|
|
|
|
resScope = scb[2] & 0xf0 >> 4
|
|
resType = scb[2] & 0x0f
|
|
resKey = util.GetUnalignedUint64(buf[0:8])
|
|
|
|
res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID)
|
|
if res == nil {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID)
|
|
if curRes == nil {
|
|
return api.SAMStatGood
|
|
}
|
|
|
|
if !scsiResOp.IsCurrentReservation(tgtName, devUUID, res) {
|
|
return api.SAMStatGood
|
|
}
|
|
|
|
if resKey != res.Key {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
if curRes.Scope != resScope || curRes.Type != resType {
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_RELEASE_OF_PERSISTENT_RESERVATION)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
scsiResOp.SetCurrentReservation(tgtName, devUUID, nil)
|
|
res.Scope = 0
|
|
res.Type = 0
|
|
|
|
switch resType {
|
|
case PR_TYPE_WRITE_EXCLUSIVE,
|
|
PR_TYPE_EXCLUSIVE_ACCESS,
|
|
PR_TYPE_WRITE_EXCLUSIVE_REGONLY,
|
|
PR_TYPE_EXCLUSIVE_ACCESS_REGONLY,
|
|
PR_TYPE_WRITE_EXCLUSIVE_ALLREG,
|
|
PR_TYPE_EXCLUSIVE_ACCESS_ALLREG:
|
|
break
|
|
default:
|
|
goto sense
|
|
}
|
|
|
|
resList = scsiResOp.GetReservationList(tgtName, devUUID)
|
|
for _, tmpRes := range resList {
|
|
if tmpRes.ID == res.ID {
|
|
continue
|
|
}
|
|
//TODO send sense code
|
|
}
|
|
scsiResOp.Save(tgtName, devUUID)
|
|
return api.SAMStatGood
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCPRClear(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
buf []byte = cmd.OutSDBBuffer.Buffer
|
|
curRes *api.SCSIReservation
|
|
res *api.SCSIReservation
|
|
ok bool = false
|
|
resKey uint64
|
|
)
|
|
|
|
tgtName := cmd.Target.Name
|
|
devUUID := cmd.Device.UUID
|
|
scsiResOp := GetSCSIReservationOperator()
|
|
resList := scsiResOp.GetReservationList(tgtName, devUUID)
|
|
|
|
ok = reservationCheck(host, cmd)
|
|
if !ok {
|
|
goto sense
|
|
}
|
|
|
|
resKey = util.GetUnalignedUint64(buf[0:8])
|
|
|
|
res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID)
|
|
|
|
if res == nil {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
if res.Key != resKey {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID)
|
|
|
|
if curRes != nil {
|
|
curRes.Scope = 0
|
|
curRes.Type = 0
|
|
scsiResOp.SetCurrentReservation(tgtName, devUUID, nil)
|
|
}
|
|
|
|
for _, tmpRes := range resList {
|
|
if tmpRes != res {
|
|
//TODO send sense code
|
|
}
|
|
scsiResOp.DeleteAndRemoveReservation(tgtName, devUUID, tmpRes)
|
|
}
|
|
scsiResOp.IncPRGeneration(tgtName, devUUID)
|
|
scsiResOp.Save(tgtName, devUUID)
|
|
return api.SAMStatGood
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCPRPreempt(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
buf []byte = cmd.OutSDBBuffer.Buffer
|
|
scb []byte = cmd.SCB
|
|
ok bool = false
|
|
resKey uint64
|
|
sAResKey uint64
|
|
res *api.SCSIReservation
|
|
curRes *api.SCSIReservation
|
|
resReleased bool
|
|
removeAllRes bool
|
|
resScope uint8
|
|
resType uint8
|
|
)
|
|
|
|
tgtName := cmd.Target.Name
|
|
devUUID := cmd.Device.UUID
|
|
scsiResOp := GetSCSIReservationOperator()
|
|
resList := scsiResOp.GetReservationList(tgtName, devUUID)
|
|
|
|
ok = reservationCheck(host, cmd)
|
|
if !ok {
|
|
goto sense
|
|
}
|
|
resScope = scb[2] & 0xf0 >> 4
|
|
resType = scb[2] & 0x0f
|
|
resKey = util.GetUnalignedUint64(buf[0:8])
|
|
sAResKey = util.GetUnalignedUint64(buf[8:16])
|
|
|
|
res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID)
|
|
|
|
if res == nil {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
if res.Key != resKey {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
if sAResKey != 0 {
|
|
ok = scsiResOp.IsKeyExists(tgtName, devUUID, sAResKey)
|
|
if ok {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
}
|
|
|
|
curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID)
|
|
if curRes != nil {
|
|
if curRes.Type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG ||
|
|
curRes.Type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG {
|
|
if sAResKey == 0 {
|
|
if resType != curRes.Type ||
|
|
resScope != curRes.Scope {
|
|
resReleased = true
|
|
}
|
|
res.Type = resType
|
|
res.Scope = resScope
|
|
scsiResOp.SetCurrentReservation(tgtName, devUUID, res)
|
|
removeAllRes = true
|
|
}
|
|
} else {
|
|
if curRes.Key == resKey {
|
|
if resType != curRes.Type ||
|
|
resScope != curRes.Scope {
|
|
resReleased = true
|
|
}
|
|
res.Type = resType
|
|
res.Scope = resScope
|
|
scsiResOp.SetCurrentReservation(tgtName, devUUID, res)
|
|
} else {
|
|
if sAResKey == 0 {
|
|
goto sense
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, tmpRes := range resList {
|
|
if tmpRes == res {
|
|
continue
|
|
}
|
|
|
|
if res.Key == resKey || removeAllRes {
|
|
//TODO send sense code
|
|
scsiResOp.RemoveReservation(tgtName, devUUID, res)
|
|
} else {
|
|
if resReleased {
|
|
//TODO send sense code
|
|
}
|
|
}
|
|
}
|
|
scsiResOp.IncPRGeneration(tgtName, devUUID)
|
|
scsiResOp.Save(tgtName, devUUID)
|
|
return api.SAMStatGood
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
func SPCPRRegisterAndMove(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
buf []byte = cmd.OutSDBBuffer.Buffer
|
|
scb []byte = cmd.SCB
|
|
resKey uint64
|
|
sAResKey uint64
|
|
curRes, dstReg, res *api.SCSIReservation
|
|
paramListLen uint32
|
|
unreg uint8
|
|
aptpl uint8
|
|
tpidDataLen uint32
|
|
//idLen uint32
|
|
)
|
|
|
|
tgtName := cmd.Target.Name
|
|
devUUID := cmd.Device.UUID
|
|
scsiResOp := GetSCSIReservationOperator()
|
|
resList := scsiResOp.GetReservationList(tgtName, devUUID)
|
|
|
|
paramListLen = util.GetUnalignedUint32(scb[5:9])
|
|
if paramListLen < 24 {
|
|
goto sense
|
|
}
|
|
aptpl = buf[17] & 0x01
|
|
if aptpl != 0 { /* no reported in capabilities */
|
|
goto sense
|
|
}
|
|
|
|
unreg = buf[17] & 0x02
|
|
|
|
resKey = util.GetUnalignedUint64(buf[0:8])
|
|
sAResKey = util.GetUnalignedUint64(buf[8:16])
|
|
|
|
tpidDataLen = util.GetUnalignedUint32(buf[20:25])
|
|
if tpidDataLen < 24 || (tpidDataLen%4) != 0 {
|
|
goto sense
|
|
}
|
|
|
|
if (paramListLen - 24) < tpidDataLen {
|
|
goto sense
|
|
}
|
|
|
|
res = scsiResOp.GetReservation(tgtName, devUUID, cmd.ITNexusID)
|
|
curRes = scsiResOp.GetCurrentReservation(tgtName, devUUID)
|
|
if res == nil {
|
|
if curRes != nil {
|
|
return api.SAMStatReservationConflict
|
|
} else {
|
|
goto sense
|
|
}
|
|
}
|
|
|
|
if scsiResOp.IsCurrentReservation(tgtName, devUUID, res) {
|
|
return api.SAMStatGood
|
|
}
|
|
|
|
if res.Key != resKey {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
if sAResKey == 0 {
|
|
return api.SAMStatReservationConflict
|
|
}
|
|
|
|
for _, dstReg = range resList {
|
|
if dstReg.Key == sAResKey {
|
|
goto found
|
|
}
|
|
}
|
|
|
|
goto sense
|
|
found:
|
|
//TODO check transportid
|
|
scsiResOp.SetCurrentReservation(tgtName, devUUID, dstReg)
|
|
if unreg != 0 {
|
|
scsiResOp.RemoveReservation(tgtName, devUUID, res)
|
|
}
|
|
scsiResOp.IncPRGeneration(tgtName, devUUID)
|
|
scsiResOp.Save(tgtName, devUUID)
|
|
return api.SAMStatGood
|
|
sense:
|
|
if cmd.InSDBBuffer != nil {
|
|
cmd.InSDBBuffer.Resid = 0
|
|
}
|
|
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
|
return api.SAMStatCheckCondition
|
|
}
|
|
|
|
/*
|
|
* SPCRequestSense Implements SCSI REQUEST SENSE command
|
|
* The REQUEST SENSE command requests the device server to return parameter data that contains sense data.
|
|
*
|
|
* Reference : SPC4r11
|
|
* 6.39 - REQUEST SENSE
|
|
*/
|
|
func SPCRequestSense(host int, cmd *api.SCSICommand) api.SAMStat {
|
|
var (
|
|
allocationLength uint32
|
|
actualLength uint32
|
|
)
|
|
|
|
allocationLength = util.GetUnalignedUint32(cmd.SCB[4:8])
|
|
if allocationLength > cmd.InSDBBuffer.Length {
|
|
allocationLength = cmd.InSDBBuffer.Length
|
|
}
|
|
BuildSenseData(cmd, NO_SENSE, NO_ADDITIONAL_SENSE)
|
|
if cmd.SenseBuffer.Length < allocationLength {
|
|
actualLength = cmd.SenseBuffer.Length
|
|
} else {
|
|
actualLength = allocationLength
|
|
}
|
|
copy(cmd.InSDBBuffer.Buffer, cmd.SenseBuffer.Buffer[:actualLength])
|
|
cmd.InSDBBuffer.Resid = uint32(actualLength)
|
|
|
|
// reset sense buffer in cmnd
|
|
cmd.SenseBuffer = &api.SenseBuffer{}
|
|
|
|
return api.SAMStatGood
|
|
}
|