support iscsi task function
This commit is contained in:
@@ -36,6 +36,7 @@ script:
|
||||
- ./autogen.sh
|
||||
- ./configure
|
||||
- make
|
||||
- ./test-tool/iscsi-test-cu -d -A -V --test=iSCSI.iSCSITMF iscsi://127.0.0.1:3260/${TARGET}/0
|
||||
- ./test-tool/iscsi-test-cu -d -A --test=SCSI.TestUnitReady iscsi://127.0.0.1:3260/${TARGET}/0
|
||||
- ./test-tool/iscsi-test-cu -d -A --test=SCSI.ReadCapacity10 iscsi://127.0.0.1:3260/${TARGET}/0
|
||||
- ./test-tool/iscsi-test-cu -d -A --test=SCSI.ReadCapacity16 iscsi://127.0.0.1:3260/${TARGET}/0
|
||||
|
||||
@@ -151,11 +151,20 @@ type SCSIDataBuffer struct {
|
||||
Resid int32
|
||||
}
|
||||
|
||||
type SCSICommandState uint64
|
||||
|
||||
var (
|
||||
SCSICommandQueued SCSICommandState = 1
|
||||
SCSICommandProcessed SCSICommandState = 2
|
||||
SCSICommandAsync SCSICommandState = 3
|
||||
SCSICommandNotLast SCSICommandState = 4
|
||||
)
|
||||
|
||||
type SCSICommand struct {
|
||||
Target *SCSITarget
|
||||
DeviceID uint64
|
||||
Device *SCSILu
|
||||
State uint64
|
||||
State SCSICommandState
|
||||
Direction SCSIDataDirection
|
||||
InSDBBuffer SCSIDataBuffer
|
||||
OutSDBBuffer SCSIDataBuffer
|
||||
|
||||
@@ -56,6 +56,12 @@ var opCodeMap = map[OpCode]string{
|
||||
|
||||
const DataPadding = 4
|
||||
|
||||
type ISCSITaskManagementFunc struct {
|
||||
Result byte
|
||||
TaskFunc uint32
|
||||
ReferencedTaskTag uint32
|
||||
}
|
||||
|
||||
type ISCSICommand struct {
|
||||
OpCode OpCode
|
||||
RawHeader []byte
|
||||
@@ -99,6 +105,9 @@ type ISCSICommand struct {
|
||||
Status byte
|
||||
SCSIResponse byte
|
||||
|
||||
// Task request
|
||||
ISCSITaskManagementFunc
|
||||
|
||||
// R2T
|
||||
R2TSN uint32
|
||||
DesiredLength uint32
|
||||
@@ -187,13 +196,13 @@ func parseHeader(data []byte) (*ISCSICommand, error) {
|
||||
// TODO: sync.Pool
|
||||
m := &ISCSICommand{}
|
||||
m.Immediate = 0x40&data[0] == 0x40
|
||||
m.OpCode = OpCode(data[0] & 0x3f)
|
||||
m.OpCode = OpCode(data[0] & ISCSI_OPCODE_MASK)
|
||||
m.Final = 0x80&data[1] == 0x80
|
||||
m.AHSLen = int(data[4]) * 4
|
||||
m.DataLen = int(ParseUint(data[5:8]))
|
||||
m.TaskTag = uint32(ParseUint(data[16:20]))
|
||||
switch m.OpCode {
|
||||
case OpSCSICmd, OpSCSITaskReq:
|
||||
case OpSCSICmd:
|
||||
m.LUN = [8]byte{data[9]}
|
||||
m.ExpectedDataLen = uint32(ParseUint(data[20:24]))
|
||||
m.CmdSN = uint32(ParseUint(data[24:28]))
|
||||
@@ -201,6 +210,10 @@ func parseHeader(data []byte) (*ISCSICommand, error) {
|
||||
m.Write = data[1]&0x20 == 0x20
|
||||
m.CDB = data[32:48]
|
||||
m.ExpStatSN = uint32(ParseUint(data[28:32]))
|
||||
fallthrough
|
||||
case OpSCSITaskReq:
|
||||
m.ReferencedTaskTag = uint32(ParseUint(data[20:24]))
|
||||
m.TaskFunc = uint32(data[1] & ISCSI_FLAG_TM_FUNC_MASK)
|
||||
case OpSCSIResp:
|
||||
case OpSCSIOut:
|
||||
m.LUN = [8]byte{data[9]}
|
||||
@@ -399,7 +412,7 @@ func (m *ISCSICommand) scsiTMFRespBytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteByte(byte(OpSCSITaskResp))
|
||||
buf.WriteByte(0x80)
|
||||
buf.WriteByte(0x00)
|
||||
buf.WriteByte(m.Result)
|
||||
buf.WriteByte(0x00)
|
||||
|
||||
// Skip through to byte 16
|
||||
|
||||
@@ -93,11 +93,12 @@ const (
|
||||
)
|
||||
|
||||
type iscsiTask struct {
|
||||
tag uint32
|
||||
conn *iscsiConnection
|
||||
cmd *ISCSICommand
|
||||
scmd *api.SCSICommand
|
||||
state taskState
|
||||
tag uint32
|
||||
conn *iscsiConnection
|
||||
cmd *ISCSICommand
|
||||
scmd *api.SCSICommand
|
||||
state taskState
|
||||
result byte
|
||||
|
||||
offset int
|
||||
r2tCount int
|
||||
@@ -143,11 +144,13 @@ func (conn *iscsiConnection) ReInstatement(newConn *iscsiConnection) {
|
||||
conn.conn = newConn.conn
|
||||
}
|
||||
|
||||
func (conn *iscsiConnection) buildRespPackage(oc OpCode) error {
|
||||
func (conn *iscsiConnection) buildRespPackage(oc OpCode, task *iscsiTask) error {
|
||||
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: &api.SCSICommand{}}
|
||||
conn.txIOState = IOSTATE_TX_BHS
|
||||
conn.statSN += 1
|
||||
task := conn.rxTask
|
||||
if task == nil {
|
||||
task = conn.rxTask
|
||||
}
|
||||
resp := &ISCSICommand{
|
||||
StatSN: conn.req.ExpStatSN,
|
||||
TaskTag: conn.req.TaskTag,
|
||||
@@ -182,11 +185,17 @@ func (conn *iscsiConnection) buildRespPackage(oc OpCode) error {
|
||||
if scmd.Result != 0 && scmd.SenseBuffer != nil {
|
||||
resp.RawData = scmd.SenseBuffer.Bytes()
|
||||
}
|
||||
case OpNoopIn, OpSCSITaskResp, OpReject:
|
||||
case OpNoopIn, OpReject:
|
||||
resp.OpCode = oc
|
||||
resp.Final = true
|
||||
resp.NSG = FullFeaturePhase
|
||||
resp.ExpCmdSN = conn.req.CmdSN + 1
|
||||
case OpSCSITaskResp:
|
||||
resp.OpCode = oc
|
||||
resp.Final = true
|
||||
resp.NSG = FullFeaturePhase
|
||||
resp.ExpCmdSN = conn.req.CmdSN + 1
|
||||
resp.Result = task.result
|
||||
case OpLoginResp:
|
||||
resp.OpCode = OpLoginResp
|
||||
resp.Transit = conn.loginParam.tgtTrans
|
||||
@@ -209,3 +218,41 @@ func (conn *iscsiConnection) buildRespPackage(oc OpCode) error {
|
||||
conn.resp = resp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *iscsiConnection) State() string {
|
||||
switch conn.state {
|
||||
case CONN_STATE_FREE:
|
||||
return "free"
|
||||
case CONN_STATE_SECURITY:
|
||||
return "begin security"
|
||||
case CONN_STATE_SECURITY_AUTH:
|
||||
return "security auth"
|
||||
case CONN_STATE_SECURITY_DONE:
|
||||
return "done security"
|
||||
case CONN_STATE_SECURITY_LOGIN:
|
||||
return "security login"
|
||||
case CONN_STATE_SECURITY_FULL:
|
||||
return "security full"
|
||||
case CONN_STATE_LOGIN:
|
||||
return "begin login"
|
||||
case CONN_STATE_LOGIN_FULL:
|
||||
return "done login"
|
||||
case CONN_STATE_FULL:
|
||||
return "full feature"
|
||||
case CONN_STATE_KERNEL:
|
||||
return "kernel"
|
||||
case CONN_STATE_CLOSE:
|
||||
return "close"
|
||||
case CONN_STATE_EXIT:
|
||||
return "exit"
|
||||
case CONN_STATE_SCSI:
|
||||
return "scsi"
|
||||
case CONN_STATE_INIT:
|
||||
return "init"
|
||||
case CONN_STATE_START:
|
||||
return "start"
|
||||
case CONN_STATE_READY:
|
||||
return "ready"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016 The GoStor Authors All rights reserved.
|
||||
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.
|
||||
@@ -45,12 +45,12 @@ type ISCSITargetDriver struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
scsi.RegisterTargetDriver("iscsi", NewISCSITargetDriver)
|
||||
scsi.RegisterTargetDriver(iSCSIDriverName, NewISCSITargetDriver)
|
||||
}
|
||||
|
||||
func NewISCSITargetDriver(base *scsi.SCSITargetService) (scsi.SCSITargetDriver, error) {
|
||||
return &ISCSITargetDriver{
|
||||
Name: "iscsi",
|
||||
Name: iSCSIDriverName,
|
||||
iSCSITargets: map[string]*ISCSITarget{},
|
||||
SCSI: base,
|
||||
TSIHPool: map[uint16]bool{0: true, 65535: true},
|
||||
@@ -111,11 +111,11 @@ func (s *ISCSITargetDriver) AddiSCSIPortal(tgtName string, tpgt uint16, portal s
|
||||
)
|
||||
|
||||
if target, ok = s.iSCSITargets[tgtName]; !ok {
|
||||
return fmt.Errorf("no target %s", tgtName)
|
||||
return fmt.Errorf("No such target: %s", tgtName)
|
||||
}
|
||||
|
||||
if tpgtInfo, ok = target.TPGTs[tpgt]; !ok {
|
||||
return fmt.Errorf("no tpgt %d", tpgt)
|
||||
return fmt.Errorf("No such TPGT: %d", tpgt)
|
||||
}
|
||||
tgtPortals := tpgtInfo.Portals
|
||||
|
||||
@@ -356,7 +356,7 @@ func (s *ISCSITargetDriver) iscsiExecLogin(conn *iscsiConnection) error {
|
||||
conn.state = CONN_STATE_LOGIN
|
||||
}
|
||||
|
||||
return conn.buildRespPackage(OpLoginResp)
|
||||
return conn.buildRespPackage(OpLoginResp, nil)
|
||||
}
|
||||
|
||||
func iscsiExecLogout(conn *iscsiConnection) error {
|
||||
@@ -411,19 +411,15 @@ func (s *ISCSITargetDriver) iscsiExecText(conn *iscsiConnection) error {
|
||||
}
|
||||
|
||||
func iscsiExecNoopOut(conn *iscsiConnection) error {
|
||||
return conn.buildRespPackage(OpNoopIn)
|
||||
}
|
||||
|
||||
func iscsiExecTMFunction(conn *iscsiConnection) error {
|
||||
return conn.buildRespPackage(OpSCSITaskResp)
|
||||
return conn.buildRespPackage(OpNoopIn, nil)
|
||||
}
|
||||
|
||||
func iscsiExecReject(conn *iscsiConnection) error {
|
||||
return conn.buildRespPackage(OpReject)
|
||||
return conn.buildRespPackage(OpReject, nil)
|
||||
}
|
||||
|
||||
func iscsiExecR2T(conn *iscsiConnection) error {
|
||||
return conn.buildRespPackage(OpReady)
|
||||
return conn.buildRespPackage(OpReady, nil)
|
||||
}
|
||||
|
||||
func (s *ISCSITargetDriver) txHandler(conn *iscsiConnection) {
|
||||
@@ -483,27 +479,21 @@ func (s *ISCSITargetDriver) txHandler(conn *iscsiConnection) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("connection state: %d", conn.state)
|
||||
log.Debugf("connection state: %v", conn.State())
|
||||
switch conn.state {
|
||||
case CONN_STATE_CLOSE, CONN_STATE_EXIT:
|
||||
log.Warnf("set connection to close")
|
||||
conn.state = CONN_STATE_CLOSE
|
||||
case CONN_STATE_SECURITY_LOGIN:
|
||||
conn.state = CONN_STATE_LOGIN
|
||||
log.Debugf("CONN_STATE_LOGIN")
|
||||
case CONN_STATE_LOGIN:
|
||||
log.Debugf("CONN_STATE_LOGIN")
|
||||
conn.rxIOState = IOSTATE_RX_BHS
|
||||
s.handler(DATAIN, conn)
|
||||
case CONN_STATE_SECURITY_FULL, CONN_STATE_LOGIN_FULL:
|
||||
if conn.session.SessionType == SESSION_NORMAL {
|
||||
conn.state = CONN_STATE_KERNEL
|
||||
log.Debugf("CONN_STATE_KERNEL")
|
||||
conn.state = CONN_STATE_SCSI
|
||||
log.Debugf("CONN_STATE_SCSI")
|
||||
} else {
|
||||
conn.state = CONN_STATE_FULL
|
||||
log.Debugf("CONN_STATE_FULL")
|
||||
}
|
||||
conn.rxIOState = IOSTATE_RX_BHS
|
||||
s.handler(DATAIN, conn)
|
||||
@@ -560,17 +550,19 @@ func (s *ISCSITargetDriver) scsiCommandHandler(conn *iscsiConnection) (err error
|
||||
return
|
||||
} else {
|
||||
if scmd.Direction == api.SCSIDataRead {
|
||||
conn.buildRespPackage(OpSCSIIn)
|
||||
conn.buildRespPackage(OpSCSIIn, task)
|
||||
} else {
|
||||
conn.buildRespPackage(OpSCSIResp)
|
||||
conn.buildRespPackage(OpSCSIResp, task)
|
||||
}
|
||||
conn.rxTask = nil
|
||||
}
|
||||
case OpSCSITaskReq:
|
||||
// task management function
|
||||
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, state: taskPending}
|
||||
conn.txIOState = IOSTATE_TX_BHS
|
||||
iscsiExecTMFunction(conn)
|
||||
task := &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: nil}
|
||||
conn.rxTask = task
|
||||
if err = s.iscsiTaskQueueHandler(task); err != nil {
|
||||
return
|
||||
}
|
||||
case OpSCSIOut:
|
||||
log.Debugf("iSCSI Data-out processing...")
|
||||
var task *iscsiTask
|
||||
@@ -612,7 +604,7 @@ func (s *ISCSITargetDriver) scsiCommandHandler(conn *iscsiConnection) (err error
|
||||
if err = s.iscsiExecTask(task); err != nil {
|
||||
return
|
||||
} else {
|
||||
conn.buildRespPackage(OpSCSIResp)
|
||||
conn.buildRespPackage(OpSCSIResp, task)
|
||||
conn.rxTask = nil
|
||||
}
|
||||
case OpNoopOut:
|
||||
@@ -657,13 +649,16 @@ func (s *ISCSITargetDriver) iscsiTaskQueueHandler(task *iscsiTask) error {
|
||||
if len(sess.PendingTasks) == 0 {
|
||||
return nil
|
||||
}
|
||||
sess.PendingTasksMutex.Lock()
|
||||
task = sess.PendingTasks.Pop().(*iscsiTask)
|
||||
cmd = task.cmd
|
||||
if cmd.CmdSN != cmdsn {
|
||||
sess.PendingTasks.Push(task)
|
||||
sess.PendingTasksMutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
task.state = taskSCSI
|
||||
sess.PendingTasksMutex.Unlock()
|
||||
goto retry
|
||||
} else {
|
||||
if cmd.CmdSN < sess.ExpCmdSN {
|
||||
@@ -673,8 +668,10 @@ func (s *ISCSITargetDriver) iscsiTaskQueueHandler(task *iscsiTask) error {
|
||||
}
|
||||
log.Debugf("add task(%d) into task queue", task.cmd.CmdSN)
|
||||
// add this connection into queue and set this task as pending task
|
||||
sess.PendingTasksMutex.Lock()
|
||||
task.state = taskPending
|
||||
sess.PendingTasks.Push(task)
|
||||
sess.PendingTasksMutex.Unlock()
|
||||
return fmt.Errorf("pending")
|
||||
}
|
||||
|
||||
@@ -716,6 +713,46 @@ func (s *ISCSITargetDriver) iscsiExecTask(task *iscsiTask) error {
|
||||
|
||||
case OpNoopOut:
|
||||
// just do it in iscsi layer
|
||||
case OpSCSITaskReq:
|
||||
sess := task.conn.session
|
||||
switch cmd.TaskFunc {
|
||||
case ISCSI_TM_FUNC_ABORT_TASK:
|
||||
stask := &iscsiTask{}
|
||||
sess.PendingTasksMutex.Lock()
|
||||
for i, t := range sess.PendingTasks {
|
||||
if cmd.ReferencedTaskTag == t.tag {
|
||||
stask = sess.PendingTasks[i]
|
||||
sess.PendingTasks = append(sess.PendingTasks[:i], sess.PendingTasks[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
sess.PendingTasksMutex.Unlock()
|
||||
if stask == nil {
|
||||
task.result = ISCSI_TMF_RSP_NO_TASK
|
||||
} else {
|
||||
// abort this task
|
||||
log.Debugf("abort the task[%v]", stask.tag)
|
||||
stask.scmd.Result = api.SAM_STAT_TASK_ABORTED
|
||||
stask.conn.buildRespPackage(OpSCSIResp, stask)
|
||||
stask.conn.rxTask = nil
|
||||
s.handler(DATAOUT, stask.conn)
|
||||
task.result = ISCSI_TMF_RSP_COMPLETE
|
||||
}
|
||||
case ISCSI_TM_FUNC_ABORT_TASK_SET:
|
||||
case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET:
|
||||
case ISCSI_TM_FUNC_CLEAR_ACA:
|
||||
fallthrough
|
||||
case ISCSI_TM_FUNC_CLEAR_TASK_SET:
|
||||
fallthrough
|
||||
case ISCSI_TM_FUNC_TARGET_WARM_RESET, ISCSI_TM_FUNC_TARGET_COLD_RESET, ISCSI_TM_FUNC_TASK_REASSIGN:
|
||||
task.result = ISCSI_TMF_RSP_NOT_SUPPORTED
|
||||
return fmt.Errorf("The task function is not supported")
|
||||
default:
|
||||
task.result = ISCSI_TMF_RSP_REJECTED
|
||||
return fmt.Errorf("Unknown task function")
|
||||
}
|
||||
// return response to initiator
|
||||
return task.conn.buildRespPackage(OpSCSITaskResp, task)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
)
|
||||
|
||||
const iSCSIDriverName = "iscsi"
|
||||
|
||||
const (
|
||||
IOSTATE_FREE = iota
|
||||
|
||||
@@ -53,7 +55,7 @@ const (
|
||||
IOSTATE_TX_END
|
||||
)
|
||||
|
||||
var ISCSI_OPCODE_MASK = 0x3F
|
||||
var ISCSI_OPCODE_MASK byte = 0x3F
|
||||
|
||||
type ISCSIDiscoveryMethod string
|
||||
|
||||
@@ -95,7 +97,7 @@ type ISCSITarget struct {
|
||||
/*
|
||||
* RFC 3720 iSCSI SSID = ISID , TPGT
|
||||
* ISID is an 6 bytes number, TPGT is an 2 bytes number
|
||||
* We combime ISID and TPGT together to be SSID
|
||||
* We combine ISID and TPGT together to be SSID
|
||||
*/
|
||||
func MakeSSID(ISID uint64, TPGT uint16) uint64 {
|
||||
SSID := ISID<<16 | uint64(TPGT)
|
||||
|
||||
@@ -253,6 +253,7 @@ type ISCSISession struct {
|
||||
ConnectionsRWMutex sync.RWMutex
|
||||
Commands []*ISCSICommand
|
||||
PendingTasks taskQueue
|
||||
PendingTasksMutex sync.RWMutex
|
||||
MaxQueueCommand uint32
|
||||
SessionParam ISCSISessionParamList
|
||||
Info string
|
||||
|
||||
55
pkg/port/iscsit/task.go
Normal file
55
pkg/port/iscsit/task.go
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// iSCSI task management
|
||||
package iscsit
|
||||
|
||||
const (
|
||||
ISCSI_FLAG_TM_FUNC_MASK byte = 0x7F
|
||||
|
||||
// Function values
|
||||
// aborts the task identified by the Referenced Task Tag field
|
||||
ISCSI_TM_FUNC_ABORT_TASK = 1
|
||||
// aborts all Tasks issued via this session on the logical unit
|
||||
ISCSI_TM_FUNC_ABORT_TASK_SET = 2
|
||||
// clears the Auto Contingent Allegiance condition
|
||||
ISCSI_TM_FUNC_CLEAR_ACA = 3
|
||||
// aborts all Tasks in the appropriate task set as defined by the TST field in the Control mode page
|
||||
ISCSI_TM_FUNC_CLEAR_TASK_SET = 4
|
||||
ISCSI_TM_FUNC_LOGICAL_UNIT_RESET = 5
|
||||
ISCSI_TM_FUNC_TARGET_WARM_RESET = 6
|
||||
ISCSI_TM_FUNC_TARGET_COLD_RESET = 7
|
||||
// reassigns connection allegiance for the task identified by the Referenced Task Tag field to this connection, thus resuming the iSCSI exchanges for the task
|
||||
ISCSI_TM_FUNC_TASK_REASSIGN = 8
|
||||
|
||||
// Response values
|
||||
// Function complete
|
||||
ISCSI_TMF_RSP_COMPLETE = 0x00
|
||||
// Task does not exist
|
||||
ISCSI_TMF_RSP_NO_TASK = 0x01
|
||||
// LUN does not exist
|
||||
ISCSI_TMF_RSP_NO_LUN = 0x02
|
||||
// Task still allegiant
|
||||
ISCSI_TMF_RSP_TASK_ALLEGIANT = 0x03
|
||||
// Task allegiance reassignment not supported
|
||||
ISCSI_TMF_RSP_NO_FAILOVER = 0x04
|
||||
// Task management function not supported
|
||||
ISCSI_TMF_RSP_NOT_SUPPORTED = 0x05
|
||||
// Function authorization failed
|
||||
ISCSI_TMF_RSP_AUTH_FAILED = 0x06
|
||||
// Function rejected
|
||||
ISCSI_TMF_RSP_REJECTED = 0xff
|
||||
)
|
||||
@@ -89,6 +89,7 @@ func luPerformCommand(tid int, cmd *api.SCSICommand) api.SAMStat {
|
||||
fnop := fn.(SCSIDeviceOperation)
|
||||
// TODO host := cmd.ITNexus.Host
|
||||
host := 0
|
||||
cmd.State = api.SCSICommandProcessed
|
||||
return fnop.CommandPerformFunc(host, cmd)
|
||||
}
|
||||
return api.SAMStatGood
|
||||
|
||||
Reference in New Issue
Block a user