/* 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<> 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<> 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< 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.Warnf("DEBUG: 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 }