README
¶
go-tcmu
Go bindings to attach Go Reader
s and Writer
s to the Linux kernel via SCSI.
It connects to the TCM Userspace kernel API, and provides a loopback device that responds to SCSI commands. This project is based on open-iscsi/tcmu-runner, but in pure Go.
Overview
This package creates two types of Handlers (much like net/http
) for SCSI block device commands. It wraps the implementation details of the kernel API, and sets up (a) a TCMU SCSI device and connect that to (b) a loopback SCSI target.
From here, the Linux IO Target kernel stack can expose the SCSI target however it likes. This includes iSCSI, vHost, etc. For further details, see the LIO wiki.
Usage
First, to use this package, you'll need the appropriate kernel modules and configfs mounted
Make sure configfs is mounted
This may already be true on your system, depending on kernel configuration. Many distributions do this by default. Check if it's mounted to /sys/kernel/config
with
mount | grep configfs
Which should respond
configfs on /sys/kernel/config type configfs (rw,relatime)
To mount it explicitly:
sudo modprobe configfs
sudo mkdir -p /sys/kernel/config
sudo mount -t configfs none /sys/kernel/config
Use the TCMU module
Many distros include the module, but few activate it by default.
sudo modprobe target_core_user
Now that that's settled, there's tcmufile.go for a quick example binary that serves an image file under /dev/tcmufile/myfile.
For creating your custom SCSI targets based on a ReadWriterAt:
handler := &tcmu.SCSIHandler{
HBA: 30, // Choose a virtual HBA number. 30 is fine.
LUN: 0, // The LUN attached to this HBA. Multiple LUNs can work on the same HBA, this differentiates them.
WWN: tcmu.NaaWWN{
OUI: "000000", // Or provide your OUI
VendorID: tcmu.GenerateSerial("foobar"), // Or provide a vendor id/serial number
// Optional: Provide further information for your WWN
// VendorIDExt: "0123456789abcdef",
},
VolumeName: "myVolName", // The name of your volume.
DataSizes: tcmu.DataSizes{
VolumeSize: 5 * 1024 * 1024, // Size in bytes, eg, 5GiB
BlockSize: 1024, // Size of logical blocks, eg, 1K
},
DevReady: tcmu.SingleThreadedDevReady(
tcmu.ReadWriterAtCmdHandler{ // Or replace with your own handler
RW: rw,
}),
}
d, _ := tcmu.OpenTCMUDevice("/dev/myDevDirectory", handler)
defer d.Close()
This will create a device named /dev/myDevDirectory/myVolName
with the mentioned details. It is now ready for formatting and treating like a block device.
If you wish to handle more SCSI commands, you can implement a replacement for the ReadWriterAtCmdHandler
following the interface:
type SCSICmdHandler interface {
HandleCommand(cmd *SCSICmd) (SCSIResponse, error)
}
If the default functionality was acceptable, the library contains a number of helpful Emulate
functions that you can call to achieve the basic functionality.
Documentation
¶
Overview ¶
tcmu is a package that connects to the TCM in Userspace kernel module, a part of the LIO stack. It provides the ability to emulate a SCSI storage device in pure Go.
Index ¶
- func CachingModePage(w io.Writer, wce bool)
- func FixedString(s string, length int) []byte
- func GenerateSerial(name string) string
- type DataSizes
- type DevReadyFunc
- type Device
- type InquiryInfo
- type NaaWWN
- type ReadWriterAt
- type ReadWriterAtCmdHandler
- type SCSICmd
- func (c *SCSICmd) CdbLen() int
- func (c *SCSICmd) CheckCondition(key byte, asc uint16) SCSIResponse
- func (c *SCSICmd) Command() byte
- func (c *SCSICmd) Device() *Device
- func (c *SCSICmd) GetCDB(index int) byte
- func (c *SCSICmd) IllegalRequest() SCSIResponse
- func (c *SCSICmd) LBA() uint64
- func (c *SCSICmd) MediumError() SCSIResponse
- func (c *SCSICmd) NotHandled() SCSIResponse
- func (c *SCSICmd) Ok() SCSIResponse
- func (c *SCSICmd) Read(b []byte) (n int, err error)
- func (c *SCSICmd) RespondSenseData(status byte, sense []byte) SCSIResponse
- func (c *SCSICmd) RespondStatus(status byte) SCSIResponse
- func (c *SCSICmd) TargetFailure() SCSIResponse
- func (c *SCSICmd) Write(b []byte) (n int, err error)
- func (c *SCSICmd) XferLen() uint32
- type SCSICmdHandler
- type SCSIHandler
- type SCSIResponse
- func EmulateEvpdInquiry(cmd *SCSICmd, inq *InquiryInfo) (SCSIResponse, error)
- func EmulateInquiry(cmd *SCSICmd, inq *InquiryInfo) (SCSIResponse, error)
- func EmulateModeSelect(cmd *SCSICmd, wce bool) (SCSIResponse, error)
- func EmulateModeSense(cmd *SCSICmd, wce bool) (SCSIResponse, error)
- func EmulateRead(cmd *SCSICmd, r io.ReaderAt) (SCSIResponse, error)
- func EmulateReadCapacity16(cmd *SCSICmd) (SCSIResponse, error)
- func EmulateServiceActionIn(cmd *SCSICmd) (SCSIResponse, error)
- func EmulateStdInquiry(cmd *SCSICmd, inq *InquiryInfo) (SCSIResponse, error)
- func EmulateTestUnitReady(cmd *SCSICmd) (SCSIResponse, error)
- func EmulateWrite(cmd *SCSICmd, r io.WriterAt) (SCSIResponse, error)
- type WWN
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CachingModePage ¶
func FixedString ¶
func GenerateSerial ¶
Types ¶
type DevReadyFunc ¶
type DevReadyFunc func(chan *SCSICmd, chan SCSIResponse) error
func MultiThreadedDevReady ¶
func MultiThreadedDevReady(h SCSICmdHandler, threads int) DevReadyFunc
func SingleThreadedDevReady ¶
func SingleThreadedDevReady(h SCSICmdHandler) DevReadyFunc
type Device ¶
type Device struct {
// contains filtered or unexported fields
}
func OpenTCMUDevice ¶
func OpenTCMUDevice(devPath string, scsi *SCSIHandler) (*Device, error)
OpenTCMUDevice creates the virtual device based on the details in the SCSIHandler, eventually creating a device under devPath (eg, "/dev") with the file name scsi.VolumeName. The returned Device represents the open device connection to the kernel, and must be closed.
func (*Device) GetDevConfig ¶
type InquiryInfo ¶
InquiryInfo holds the general vendor information for the emulated SCSI Device. Fields used from this will be padded or trunacted to meet the spec.
type NaaWWN ¶
type NaaWWN struct { // OUI is the first three bytes (six hex digits), in ASCII, of your // IEEE Organizationally Unique Identifier, eg, "05abcd". OUI string // The VendorID is the first four bytes (eight hex digits), in ASCII, of // the device's vendor-specific ID (perhaps a serial number), eg, "2416c05f". VendorID string // The VendorIDExt is an optional eight more bytes (16 hex digits) in the same format // as the above, if necessary. VendorIDExt string }
NaaWWN represents the World Wide Name of the SCSI device we are emulating, using the Network Address Authority standard.
type ReadWriterAtCmdHandler ¶
type ReadWriterAtCmdHandler struct { RW ReadWriterAt Inq *InquiryInfo }
func (ReadWriterAtCmdHandler) HandleCommand ¶
func (h ReadWriterAtCmdHandler) HandleCommand(cmd *SCSICmd) (SCSIResponse, error)
type SCSICmd ¶
type SCSICmd struct { // Buf, if provided, may be used as a scratch buffer for copying data to and from the kernel. Buf []byte // contains filtered or unexported fields }
SCSICmd represents a single SCSI command recieved from the kernel to the virtual target.
func (*SCSICmd) CheckCondition ¶
func (c *SCSICmd) CheckCondition(key byte, asc uint16) SCSIResponse
CheckCondition returns a response providing extra sense data. Takes a Sense Key and an Additional Sense Code.
func (*SCSICmd) Command ¶
Command returns the SCSI command byte for the command. Useful when used as a comparison to the constants in the scsi package: c.Command() == scsi.Read6
func (*SCSICmd) IllegalRequest ¶
func (c *SCSICmd) IllegalRequest() SCSIResponse
IllegalRequest is a preset response for a request that is malformed or unexpected.
func (*SCSICmd) MediumError ¶
func (c *SCSICmd) MediumError() SCSIResponse
MediumError is a preset response for a read error condition from the device
func (*SCSICmd) NotHandled ¶
func (c *SCSICmd) NotHandled() SCSIResponse
NotHandled creates a response and sense data that tells the kernel this device does not emulate this command.
func (*SCSICmd) Ok ¶
func (c *SCSICmd) Ok() SCSIResponse
Ok creates a SCSIResponse to this command with SAM_STAT_GOOD, the common case for commands that succeed.
func (*SCSICmd) Read ¶
Read, for a SCSICmd, is a io.Reader from the data buffer attached to this SCSI command. If there's data to be written to the virtual device, this is the way to access it.
func (*SCSICmd) RespondSenseData ¶
func (c *SCSICmd) RespondSenseData(status byte, sense []byte) SCSIResponse
RespondSenseData returns a SCSIResponse with the given status byte set and takes a byte array representing the SCSI sense data to be written.
func (*SCSICmd) RespondStatus ¶
func (c *SCSICmd) RespondStatus(status byte) SCSIResponse
RespondStatus returns a SCSIResponse with the given status byte set. Ok() is equivalent to RespondStatus(scsi.SamStatGood).
func (*SCSICmd) TargetFailure ¶
func (c *SCSICmd) TargetFailure() SCSIResponse
TargetFailure is a preset response for returning a hardware error.
type SCSICmdHandler ¶
type SCSICmdHandler interface {
HandleCommand(cmd *SCSICmd) (SCSIResponse, error)
}
SCSICmdHandler is a simple request/response handler for SCSI commands coming to TCMU. A SCSI error is reported as an SCSIResponse with an error bit set, while returning a Go error is for flagrant, process-ending errors (OOM, perhaps).
type SCSIHandler ¶
type SCSIHandler struct { // The volume name and resultant device name. VolumeName string // The size of the device and the blocksize for the device. DataSizes DataSizes // The loopback HBA for the emulated SCSI device HBA int // The LUN for the emulated HBA LUN int // The SCSI World Wide Identifer for the device WWN WWN // Called once the device is ready. Should spawn a goroutine (or several) // to handle commands coming in the first channel, and send their associated // responses down the second channel, ordering optional. DevReady DevReadyFunc }
SCSIHandler is the high-level data for the emulated SCSI device.
func BasicSCSIHandler ¶
func BasicSCSIHandler(rw ReadWriterAt) *SCSIHandler
type SCSIResponse ¶
type SCSIResponse struct {
// contains filtered or unexported fields
}
A SCSIResponse is generated from methods on SCSICmd.
func EmulateEvpdInquiry ¶
func EmulateEvpdInquiry(cmd *SCSICmd, inq *InquiryInfo) (SCSIResponse, error)
func EmulateInquiry ¶
func EmulateInquiry(cmd *SCSICmd, inq *InquiryInfo) (SCSIResponse, error)
func EmulateModeSelect ¶
func EmulateModeSelect(cmd *SCSICmd, wce bool) (SCSIResponse, error)
EmulateModeSelect checks that the only mode selected is the static one returned from EmulateModeSense. `wce` should match the Write Cache Enabled of the EmulateModeSense call.
func EmulateModeSense ¶
func EmulateModeSense(cmd *SCSICmd, wce bool) (SCSIResponse, error)
EmulateModeSense responds to a static Mode Sense command. `wce` enables or diables the SCSI "Write Cache Enabled" flag.
func EmulateRead ¶
func EmulateRead(cmd *SCSICmd, r io.ReaderAt) (SCSIResponse, error)
func EmulateReadCapacity16 ¶
func EmulateReadCapacity16(cmd *SCSICmd) (SCSIResponse, error)
func EmulateServiceActionIn ¶
func EmulateServiceActionIn(cmd *SCSICmd) (SCSIResponse, error)
func EmulateStdInquiry ¶
func EmulateStdInquiry(cmd *SCSICmd, inq *InquiryInfo) (SCSIResponse, error)
func EmulateTestUnitReady ¶
func EmulateTestUnitReady(cmd *SCSICmd) (SCSIResponse, error)
func EmulateWrite ¶
func EmulateWrite(cmd *SCSICmd, r io.WriterAt) (SCSIResponse, error)