Files
gotgt/pkg/scsi/sbc.go
Lei Xue b2776dc5c2 fix critical bugs and improve iSCSI protocol compliance
- 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>
2026-03-14 12:11:23 +08:00

848 lines
27 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 block command processing
package scsi
import (
"bytes"
"encoding/binary"
"fmt"
"unsafe"
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/util"
"github.com/gostor/gotgt/pkg/version"
log "github.com/sirupsen/logrus"
)
const (
PR_SPECIAL = (1 << 5)
PR_WE_FA = (1 << 4)
PR_EA_FA = (1 << 3)
PR_RR_FR = (1 << 2)
PR_WE_FN = (1 << 1)
PR_EA_FN = (1 << 0)
)
var (
EnableORWrite16 = true
EnablePersistentReservation = true
)
type SBCSCSIDeviceProtocol struct {
BaseSCSIDeviceProtocol
}
func (sbc SBCSCSIDeviceProtocol) PerformCommand(opcode int) interface{} {
return sbc.SCSIDeviceOps[opcode]
}
func (sbc SBCSCSIDeviceProtocol) PerformServiceAction(opcode int, action uint8) interface{} {
var sa *SCSIServiceAction
for _, sa = range sbc.SCSIDeviceOps[opcode].ServiceAction {
if sa.ServiceAction == action {
return sa
}
}
return nil
}
func (sbc SBCSCSIDeviceProtocol) InitLu(lu *api.SCSILu) error {
// init LU's phy attribute
lu.Attrs.DeviceType = sbc.DeviceType
lu.Attrs.Qualifier = false
lu.Attrs.ThinProvisioning = false
lu.Attrs.Removable = false
lu.Attrs.Readonly = false
lu.Attrs.SWP = false
lu.Attrs.SenseFormat = false
lu.Attrs.VendorID = SCSIVendorID
lu.Attrs.ProductID = SCSIProductID
lu.Attrs.ProductRev = version.SCSIVersion
lu.Attrs.SCSIID = fmt.Sprintf("gotgt-scsi-%d%d", 0, lu.UUID)
lu.Attrs.SCSISN = fmt.Sprintf("gotgt-beaf-%d%d", 0, lu.UUID)
/*
SCSIID for PAGE83 T10 VENDOR IDENTIFICATION field
It is going to be the iSCSI target iqn name
leave it with a default target name
*/
lu.Attrs.SCSIID = SCSIID
/*
The PRODUCT SERIAL NUMBER field contains
right-aligned ASCII data (see 4.3.1)
that is a vendor specific serial number.
If the product serial number is not available,
the device server shall return ASCII spaces (20h) in this field.
leave it with 4 spaces (20h)
*/
lu.Attrs.SCSISN = " "
lu.Attrs.VersionDesction = [8]uint16{
0x0320, // SBC-2 no version claimed
0x0960, // iSCSI no version claimed
0x0300, // SPC-3 no version claimed
0x0060, // SAM-3 no version claimed
}
if lu.BlockShift == 0 {
lu.BlockShift = api.DefaultBlockShift
}
pages := []api.ModePage{}
// Vendor uniq - However most apps seem to call for mode page 0
//pages = append(pages, api.ModePage{0, 0, []byte{}})
// Disconnect page
pages = append(pages, api.ModePage{PageCode: 2, SubPageCode: 0, Size: 14, Data: []byte{0x80, 0x80, 0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
// Caching Page
pages = append(pages, api.ModePage{PageCode: 8, SubPageCode: 0, Size: 18, Data: []byte{0x14, 0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0, 0, 0, 0, 0, 0}})
// Control page
pages = append(pages, api.ModePage{PageCode: 0x0a, SubPageCode: 0, Size: 10, Data: []byte{2, 0x10, 0, 0, 0, 0, 0, 0, 2, 0, 0x08, 0, 0, 0, 0, 0, 0, 0}})
// Control Extensions mode page: TCMOS:1
pages = append(pages, api.ModePage{PageCode: 0x0a, SubPageCode: 1, Size: 0x1c, Data: []byte{0x04, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
// Informational Exceptions Control page
pages = append(pages, api.ModePage{PageCode: 0x1c, SubPageCode: 0, Size: 10, Data: []byte{8, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
lu.ModePages = pages
mbd := util.MarshalUint32(uint32(0xffffffff))
if size := lu.Size >> lu.BlockShift; size>>32 == 0 {
mbd = util.MarshalUint32(uint32(size))
}
lu.ModeBlockDescriptor = append(mbd, util.MarshalUint32(uint32(1<<lu.BlockShift))...)
return nil
}
func (sbc SBCSCSIDeviceProtocol) ConfigLu(lu *api.SCSILu) error {
return nil
}
func (sbc SBCSCSIDeviceProtocol) OnlineLu(lu *api.SCSILu) error {
return nil
}
func (sbc SBCSCSIDeviceProtocol) OfflineLu(lu *api.SCSILu) error {
return nil
}
func (sbc SBCSCSIDeviceProtocol) ExitLu(lu *api.SCSILu) error {
return nil
}
func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol {
var sbc = SBCSCSIDeviceProtocol{
BaseSCSIDeviceProtocol{
DeviceType: deviceType,
SCSIDeviceOps: []SCSIDeviceOperation{},
},
}
for i := 0; i < 256; i++ {
sbc.SCSIDeviceOps = append(sbc.SCSIDeviceOps, NewSCSIDeviceOperation(SPCIllegalOp, nil, 0))
}
sbc.SCSIDeviceOps[api.TEST_UNIT_READY] = NewSCSIDeviceOperation(SPCTestUnit, nil, 0)
sbc.SCSIDeviceOps[api.REQUEST_SENSE] = NewSCSIDeviceOperation(SPCRequestSense, nil, 0)
sbc.SCSIDeviceOps[api.FORMAT_UNIT] = NewSCSIDeviceOperation(SBCFormatUnit, nil, 0)
sbc.SCSIDeviceOps[api.READ_6] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.WRITE_6] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN|PR_WE_FA|PR_WE_FN)
sbc.SCSIDeviceOps[api.INQUIRY] = NewSCSIDeviceOperation(SPCInquiry, nil, 0)
sbc.SCSIDeviceOps[api.MODE_SELECT] = NewSCSIDeviceOperation(SBCModeSelect, nil, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN)
sbc.SCSIDeviceOps[api.RESERVE] = NewSCSIDeviceOperation(SBCReserve, nil, 0)
sbc.SCSIDeviceOps[api.RELEASE] = NewSCSIDeviceOperation(SBCRelease, nil, 0)
sbc.SCSIDeviceOps[api.MODE_SENSE] = NewSCSIDeviceOperation(SBCModeSense, nil, PR_WE_FA|PR_EA_FA|PR_EA_FN|PR_WE_FN)
sbc.SCSIDeviceOps[api.START_STOP] = NewSCSIDeviceOperation(SPCStartStop, nil, PR_SPECIAL)
sbc.SCSIDeviceOps[api.SEND_DIAGNOSTIC] = NewSCSIDeviceOperation(SPCSendDiagnostics, nil, 0)
sbc.SCSIDeviceOps[api.ALLOW_MEDIUM_REMOVAL] = NewSCSIDeviceOperation(SPCPreventAllowMediaRemoval, nil, 0)
sbc.SCSIDeviceOps[api.READ_CAPACITY] = NewSCSIDeviceOperation(SBCReadCapacity, nil, 0)
sbc.SCSIDeviceOps[api.READ_10] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.WRITE_10] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_WE_FA|PR_EA_FA|PR_EA_FN|PR_WE_FN)
sbc.SCSIDeviceOps[api.WRITE_VERIFY] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.VERIFY_10] = NewSCSIDeviceOperation(SBCVerify, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.PRE_FETCH_10] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.SYNCHRONIZE_CACHE] = NewSCSIDeviceOperation(SBCSyncCache, nil, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN)
sbc.SCSIDeviceOps[api.WRITE_SAME] = NewSCSIDeviceOperation(SBCReadWrite, nil, 0)
sbc.SCSIDeviceOps[api.UNMAP] = NewSCSIDeviceOperation(SBCUnmap, nil, 0)
sbc.SCSIDeviceOps[api.MODE_SELECT_10] = NewSCSIDeviceOperation(SBCModeSelect, nil, PR_WE_FA|PR_EA_FA|PR_EA_FN|PR_WE_FN)
sbc.SCSIDeviceOps[api.MODE_SENSE_10] = NewSCSIDeviceOperation(SBCModeSense, nil, PR_WE_FA|PR_WE_FN|PR_EA_FA|PR_EA_FN)
if EnablePersistentReservation {
sbc.SCSIDeviceOps[api.PERSISTENT_RESERVE_IN] = NewSCSIDeviceOperation(SPCServiceAction, []*SCSIServiceAction{
{ServiceAction: PR_IN_READ_KEYS, CommandPerformFunc: SPCPRReadKeys},
{ServiceAction: PR_IN_READ_RESERVATION, CommandPerformFunc: SPCPRReadReservation},
{ServiceAction: PR_IN_REPORT_CAPABILITIES, CommandPerformFunc: SPCPRReportCapabilities},
}, 0)
sbc.SCSIDeviceOps[api.PERSISTENT_RESERVE_OUT] = NewSCSIDeviceOperation(SPCServiceAction, []*SCSIServiceAction{
{ServiceAction: PR_OUT_REGISTER, CommandPerformFunc: SPCPRRegister},
{ServiceAction: PR_OUT_RESERVE, CommandPerformFunc: SPCPRReserve},
{ServiceAction: PR_OUT_RELEASE, CommandPerformFunc: SPCPRRelease},
{ServiceAction: PR_OUT_CLEAR, CommandPerformFunc: SPCPRClear},
{ServiceAction: PR_OUT_PREEMPT, CommandPerformFunc: SPCPRPreempt},
// {ServiceAction: PR_OUT_PREEMPT_AND_ABORT, CommandPerformFunc: SPCPRPreempt},
{ServiceAction: PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY, CommandPerformFunc: SPCPRRegister},
{ServiceAction: PR_OUT_REGISTER_AND_MOVE, CommandPerformFunc: SPCPRRegisterAndMove},
}, 0)
}
sbc.SCSIDeviceOps[api.READ_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.WRITE_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN|PR_WE_FA|PR_WE_FN)
if EnableORWrite16 {
sbc.SCSIDeviceOps[api.ORWRITE_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
}
sbc.SCSIDeviceOps[api.WRITE_VERIFY_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.VERIFY_16] = NewSCSIDeviceOperation(SBCVerify, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.PRE_FETCH_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.SYNCHRONIZE_CACHE_16] = NewSCSIDeviceOperation(SBCSyncCache, nil, PR_EA_FA|PR_EA_FN|PR_WE_FA|PR_WE_FN)
sbc.SCSIDeviceOps[api.WRITE_SAME_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, 0)
sbc.SCSIDeviceOps[api.SERVICE_ACTION_IN] = NewSCSIDeviceOperation(SBCServiceAction, nil, 0)
sbc.SCSIDeviceOps[api.REPORT_LUNS] = NewSCSIDeviceOperation(SPCReportLuns, nil, 0)
sbc.SCSIDeviceOps[api.MAINT_PROTOCOL_IN] = NewSCSIDeviceOperation(SPCServiceAction, []*SCSIServiceAction{
{ServiceAction: 0x0C, CommandPerformFunc: SPCReportSupportedOperationCodes},
}, 0)
sbc.SCSIDeviceOps[api.EXCHANGE_MEDIUM] = NewSCSIDeviceOperation(SPCIllegalOp, nil, 0)
sbc.SCSIDeviceOps[api.READ_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.WRITE_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_WE_FA|PR_EA_FA|PR_WE_FA|PR_WE_FN)
sbc.SCSIDeviceOps[api.WRITE_VERIFY_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.VERIFY_12] = NewSCSIDeviceOperation(SBCVerify, nil, PR_EA_FA|PR_EA_FN)
sbc.SCSIDeviceOps[api.COMPARE_AND_WRITE] = NewSCSIDeviceOperation(SBCCompareAndWrite, nil, PR_EA_FA|PR_EA_FN)
return sbc
}
func SBCModeSelect(host int, cmd *api.SCSICommand) api.SAMStat {
return api.SAMStatGood
}
func SBCModeSense(host int, cmd *api.SCSICommand) api.SAMStat {
// DPOFUA = 0x10
var deviceSpecific uint8 = 0x10
if err := SPCModeSense(host, cmd); err.Err != nil {
return err
}
// If this is a read-only lun, we must set the write protect bit
if cmd.Device.Attrs.Readonly || cmd.Device.Attrs.SWP {
deviceSpecific |= 0x80
}
if cmd.SCB[0] == 0x1a {
cmd.InSDBBuffer.Buffer[2] = deviceSpecific
} else {
cmd.InSDBBuffer.Buffer[3] = deviceSpecific
}
return api.SAMStatGood
}
/*
* SBCFormatUnit Implements SCSI FORMAT UNIT command
* The FORMAT UNIT command requests that the device server format the medium into application client
* accessible logical blocks as specified in the number of blocks and block length values received
* in the last mode parameter block descriptor in a MODE SELECT command (see SPC-3). In addition,
* the device server may certify the medium and create control structures for the management of the medium and defects.
* The degree that the medium is altered by this command is vendor-specific.
*
* Reference : SBC2r16
* 5.2 - FORMAT UNIT
*/
func SBCFormatUnit(host int, cmd *api.SCSICommand) api.SAMStat {
var (
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
)
if err := deviceReserve(cmd); err != nil {
return api.SAMStatReservationConflict
}
if !cmd.Device.Attrs.Online {
key = NOT_READY
asc = ASC_MEDIUM_NOT_PRESENT
goto sense
}
if cmd.Device.Attrs.Readonly || cmd.Device.Attrs.SWP {
key = DATA_PROTECT
asc = ASC_WRITE_PROTECT
goto sense
}
if cmd.SCB[1]&0x80 != 0 {
// we dont support format protection information
goto sense
}
if cmd.SCB[1]&0x10 != 0 {
// we dont support format data
goto sense
}
if cmd.SCB[1]&0x07 != 0 {
// defect list format must be 0
goto sense
}
return api.SAMStatGood
sense:
BuildSenseData(cmd, key, asc)
return api.SAMStatCheckCondition
}
func SBCUnmap(host int, cmd *api.SCSICommand) api.SAMStat {
// check ANCHOR
if cmd.SCB[1]&0x01 != 0 {
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
return api.SAMStatCheckCondition
}
const blockDescLen = 16
var blockDescs []api.UnmapBlockDescriptor
for off := 8; uint32(off+blockDescLen) <= cmd.OutSDBBuffer.Length; off += blockDescLen {
lba := binary.BigEndian.Uint64(cmd.OutSDBBuffer.Buffer[off : off+8])
num := binary.BigEndian.Uint32(cmd.OutSDBBuffer.Buffer[off+8 : off+12])
blockDescs = append(blockDescs, api.UnmapBlockDescriptor{
Offset: lba << cmd.Device.BlockShift,
TL: num << cmd.Device.BlockShift,
})
}
if len(blockDescs) == 0 {
return api.SAMStatGood
}
if err := cmd.Device.Storage.Unmap(blockDescs); err != nil {
BuildSenseData(cmd, MEDIUM_ERROR, NO_ADDITIONAL_SENSE)
return api.SAMStatCheckCondition
}
return api.SAMStatGood
}
/*
* SBCReadWrite Implements SCSI READ(10/12/16), WRITE(10/12/16), WRITE AND VERIFY(10/12/16), WRITE SAME(10/12/16)
* The READ command requests that the device server read the specified logical block(s) and transfer them to the data-in buffer.
* The WRITE command requests that the device server transfer the specified logical block(s) from the data-out buffer and write them.
* The WRITE AND VERIFY command requests that the device server transfer the specified logical block(s) from the data-out buffer,
* write them to the medium, and then verify that they are correctly written.
*
* Reference : SBC2r16
* 5.6 - READ (10)
* 5.7 - READ (12)
* 5.8 - READ (16)
* 5.25 - WRITE (10)
* 5.26 - WRITE (12)
* 5.27 - WRITE (16)
* 5.29 - WRITE AND VERIFY (10)
* 5.30 - WRITE AND VERIFY (12)
* 5.31 - WRITE AND VERIFY (16)
*/
func SBCReadWrite(host int, cmd *api.SCSICommand) api.SAMStat {
var (
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
dev = cmd.Device
scb = cmd.SCB
opcode = api.SCSICommandType(scb[0])
lba uint64
tl uint32
err error
totalBlocks uint64
)
if dev.Attrs.Removable && !dev.Attrs.Online {
key = NOT_READY
asc = ASC_MEDIUM_NOT_PRESENT
log.Warnf("sense")
goto sense
}
switch opcode {
case api.READ_10, api.READ_12, api.READ_16, api.WRITE_10, api.WRITE_12, api.WRITE_16, api.ORWRITE_16,
api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16, api.COMPARE_AND_WRITE:
// We only support protection information type 0
if scb[1]&0xe0 != 0 {
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
log.Warnf("sense data(ILLEGAL_REQUEST,ASC_INVALID_FIELD_IN_CDB) encounter")
goto sense
}
case api.WRITE_SAME, api.WRITE_SAME_16:
// We dont support resource-provisioning so ANCHOR bit == 1 is an error.
if scb[1]&0x10 != 0 {
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
// We only support unmap for thin provisioned LUNS
if (scb[1]&0x08 != 0) && !dev.Attrs.ThinProvisioning {
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
// We only support protection information type 0
if scb[1]&0xe0 != 0 {
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
// LBDATA and PBDATA can not both be set
if (scb[1] & 0x06) == 0x06 {
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
}
if dev.Attrs.Readonly || dev.Attrs.SWP {
switch opcode {
case api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16, api.ORWRITE_16,
api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16, api.WRITE_SAME, api.WRITE_SAME_16,
api.PRE_FETCH_10, api.PRE_FETCH_16, api.COMPARE_AND_WRITE:
key = DATA_PROTECT
asc = ASC_WRITE_PROTECT
log.Warnf("sense data(data protect) and asc(ASC_WRITE_PROTECT) encounter")
goto sense
}
}
lba = getSCSIReadWriteOffset(scb)
tl = getSCSIReadWriteCount(scb)
// Calculate total blocks
totalBlocks = dev.Size >> dev.BlockShift
log.Debugf("SBCReadWrite: opcode=0x%x, lba=%d, tl=%d, totalBlocks=%d", opcode, lba, tl, totalBlocks)
// Verify that we are not doing i/o beyond the end-of-lun
// Even when transfer length is 0, we must validate the LBA is within range
if lba >= totalBlocks || lba+uint64(tl) < lba || lba+uint64(tl) > totalBlocks {
key = ILLEGAL_REQUEST
asc = ASC_LBA_OUT_OF_RANGE
log.Warnf("SBCReadWrite: LBA out of range (lba=%d, tl=%d, totalBlocks=%d)", lba, tl, totalBlocks)
goto sense
}
// If transfer length is 0, return GOOD status immediately (no data to transfer)
if tl == 0 {
return api.SAMStatGood
}
cmd.Offset = lba << dev.BlockShift
cmd.TL = tl << dev.BlockShift
// Handle residuals
switch opcode {
case api.READ_6, api.READ_10, api.READ_12, api.READ_16:
/*
if (cmd->tl != scsi_get_in_length(cmd))
scsi_set_in_resid_by_actual(cmd, cmd->tl);
*/
case api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16, api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16:
/*
if (cmd->tl != scsi_get_out_length(cmd)) {
scsi_set_out_resid_by_actual(cmd, cmd->tl);
/* We need to clamp the size of the in-buffer
* so that we dont try to write > cmd->tl in the
* backend store.
*
if (cmd->tl < scsi_get_out_length(cmd)) {
scsi_set_out_length(cmd, cmd->tl);
}
}
*/
}
err, key, asc = bsPerformCommand(dev.Storage, cmd)
if err != nil {
log.Errorf("Error from backend: %v", err)
BuildSenseData(cmd, key, asc)
return api.SAMStatBusy
} else {
return api.SAMStatGood
}
sense:
BuildSenseData(cmd, key, asc)
return api.SAMStatCheckCondition
}
func SBCReserve(host int, cmd *api.SCSICommand) api.SAMStat {
if err := deviceReserve(cmd); err != nil {
return api.SAMStatReservationConflict
}
return api.SAMStatGood
}
func SBCRelease(host int, cmd *api.SCSICommand) api.SAMStat {
lun := *(*uint64)(unsafe.Pointer(&cmd.Lun))
if err := deviceRelease(cmd.Target.TID, cmd.ITNexusID, lun, false); err != nil {
return api.SAMStatReservationConflict
}
return api.SAMStatGood
}
/*
* SBCCompareAndWrite Implements SCSI COMPARE AND WRITE command (0x89)
* The COMPARE AND WRITE command requests that the device server compare the specified
* logical block(s) with data transferred from the data-out buffer and, if they match,
* write the new data from the data-out buffer to the specified logical block(s).
*
* Reference : SBC3r35
* 5.3 - COMPARE AND WRITE
*/
func SBCCompareAndWrite(host int, cmd *api.SCSICommand) api.SAMStat {
var (
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
dev = cmd.Device
scb = cmd.SCB
lba uint64
numBlocks uint32
offset uint64
blockSize uint64
totalCompareLen uint64
expectedDataLen uint64
err error
existingData []byte
compareData []byte
writeData []byte
)
if dev.Attrs.Removable && !dev.Attrs.Online {
key = NOT_READY
asc = ASC_MEDIUM_NOT_PRESENT
goto sense
}
// We only support protection information type 0
if scb[1]&0xe0 != 0 {
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
if dev.Attrs.Readonly || dev.Attrs.SWP {
key = DATA_PROTECT
asc = ASC_WRITE_PROTECT
goto sense
}
// Number of logical blocks (one byte: bits 0-7)
numBlocks = uint32(scb[13])
if numBlocks == 0 {
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
lba = getSCSIReadWriteOffset(scb)
// Verify that we are not doing i/o beyond the end-of-lun
if lba+uint64(numBlocks) < lba || lba+uint64(numBlocks) > dev.Size>>dev.BlockShift {
key = ILLEGAL_REQUEST
asc = ASC_LBA_OUT_OF_RANGE
log.Warnf("COMPARE_AND_WRITE: lba out of range: lba: %d, num: %d, size: %d", lba, numBlocks, dev.Size>>dev.BlockShift)
goto sense
}
offset = lba << dev.BlockShift
blockSize = uint64(1 << dev.BlockShift)
totalCompareLen = uint64(numBlocks) * blockSize
// Data-out buffer contains: compare data followed by write data
// Total length should be 2 * numBlocks * blockSize
expectedDataLen = 2 * totalCompareLen
if uint64(cmd.OutSDBBuffer.Length) < expectedDataLen {
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
log.Warnf("COMPARE_AND_WRITE: data length too short: got %d, expected %d", cmd.OutSDBBuffer.Length, expectedDataLen)
goto sense
}
compareData = cmd.OutSDBBuffer.Buffer[:totalCompareLen]
writeData = cmd.OutSDBBuffer.Buffer[totalCompareLen:expectedDataLen]
// Read existing data from storage
existingData, err = dev.Storage.Read(int64(offset), int64(totalCompareLen))
if err != nil {
log.Errorf("COMPARE_AND_WRITE: failed to read data: %v", err)
key = MEDIUM_ERROR
asc = ASC_READ_ERROR
goto sense
}
// Compare data
if !bytes.Equal(existingData, compareData) {
key = MISCOMPARE
asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION
log.Warnf("COMPARE_AND_WRITE: data miscompare at LBA %d", lba)
goto sense
}
// Data matches, write new data
err = dev.Storage.Write(writeData, int64(offset))
if err != nil {
log.Errorf("COMPARE_AND_WRITE: failed to write data: %v", err)
key = MEDIUM_ERROR
asc = ASC_WRITE_ERROR
goto sense
}
return api.SAMStatGood
sense:
BuildSenseData(cmd, key, asc)
return api.SAMStatCheckCondition
}
/*
* SBCReadCapacity Implements SCSI READ CAPACITY(10) command
* The READ CAPACITY (10) command requests that the device server transfer 8 bytes of parameter data
* describing the capacity and medium format of the direct-access block device to the data-in buffer.
* This command may be processed as if it has a HEAD OF QUEUE task attribute. If the logical unit supports
* protection information, the application client should use the READ CAPACITY (16) command instead of
* the READ CAPACITY (10) command.
*
* Reference : SBC2r16
* 5.10 - READ CAPACITY(10)
*/
func SBCReadCapacity(host int, cmd *api.SCSICommand) api.SAMStat {
var (
scb = cmd.SCB
key = ILLEGAL_REQUEST
asc = ASC_LUN_NOT_SUPPORTED
bshift = cmd.Device.BlockShift
size = cmd.Device.Size >> bshift
)
if cmd.Device.Attrs.Removable && !cmd.Device.Attrs.Online {
key = NOT_READY
asc = ASC_MEDIUM_NOT_PRESENT
goto sense
}
if (scb[8]&0x1 == 0) && (scb[2]|scb[3]|scb[4]|scb[5]) != 0 {
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
if cmd.InSDBBuffer.Length < 8 {
goto overflow
}
// data[0] = (size >> 32) ? __cpu_to_be32(0xffffffff) : __cpu_to_be32(size - 1);
if size>>32 != 0 {
copy(cmd.InSDBBuffer.Buffer, util.MarshalUint32(uint32(0xffffffff)))
} else {
copy(cmd.InSDBBuffer.Buffer, util.MarshalUint32(uint32(size-1)))
}
// data[1] = __cpu_to_be32(1U << bshift);
copy(cmd.InSDBBuffer.Buffer[4:], util.MarshalUint32(uint32(1<<bshift)))
overflow:
cmd.InSDBBuffer.Resid = 8
return api.SAMStatGood
sense:
if cmd.InSDBBuffer != nil {
cmd.InSDBBuffer.Resid = 0
}
BuildSenseData(cmd, key, asc)
return api.SAMStatCheckCondition
}
/* SBCVerify Implements SCSI VERIFY(10) command
* The VERIFY (10) command requests that the device server verify the specified logical block(s) on the medium.
*
* Reference : SBC2r16
* 5.20 - VERIFY(10)
*/
func SBCVerify(host int, cmd *api.SCSICommand) api.SAMStat {
var (
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
dev = cmd.Device
scb = cmd.SCB
lba uint64
tl uint32
err error
totalBlocks uint64
)
if dev.Attrs.Removable && !dev.Attrs.Online {
key = NOT_READY
asc = ASC_MEDIUM_NOT_PRESENT
goto sense
}
if scb[1]&0xe0 != 0 {
// We only support protection information type 0
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
lba = getSCSIReadWriteOffset(scb)
tl = getSCSIReadWriteCount(scb)
// Verify that we are not doing i/o beyond the end-of-lun
// Must check LBA range before BYTCHK early return per SBC spec
totalBlocks = dev.Size >> dev.BlockShift
if lba >= totalBlocks || lba+uint64(tl) < lba || lba+uint64(tl) > totalBlocks {
key = ILLEGAL_REQUEST
asc = ASC_LBA_OUT_OF_RANGE
goto sense
}
if scb[1]&0x02 == 0 {
// BYTCHK=0: no data compare with the media
return api.SAMStatGood
}
cmd.Offset = lba << dev.BlockShift
cmd.TL = tl << dev.BlockShift
err, key, asc = bsPerformCommand(dev.Storage, cmd)
if err != nil {
goto sense
}
return api.SAMStatGood
sense:
BuildSenseData(cmd, key, asc)
return api.SAMStatCheckCondition
}
/*
* SBCReadCapacity16 Implements SCSI READ CAPACITY(16) command
* The READ CAPACITY (16) command requests that the device server transfer parameter data
* describing the capacity and medium format of the direct-access block device to the data-in buffer.
*
* Reference : SBC2r16
* 5.11 - READ CAPACITY(16)
*/
func SBCReadCapacity16(host int, cmd *api.SCSICommand) api.SAMStat {
var (
bshift = cmd.Device.BlockShift
size = cmd.Device.Size >> bshift
allocationLength uint32
)
allocationLength = util.GetUnalignedUint32(cmd.SCB[10:14])
copy(cmd.InSDBBuffer.Buffer, util.MarshalUint64(uint64(size-1)))
if allocationLength > 12 {
copy(cmd.InSDBBuffer.Buffer[8:], util.MarshalUint32(uint32(1<<bshift)))
if allocationLength > 16 {
var lbpme int
if cmd.Device.Attrs.ThinProvisioning {
lbpme = 1
}
val := (cmd.Device.Attrs.Lbppbe << 16) | (lbpme << 15) | cmd.Device.Attrs.LowestAlignedLBA
copy(cmd.InSDBBuffer.Buffer[12:], util.MarshalUint32(uint32(val)))
}
}
return api.SAMStatGood
}
func SBCGetLbaStatus(host int, cmd *api.SCSICommand) api.SAMStat {
var (
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
dev = cmd.Device
scb = cmd.SCB
lba uint64
tl uint32
totalBlocks uint64
)
if dev.Attrs.Removable && !dev.Attrs.Online {
key = NOT_READY
asc = ASC_MEDIUM_NOT_PRESENT
goto sense
}
if scb[1]&0xe0 != 0 {
// We only support protection information type 0
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
goto sense
}
if scb[1]&0x02 == 0 {
// no data compare with the media
return api.SAMStatGood
}
lba = getSCSIReadWriteOffset(scb)
tl = getSCSIReadWriteCount(scb)
// Verify that we are not doing i/o beyond the end-of-lun
totalBlocks = dev.Size >> dev.BlockShift
log.Debugf("dev.Size=%d, BlockShift=%d, totalBlocks=%d", dev.Size, dev.BlockShift, totalBlocks)
if lba >= totalBlocks || lba+uint64(tl) < lba || lba+uint64(tl) > totalBlocks {
key = ILLEGAL_REQUEST
asc = ASC_LBA_OUT_OF_RANGE
log.Warnf("sense: lba: %d, tl: %d, totalBlocks: %d", lba, tl, totalBlocks)
goto sense
}
return api.SAMStatGood
sense:
if cmd.InSDBBuffer != nil {
cmd.InSDBBuffer.Resid = 0
}
BuildSenseData(cmd, key, asc)
return api.SAMStatCheckCondition
}
func SBCServiceAction(host int, cmd *api.SCSICommand) api.SAMStat {
opcode := api.SCSICommandType(cmd.SCB[1] & 0x1f)
switch opcode {
case api.READ_CAPACITY:
return SBCReadCapacity(host, cmd)
case api.SAI_READ_CAPACITY_16:
return SBCReadCapacity16(host, cmd)
case api.SAI_GET_LBA_STATUS:
return SBCGetLbaStatus(host, cmd)
}
return api.SAMStatGood
}
/*
* SBCSyncCache Implements SCSI SYNCHRONIZE CACHE(10) and SYNCHRONIZE CACHE(16) command
* The SYNCHRONIZE CACHE command requests that the device server ensure that
* the specified logical blocks have their most recent data values recorded in
* non-volatile cache and/or on the medium, based on the SYNC_NV bit.
*
* Reference : SBC2r16
* 5.18 - SYNCHRONIZE CACHE (10)
* 5.19 - SYNCHRONIZE CACHE (16)
*/
func SBCSyncCache(host int, cmd *api.SCSICommand) api.SAMStat {
scb := cmd.SCB
lba := getSCSIReadWriteOffset(scb)
tl := getSCSIReadWriteCount(scb)
dev := cmd.Device
cmd.Offset = lba << dev.BlockShift
cmd.TL = tl << dev.BlockShift
err, key, asc := bsPerformCommand(dev.Storage, cmd)
if err != nil {
BuildSenseData(cmd, key, asc)
return api.SAMStatCheckCondition
}
return api.SAMStatGood
}