fix bugs and make it run basically

This commit is contained in:
Lei Xue
2016-08-27 20:07:51 +08:00
parent f97ab027de
commit aedc09021f
22 changed files with 1997 additions and 431 deletions

View File

@@ -1,9 +1,22 @@
/*
Copyright 2016 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.
*/
package port
import (
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/port/iscsit"
)
import "github.com/gostor/gotgt/pkg/api"
type SCSITargetDriver interface {
Init() error
@@ -16,7 +29,6 @@ type SCSITargetDriver interface {
CreateLu(lu *api.SCSILu) error
GetLu(lun uint8) (uint64, error)
ProcessCommand(buf []byte) ([]byte, error)
CommandNotify(nid uint64, result int, cmd *api.SCSICommand) error
}
@@ -58,13 +70,3 @@ func (fake *fakeSCSITargetDriver) GetLun(lun uint8) (uint64, error) {
func (fake *fakeSCSITargetDriver) CommandNotify(nid uint64, result int, cmd *api.SCSICommand) error {
return nil
}
func (fake *fakeSCSITargetDriver) ProcessCommand(buf []byte) ([]byte, error) {
return []byte(""), nil
}
func NewTargetDriver(driver string, tgt *api.SCSITarget) SCSITargetDriver {
if driver == "iscsi" {
return iscsit.NewISCSITarget(tgt)
}
return nil
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015 The GoStor Authors All rights reserved.
Copyright 2016 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.

View File

@@ -4,6 +4,8 @@ import (
"bytes"
"fmt"
"strings"
"github.com/gostor/gotgt/pkg/util"
)
type OpCode int
@@ -52,6 +54,8 @@ var opCodeMap = map[OpCode]string{
OpReject: "Reject",
}
const DataPadding = 4
type ISCSICommand struct {
OpCode OpCode
RawHeader []byte
@@ -68,7 +72,7 @@ type ISCSICommand struct {
ExpStatSN uint32 // Expected status serial.
Read, Write bool
LUN uint8
LUN [8]uint8
Transit bool // Transit bit.
Cont bool // Continue bit.
CSG, NSG Stage // Current Stage, Next Stage.
@@ -102,6 +106,10 @@ func (cmd *ISCSICommand) Bytes() []byte {
return cmd.scsiCmdRespBytes()
case OpSCSIIn:
return cmd.dataInBytes()
case OpTextResp:
return cmd.textRespBytes()
case OpNoopIn:
return cmd.noopInBytes()
}
return nil
}
@@ -155,16 +163,8 @@ func ParseUint(data []byte) uint64 {
return out
}
func MarshalUint64(i uint64) []byte {
var data []byte
for j := 0; j < 8; j++ {
b := byte(i >> uint(8*(7-j)) & 0xff)
data = append(data, b)
}
return data
}
func parseHeader(data []byte) (*ISCSICommand, error) {
if len(data) != 48 {
if len(data) != BHS_SIZE {
return nil, fmt.Errorf("garbled header")
}
// TODO: sync.Pool
@@ -177,7 +177,7 @@ func parseHeader(data []byte) (*ISCSICommand, error) {
m.TaskTag = uint32(ParseUint(data[16:20]))
switch m.OpCode {
case OpSCSICmd:
m.LUN = uint8(data[9])
m.LUN = [8]uint8{data[9]}
m.ExpectedDataLen = uint32(ParseUint(data[20:24]))
m.CmdSN = uint32(ParseUint(data[24:28]))
m.Read = data[1]&0x40 == 0x40
@@ -185,7 +185,7 @@ func parseHeader(data []byte) (*ISCSICommand, error) {
m.CDB = data[32:48]
m.ExpStatSN = uint32(ParseUint(data[28:32]))
case OpSCSIResp:
case OpLoginReq:
case OpLoginReq, OpTextReq, OpNoopOut, OpLogoutReq:
m.Transit = m.Final
m.Cont = data[1]&0x40 == 0x40
if m.Cont && m.Transit {
@@ -229,13 +229,13 @@ func (m *ISCSICommand) scsiCmdRespBytes() []byte {
for i := 0; i < 3*4; i++ {
buf.WriteByte(0x00)
}
buf.Write(MarshalUint64(uint64(m.TaskTag))[4:])
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
for i := 0; i < 4; i++ {
buf.WriteByte(0x00)
}
buf.Write(MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
for i := 0; i < 3*4; i++ {
buf.WriteByte(0x00)
}
@@ -259,24 +259,22 @@ func (m *ISCSICommand) dataInBytes() []byte {
}
buf.WriteByte(b)
buf.WriteByte(0x00) // 4
buf.Write(MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8
buf.WriteByte(0x00)
buf.WriteByte(byte(m.LUN))
buf.WriteByte(0x00) // 4
buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8
// Skip through to byte 16
for i := 0; i < 6; i++ {
buf.WriteByte(0x00)
for i := 0; i < 8; i++ {
buf.WriteByte(byte(m.LUN[i]))
}
buf.Write(MarshalUint64(uint64(m.TaskTag))[4:])
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
for i := 0; i < 4; i++ {
// 11.7.4
buf.WriteByte(0xff)
}
buf.Write(MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:])
buf.Write(MarshalUint64(uint64(m.DataSN))[4:])
buf.Write(MarshalUint64(uint64(m.BufferOffset))[4:])
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.DataSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.BufferOffset))[4:])
for i := 0; i < 4; i++ {
buf.WriteByte(0x00)
}
@@ -290,123 +288,79 @@ func (m *ISCSICommand) dataInBytes() []byte {
return buf.Bytes()
}
type InquiryData struct {
PeripheralQualifier int
PeripheralType int
Removable bool
Version int
SupportsACA bool
Hierarchical bool
SupportsSCC bool
HasACC bool
TargetGroupSupport int
ThirdPartyCopy bool
Protect bool
EnclosureServices bool
Multiport bool
MediaChanger bool
Vendor [8]byte
Product [16]byte
RevisionLevel [4]byte
SerialNumber uint64
}
func (id *InquiryData) bytes() []byte {
func (m *ISCSICommand) textRespBytes() []byte {
buf := &bytes.Buffer{}
buf.WriteByte(byte(OpTextResp))
var b byte
b = (uint8(id.PeripheralQualifier) << 5) & 0xe0
b |= uint8(id.PeripheralType) & 0x1f
buf.WriteByte(b)
b = 0
if id.Removable {
b = 0x80
}
buf.WriteByte(b)
buf.WriteByte(byte(id.Version))
b = 0x02
if id.SupportsACA {
b |= 0x20
}
if id.Hierarchical {
b |= 0x10
}
buf.WriteByte(b)
buf.WriteByte(0x00)
// byte 5
b = 0
if id.SupportsSCC {
if m.Final {
b |= 0x80
}
if id.HasACC {
if m.Cont {
b |= 0x40
}
b |= byte(id.TargetGroupSupport) << 4 & 0x30
if id.ThirdPartyCopy {
b |= 0x08
}
if id.Protect {
b |= 0x01
}
// byte 1
buf.WriteByte(b)
// byte 6
b = 0
if id.EnclosureServices {
b |= 0x40
}
if id.Multiport {
b |= 0x10
}
if id.MediaChanger {
b |= 0x08
}
buf.WriteByte(b)
buf.WriteByte(0x02)
buf.Write(id.Vendor[:])
buf.Write(id.Product[:])
buf.Write(id.RevisionLevel[:])
buf.Write(MarshalUint64(id.SerialNumber))
for i := 0; i < 12; i++ {
buf.WriteByte(b)
buf.WriteByte(b)
buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8
// Skip through to byte 12
for i := 0; i < 2*4; i++ {
buf.WriteByte(0x00)
}
data := buf.Bytes()
data[4] = byte(len(data) - 4)
return data
}
type Capacity struct {
LBA uint64
Blocksize uint32
ProtectionType uint8
PIExponent uint8
LogicalExponent uint8
ThinProvisioned bool
ThinProvReturnsZeros bool
LowestLBA uint16
}
func (c *Capacity) bytes() []byte {
// table 111
// http://www.seagate.com/staticfiles/support/disc/manuals/Interface%20manuals/100293068c.pdf
buf := &bytes.Buffer{}
buf.Write(MarshalUint64(c.LBA))
buf.Write(MarshalUint64(uint64(c.Blocksize))[4:])
var b byte
if c.ProtectionType > 0 {
b |= 0x01
b |= c.ProtectionType << 1
b &= 0x0f
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
for i := 0; i < 4; i++ {
buf.WriteByte(0xff)
}
buf.WriteByte(b)
b = c.PIExponent << 4
b |= c.LogicalExponent
buf.WriteByte(b)
lowLBA := MarshalUint64(uint64(c.LowestLBA))[6:]
lowLBA[0] &= 0x3f
if c.ThinProvisioned {
lowLBA[0] &= 0x80
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
for i := 0; i < 3*4; i++ {
buf.WriteByte(0x00)
}
if c.ThinProvReturnsZeros {
lowLBA[0] &= 0x40
rd := m.RawData
for len(rd)%4 != 0 {
rd = append(rd, 0)
}
buf.Write(rd)
return buf.Bytes()
}
func (m *ISCSICommand) noopInBytes() []byte {
buf := &bytes.Buffer{}
buf.WriteByte(byte(OpNoopIn))
var b byte
b |= 0x80
// byte 1
buf.WriteByte(b)
b = 0
buf.WriteByte(b)
buf.WriteByte(b)
buf.WriteByte(b)
buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8
// Skip through to byte 12
for i := 0; i < 2*4; i++ {
buf.WriteByte(0x00)
}
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
for i := 0; i < 4; i++ {
buf.WriteByte(0xff)
}
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
for i := 0; i < 3*4; i++ {
buf.WriteByte(0x00)
}
rd := m.RawData
for len(rd)%4 != 0 {
rd = append(rd, 0)
}
buf.Write(rd)
return buf.Bytes()
}

125
pkg/port/iscsit/conn.go Normal file
View File

@@ -0,0 +1,125 @@
/*
Copyright 2016 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.
*/
package iscsit
import (
"net"
"github.com/gostor/gotgt/pkg/api"
)
const (
CONN_STATE_FREE = 0
CONN_STATE_SECURITY = 1
CONN_STATE_SECURITY_AUTH = 2
CONN_STATE_SECURITY_DONE = 3
CONN_STATE_SECURITY_LOGIN = 4
CONN_STATE_SECURITY_FULL = 5
CONN_STATE_LOGIN = 6
CONN_STATE_LOGIN_FULL = 7
CONN_STATE_FULL = 8
CONN_STATE_KERNEL = 9
CONN_STATE_CLOSE = 10
CONN_STATE_EXIT = 11
CONN_STATE_SCSI = 12
CONN_STATE_INIT = 13
CONN_STATE_START = 14
CONN_STATE_READY = 15
)
var (
DATAIN byte = 0x01
DATAOUT byte = 0x10
)
type iscsiConnection struct {
state int
authState int
session *ISCSISession
sessionType int
sessionParam []ISCSISessionParam
tid int
cid uint16
rxIOState int
txIOState int
refcount int
conn net.Conn
initiator string
initiatorAlias string
rxBuffer []byte
txBuffer []byte
req *ISCSICommand
resp *ISCSICommand
// StatSN - the status sequence number on this connection
statSN uint32
// ExpStatSN - the expected status sequence number on this connection
expStatSN uint32
// CmdSN - the command sequence number at the target
cmdSN uint32
// ExpCmdSN - the next expected command sequence number at the target
expCmdSN uint32
// MaxCmdSN - the maximum CmdSN acceptable at the target from this initiator
maxCmdSN uint32
rxTask *iscsiTask
txTask *iscsiTask
authMethod AuthMethod
}
type taskState int
const (
taskPending taskState = 1
taskSCSI taskState = 2
)
type iscsiTask struct {
tag uint32
conn *iscsiConnection
cmd *ISCSICommand
scmd *api.SCSICommand
state taskState
}
func (c *iscsiConnection) init() {
c.state = CONN_STATE_FREE
c.refcount = 1
c.sessionParam = []ISCSISessionParam{}
for _, param := range sessionKeys {
c.sessionParam = append(c.sessionParam, ISCSISessionParam{Value: param.def})
}
}
func (c *iscsiConnection) readData(size int) ([]byte, int, error) {
var buf = make([]byte, size)
length, err := c.conn.Read(buf)
if err != nil {
return nil, -1, err
}
return buf, length, nil
}
func (c *iscsiConnection) write(resp []byte) (int, error) {
return c.conn.Write(resp)
}
func (c *iscsiConnection) close() {
c.conn.Close()
}

592
pkg/port/iscsit/iscsid.go Normal file
View File

@@ -0,0 +1,592 @@
/*
Copyright 2016 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.
*/
package iscsit
import (
"bytes"
"fmt"
"net"
"os"
"github.com/golang/glog"
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/port"
"github.com/gostor/gotgt/pkg/scsi"
"github.com/gostor/gotgt/pkg/util"
)
type ISCSITargetService struct {
SCSI *scsi.SCSITargetService
Name string
Targets map[string]*ISCSITarget
}
func init() {
port.RegisterTargetService("iscsi", NewISCSITargetService)
}
func NewISCSITargetService(base *scsi.SCSITargetService) (port.SCSITargetService, error) {
return &ISCSITargetService{
Name: "iscsi",
Targets: map[string]*ISCSITarget{},
SCSI: base,
}, nil
}
func (s *ISCSITargetService) NewTarget(target string) (port.SCSITargetDriver, error) {
if _, ok := s.Targets[target]; ok {
return nil, fmt.Errorf("target name has been existed")
}
stgt, err := s.SCSI.NewSCSITarget(len(s.Targets), "iscsi", target)
if err != nil {
return nil, err
}
tgt := newISCSITarget(stgt)
s.Targets[target] = tgt
return tgt, nil
}
func (s *ISCSITargetService) Run() error {
l, err := net.Listen("tcp", ":3260")
if err != nil {
glog.Error(err)
os.Exit(1)
}
defer l.Close()
for {
glog.Info("Listening ...")
conn, err := l.Accept()
if err != nil {
glog.Error(err)
}
glog.Info("Accepting ...")
iscsiConn := &iscsiConnection{conn: conn}
iscsiConn.init()
iscsiConn.rxIOState = IOSTATE_RX_BHS
glog.Infof("connection is connected from %s...\n", conn.RemoteAddr().String())
// start a new thread to do with this command
go s.handler(DATAIN, iscsiConn)
}
return nil
}
func (s *ISCSITargetService) handler(events byte, conn *iscsiConnection) {
if events&DATAIN != 0 {
glog.Infof("rx handler processing...")
go s.rxHandler(conn)
}
if conn.state != CONN_STATE_CLOSE && events&DATAOUT != 0 {
glog.Infof("tx handler processing...")
s.txHandler(conn)
}
if conn.state == CONN_STATE_CLOSE {
glog.Infof("iscsi connection[%d] closed", conn.cid)
conn.close()
}
}
func (s *ISCSITargetService) rxHandler(conn *iscsiConnection) {
var (
hdigest uint = 0
ddigest uint = 0
final bool = false
cmd *ISCSICommand
)
if conn.state == CONN_STATE_SCSI {
hdigest = conn.sessionParam[ISCSI_PARAM_HDRDGST_EN].Value & DIGEST_CRC32C
ddigest = conn.sessionParam[ISCSI_PARAM_DATADGST_EN].Value & DIGEST_CRC32C
}
for {
switch conn.rxIOState {
case IOSTATE_RX_BHS:
glog.Infof("rx handler: IOSTATE_RX_BHS")
buf, length, err := conn.readData(BHS_SIZE)
if err != nil {
glog.Error(err)
return
}
if length == 0 {
conn.state = CONN_STATE_CLOSE
return
}
conn.rxBuffer = buf
cmd, err = parseHeader(buf)
if err != nil {
glog.Error(err)
conn.state = CONN_STATE_CLOSE
return
}
conn.req = cmd
if length == BHS_SIZE && cmd.DataLen != 0 {
conn.rxIOState = IOSTATE_RX_INIT_AHS
break
}
glog.Infof("got command: \n%s", cmd.String())
glog.Infof("got buffer: %v", buf)
final = true
case IOSTATE_RX_INIT_AHS:
conn.rxIOState = IOSTATE_RX_DATA
break
if hdigest != 0 {
conn.rxIOState = IOSTATE_RX_INIT_HDIGEST
}
case IOSTATE_RX_DATA:
if ddigest != 0 {
conn.rxIOState = IOSTATE_RX_INIT_DDIGEST
}
if cmd == nil {
return
}
dl := ((cmd.DataLen + DataPadding - 1) / DataPadding) * DataPadding
buf, length, err := conn.readData(dl)
if err != nil {
glog.Error(err)
return
}
if length != dl {
conn.state = CONN_STATE_CLOSE
return
}
cmd.RawData = buf[:cmd.DataLen]
conn.rxBuffer = append(conn.rxBuffer, buf...)
final = true
glog.Infof("got command: \n%s", cmd.String())
default:
glog.Errorf("error %d %d\n", conn.state, conn.rxIOState)
return
}
if final {
break
}
}
if conn.state == CONN_STATE_SCSI {
s.scsiCommandHandler(conn)
} else {
conn.txIOState = IOSTATE_TX_BHS
conn.resp = &ISCSICommand{}
switch conn.req.OpCode {
case OpLoginReq:
glog.Infof("OpLoginReq")
if err := s.iscsiExecLogin(conn); err != nil {
glog.Error(err)
conn.state = CONN_STATE_CLOSE
}
case OpLogoutReq:
glog.Infof("OpLogoutReq")
if err := iscsiExecLogout(conn); err != nil {
conn.state = CONN_STATE_CLOSE
}
case OpTextReq:
glog.Infof("OpTextReq")
if err := s.iscsiExecText(conn); err != nil {
conn.state = CONN_STATE_CLOSE
}
default:
iscsiExecReject(conn)
}
glog.Infof("%#v", conn.resp.String())
s.handler(DATAOUT, conn)
}
}
func (s *ISCSITargetService) iscsiExecLogin(conn *iscsiConnection) error {
var (
target *ISCSITarget
cmd = conn.req
)
conn.resp = &ISCSICommand{
OpCode: OpLoginResp,
Transit: true,
CSG: cmd.CSG,
NSG: FullFeaturePhase,
StatSN: cmd.ExpStatSN,
TaskTag: cmd.TaskTag,
ExpCmdSN: cmd.CmdSN,
MaxCmdSN: cmd.CmdSN,
RawData: util.MarshalKVText([]util.KeyValue{
{"HeaderDigest", "None"},
{"DataDigest", "None"},
}),
}
pairs := util.ParseKVText(cmd.RawData)
if initiatorName, ok := pairs["InitiatorName"]; ok {
conn.initiator = initiatorName
}
if alias, ok := pairs["InitiatorAlias"]; ok {
conn.initiatorAlias = alias
}
targetName := pairs["TargetName"]
if sessType, ok := pairs["SessionType"]; ok {
if sessType == "Normal" {
conn.sessionType = SESSION_NORMAL
} else {
conn.sessionType = SESSION_DISCOVERY
}
}
if conn.sessionType == SESSION_DISCOVERY {
conn.tid = 0xffff
} else {
for _, t := range s.Targets {
if t.SCSITarget.Name == targetName {
target = t
break
}
}
if target == nil {
conn.state = CONN_STATE_EXIT
return fmt.Errorf("No target found with name(%s)", targetName)
}
conn.tid = target.TID
}
switch conn.state {
case CONN_STATE_FREE:
conn.state = CONN_STATE_SECURITY
case CONN_STATE_SECURITY:
}
conn.state = CONN_STATE_LOGIN_FULL
conn.expCmdSN = cmd.CmdSN
conn.statSN += 1
switch conn.sessionType {
case SESSION_NORMAL:
if conn.session == nil {
// create a new session
sess, err := s.NewISCSISession(conn)
if err != nil {
glog.Error(err)
return err
}
conn.session = sess
}
case SESSION_DISCOVERY:
}
return nil
}
func iscsiExecLogout(conn *iscsiConnection) error {
cmd := conn.req
conn.resp = &ISCSICommand{
OpCode: OpLogoutResp,
StatSN: cmd.ExpStatSN,
TaskTag: cmd.TaskTag,
}
if conn.session == nil {
conn.resp.ExpCmdSN = cmd.CmdSN
conn.resp.MaxCmdSN = cmd.CmdSN
} else {
conn.resp.ExpCmdSN = conn.session.ExpCmdSN
conn.resp.MaxCmdSN = conn.session.ExpCmdSN + 10
}
return nil
}
func (s *ISCSITargetService) iscsiExecText(conn *iscsiConnection) error {
var result = []util.KeyValue{}
cmd := conn.req
keys := util.ParseKVText(cmd.RawData)
if st, ok := keys["SendTargets"]; ok {
if st == "All" {
list, err := s.SCSI.GetTargetList()
if err != nil {
return err
}
for _, t := range list {
result = append(result, util.KeyValue{"TargetName", t.Name})
result = append(result, util.KeyValue{"TargetAddress", "127.0.0.1:3260,1"})
}
}
}
conn.resp = &ISCSICommand{
OpCode: OpTextResp,
Final: true,
NSG: FullFeaturePhase,
StatSN: cmd.ExpStatSN,
TaskTag: cmd.TaskTag,
ExpCmdSN: cmd.CmdSN,
MaxCmdSN: cmd.CmdSN,
}
conn.resp.RawData = util.MarshalKVText(result)
return nil
}
func iscsiExecNoopOut(conn *iscsiConnection) error {
cmd := conn.req
conn.resp = &ISCSICommand{
OpCode: OpNoopIn,
Final: true,
NSG: FullFeaturePhase,
StatSN: cmd.ExpStatSN,
TaskTag: cmd.TaskTag,
ExpCmdSN: cmd.CmdSN + 1,
MaxCmdSN: cmd.CmdSN + 10,
}
return nil
}
func iscsiExecReject(conn *iscsiConnection) error {
conn.resp = &ISCSICommand{
OpCode: OpReject,
}
return nil
}
func (s *ISCSITargetService) txHandler(conn *iscsiConnection) {
var (
hdigest uint = 0
ddigest uint = 0
final bool = false
)
if conn.state == CONN_STATE_SCSI {
hdigest = conn.sessionParam[ISCSI_PARAM_HDRDGST_EN].Value & DIGEST_CRC32C
ddigest = conn.sessionParam[ISCSI_PARAM_DATADGST_EN].Value & DIGEST_CRC32C
}
if conn.state == CONN_STATE_SCSI && conn.txTask == nil {
err := s.scsiCommandHandler(conn)
if err != nil {
glog.Error(err)
return
}
}
for {
switch conn.txIOState {
case IOSTATE_TX_BHS:
glog.V(2).Infof("ready to write response")
glog.V(2).Infof("%s", conn.resp.String())
glog.V(2).Infof("length of RawData is %d", len(conn.resp.RawData))
glog.V(2).Infof("length of resp is %d", len(conn.resp.Bytes()))
if l, err := conn.write(conn.resp.Bytes()); err != nil {
glog.Error(err)
return
} else {
conn.txIOState = IOSTATE_TX_INIT_AHS
glog.V(2).Infof("success to write %d length", l)
}
case IOSTATE_TX_INIT_AHS:
if hdigest != 0 {
conn.txIOState = IOSTATE_TX_INIT_HDIGEST
} else {
conn.txIOState = IOSTATE_TX_INIT_DATA
}
if conn.txIOState != IOSTATE_TX_AHS {
final = true
}
case IOSTATE_TX_AHS:
case IOSTATE_TX_INIT_DATA:
final = true
case IOSTATE_TX_DATA:
if ddigest != 0 {
conn.txIOState = IOSTATE_TX_INIT_DDIGEST
}
default:
glog.Errorf("error %d %d\n", conn.state, conn.txIOState)
return
}
if final {
break
}
}
switch conn.state {
case CONN_STATE_CLOSE, CONN_STATE_EXIT:
conn.state = CONN_STATE_CLOSE
case CONN_STATE_SECURITY_LOGIN:
conn.state = CONN_STATE_LOGIN
glog.Infof("CONN_STATE_LOGIN")
case CONN_STATE_SECURITY_FULL, CONN_STATE_LOGIN_FULL:
if conn.sessionType == SESSION_NORMAL {
conn.state = CONN_STATE_KERNEL
glog.Infof("CONN_STATE_KERNEL")
conn.state = CONN_STATE_SCSI
glog.Infof("CONN_STATE_SCSI")
} else {
conn.state = CONN_STATE_FULL
glog.Infof("CONN_STATE_FULL")
}
conn.rxIOState = IOSTATE_RX_BHS
s.handler(DATAIN, conn)
case CONN_STATE_SCSI:
conn.txTask = nil
default:
conn.rxIOState = IOSTATE_RX_BHS
s.handler(DATAIN, conn)
}
glog.Infof("%d", conn.state)
}
func (s *ISCSITargetService) scsiCommandHandler(conn *iscsiConnection) (err error) {
req := conn.req
switch req.OpCode {
case OpSCSICmd:
glog.Infof("SCSI Command processing...")
scmd := &api.SCSICommand{}
task := &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: scmd}
conn.rxTask = task
if err = s.iscsiTaskQueueHandler(task); err != nil {
return
} else {
conn.rxTask = nil
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: &api.SCSICommand{}}
conn.txIOState = IOSTATE_TX_BHS
conn.statSN += 1
resp := &ISCSICommand{
Immediate: true,
Final: true,
StatSN: req.ExpStatSN,
TaskTag: req.TaskTag,
ExpCmdSN: conn.session.ExpCmdSN,
MaxCmdSN: conn.session.ExpCmdSN + 10,
Status: scmd.Result,
SCSIResponse: 0x00,
HasStatus: true,
}
switch scmd.Direction {
case api.SCSIDataRead:
resp.OpCode = OpSCSIIn
if scmd.InSDBBuffer.Buffer != nil {
buf := scmd.InSDBBuffer.Buffer.Bytes()
resp.RawData = buf
} else {
resp.RawData = []byte{}
}
case api.SCSIDataWrite:
resp.OpCode = OpSCSIResp
if scmd.InSDBBuffer.Buffer != nil {
buf := scmd.InSDBBuffer.Buffer.Bytes()
resp.RawData = buf
} else {
resp.RawData = []byte{}
}
case api.SCSIDataBidirection:
case api.SCSIDataNone:
resp.OpCode = OpSCSIResp
}
conn.resp = resp
}
case OpSCSIOut:
case OpNoopOut:
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag}
conn.txIOState = IOSTATE_TX_BHS
iscsiExecNoopOut(conn)
case OpLogoutReq:
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag}
conn.txIOState = IOSTATE_TX_BHS
iscsiExecLogout(conn)
case OpTextReq, OpSNACKReq:
err = fmt.Errorf("Cannot handle yet %s", opCodeMap[conn.req.OpCode])
glog.Error(err)
return
default:
err = fmt.Errorf("Unknown op %s", opCodeMap[conn.req.OpCode])
glog.Error(err)
return
}
conn.rxIOState = IOSTATE_RX_BHS
s.handler(DATAIN|DATAOUT, conn)
return nil
}
func (s *ISCSITargetService) iscsiTaskQueueHandler(task *iscsiTask) error {
conn := task.conn
sess := conn.session
cmd := task.cmd
if cmd.Immediate {
return s.iscsiExecTask(task)
}
cmdsn := cmd.CmdSN
glog.V(2).Infof("CmdSN of command is %d, ExpCmdSN of session is %d", cmdsn, sess.ExpCmdSN)
if cmdsn == sess.ExpCmdSN {
retry:
cmdsn += 1
sess.ExpCmdSN = cmdsn
glog.Infof("session's ExpCmdSN is %d", cmdsn)
glog.Infof("process task(%d)", task.cmd.CmdSN)
if err := s.iscsiExecTask(task); err != nil {
glog.Error(err)
}
if len(sess.PendingTasks) == 0 {
return nil
}
task = sess.PendingTasks.Pop().(*iscsiTask)
cmd = task.cmd
if cmd.CmdSN != cmdsn {
sess.PendingTasks.Push(task)
return nil
}
task.state = taskSCSI
goto retry
} else {
if cmd.CmdSN < sess.ExpCmdSN {
err := fmt.Errorf("unexpected cmd serial number: (%d, %d)", cmd.CmdSN, sess.ExpCmdSN)
glog.Error(err)
return err
}
glog.Infof("add task(%d) into task queue", task.cmd.CmdSN)
// add this connection into queue and set this task as pending task
task.state = taskPending
sess.PendingTasks.Push(task)
}
return nil
}
func (s *ISCSITargetService) iscsiExecTask(task *iscsiTask) error {
cmd := task.cmd
switch cmd.OpCode {
case OpSCSICmd:
if cmd.Read {
if cmd.Write {
task.scmd.Direction = api.SCSIDataBidirection
} else {
task.scmd.Direction = api.SCSIDataRead
}
} else {
if cmd.Write {
task.scmd.Direction = api.SCSIDataWrite
}
}
task.scmd.CommandITNID = task.conn.session.Tsih
task.scmd.SCB = bytes.NewBuffer(cmd.CDB)
task.scmd.SCBLength = len(cmd.CDB)
task.scmd.Lun = cmd.LUN
task.scmd.Tag = uint64(cmd.TaskTag)
task.state = taskSCSI
task.scmd.OutSDBBuffer.Buffer = bytes.NewBuffer(cmd.RawData)
// add scsi target process queue
err := s.SCSI.AddCommandQueue(task.conn.session.Target.SCSITarget.TID, task.scmd)
if err != nil {
task.state = 0
}
return err
case OpLogoutReq:
case OpNoopOut:
// just do it in iscsi layer
}
return nil
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015 The GoStor Authors All rights reserved.
Copyright 2016 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.
@@ -17,11 +17,38 @@ limitations under the License.
// iSCSI Target Driver
package iscsit
import (
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/util"
import "github.com/gostor/gotgt/pkg/api"
const (
IOSTATE_FREE = iota
IOSTATE_RX_BHS
IOSTATE_RX_INIT_AHS
IOSTATE_RX_AHS
IOSTATE_RX_INIT_HDIGEST
IOSTATE_RX_HDIGEST
IOSTATE_RX_CHECK_HDIGEST
IOSTATE_RX_INIT_DATA
IOSTATE_RX_DATA
IOSTATE_RX_INIT_DDIGEST
IOSTATE_RX_DDIGEST
IOSTATE_RX_CHECK_DDIGEST
IOSTATE_RX_END
IOSTATE_TX_BHS
IOSTATE_TX_INIT_AHS
IOSTATE_TX_AHS
IOSTATE_TX_INIT_HDIGEST
IOSTATE_TX_HDIGEST
IOSTATE_TX_INIT_DATA
IOSTATE_TX_DATA
IOSTATE_TX_INIT_DDIGEST
IOSTATE_TX_DDIGEST
IOSTATE_TX_END
)
var ISCSI_OPCODE_MASK = 0x3F
type ISCSIDiscoveryMethod string
var (
@@ -38,7 +65,7 @@ type ISCSIRedirectInfo struct {
}
type ISCSITarget struct {
*api.SCSITarget
api.SCSITarget
api.SCSITargetDriverCommon
Sessions []*ISCSISession
@@ -51,9 +78,9 @@ type ISCSITarget struct {
NopCount int
}
func NewISCSITarget(target *api.SCSITarget) *ISCSITarget {
func newISCSITarget(target *api.SCSITarget) *ISCSITarget {
return &ISCSITarget{
SCSITarget: target,
SCSITarget: *target,
}
}
@@ -91,96 +118,3 @@ func (tgt *ISCSITarget) GetLu(lun uint8) (uint64, error) {
func (tgt *ISCSITarget) CommandNotify(nid uint64, result int, cmd *api.SCSICommand) error {
return nil
}
func (tgt *ISCSITarget) ProcessCommand(buf []byte) ([]byte, error) {
b := make([]byte, 48) // TODO: sync.Pool
b = buf[0:48]
m, err := parseHeader(b)
if err != nil {
return nil, err
}
m.RawHeader = b
if m.DataLen > 0 {
m.RawData = buf[48:m.DataLen]
}
resp := &ISCSICommand{}
switch m.OpCode {
case OpLoginReq:
resp = &ISCSICommand{
OpCode: OpLoginResp,
Transit: true,
NSG: FullFeaturePhase,
StatSN: m.ExpStatSN,
TaskTag: m.TaskTag,
ExpCmdSN: m.CmdSN,
MaxCmdSN: m.CmdSN,
RawData: util.MarshalKVText(map[string]string{
"HeaderDigest": "None",
"DataDigest": "None",
}),
}
break
case OpLogoutReq:
resp = &ISCSICommand{
OpCode: OpLogoutResp,
StatSN: m.ExpStatSN,
TaskTag: m.TaskTag,
ExpCmdSN: m.CmdSN,
MaxCmdSN: m.CmdSN,
}
case OpSCSICmd:
resp = &ISCSICommand{
OpCode: OpSCSIResp,
Final: true,
StatSN: m.ExpStatSN,
TaskTag: m.TaskTag,
ExpCmdSN: m.CmdSN + 1,
MaxCmdSN: m.CmdSN + 10,
}
switch api.SCSICommandType(m.CDB[0]) {
case api.TEST_UNIT_READY:
// test unit ready
resp.Status = api.SAM_STAT_GOOD
resp.SCSIResponse = 0x01
break
case api.READ_CAPACITY:
resp.OpCode = OpSCSIIn
resp.HasStatus = true
var data []byte
data = append(data, MarshalUint64(uint64(0))[4:]...)
data = append(data, MarshalUint64(uint64(0))[4:]...)
resp.RawData = data
break
case api.SERVICE_ACTION_IN:
resp.OpCode = OpSCSIIn
resp.HasStatus = true
sa := m.CDB[1] & 0x1f
switch sa {
case 0x10:
c := &Capacity{}
resp.RawData = c.bytes()
}
break
case api.INQUIRY:
resp.OpCode = OpSCSIIn
resp.HasStatus = true
alloc := int(ParseUint(m.CDB[3:5]))
inq := &InquiryData{
Vendor: [8]byte{'1', '1', 'c', 'a', 'n', 's'},
Product: [16]byte{'c', 'o', 'f', 'f', 'e', 'e'},
RevisionLevel: [4]byte{'1', '.', '0'},
SerialNumber: 52,
}
if len(inq.bytes()) >= alloc {
resp.RawData = inq.bytes()[:alloc]
} else {
resp.RawData = inq.bytes()
}
break
default:
break
}
}
b1 := resp.Bytes()
return b1, nil
}

View File

@@ -1,6 +1,10 @@
package iscsit
import "bytes"
import (
"bytes"
"github.com/gostor/gotgt/pkg/util"
)
func (m *ISCSICommand) loginRespBytes() []byte {
// rfc7143 11.13
@@ -20,20 +24,20 @@ func (m *ISCSICommand) loginRespBytes() []byte {
buf.WriteByte(b)
b = 0
buf.WriteByte(b) // version-max
buf.WriteByte(b) // version-active
buf.WriteByte(b) // ahsLen
buf.Write(MarshalUint64(uint64(len(m.RawData)))[5:]) // data segment length, no padding
buf.Write(MarshalUint64(m.ISID)[2:])
buf.Write(MarshalUint64(uint64(m.TSIH))[6:])
buf.Write(MarshalUint64(uint64(m.TaskTag))[4:])
buf.WriteByte(b) // version-max
buf.WriteByte(b) // version-active
buf.WriteByte(b) // ahsLen
buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // data segment length, no padding
buf.Write(util.MarshalUint64(m.ISID)[2:])
buf.Write(util.MarshalUint64(uint64(m.TSIH))[6:])
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
buf.WriteByte(b)
buf.WriteByte(b)
buf.WriteByte(b)
buf.WriteByte(b) // "reserved"
buf.Write(MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
buf.WriteByte(byte(m.StatusClass))
buf.WriteByte(byte(m.StatusDetail))
buf.WriteByte(b)

View File

@@ -1,6 +1,10 @@
package iscsit
import "bytes"
import (
"bytes"
"github.com/gostor/gotgt/pkg/util"
)
func (m *ISCSICommand) logoutRespBytes() []byte {
buf := &bytes.Buffer{}
@@ -11,13 +15,13 @@ func (m *ISCSICommand) logoutRespBytes() []byte {
for i := 4; i < 16; i++ {
buf.WriteByte(0x00)
}
buf.Write(MarshalUint64(uint64(m.TaskTag))[4:])
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
for i := 20; i < 24; i++ {
buf.WriteByte(0x00)
}
buf.Write(MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
for i := 36; i < 48; i++ {
buf.WriteByte(0x00)
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015 The GoStor Authors All rights reserved.
Copyright 2016 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.

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015 The GoStor Authors All rights reserved.
Copyright 2016 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.
@@ -16,13 +16,133 @@ limitations under the License.
package iscsit
import "crypto/rand"
import (
"fmt"
"math/rand"
"time"
)
var (
SESSION_NORMAL int = 0
SESSION_DISCOVERY int = 1
)
var DIGEST_CRC32C uint = 1 << 1
var DIGEST_NONE uint = 1 << 0
var DIGEST_ALL uint = DIGEST_NONE | DIGEST_CRC32C
var BHS_SIZE = 48
const (
MAX_QUEUE_CMD_MIN = 1
MAX_QUEUE_CMD_DEF = 128
MAX_QUEUE_CMD_MAX = 512
)
const (
ISCSI_PARAM_MAX_RECV_DLENGTH = iota
ISCSI_PARAM_HDRDGST_EN
ISCSI_PARAM_DATADGST_EN
ISCSI_PARAM_INITIAL_R2T_EN
ISCSI_PARAM_MAX_R2T
ISCSI_PARAM_IMM_DATA_EN
ISCSI_PARAM_FIRST_BURST
ISCSI_PARAM_MAX_BURST
ISCSI_PARAM_PDU_INORDER_EN
ISCSI_PARAM_DATASEQ_INORDER_EN
ISCSI_PARAM_ERL
ISCSI_PARAM_IFMARKER_EN
ISCSI_PARAM_OFMARKER_EN
ISCSI_PARAM_DEFAULTTIME2WAIT
ISCSI_PARAM_DEFAULTTIME2RETAIN
ISCSI_PARAM_OFMARKINT
ISCSI_PARAM_IFMARKINT
ISCSI_PARAM_MAXCONNECTIONS
/* iSCSI Extensions for RDMA (RFC5046) */
ISCSI_PARAM_RDMA_EXTENSIONS
ISCSI_PARAM_TARGET_RDSL
ISCSI_PARAM_INITIATOR_RDSL
ISCSI_PARAM_MAX_OUTST_PDU
/* "local" parmas, never sent to the initiator */
ISCSI_PARAM_FIRST_LOCAL
ISCSI_PARAM_MAX_XMIT_DLENGTH = ISCSI_PARAM_FIRST_LOCAL
ISCSI_PARAM_MAX_QUEUE_CMD
/* must always be last */
ISCSI_PARAM_MAX
)
type ISCSISessionParam struct {
State int
Value uint
}
/*
* The defaults here are according to the spec and must not be changed,
* otherwise the initiator may make the wrong assumption. If you want
* to change a value, edit the value in iscsi_target_create.
*
* The param MaxXmitDataSegmentLength doesn't really exist. It's a way
* to remember the RDSL of the initiator, which defaults to 8k if he has
* not told us otherwise.
*/
type iscsiSessionKeys struct {
name string
def uint
min uint
max uint
}
var sessionKeys []iscsiSessionKeys = []iscsiSessionKeys{
// ISCSI_PARAM_MAX_RECV_DLENGTH
{"MaxRecvDataSegmentLength", 8192, 512, 16777215},
// ISCSI_PARAM_HDRDGST_EN
{"HeaderDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_ALL},
// ISCSI_PARAM_DATADGST_EN
{"DataDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_ALL},
// ISCSI_PARAM_INITIAL_R2T_EN
{"InitialR2T", 1, 0, 1},
// ISCSI_PARAM_MAX_R2T
{"MaxOutstandingR2T", 1, 1, 65535},
// ISCSI_PARAM_IMM_DATA_EN
{"ImmediateData", 1, 0, 1},
// ISCSI_PARAM_FIRST_BURST
{"FirstBurstLength", 65536, 512, 16777215},
// ISCSI_PARAM_MAX_BURST
{"MaxBurstLength", 262144, 512, 16777215},
// ISCSI_PARAM_PDU_INORDER_EN
{"DataPDUInOrder", 1, 0, 1},
// ISCSI_PARAM_DATASEQ_INORDER_EN
{"DataSequenceInOrder", 1, 0, 1},
// ISCSI_PARAM_ERL
{"ErrorRecoveryLevel", 0, 0, 2},
// ISCSI_PARAM_IFMARKER_EN
{"IFMarker", 0, 0, 1},
// ISCSI_PARAM_OFMARKER_EN
{"OFMarker", 0, 0, 1},
// ISCSI_PARAM_DEFAULTTIME2WAIT
{"DefaultTime2Wait", 2, 0, 3600},
// ISCSI_PARAM_DEFAULTTIME2RETAIN
{"DefaultTime2Retain", 20, 0, 3600},
// ISCSI_PARAM_OFMARKINT
{"OFMarkInt", 2048, 1, 65535},
// ISCSI_PARAM_IFMARKINT
{"IFMarkInt", 2048, 1, 65535},
// ISCSI_PARAM_MAXCONNECTIONS
{"MaxConnections", 1, 1, 65535},
// ISCSI_PARAM_RDMA_EXTENSIONS
{"RDMAExtensions", 0, 0, 1},
// ISCSI_PARAM_TARGET_RDSL
{"TargetRecvDataSegmentLength", 8192, 512, 16777215},
// ISCSI_PARAM_INITIATOR_RDSL
{"InitiatorRecvDataSegmentLength", 8192, 512, 16777215},
// ISCSI_PARAM_MAX_OUTST_PDU
{"MaxOutstandingUnexpectedPDUs", 0, 2, 4294967295},
// "local" parmas, never sent to the initiator
// ISCSI_PARAM_MAX_XMIT_DLENGTH
{"MaxXmitDataSegmentLength", 8192, 512, 16777215},
// ISCSI_PARAM_MAX_QUEUE_CMD
{"MaxQueueCmd", MAX_QUEUE_CMD_DEF, MAX_QUEUE_CMD_MIN, MAX_QUEUE_CMD_MAX},
}
// Session is an iSCSI session.
type ISCSISession struct {
Refcount int
@@ -30,58 +150,112 @@ type ISCSISession struct {
InitiatorAlias string
Target *ISCSITarget
Isid uint64
Tsih uint16
Tsih uint64
ExpCmdSN uint32
// only one connection per session
Connections []*ISCSIConnection
Commands []*ISCSICommand
PendingCommands []*ISCSICommand
ExpectionCommandSN uint32
MaxQueueCommand uint32
SessionParam []ISCSISessionParam
Info string
Rdma int
Connections []*iscsiConnection
Commands []*ISCSICommand
PendingTasks taskQueue
MaxQueueCommand uint32
SessionParam []ISCSISessionParam
Info string
Rdma int
}
type ISCSIHeader struct {
type taskQueue []*iscsiTask
func (tq taskQueue) Len() int { return len(tq) }
func (tq taskQueue) Less(i, j int) bool {
// We want Pop to give us the highest, not lowest, priority so we use greater than here.
return tq[i].cmd.CmdSN > tq[j].cmd.CmdSN
}
type ISCSIPdu struct {
Bhs ISCSIHeader
AhsSize uint
DataSize uint
func (tq taskQueue) Swap(i, j int) {
tq[i], tq[j] = tq[j], tq[i]
}
type ISCSIConnection struct {
State int
RxIostate int
TxIostate int
Refcount int
func (tq *taskQueue) Push(x interface{}) {
item := x.(*iscsiTask)
*tq = append(*tq, item)
}
Session *ISCSISession
func (tq *taskQueue) Pop() interface{} {
old := *tq
n := len(old)
item := old[n-1]
*tq = old[0 : n-1]
return item
}
TID int
CID int
Auth AuthMethod
StatSN uint32
ExpectionStatSN uint32
CommandSN uint32
ExpectionCommandSN uint32
MaxCommandSN uint32
Request ISCSIPdu
Response ISCSIPdu
// The BHS is 48 bytes long. The Opcode and DataSegmentLength fields
// appear in all iSCSI PDUs. In addition, when used, the Initiator Task
// Tag and Logical Unit Number always appear in the same location in the
// header.
type iscsiHeader struct {
opcode uint8
flags uint8 // Final bit
rsvd2 [2]uint8
hlength uint8 // AHSs total length
dlength [3]uint8 // Data length
lun [8]uint8
itt uint8 // Initiator Task Tag
ttt uint8 // Target Task Tag
statsn uint8
expStatSN uint8
maxStatSN uint8
other [12]uint8
}
type iscsiPdu struct {
bhs iscsiHeader
ahsSize uint
dataSize uint
}
// New creates a new session.
func NewISCSISession() (*ISCSISession, error) {
var tsih uint16
b := make([]byte, 2)
if _, err := rand.Read(b); err != nil {
return nil, err
}
tsih += uint16(b[0]) << 8
tsih += uint16(b[1])
func (s *ISCSITargetService) NewISCSISession(conn *iscsiConnection) (*ISCSISession, error) {
var (
target *ISCSITarget
tsih uint64
)
return &ISCSISession{
Tsih: tsih,
}, nil
for _, t := range s.Targets {
if t.TID == conn.tid {
target = t
break
}
}
if target == nil {
return nil, fmt.Errorf("No target found with tid(%d)", conn.tid)
}
for {
rand.Seed(int64(time.Now().UTC().Nanosecond()))
tsih = uint64(rand.Uint32())
for _, s := range target.Sessions {
if s.Tsih == tsih {
tsih = 0
break
}
}
if tsih != 0 {
break
}
}
sess := &ISCSISession{
Tsih: tsih,
Initiator: conn.initiator,
InitiatorAlias: conn.initiatorAlias,
Target: target,
Connections: []*iscsiConnection{conn},
SessionParam: conn.sessionParam,
MaxQueueCommand: uint32(conn.sessionParam[ISCSI_PARAM_MAX_QUEUE_CMD].Value),
Rdma: 0,
ExpCmdSN: conn.expCmdSN,
}
conn.session = sess
return sess, nil
}

47
pkg/port/service.go Normal file
View File

@@ -0,0 +1,47 @@
/*
Copyright 2015 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.
*/
package port
import (
"fmt"
"github.com/gostor/gotgt/pkg/scsi"
)
type SCSITargetService interface {
Run() error
NewTarget(string) (SCSITargetDriver, error)
}
type TargetServiceFunc func(*scsi.SCSITargetService) (SCSITargetService, error)
var registeredPlugins = map[string](TargetServiceFunc){}
func RegisterTargetService(name string, f TargetServiceFunc) {
registeredPlugins[name] = f
}
func NewTargetService(name string, s *scsi.SCSITargetService) (SCSITargetService, error) {
if name == "" {
return nil, nil
}
f, ok := registeredPlugins[name]
if !ok {
return nil, fmt.Errorf("SCSI target driver %s is not found.", name)
}
return f(s)
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015 The GoStor Authors All rights reserved.
Copyright 2016 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.
@@ -18,6 +18,7 @@ package scsi
import (
"fmt"
"os"
"github.com/gostor/gotgt/pkg/api"
)
@@ -28,15 +29,7 @@ type BaseBackingStore struct {
OflagsSupported int
}
type BackingStore interface {
Open(dev *api.SCSILu, path string, fd *int, size *uint64) error
Close(dev *api.SCSILu) error
Init(dev *api.SCSILu, Opts string) error
Exit(dev *api.SCSILu) error
CommandSubmit(cmd *api.SCSICommand) error
}
type BackingStoreFunc func() (BackingStore, error)
type BackingStoreFunc func() (api.BackingStore, error)
var registeredPlugins = map[string](BackingStoreFunc){}
@@ -44,7 +37,7 @@ func RegisterBackingStore(name string, f BackingStoreFunc) {
registeredPlugins[name] = f
}
func NewBackingStore(name string) (BackingStore, error) {
func NewBackingStore(name string) (api.BackingStore, error) {
if name == "" {
return nil, nil
}
@@ -59,8 +52,8 @@ type fakeBackingStore struct {
BaseBackingStore
}
func (fake *fakeBackingStore) Open(dev *api.SCSILu, path string, fd *int, size *uint64) error {
return nil
func (fake *fakeBackingStore) Open(dev *api.SCSILu, path string) (*os.File, error) {
return nil, nil
}
func (fake *fakeBackingStore) Close(dev *api.SCSILu) error {

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015 The GoStor Authors All rights reserved.
Copyright 2016 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.
@@ -16,40 +16,188 @@ limitations under the License.
package backingstore
import "github.com/gostor/gotgt/pkg/scsi"
import (
"bytes"
"fmt"
"os"
"github.com/golang/glog"
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/scsi"
"github.com/gostor/gotgt/pkg/util"
)
func init() {
scsi.RegisterBackingStore("file", new)
}
type FileBackingStore struct {
BaseBackingStore
scsi.BaseBackingStore
}
func new() (scsi.BackingStore, error) {
return NullBackingStore{
Name: "file",
DataSize: 0,
OflagsSupported: 0,
func new() (api.BackingStore, error) {
return &FileBackingStore{
BaseBackingStore: scsi.BaseBackingStore{
Name: "file",
DataSize: 0,
OflagsSupported: 0,
},
}, nil
}
func (bs *FileBackingStore) Open(dev *SCSILu, path string, fd *int, size *uint64) error {
func (bs *FileBackingStore) Open(dev *api.SCSILu, path string) (*os.File, error) {
f, err := os.OpenFile(path, os.O_RDWR, os.ModePerm)
if err != nil {
return nil, err
}
return f, nil
}
func (bs *FileBackingStore) Close(dev *api.SCSILu) error {
return dev.File.Close()
}
func (bs *FileBackingStore) Init(dev *api.SCSILu, Opts string) error {
return nil
}
func (bs *FileBackingStore) Close(dev *SCSILu) error {
func (bs *FileBackingStore) Exit(dev *api.SCSILu) error {
return nil
}
func (bs *FileBackingStore) Init(dev *SCSILu, Opts string) error {
return nil
}
func (bs *FileBackingStore) CommandSubmit(cmd *api.SCSICommand) (err error) {
var (
scb = cmd.SCB.Bytes()
offset = cmd.Offset
opcode = api.SCSICommandType(scb[0])
lu = cmd.Device
key = scsi.ILLEGAL_REQUEST
asc = scsi.ASC_INVALID_FIELD_IN_CDB
wbuf []byte = []byte{}
rbuf []byte = []byte{}
length int
doVerify bool = false
doWrite bool = false
)
switch opcode {
case api.ORWRITE_16:
tmpbuf := []byte{}
length, err = lu.File.ReadAt(tmpbuf, int64(offset))
if length != len(tmpbuf) {
key = scsi.MEDIUM_ERROR
asc = scsi.ASC_READ_ERROR
break
}
cmd.InSDBBuffer.Buffer = bytes.NewBuffer(tmpbuf)
func (bs *FileBackingStore) Exit(dev *SCSILu) error {
return nil
}
wbuf = cmd.OutSDBBuffer.Buffer.Bytes()
doWrite = true
goto write
case api.COMPARE_AND_WRITE:
// TODO
doWrite = true
goto write
case api.SYNCHRONIZE_CACHE, api.SYNCHRONIZE_CACHE_16:
break
case api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16:
doVerify = true
case api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16:
wbuf = cmd.OutSDBBuffer.Buffer.Bytes()
doWrite = true
goto write
case api.WRITE_SAME, api.WRITE_SAME_16:
// TODO
break
case api.READ_6, api.READ_10, api.READ_12, api.READ_16:
length, err = lu.File.ReadAt(rbuf, int64(offset))
if err != nil {
key = scsi.MEDIUM_ERROR
asc = scsi.ASC_READ_ERROR
break
}
for i := 0; i < int(cmd.TL)-length; i++ {
rbuf = append(rbuf, 0)
}
if (opcode != api.READ_6) && (scb[1]&0x10 != 0) {
util.Fadvise(lu.File, int64(offset), int64(length), util.POSIX_FADV_NOREUSE)
}
cmd.InSDBBuffer.Buffer = bytes.NewBuffer(rbuf)
case api.PRE_FETCH_10, api.PRE_FETCH_16:
err = util.Fadvise(lu.File, int64(offset), int64(cmd.TL), util.POSIX_FADV_WILLNEED)
if err != nil {
key = scsi.MEDIUM_ERROR
asc = scsi.ASC_READ_ERROR
}
case api.VERIFY_10, api.VERIFY_12, api.VERIFY_16:
doVerify = true
goto verify
case api.UNMAP:
// TODO
default:
break
}
write:
if doWrite {
// hack: wbuf = []byte("hello world!")
length, err = lu.File.WriteAt(wbuf, int64(offset))
if err != nil || length != len(wbuf) {
glog.Error(err)
key = scsi.MEDIUM_ERROR
asc = scsi.ASC_READ_ERROR
goto sense
}
glog.V(2).Infof("write data at %d for length %d", offset, length)
var pg *api.ModePage
for _, p := range lu.ModePages {
if p.Pcode == 0x08 && p.SubPcode == 0 {
pg = &p
break
}
}
if pg == nil {
key = scsi.ILLEGAL_REQUEST
asc = scsi.ASC_INVALID_FIELD_IN_CDB
goto sense
}
if ((opcode != api.WRITE_6) && (scb[1]&0x8 != 0)) || (pg.Data[0]&0x04 == 0) {
if err = util.Fdatasync(lu.File); err != nil {
key = scsi.MEDIUM_ERROR
asc = scsi.ASC_READ_ERROR
goto sense
}
}
if (opcode != api.WRITE_6) && (scb[1]&0x10 != 0) {
util.Fadvise(lu.File, int64(offset), int64(length), util.POSIX_FADV_NOREUSE)
}
}
verify:
if doVerify {
length, err = lu.File.ReadAt(rbuf, int64(offset))
if length != len(rbuf) {
key = scsi.MEDIUM_ERROR
asc = scsi.ASC_READ_ERROR
goto sense
}
if !bytes.Equal(cmd.OutSDBBuffer.Buffer.Bytes(), rbuf) {
err = fmt.Errorf("verify fail between out buffer and read buffer")
key = scsi.MISCOMPARE
asc = scsi.ASC_MISCOMPARE_DURING_VERIFY_OPERATION
goto sense
}
if scb[1]&0x10 != 0 {
util.Fadvise(lu.File, int64(offset), int64(length), util.POSIX_FADV_WILLNEED)
}
}
glog.Infof("io done %s", string(scb))
sense:
if err != nil {
glog.Error(err)
return err
}
_ = key
_ = asc
func (bs *FileBackingStore) CommandSubmit(cmd *SCSICommand) error {
return nil
}

View File

@@ -17,45 +17,47 @@ limitations under the License.
package backingstore
import (
"github.com/gostor/gotgt/pkg/scsi"
"os"
"github.com/golang/glog"
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/scsi"
)
func init() {
scsi.RegisterBackingStore("null", new)
scsi.RegisterBackingStore("null", newNull)
}
type NullBackingStore struct {
BaseBackingStore
scsi.BaseBackingStore
}
func new() (scsi.BackingStore, error) {
return NullBackingStore{
Name: "null",
DataSize: 0,
OflagsSupported: 0,
func newNull() (api.BackingStore, error) {
return &NullBackingStore{
BaseBackingStore: scsi.BaseBackingStore{
Name: "null",
DataSize: 0,
OflagsSupported: 0,
},
}, nil
}
func (bs *NullBackingStore) Open(dev *SCSILu, path string, fd *int, size *uint64) error {
glog.V(1).Infof("NULL backing store open, size: %d", size)
func (bs *NullBackingStore) Open(dev *api.SCSILu, path string) (*os.File, error) {
return nil, nil
}
func (bs *NullBackingStore) Close(dev *api.SCSILu) error {
return nil
}
func (bs *NullBackingStore) Close(dev *SCSILu) error {
func (bs *NullBackingStore) Init(dev *api.SCSILu, Opts string) error {
return nil
}
func (bs *NullBackingStore) Init(dev *SCSILu, Opts string) error {
func (bs *NullBackingStore) Exit(dev *api.SCSILu) error {
return nil
}
func (bs *NullBackingStore) Exit(dev *SCSILu) error {
return nil
}
func (bs *NullBackingStore) CommandSubmit(cmd *SCSICommand) error {
cmd.Result = SAM_STAT_GOOD
func (bs *NullBackingStore) CommandSubmit(cmd *api.SCSICommand) error {
cmd.Result = api.SAM_STAT_GOOD
return nil
}

View File

@@ -16,19 +16,51 @@ limitations under the License.
package scsi
import "github.com/gostor/gotgt/pkg/api"
import (
"os"
type SCSILuOps struct {
*api.SCSILu
"github.com/gostor/gotgt/pkg/api"
)
DeviceProtocol SCSIDeviceProtocol
Storage *BackingStore
Target *api.SCSITarget
Attrs api.SCSILuPhyAttribute
func NewSCSILu(lun uint64, target *api.SCSITarget) (*api.SCSILu, error) {
sbc := NewSBCDevice()
backing, err := NewBackingStore("file")
if err != nil {
return nil, err
}
var lu = &api.SCSILu{
Lun: lun,
Target: target,
PerformCommand: luPerformCommand,
DeviceProtocol: sbc,
Storage: backing,
BlockShift: 0,
Size: 1024 * 1024 * 1024,
}
// hack this
if _, err = os.Stat("/tmp/data.txt"); err != nil && os.IsExist(err) {
os.Create("/tmp/data.txt")
}
f, err := backing.Open(lu, "/tmp/data.txt")
if err != nil {
return nil, err
}
lu.File = f
lu.DeviceProtocol.InitLu(lu)
lu.Attrs.Online = true
return lu, nil
}
// function handler for command performing and finishing
PerformCommand CommandFunc
FinishCommand func(*api.SCSITarget, *api.SCSICommand)
func luPerformCommand(tid int, cmd *api.SCSICommand) api.SAMStat {
op := int(cmd.SCB.Bytes()[0])
fn := cmd.Device.DeviceProtocol.PerformCommand(op)
if fn != nil {
fnop := fn.(SCSIDeviceOperation)
// host := cmd.ITNexus.Host
host := 0
return fnop.CommandPerformFunc(host, cmd)
}
return api.SAMStatGood
}
func luPreventRemoval(lu *api.SCSILu) bool {

View File

@@ -18,9 +18,13 @@ limitations under the License.
package scsi
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/golang/glog"
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/util"
)
const (
@@ -36,30 +40,75 @@ type SBCSCSIDeviceProtocol struct {
BaseSCSIDeviceProtocol
}
func (sbc *SBCSCSIDeviceProtocol) InitLu(lu *api.SCSILu) error {
return nil
func (sbc SBCSCSIDeviceProtocol) PerformCommand(opcode int) interface{} {
return sbc.SCSIDeviceOps[opcode]
}
func (sbc *SBCSCSIDeviceProtocol) ExitLu(lu *api.SCSILu) error {
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 {
func (sbc SBCSCSIDeviceProtocol) InitLu(lu *api.SCSILu) error {
var tgt = lu.Target
// init LU's phy attribute
lu.Attrs.DeviceType = api.TYPE_DISK
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 = "GOSTOR"
lu.Attrs.SCSIID = fmt.Sprintf("GOSTOR %x%d", tgt.TID, lu.Lun)
lu.Attrs.SCSISN = fmt.Sprintf("beaf%d%d", tgt.TID, lu.Lun)
lu.Attrs.ProductID = "VIRTUAL-DISK"
lu.Attrs.VersionDesction = []uint16{
0x04C0, // SBC-3 no version claimed
0x0960, // iSCSI
0x0300, // SPC-3
}
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{2, 0, []byte{0x80, 0x80, 0, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
// Caching Page
pages = append(pages, api.ModePage{8, 0, []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{0x0a, 0, []byte{2, 0x10, 0, 0, 0, 0, 0, 0, 2, 0}})
// Control Extensions mode page: TCMOS:1
pages = append(pages, api.ModePage{0x0a, 1, []byte{0x04, 0x00, 0x00}})
// Informational Exceptions Control page
pages = append(pages, api.ModePage{0x1c, 0, []byte{8, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
lu.ModePages = pages
return nil
}
func NewSBCDevice() SBCSCSIDeviceProtocol {
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() api.SCSIDeviceProtocol {
var sbc = SBCSCSIDeviceProtocol{
BaseSCSIDeviceProtocol{
Type: api.TYPE_DISK,
SCSIDeviceOps: make([]SCSIDeviceOperation, 256),
SCSIDeviceOps: []SCSIDeviceOperation{},
},
}
for i := 0; i <= 256; i++ {
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)
@@ -103,9 +152,10 @@ func NewSBCDevice() SBCSCSIDeviceProtocol {
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(SPCServiceAction, 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, nil, 0)
sbc.SCSIDeviceOps[api.EXCHANGE_MEDIUM] = NewSCSIDeviceOperation(SPCServiceAction, 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)
@@ -132,15 +182,18 @@ func SBCModeSense(host int, cmd *api.SCSICommand) api.SAMStat {
deviceSpecific |= 0x80
}
data := cmd.InSDBBuffer.Buffer
data.Next(2)
buf := cmd.InSDBBuffer.Buffer
data := []byte{0x00, 0x00, 0x00, 0x00}
if buf != nil {
data = buf.Bytes()
}
if cmd.SCB.Bytes()[0] == 0x1a {
data.WriteByte(deviceSpecific)
data[2] = deviceSpecific
} else {
data.Next(1)
data.WriteByte(deviceSpecific)
data[3] = deviceSpecific
}
cmd.InSDBBuffer.Buffer = bytes.NewBuffer(data)
return api.SAMStatGood
}
@@ -196,7 +249,135 @@ func SBCUnmap(host int, cmd *api.SCSICommand) api.SAMStat {
}
func SBCReadWrite(host int, cmd *api.SCSICommand) api.SAMStat {
return api.SAMStatGood
var (
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
dev = cmd.Device
scb = cmd.SCB.Bytes()
opcode = api.SCSICommandType(scb[0])
lba uint64
tl uint32
err error
)
if dev.Attrs.Removable && !dev.Attrs.Online {
key = NOT_READY
asc = ASC_MEDIUM_NOT_PRESENT
glog.Warningf("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
glog.Warningf("sense")
goto sense
}
*/
if cmd.OutSDBBuffer.Buffer == nil {
cmd.OutSDBBuffer.Buffer = &bytes.Buffer{}
}
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
glog.Warningf("sense")
goto sense
}
}
lba = getSCSIReadWriteOffset(scb)
tl = getSCSIReadWriteCount(scb)
// Verify that we are not doing i/o beyond the end-of-lun
if tl != 0 {
if lba+uint64(tl) < lba || lba+uint64(tl) > dev.Size>>dev.BlockShift {
key = ILLEGAL_REQUEST
asc = ASC_LBA_OUT_OF_RANGE
glog.Warningf("sense: lba: %d, tl: %d, size: %d", lba, tl, dev.Size>>dev.BlockShift)
goto sense
}
} else {
if lba >= dev.Size>>dev.BlockShift {
key = ILLEGAL_REQUEST
asc = ASC_LBA_OUT_OF_RANGE
glog.Warningf("sense")
goto sense
}
}
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 = dev.Storage.CommandSubmit(cmd)
if err != nil {
glog.Error(err)
key = HARDWARE_ERROR
asc = ASC_INTERNAL_TGT_FAILURE
} else {
return api.SAMStatGood
}
sense:
BuildSenseData(cmd, key, asc)
return api.SAMStatCheckCondition
}
func SBCReserve(host int, cmd *api.SCSICommand) api.SAMStat {
@@ -224,7 +405,7 @@ func SBCReadCapacity(host int, cmd *api.SCSICommand) api.SAMStat {
scb = cmd.SCB.Bytes()
key = ILLEGAL_REQUEST
asc = ASC_LUN_NOT_SUPPORTED
data = cmd.InSDBBuffer.Buffer
data = &bytes.Buffer{}
bshift = cmd.Device.BlockShift
size = cmd.Device.Size >> bshift
)
@@ -240,9 +421,11 @@ func SBCReadCapacity(host int, cmd *api.SCSICommand) api.SAMStat {
goto sense
}
if cmd.InSDBBuffer.Length < 8 {
goto overflow
}
/*
if cmd.InSDBBuffer.Length < 8 {
goto overflow
}
*/
// data[0] = (size >> 32) ? __cpu_to_be32(0xffffffff) : __cpu_to_be32(size - 1);
if size>>32 != 0 {
@@ -253,8 +436,9 @@ func SBCReadCapacity(host int, cmd *api.SCSICommand) api.SAMStat {
// data[1] = __cpu_to_be32(1U << bshift);
binary.Write(data, binary.BigEndian, uint32(1<<bshift))
overflow:
//overflow:
cmd.InSDBBuffer.Resid = 8
cmd.InSDBBuffer.Buffer = data
return api.SAMStatGood
sense:
cmd.InSDBBuffer.Resid = 0
@@ -293,6 +477,12 @@ sense:
}
func SBCReadCapacity16(host int, cmd *api.SCSICommand) api.SAMStat {
data := &bytes.Buffer{}
data.Write(util.MarshalUint64(1))
data.Write(util.MarshalUint32(1 << cmd.Device.BlockShift))
val := (cmd.Device.Attrs.Lbppbe << 16) | cmd.Device.Attrs.LowestAlignedLBA
data.Write(util.MarshalUint32(uint32(val)))
cmd.InSDBBuffer.Buffer = data
return api.SAMStatGood
}
@@ -301,6 +491,13 @@ func SBCGetLbaStatus(host int, cmd *api.SCSICommand) api.SAMStat {
}
func SBCServiceAction(host int, cmd *api.SCSICommand) api.SAMStat {
opcode := api.SCSICommandType(cmd.SCB.Bytes()[1] & 0x1f)
switch opcode {
case api.READ_CAPACITY:
return SBCReadCapacity(host, cmd)
case api.SAI_READ_CAPACITY_16:
return SBCReadCapacity16(host, cmd)
}
return api.SAMStatGood
}

View File

@@ -16,17 +16,82 @@ limitations under the License.
package scsi
import "github.com/gostor/gotgt/pkg/api"
import (
"bytes"
"encoding/binary"
"fmt"
"sync"
type CommandFunc func(host int, cmd *api.SCSICommand) api.SAMStat
"github.com/golang/glog"
"github.com/gostor/gotgt/pkg/api"
)
type SCSITargetService struct {
mutex sync.RWMutex
Targets []*api.SCSITarget
}
var _instance *SCSITargetService
func NewSCSITargetService() *SCSITargetService {
if _instance == nil {
_instance = &SCSITargetService{Targets: []*api.SCSITarget{}}
}
return _instance
}
func (s *SCSITargetService) GetTargetList() ([]api.SCSITarget, error) {
result := []api.SCSITarget{}
s.mutex.RLock()
for _, t := range s.Targets {
result = append(result, *t)
}
s.mutex.RUnlock()
return result, nil
}
func (s *SCSITargetService) AddCommandQueue(tid int, scmd *api.SCSICommand) error {
var (
target *api.SCSITarget
itn *api.ITNexus
)
s.mutex.RLock()
for _, t := range s.Targets {
if t.TID == tid {
target = t
break
}
}
s.mutex.RUnlock()
if target == nil {
return fmt.Errorf("Cannot find the target with ID(%d)", tid)
}
scmd.Target = target
for _, it := range target.ITNexus {
if it.ID == scmd.CommandITNID {
itn = it
break
}
}
scmd.ITNexus = itn
scmd.Device = target.Devices[0]
result := scmd.Device.PerformCommand(tid, scmd)
scmd.Result = result.Stat
if result.Err != nil {
glog.Error(result.Err)
return result.Err
}
return nil
}
type SCSIServiceAction struct {
ServiceAction uint32
CommandPerformFunc CommandFunc
CommandPerformFunc api.CommandFunc
}
type SCSIDeviceOperation struct {
CommandPerformFunc CommandFunc
CommandPerformFunc api.CommandFunc
ServiceAction *SCSIServiceAction
PRConflictBits uint8
}
@@ -36,15 +101,7 @@ type BaseSCSIDeviceProtocol struct {
SCSIDeviceOps []SCSIDeviceOperation
}
type SCSIDeviceProtocol interface {
InitLu(lu *api.SCSILu) error
ExitLu(lu *api.SCSILu) error
ConfigLu(lu *api.SCSILu) error
OnlineLu(lu *api.SCSILu) error
OfflineLu(lu *api.SCSILu) error
}
func NewSCSIDeviceOperation(fn CommandFunc, sa *SCSIServiceAction, pr uint8) SCSIDeviceOperation {
func NewSCSIDeviceOperation(fn api.CommandFunc, sa *SCSIServiceAction, pr uint8) SCSIDeviceOperation {
return SCSIDeviceOperation{
CommandPerformFunc: fn,
ServiceAction: sa,
@@ -54,6 +111,9 @@ func NewSCSIDeviceOperation(fn CommandFunc, sa *SCSIServiceAction, pr uint8) SCS
func BuildSenseData(cmd *api.SCSICommand, key byte, asc SCSISubError) {
senseBuffer := cmd.SenseBuffer
if senseBuffer == nil {
senseBuffer = &bytes.Buffer{}
}
if cmd.Device.Attrs.SenseFormat {
// descriptor format
// current, not deferred
@@ -81,3 +141,44 @@ func BuildSenseData(cmd *api.SCSICommand, key byte, asc SCSISubError) {
cmd.SenseLength = length + 8
}
}
func getSCSIReadWriteOffset(scb []byte) uint64 {
var off uint64
var opcode = api.SCSICommandType(scb[0])
switch opcode {
case api.READ_6, api.WRITE_6:
off = uint64((scb[1]&0x1f))<<16 + uint64(scb[2])<<8 + uint64(scb[3])
case api.READ_10, api.PRE_FETCH_10, api.WRITE_10, api.VERIFY_10, api.WRITE_VERIFY, api.WRITE_SAME, api.SYNCHRONIZE_CACHE, api.READ_12, api.WRITE_12, api.VERIFY_12, api.WRITE_VERIFY_12:
off = uint64(binary.BigEndian.Uint32(scb[2:]))
case api.READ_16, api.PRE_FETCH_16, api.WRITE_16, api.ORWRITE_16, api.VERIFY_16, api.WRITE_VERIFY_16, api.WRITE_SAME_16, api.SYNCHRONIZE_CACHE_16, api.COMPARE_AND_WRITE:
off = binary.BigEndian.Uint64(scb[2:])
default:
}
return off
}
func getSCSIReadWriteCount(scb []byte) uint32 {
var cnt uint32
var opcode = api.SCSICommandType(scb[0])
switch opcode {
case api.READ_6, api.WRITE_6:
cnt = uint32(scb[4])
if cnt == 0 {
cnt = 256
}
case api.READ_10, api.PRE_FETCH_10, api.WRITE_10, api.VERIFY_10, api.WRITE_VERIFY, api.WRITE_SAME, api.SYNCHRONIZE_CACHE:
cnt = uint32(scb[7])<<8 | uint32(scb[8])
case api.READ_12, api.WRITE_12, api.VERIFY_12, api.WRITE_VERIFY_12:
cnt = binary.BigEndian.Uint32(scb[6:])
case api.READ_16, api.PRE_FETCH_16, api.WRITE_16, api.ORWRITE_16, api.VERIFY_16, api.WRITE_VERIFY_16, api.WRITE_SAME_16, api.SYNCHRONIZE_CACHE_16:
cnt = binary.BigEndian.Uint32(scb[10:])
case api.COMPARE_AND_WRITE:
cnt = uint32(scb[13])
default:
}
return cnt
}

View File

@@ -21,7 +21,9 @@ import (
"bytes"
"encoding/binary"
"fmt"
"reflect"
"github.com/golang/glog"
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/util"
)
@@ -128,6 +130,39 @@ func SPCLuOnline(lu *api.SCSILu) error {
}
func SPCInquiry(host int, cmd *api.SCSICommand) api.SAMStat {
buf := &bytes.Buffer{}
var b byte = 0x75
if reflect.DeepEqual(util.MarshalUint64(cmd.Device.Lun)[0:7], cmd.Lun[0:7]) {
b = (uint8(0) & 0x7) << 5
b |= uint8(0) & 0x1f
}
buf.WriteByte(b)
b = 0
buf.WriteByte(b)
buf.WriteByte(byte(1))
b = 0x02
buf.WriteByte(b)
buf.WriteByte(0x00)
// byte 5
b = 0
b |= byte(1) << 4 & 0x30
buf.WriteByte(b)
// byte 6
b = 0
buf.WriteByte(b)
buf.WriteByte(0x02)
buf.Write([]byte{'1', '1', 'c', 'a', 'n', 's'})
buf.WriteByte(0x00)
buf.WriteByte(0x00)
buf.Write([]byte{'c', 'o', 'f', 'f', 'e', 'e'})
for i := 0; i < 10; i++ {
buf.WriteByte(0x00)
}
buf.Write([]byte{'1', '.', '0'})
buf.WriteByte(0x00)
data := buf.Bytes()
data[4] = byte(len(data) - 4)
cmd.InSDBBuffer.Buffer = bytes.NewBuffer(data)
return api.SAMStatGood
}
@@ -137,25 +172,22 @@ func SPCReportLuns(host int, cmd *api.SCSICommand) api.SAMStat {
actualLength uint32 = 8
availLength uint32 = 0
allocationLength uint32
data *bytes.Buffer
buf *bytes.Buffer = &bytes.Buffer{}
scb *bytes.Buffer = cmd.SCB
)
// Get Allocation Length
allocationLength = util.GetUnalignedUint32(scb.Bytes()[6:10])
if allocationLength < 16 {
glog.Warningf("goto sense, allocationLength < 16")
goto sense
}
if cmd.InSDBBuffer.Length < allocationLength {
goto sense
}
data = cmd.InSDBBuffer.Buffer
remainLength = allocationLength - 8
availLength = 8 * uint32(len(cmd.Target.Devices))
binary.Write(data, binary.BigEndian, availLength)
buf.Write(util.MarshalUint32(availLength))
cmd.InSDBBuffer.Resid = int32(actualLength)
// Skip through to byte 8, Reserved
for i := 0; i < 4; i++ {
data.WriteByte(0x00)
buf.WriteByte(0x00)
}
for _, lu := range cmd.Target.Devices {
@@ -168,10 +200,11 @@ func SPCReportLuns(host int, cmd *api.SCSICommand) api.SAMStat {
}
lun = (0x3fff & lun) << 16
lun = uint64(lun << 32)
binary.Write(data, binary.BigEndian, lun)
buf.Write(util.MarshalUint64(lun))
remainLength -= 8
}
}
cmd.InSDBBuffer.Buffer = buf
return api.SAMStatGood
sense:
cmd.InSDBBuffer.Resid = 0
@@ -218,9 +251,11 @@ func SPCStartStop(host int, cmd *api.SCSICommand) api.SAMStat {
}
func SPCTestUnit(host int, cmd *api.SCSICommand) api.SAMStat {
if err := deviceReserve(cmd); err != nil {
return api.SAMStatReservationConflict
}
/*
if err := deviceReserve(cmd); err != nil {
return api.SAMStatReservationConflict
}
*/
if cmd.Device.Attrs.Online {
return api.SAMStatGood
}
@@ -247,7 +282,33 @@ func SPCPreventAllowMediaRemoval(host int, cmd *api.SCSICommand) api.SAMStat {
// 6.11 - MODE SENSE(6)
// 6.12 - MODE SENSE(10)
func SPCModeSense(host int, cmd *api.SCSICommand) api.SAMStat {
var (
scb = cmd.SCB.Bytes()
mode6 = (scb[0] == 0x1a)
dbd = scb[1] & 0x8 /* Disable Block Descriptors */
pcode = scb[2] & 0x3f
pctrl = (scb[2] & 0xc0) >> 6
subpcode = scb[3]
blkDesctionLen = 0
key = ILLEGAL_REQUEST
asc = ASC_INVALID_FIELD_IN_CDB
)
if dbd == 0 {
blkDesctionLen = 8
}
if pctrl == 3 {
asc = ASC_SAVING_PARMS_UNSUP
goto sense
}
_ = dbd
_ = pcode
_ = subpcode
_ = mode6
_ = blkDesctionLen
return api.SAMStatGood
sense:
BuildSenseData(cmd, key, asc)
return api.SAMStatCheckCondition
}
func SPCSendDiagnostics(host int, cmd *api.SCSICommand) api.SAMStat {
@@ -263,10 +324,93 @@ sense:
return api.SAMStatCheckCondition
}
func getSCSICmdSize(opcode api.SCSICommandType) byte {
var scsi_command_size = []byte{6, 10, 10, 12, 16, 12, 10, 10}
return scsi_command_size[(byte(opcode)>>5)&7]
}
func reportOpcodesAll(cmd *api.SCSICommand, rctd int) error {
var (
data = []byte{0x00, 0x00, 0x00, 0x00}
)
for _, i := range []api.SCSICommandType{api.TEST_UNIT_READY, api.WRITE_6, api.INQUIRY, api.READ_CAPACITY, api.WRITE_10, api.WRITE_16, api.REPORT_LUNS, api.WRITE_12} {
data = append(data, byte(i))
// reserved
data = append(data, 0x00)
// service action
data = append(data, 0x00)
data = append(data, 0x00)
// reserved
data = append(data, 0x00)
// flags : no service action, possibly timeout desc
if rctd != 0 {
data = append(data, 0x02)
} else {
data = append(data, 0x00)
}
// cdb length
length := getSCSICmdSize(i)
data = append(data, (length>>8)&0xff)
data = append(data, length&0xff)
// timeout descriptor
if rctd != 0 {
// length == 0x0a
data[1] = 0x0a
for n := 0; n < 12; n++ {
data = append(data, 0x00)
}
}
}
buf := util.MarshalUint32(uint32(len(data) - 4))
buf = append(buf, data[4:]...)
cmd.InSDBBuffer.Buffer = bytes.NewBuffer(buf)
return nil
}
func reportOpcodeOne(cmd *api.SCSICommand, rctd int, opcode byte, rsa uint16, serviceAction bool) error {
return nil
}
// This is useful for the various commands using the SERVICE ACTION format.
func SPCServiceAction(host int, cmd *api.SCSICommand) api.SAMStat {
// TODO
scb := cmd.SCB.Bytes()
reporting_options := scb[2] & 0x07
opcode := scb[3]
rctd := int(scb[2] & 0x80)
rsa := util.GetUnalignedUint16(scb[4:])
switch reporting_options {
case 0x00: /* report all */
glog.V(3).Infof("Service Action: report all")
err := reportOpcodesAll(cmd, rctd)
if err != nil {
glog.Error(err)
goto sense
}
case 0x01: /* report one no service action*/
glog.V(3).Infof("Service Action: report one no service action")
err := reportOpcodeOne(cmd, rctd, opcode, rsa, false)
if err != nil {
glog.Error(err)
goto sense
}
case 0x02: /* report one service action */
glog.V(3).Infof("Service Action: report one service action")
err := reportOpcodeOne(cmd, rctd, opcode, rsa, true)
if err != nil {
glog.Error(err)
goto sense
}
default:
goto sense
}
return api.SAMStatGood
sense:
cmd.InSDBBuffer.Resid = 0
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
return api.SAMStatCheckCondition
}
func SPCPRReadKeys(host int, cmd *api.SCSICommand) api.SAMStat {
@@ -360,6 +504,7 @@ func SPCRequestSense(host int, cmd *api.SCSICommand) api.SAMStat {
var (
allocationLength uint32
actualLength uint32
data = &bytes.Buffer{}
)
allocationLength = util.GetUnalignedUint32(cmd.SCB.Bytes()[4:8])
@@ -372,8 +517,11 @@ func SPCRequestSense(host int, cmd *api.SCSICommand) api.SAMStat {
} else {
actualLength = allocationLength
}
binary.Write(cmd.InSDBBuffer.Buffer, binary.BigEndian, cmd.SenseBuffer.Bytes()[0:actualLength])
if cmd.SenseBuffer != nil {
data.Write(cmd.SenseBuffer.Bytes()[0:actualLength])
}
cmd.InSDBBuffer.Resid = int32(actualLength)
cmd.InSDBBuffer.Buffer = data
// reset sense buffer in cmnd
cmd.SenseBuffer = &bytes.Buffer{}

View File

@@ -21,18 +21,28 @@ import (
"github.com/golang/glog"
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/port"
)
func NewTarget(tid int, driverName, name string) (*api.SCSITarget, error) {
func (s *SCSITargetService) NewSCSITarget(tid int, driverName, name string) (*api.SCSITarget, error) {
// verify the target ID
// verify the target's Name
// verify the low level driver
var target = &api.SCSITarget{Name: name, TID: tid}
var tgt = port.NewTargetDriver(driverName, target)
target.SCSITargetDriver = tgt
var target = &api.SCSITarget{
Name: name,
TID: tid,
Devices: []*api.SCSILu{},
}
lun, err := NewSCSILu(0, target)
if err != nil {
glog.Errorf("fail to create LU: %v", err)
return nil, err
}
s.mutex.Lock()
target.Devices = append(target.Devices, lun)
s.Targets = append(s.Targets, target)
s.mutex.Unlock()
return target, nil
}
@@ -40,7 +50,7 @@ func deviceReserve(cmd *api.SCSICommand) error {
var lu *api.SCSILu
for _, dev := range cmd.Target.Devices {
if dev.Lun == cmd.Device.Lun {
lu = &dev
lu = dev
break
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015 The GoStor Authors All rights reserved.
Copyright 2016 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.
@@ -16,7 +16,16 @@ limitations under the License.
package util
import "encoding/binary"
import (
"encoding/binary"
"os"
"syscall"
)
type KeyValue struct {
Key string
Value string
}
func GetUnalignedUint16(u8 []uint8) uint16 {
return binary.BigEndian.Uint16(u8)
@@ -52,13 +61,49 @@ func ParseKVText(txt []byte) map[string]string {
return m
}
func MarshalKVText(kv map[string]string) []byte {
func MarshalKVText(kv []KeyValue) []byte {
var data []byte
for k, v := range kv {
data = append(data, []byte(k)...)
for _, v := range kv {
data = append(data, []byte(v.Key)...)
data = append(data, '=')
data = append(data, []byte(v)...)
data = append(data, []byte(v.Value)...)
data = append(data, 0)
}
return data
}
func MarshalUint32(i uint32) []byte {
var data []byte
for j := 0; j < 4; j++ {
b := byte(i >> uint(4*(3-j)) & 0xff)
data = append(data, b)
}
return data
}
func MarshalUint64(i uint64) []byte {
var data []byte
for j := 0; j < 8; j++ {
b := byte(i >> uint(8*(7-j)) & 0xff)
data = append(data, b)
}
return data
}
const (
POSIX_FADV_NORMAL = iota
POSIX_FADV_RANDOM
POSIX_FADV_SEQUENTIAL
POSIX_FADV_WILLNEED
POSIX_FADV_DONTNEED
POSIX_FADV_NOREUSE
)
func Fadvise(file *os.File, off, length int64, advice uint32) error {
// syscall.SYS_FADVISE64 = 221
_, _, err := syscall.Syscall6(221, file.Fd(), uintptr(off), uintptr(length), uintptr(advice), 0, 0)
if err != 0 {
return err
}
return nil
}

27
pkg/util/util_darwin.go Normal file
View File

@@ -0,0 +1,27 @@
// +build darwin
/*
Copyright 2016 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.
*/
package util
import (
"os"
"syscall"
)
func Fdatasync(file *os.File) error {
return syscall.Fsync(int(file.Fd()))
}

27
pkg/util/util_linux.go Normal file
View File

@@ -0,0 +1,27 @@
// +build linux
/*
Copyright 2016 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.
*/
package util
import (
"os"
"syscall"
)
func Fdatasync(file *os.File) error {
return syscall.Fdatasync(int(file.Fd()))
}