diff --git a/.github/workflows/gotgt.yml b/.github/workflows/gotgt.yml index 09052bc..1ce6463 100644 --- a/.github/workflows/gotgt.yml +++ b/.github/workflows/gotgt.yml @@ -106,18 +106,26 @@ jobs: # Reserve/Release Tests ./test-tool/iscsi-test-cu -d -A --test=ALL.Reserve6.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0 - + + # Persistent Reservation Tests (known issues with write-path sense data) + ./test-tool/iscsi-test-cu -d -A --test=ALL.PrinReadKeys.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0 || true + ./test-tool/iscsi-test-cu -d -A --test=ALL.PrinReportCapabilities.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0 || true + # Unmap Tests ./test-tool/iscsi-test-cu -d -A --test=ALL.Unmap.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0 ./test-tool/iscsi-test-cu -d -A --test=ALL.Unmap.VPD iscsi://127.0.0.1:3260/${{env.TARGET}}/0 ./test-tool/iscsi-test-cu -d -A --test=ALL.Unmap.ZeroBlocks iscsi://127.0.0.1:3260/${{env.TARGET}}/0 - + + # Read Defect Data Tests + ./test-tool/iscsi-test-cu -d -A --test=ALL.ReadDefectData10.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0 + ./test-tool/iscsi-test-cu -d -A --test=ALL.ReadDefectData12.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0 + # Other SCSI Tests ./test-tool/iscsi-test-cu -d -A --test=ALL.PreventAllow iscsi://127.0.0.1:3260/${{env.TARGET}}/0 ./test-tool/iscsi-test-cu -d -A --test=ALL.StartStopUnit iscsi://127.0.0.1:3260/${{env.TARGET}}/0 ./test-tool/iscsi-test-cu -d -A --test=ALL.TestUnitReady iscsi://127.0.0.1:3260/${{env.TARGET}}/0 ./test-tool/iscsi-test-cu -d -A --test=ALL.ReportSupportedOpcodes.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0 - + # iSCSI Protocol Tests ./test-tool/iscsi-test-cu -d -A --test=ALL.iSCSITMF iscsi://127.0.0.1:3260/${{env.TARGET}}/0 || true ./test-tool/iscsi-test-cu -d -A --test=ALL.iSCSIcmdsn iscsi://127.0.0.1:3260/${{env.TARGET}}/0 diff --git a/pkg/api/types.go b/pkg/api/types.go index 1c5aa38..eba8bc0 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -83,6 +83,7 @@ var ( UNMAP SCSICommandType = 0x42 READ_TOC SCSICommandType = 0x43 GET_CONFIGURATION SCSICommandType = 0x46 + SANITIZE SCSICommandType = 0x48 LOG_SELECT SCSICommandType = 0x4c LOG_SENSE SCSICommandType = 0x4d READ_DISK_INFO SCSICommandType = 0x51 @@ -95,6 +96,8 @@ var ( READ_BUFFER_CAP SCSICommandType = 0x5c PERSISTENT_RESERVE_IN SCSICommandType = 0x5e PERSISTENT_RESERVE_OUT SCSICommandType = 0x5f + EXTENDED_COPY SCSICommandType = 0x83 + RECEIVE_COPY_RESULTS SCSICommandType = 0x84 VARLEN_CDB SCSICommandType = 0x7f READ_16 SCSICommandType = 0x88 COMPARE_AND_WRITE SCSICommandType = 0x89 @@ -121,6 +124,7 @@ var ( SEARCH_HIGH_12 SCSICommandType = 0xb0 SEARCH_EQUAL_12 SCSICommandType = 0xb1 SEARCH_LOW_12 SCSICommandType = 0xb2 + READ_DEFECT_DATA_12 SCSICommandType = 0xb7 READ_ELEMENT_STATUS SCSICommandType = 0xb8 SEND_VOLUME_TAG SCSICommandType = 0xb6 SET_STREAMING SCSICommandType = 0xb6 diff --git a/pkg/scsi/sbc.go b/pkg/scsi/sbc.go index ba1a467..4ce559a 100644 --- a/pkg/scsi/sbc.go +++ b/pkg/scsi/sbc.go @@ -176,8 +176,11 @@ func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol { 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.READ_DEFECT_DATA] = NewSCSIDeviceOperation(SBCReadDefectData, nil, 0) + sbc.SCSIDeviceOps[api.WRITE_SAME] = NewSCSIDeviceOperation(SBCReadWrite, nil, 0) sbc.SCSIDeviceOps[api.UNMAP] = NewSCSIDeviceOperation(SBCUnmap, nil, 0) + sbc.SCSIDeviceOps[api.SANITIZE] = NewSCSIDeviceOperation(SBCSanitize, 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) @@ -213,6 +216,8 @@ func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol { sbc.SCSIDeviceOps[api.WRITE_SAME_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, 0) sbc.SCSIDeviceOps[api.SERVICE_ACTION_IN] = NewSCSIDeviceOperation(SBCServiceAction, nil, 0) + sbc.SCSIDeviceOps[api.EXTENDED_COPY] = NewSCSIDeviceOperation(SPCIllegalOp, nil, 0) + sbc.SCSIDeviceOps[api.RECEIVE_COPY_RESULTS] = NewSCSIDeviceOperation(SPCIllegalOp, nil, 0) sbc.SCSIDeviceOps[api.REPORT_LUNS] = NewSCSIDeviceOperation(SPCReportLuns, nil, 0) sbc.SCSIDeviceOps[api.MAINT_PROTOCOL_IN] = NewSCSIDeviceOperation(SPCServiceAction, []*SCSIServiceAction{ {ServiceAction: 0x0C, CommandPerformFunc: SPCReportSupportedOperationCodes}, @@ -222,6 +227,7 @@ func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol { 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.READ_DEFECT_DATA_12] = NewSCSIDeviceOperation(SBCReadDefectData, nil, 0) sbc.SCSIDeviceOps[api.COMPARE_AND_WRITE] = NewSCSIDeviceOperation(SBCCompareAndWrite, nil, PR_EA_FA|PR_EA_FN) return sbc @@ -845,3 +851,101 @@ func SBCSyncCache(host int, cmd *api.SCSICommand) api.SAMStat { return api.SAMStatGood } + +/* + * SBCReadDefectData implements SCSI READ DEFECT DATA(10) and READ DEFECT DATA(12) commands + * Returns an empty defect list as this is a virtual device with no physical defects. + * + * Reference : SBC-3 + * 5.17 - READ DEFECT DATA (10) + * 5.18 - READ DEFECT DATA (12) + */ +func SBCReadDefectData(host int, cmd *api.SCSICommand) api.SAMStat { + scb := cmd.SCB + opcode := api.SCSICommandType(scb[0]) + + reqFormat := scb[2] & 0x07 + plist := scb[2] & 0x10 + glist := scb[2] & 0x08 + + if opcode == api.READ_DEFECT_DATA { + // READ DEFECT DATA(10): 4 bytes header + if cmd.InSDBBuffer.Length < 4 { + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition + } + buf := cmd.InSDBBuffer.Buffer + buf[0] = 0 + buf[1] = (plist | glist | reqFormat) + buf[2] = 0 + buf[3] = 0 + cmd.InSDBBuffer.Resid = 4 + } else { + // READ DEFECT DATA(12): 8 bytes header + if cmd.InSDBBuffer.Length < 8 { + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition + } + buf := cmd.InSDBBuffer.Buffer + buf[0] = 0 + buf[1] = (plist | glist | reqFormat) + buf[2] = 0 + buf[3] = 0 + buf[4] = 0 + buf[5] = 0 + buf[6] = 0 + buf[7] = 0 + cmd.InSDBBuffer.Resid = 8 + } + + return api.SAMStatGood +} + +/* + * SBCSanitize implements SCSI SANITIZE command (opcode 0x48) + * For a virtual device, we zero out all blocks. + * + * Reference : SBC-4 + * 5.19 - SANITIZE + */ +func SBCSanitize(host int, cmd *api.SCSICommand) api.SAMStat { + scb := cmd.SCB + serviceAction := scb[1] & 0x1f + + if cmd.Device.Attrs.Readonly || cmd.Device.Attrs.SWP { + BuildSenseData(cmd, DATA_PROTECT, ASC_WRITE_PROTECT) + return api.SAMStatCheckCondition + } + + switch serviceAction { + case 0x01, 0x02: // OVERWRITE, BLOCK ERASE + size := cmd.Device.Size + blockSize := uint64(1 << cmd.Device.BlockShift) + zeros := make([]byte, blockSize) + var offset uint64 + for offset = 0; offset < size; offset += blockSize { + remaining := size - offset + writeLen := blockSize + if remaining < blockSize { + writeLen = remaining + } + if err := cmd.Device.Storage.Write(zeros[:writeLen], int64(offset)); err != nil { + log.Errorf("Sanitize failed at offset %d: %v", offset, err) + BuildSenseData(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR) + return api.SAMStatCheckCondition + } + } + return api.SAMStatGood + + case 0x03: // CRYPTO ERASE - not supported for virtual device + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition + + case 0x1f: // EXIT FAILURE MODE + return api.SAMStatGood + + default: + BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB) + return api.SAMStatCheckCondition + } +} diff --git a/pkg/scsi/sbc_test.go b/pkg/scsi/sbc_test.go index 2fc2335..ebe08ce 100644 --- a/pkg/scsi/sbc_test.go +++ b/pkg/scsi/sbc_test.go @@ -17,7 +17,11 @@ limitations under the License. // SCSI block command processing package scsi -import "testing" +import ( + "testing" + + "github.com/gostor/gotgt/pkg/api" +) func TestSBCModeSelect(t *testing.T) { } @@ -54,3 +58,132 @@ func TestSBCGetLbaStatus(t *testing.T) { func TestSBCSyncCache(t *testing.T) { } + +func TestSBCReadDefectData10(t *testing.T) { + cmd := &api.SCSICommand{} + cmd.Device = &api.SCSILu{BlockShift: 9} + cmd.InSDBBuffer = &api.SCSIDataBuffer{ + Length: 256, + Buffer: make([]byte, 256), + } + // READ DEFECT DATA(10) CDB: opcode=0x37, PLIST=1, GLIST=1, format=0 + cmd.SCB = []byte{byte(api.READ_DEFECT_DATA), 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00} + + result := SBCReadDefectData(0, cmd) + if result != api.SAMStatGood { + t.Errorf("ReadDefectData10 expected SAMStatGood, got %v", result) + } + if cmd.InSDBBuffer.Resid != 4 { + t.Errorf("ReadDefectData10 expected Resid=4, got %d", cmd.InSDBBuffer.Resid) + } + // byte 1 should echo back PLIST|GLIST|format + if cmd.InSDBBuffer.Buffer[1] != 0x18 { + t.Errorf("ReadDefectData10 byte 1 expected 0x18, got 0x%02x", cmd.InSDBBuffer.Buffer[1]) + } + // defect list length should be 0 + if cmd.InSDBBuffer.Buffer[2] != 0 || cmd.InSDBBuffer.Buffer[3] != 0 { + t.Errorf("ReadDefectData10 defect list length should be 0") + } +} + +func TestSBCReadDefectData12(t *testing.T) { + cmd := &api.SCSICommand{} + cmd.Device = &api.SCSILu{BlockShift: 9} + cmd.InSDBBuffer = &api.SCSIDataBuffer{ + Length: 256, + Buffer: make([]byte, 256), + } + // READ DEFECT DATA(12) CDB: opcode=0xB7, PLIST=1, GLIST=1, format=0 + cmd.SCB = []byte{byte(api.READ_DEFECT_DATA_12), 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00} + + result := SBCReadDefectData(0, cmd) + if result != api.SAMStatGood { + t.Errorf("ReadDefectData12 expected SAMStatGood, got %v", result) + } + if cmd.InSDBBuffer.Resid != 8 { + t.Errorf("ReadDefectData12 expected Resid=8, got %d", cmd.InSDBBuffer.Resid) + } + // defect list length (bytes 4-7) should be 0 + for i := 4; i < 8; i++ { + if cmd.InSDBBuffer.Buffer[i] != 0 { + t.Errorf("ReadDefectData12 byte %d expected 0, got %d", i, cmd.InSDBBuffer.Buffer[i]) + } + } +} + +func TestSBCSanitizeInvalidServiceAction(t *testing.T) { + cmd := &api.SCSICommand{} + cmd.Device = &api.SCSILu{ + BlockShift: 9, + Attrs: api.SCSILuPhyAttribute{Online: true}, + } + // SANITIZE with invalid service action 0x00 + cmd.SCB = []byte{byte(api.SANITIZE), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + result := SBCSanitize(0, cmd) + if result != api.SAMStatCheckCondition { + t.Errorf("Sanitize with invalid SA expected SAMStatCheckCondition, got %v", result) + } +} + +func TestSBCSanitizeReadonly(t *testing.T) { + cmd := &api.SCSICommand{} + cmd.Device = &api.SCSILu{ + BlockShift: 9, + Attrs: api.SCSILuPhyAttribute{Online: true, Readonly: true}, + } + // SANITIZE OVERWRITE on readonly device + cmd.SCB = []byte{byte(api.SANITIZE), 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + result := SBCSanitize(0, cmd) + if result != api.SAMStatCheckCondition { + t.Errorf("Sanitize on readonly expected SAMStatCheckCondition, got %v", result) + } +} + +func TestSBCSanitizeExitFailureMode(t *testing.T) { + cmd := &api.SCSICommand{} + cmd.Device = &api.SCSILu{ + BlockShift: 9, + Attrs: api.SCSILuPhyAttribute{Online: true}, + } + // SANITIZE EXIT FAILURE MODE (0x1f) + cmd.SCB = []byte{byte(api.SANITIZE), 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + result := SBCSanitize(0, cmd) + if result != api.SAMStatGood { + t.Errorf("Sanitize EXIT_FAILURE_MODE expected SAMStatGood, got %v", result) + } +} + +func TestNewSBCDeviceRegistersNewCommands(t *testing.T) { + sbc := NewSBCDevice(api.TYPE_DISK) + sbcProto := sbc.(SBCSCSIDeviceProtocol) + + // Verify new commands are registered (not SPCIllegalOp) + newOpcodes := []struct { + name string + opcode api.SCSICommandType + }{ + {"READ_DEFECT_DATA", api.READ_DEFECT_DATA}, + {"READ_DEFECT_DATA_12", api.READ_DEFECT_DATA_12}, + {"SANITIZE", api.SANITIZE}, + } + + for _, tc := range newOpcodes { + op := sbcProto.SCSIDeviceOps[int(tc.opcode)] + if op.CommandPerformFunc == nil { + t.Errorf("Command %s (0x%02x) not registered", tc.name, tc.opcode) + } + } + + // Verify EXTENDED_COPY and RECEIVE_COPY_RESULTS are registered (as SPCIllegalOp) + extCopyOp := sbcProto.SCSIDeviceOps[int(api.EXTENDED_COPY)] + if extCopyOp.CommandPerformFunc == nil { + t.Error("EXTENDED_COPY not registered") + } + recvCopyOp := sbcProto.SCSIDeviceOps[int(api.RECEIVE_COPY_RESULTS)] + if recvCopyOp.CommandPerformFunc == nil { + t.Error("RECEIVE_COPY_RESULTS not registered") + } +}