support more SCSI commands: ReadDefectData, Sanitize, and expanded CI

New SCSI commands implemented:
- READ DEFECT DATA(10/12): returns empty defect list (virtual device)
- SANITIZE: supports OVERWRITE and BLOCK ERASE (zeros all blocks)
- EXTENDED COPY / RECEIVE COPY RESULTS: registered as unsupported

New unit tests for ReadDefectData10/12, Sanitize, and command registration.

New CI libiscsi test cases:
- PersistentReservation (PrinReadKeys, PrinReportCapabilities,
  ProutRegister, ProutReserve)
- ReadDefectData10/12 (Simple)
- CompareAndWrite (Simple)
- OrWrite (Simple, BeyondEol, ZeroBlocks)
- GetLBAStatus (Simple, BeyondEol)
- ReportSupportedOpcodes (OneCommand)

Partial fix for #55

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lei Xue
2026-03-14 13:06:35 +08:00
parent 3c41cd619b
commit 36149cd4a9
4 changed files with 268 additions and 4 deletions

View File

@@ -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")
}
}