pax_global_header 0000666 0000000 0000000 00000000064 13747533545 0014532 g ustar 00root root 0000000 0000000 52 comment=6bd8382ff69378e6e27860a1a0a147eef0b42e22
tgt-1.0.80/ 0000775 0000000 0000000 00000000000 13747533545 0012416 5 ustar 00root root 0000000 0000000 tgt-1.0.80/.gitignore 0000664 0000000 0000000 00000000323 13747533545 0014404 0 ustar 00root root 0000000 0000000 #
# Normal rules
#
.*
*.o
*.o.*
*.a
*.s
*.ko
*.so
*.mod.c
*.i
*.lst
*.symtypes
*.d
*.orig
*.rej
#
# for GLOBAL
#
GTAGS
GRTAGS
GPATH
GSYMS
#
# programs
#
usr/tgtd
usr/tgtadm
usr/tgtimg
# cscope files
cscope.*
tgt-1.0.80/Makefile 0000664 0000000 0000000 00000003403 13747533545 0014056 0 ustar 00root root 0000000 0000000 VERSION ?= 1.0.80
CHECK_CC = cgcc
CHECK_CC_FLAGS = '$(CHECK_CC) -Wbitwise -Wno-return-void -no-compile $(ARCH)'
# Define a common prefix where binaries and docs install
PREFIX ?= /usr
# Export VERSION and PREFIX so sub-make knows about them
export VERSION PREFIX
# Export the feature switches so sub-make knows about them
export ISCSI_RDMA
export CEPH_RBD
export GLFS_BD
export SD_NOTIFY
.PHONY: all
all: programs doc conf scripts
# Targets for the /usr/sbin utilities
.PHONY: programs install-programs clean-programs
programs:
$(MAKE) -C usr
install-programs:
$(MAKE) -C usr install
clean-programs:
$(MAKE) -C usr clean
# Targets for man pages and other documentation
.PHONY: doc install-doc clean-doc
doc:
$(MAKE) -C doc
install-doc:
$(MAKE) -C doc install
clean-doc:
$(MAKE) -C doc clean
# Targets for scripts
.PHONY: scripts install-scripts clean-scripts
scripts:
$(MAKE) -C scripts
install-scripts:
$(MAKE) -C scripts install
clean-scripts:
$(MAKE) -C scripts clean
# Targets for configuration stubs
.PHONY: conf install-conf clean-conf
conf:
$(MAKE) -C conf
install-conf:
$(MAKE) -C conf install
clean-conf:
$(MAKE) -C conf clean
.PHONY: install
install: install-programs install-doc install-conf install-scripts
.PHONY: rpm
rpm:
@./scripts/build-pkg.sh rpm
.PHONY: deb
deb:
@./scripts/build-pkg.sh deb
.PHONY: clean
clean-pkg:
rm -fr pkg
.PHONY: clean
clean: clean-programs clean-doc clean-conf clean-scripts clean-pkg
.PHONY:check
check: ARCH=$(shell sh scripts/checkarch.sh)
check:
CC=$(CHECK_CC_FLAGS) $(MAKE) all
.PHONY:check32
check32: override ARCH=-m32
check32:
CC=$(CHECK_CC_FLAGS) $(MAKE) all
.PHONY:check64
check64: override ARCH=-m64
check64:
CC=$(CHECK_CC_FLAGS) $(MAKE) all
cscope:
find -name '*.[ch]' > cscope.files
cscope -bq
tgt-1.0.80/README 0000664 0000000 0000000 00000004702 13747533545 0013301 0 ustar 00root root 0000000 0000000 Introduction
------------
The Linux target framework (tgt) is a user space SCSI target framework
that supports the iSCSI and iSER transport protocols and that also
supports multiple methods for accessing block storage. Tgt consists of
a user-space daemon and user-space tools.
Currently, tgt supports the following SCSI transport protocols:
- iSCSI software target driver for Ethernet NICs
- iSER software target driver for Infiniband and RDMA NICs
Tgt supports the following methods for accessing local storage:
- aio, the asynchronous I/O interface also known as libaio.
- rdwr, smc and mmc, synchronous I/O based on the pread() and pwrite()
system calls.
- null, discards all data and reads zeroes.
- ssc, SCSI tape support.
- sg and bsg, SCSI pass-through.
- glfs, the GlusterFS network filesystem.
- rbd, Ceph's distributed-storage RADOS Block Device.
- sheepdog, a distributed object storage system.
Tgt can emulate the following SCSI device types:
- SBC: a virtual disk drive that can use a file to store the content.
- SMC: a virtual media jukebox that can be controlled by the "mtx"
tool.
- MMC: a virtual DVD drive that can read DVD-ROM iso files and create
burnable DVD+R. It can be combined with SMC to provide a fully
operational DVD jukebox.
- SSC: a virtual tape device (aka VTL) that can use a file to store
the content.
- OSD: a virtual object-based storage device that can use a file to
store the content (in progress).
License
-------
The code is released under the GNU General Public License version 2.
Requirements
------------
Linux kernel version 2.6.22 or newer is recommended because tgt can get
better performance with signalfd.
Target drivers have their own ways to build, configure, etc. Please
find an appropriate documentation in the doc directory. You might find
other useful information on tgt's site:
http://stgt.sourceforge.net/
Developer Notes
-------------
The central communication channel for tgt development is the mailing list
(stgt@vger.kernel.org).
First, please read the following documents (in short, follow Linux
kernel development rules):
https://www.kernel.org/doc/Documentation/CodingStyle
https://www.kernel.org/doc/Documentation/SubmittingPatches
Then, check your patches with the patch style checker prior to
submission (scripts/checkpatch.pl) like the following example.
fujita@arbre:~/git/tgt$ ./scripts/checkpatch.pl ~/0001-add-bidi-support.patch
Your patch has no obvious style problems and is ready for submission.
tgt-1.0.80/conf/ 0000775 0000000 0000000 00000000000 13747533545 0013343 5 ustar 00root root 0000000 0000000 tgt-1.0.80/conf/Makefile 0000664 0000000 0000000 00000001046 13747533545 0015004 0 ustar 00root root 0000000 0000000 sysconfdir ?= /etc
EXAMPLES = targets.conf.example targets.conf.vtl.L700 targets.conf.vtl.MSL2024
.PHONY: all
all:
.PHONY: install
install:
install -d -m 755 $(DESTDIR)$(sysconfdir)/tgt
if [ ! -f $(DESTDIR)$(sysconfdir)/tgt/targets.conf ] ; then \
install -m 644 targets.conf $(DESTDIR)$(sysconfdir)/tgt ; \
fi
install -d -m 755 $(DESTDIR)$(sysconfdir)/tgt/examples
for f in $(EXAMPLES) ; do \
install -m 644 examples/$$f $(DESTDIR)$(sysconfdir)/tgt/examples ;\
done
install -d $(DESTDIR)$(sysconfdir)/tgt/conf.d
.PHONY: clean
clean:
tgt-1.0.80/conf/examples/ 0000775 0000000 0000000 00000000000 13747533545 0015161 5 ustar 00root root 0000000 0000000 tgt-1.0.80/conf/examples/targets.conf.example 0000664 0000000 0000000 00000015736 13747533545 0021147 0 ustar 00root root 0000000 0000000 # This is a sample config file for tgt-admin.
# By default, tgt-admin looks for its config file in /etc/tgt/targets.conf
# This one includes other config files:
include /etc/tgt/temp/*.conf
# Set the driver. If not specified, defaults to "iscsi".
default-driver iscsi
# Set iSNS parameters, if needed
#iSNSServerIP 192.168.111.222
#iSNSServerPort 3205
#iSNSAccessControl On
#iSNS On
# Continue if tgtadm exits with non-zero code (equivalent of
# --ignore-errors command line option)
#ignore-errors yes
# Sample target with one LUN only. Defaults to allow access for all initiators:
backing-store /dev/LVM/somedevice
# Similar, but we use "direct-store" instead of "backing-store".
# "direct-store" reads drive parameters with sg_inq command and sets them to
# the target.
# Parameters fatched with sg_inq are:
# - Vendor identification
# - Product identification
# - Product revision level
# - Unit serial number (if present)
# We also specify "incominguser".
direct-store /dev/sdd
incominguser someuser secretpass12
# An example with multiple LUNs, disabled write-cache (tgtd enables write-cache
# by default) and vendor identification set to "MyVendor"
backing-store /dev/LVM/somedevice1 # Becomes LUN 1
backing-store /dev/LVM/somedevice2 # Becomes LUN 2
backing-store /dev/LVM/somedevice3 # Becomes LUN 3
write-cache off
vendor_id MyCompany Inc.
# Similar to the one above, but we fetch vendor_id, product_id, product_rev and
# scsi_sn from the disks.
# Vendor identification (vendor_id) is replaced in all disks by "MyVendor"
direct-store /dev/sdb # Becomes LUN 1
direct-store /dev/sdc # Becomes LUN 2
direct-store /dev/sdd # Becomes LUN 3
write-cache off
vendor_id MyCompany Inc.
# Note that "first-device-first-lun numbering" will work only for simple
# scenarios above, where _only_ direct-store _or_ backing-store is used.
# If you mix backing-store and direct-store, then all backing-store entries
# are processed before direct-store-entries.
direct-store /dev/sdb # Becomes LUN 3
backing-store /dev/sdc # Becomes LUN 1
direct-store /dev/sdd # Becomes LUN 4
backing-store /dev/sde # Becomes LUN 2
# Even more complicated example - each device has different parameters.
# You can use indentation to make the config file more readable.
# Note that LUNs will be assigned more or less randomly here (and still
# backing-store get LUNs assigned before drect-store).
# You can specify multiple mode_page parameters (they are commented out
# in this example).
# Note that some parameters (write-cache, scsi_sn) were specified "globally".
# "Global" parameters will be applied to all LUNs; they can be overwritten
# "locally", per LUN.
# If lun is not specified, it will be allocated automatically (first available).
vendor_id VENDOR1
removable 1
device-type cd
lun 1
vendor_id VENDOR2
lun 2
vendor_id back1
scsi_sn SERIAL
write-cache on
# lun 3 # lun is commented out - will be allocated automatically
vendor_id back2
#mode_page 8:0:18:0x10:0:0xff....
#mode_page 8:0:18:0x10:0:0xff....
#bs-type aio
#params element_type=4,start_address=500,quantity=3,media_home=/root/tapes
#params element_type=4,address=500,tid=1,lun=1
lun 15
# Some more parameters which can be specified locally or globally:
#scsi_id ...
#scsi_sn ...
#vendor_id ...
#product_id ...
#product_rev ...
#sense_format ...
#removable ...
#online ...
#readonly ...
#path ...
#mode_page 8:0:18:0x10:0:0xff....
#mode_page 8:0:18:0x10:0:0xff....
#device-type ...
#bs-type ... # backing store type - default rdwr, can be aio, etc...
#params element_type=4,start_address=500,quantity=3,media_home=/root/tapes
#params element_type=4,address=500,tid=1,lun=1
#allow-in-use yes # if specified globally, can't be overwritten locally
write-cache off
scsi_sn multipath-10
# Parameters below are only global. They can't be configured per LUN.
# Only allow connections from 192.168.100.1 and 192.168.200.5
initiator-address 192.168.100.1
initiator-address 192.168.200.5
# Tuning parameters (global, per target)
#MaxRecvDataSegmentLength 8192
#MaxXmitDataSegmentLength 8192
#HeaderDigest None
#DataDigest None
#InitialR2T Yes
#MaxOutstandingR2T 1
#ImmediateData Yes
#FirstBurstLength 65536
#MaxBurstLength 262144
#DataPDUInOrder Yes
#DataSequenceInOrder Yes
#ErrorRecoveryLevel 0
#IFMarker No
#OFMarker No
#DefaultTime2Wait 2
#DefaultTime2Retain 20
#OFMarkInt Reject
#IFMarkInt Reject
#MaxConnections 1
# Allowed incoming users
incominguser user1 secretpass12
incominguser user2 secretpass23
# Outgoing user
outgoinguser userA secretpassA
# TID of controller
controller_tid 10
# The device will have lun 1 unless you specify something else
backing-store /dev/LVM/somedevice
lun 10
# Devices which are in use (by system: mounted, for swap, part of RAID, or by
# userspace: dd, by tgtd for another target etc.) can't be used, unless you use
# --force flag or add 'allow-in-use yes' option
backing-store /dev/LVM/somedevice
allow-in-use yes
scsi_sn serial1
scsi_sn serial2
allow-in-use yes
# Specify controller TID of target
# Must be unique for all targets
# To reduce risk of duplicating controller TIDs, specify TID for all targets
# or none
backing-store /dev/LVM/somedevice
allow-in-use yes
controller_tid 10
# Not supported configurations, and therefore, commented out:
#
# backing-store /dev/LVM/somedevice1
# backing-store /dev/LVM/somedevice2
# lun 10
# lun 11
#
#
#
# vendor_id VENDOR1
#
#
# direct-store /dev/sdc
#
# This one will break the parser:
#
#
# vendor_id VENDOR1
#
#
# direct-store /dev/sdc
#
#
# vendor_id VENDOR1
#
#
tgt-1.0.80/conf/examples/targets.conf.vtl.L700 0000664 0000000 0000000 00000007065 13747533545 0020736 0 ustar 00root root 0000000 0000000 # Virtual tape library example for a STK L700 tape library
#
# In this case, tapes are stored in the directory /root/tapes
# size is in MB (1 GB in this case)
# using the command "tgtimg --op=new --device-type=tape --barcode="A00000001" --size=10240 --type=data --file=A00000001"
#
# The tapes can be added after startup with
# "tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 4 --params element_type=2,address=1000,barcode=A0000001,sides=1"
# for slot 0 (is nr 1000)
#
include /etc/tgt/temp/*.conf
default-driver iscsi
allow-in-use yes
#
# For every drive We need a backing store, although the tape drive will be empty,
# so we create a dummy tape "notape" in directory /root/tapes
# with the command "tgtimg --op=new --device-type=tape --barcode="" --size=1 --type=clean --file=notape"
# and create symbolic links for every drive (limitation of tgt)
# link -s /root/tapes/notape /root/tapes/notape1
# link -s /root/tapes/notape /root/tapes/notape2
# link -s /root/tapes/notape /root/tapes/notape2
#
lun 1
device-type tape
removable 1
vendor_id "HP"
product_id "LTO3 ULTRIUM"
product_rev "0001"
scsi_sn "HUM1A00001"
scsi_id "HP LTO3 ULTRIUM"
lun 2
device-type tape
removable 1
vendor_id "HP"
product_id "LTO3 ULTRIUM"
product_rev "0001"
scsi_sn "HUM1A00002"
scsi_id "HP LTO3 ULTRIUM"
lun 3
device-type tape
removable 1
vendor_id "HP"
product_id "LTO3 ULTRIUM"
product_rev "0001"
scsi_sn "HUM1A00003"
scsi_id "HP LTO3 ULTRIUM"
lun 4
device-type changer
removable 1
vendor_id "STK"
product_id "L700"
product_rev "0001"
scsi_sn "123:456:789:000"
# Dummy 'page 0'
mode_page "0:0:0"
# Page 0x02: Disconnect/Reconnect SPC-3
mode_page "0x02:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0"
# Page 0x1a: Power Condition SPC-3
mode_page "0x1a:0:18:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0"
# Page 0x1c: Informational Exceptions Control SPC-3
mode_page "0x1c:0:10:8:0:0:0:0:0:0:0:0:0"
# Page 0x1d: Element Address Assignment SMC-3 7.3.4
mode_page "0x1d:0:0x12:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0"
# Page 0x1e: Transport Geometry Parameters SMC-3 7.3.5
mode_page "0x1e:0:2:0:0"
# Page 0x1f: Device Capabilities SMC-3 7.3.2
# Page 0x1f/Subpage 0x41: Extended Device Capabilities SMC-3 7.3.3
mode_page "0x1f:0:0x12:0x0f:7:0x0f:0x0f:0x0f:0x0f:0:0:0:0:0x0f:0x0f:0x0f:0x0f:0:0:0:0"
# Type 1: Medium Transport Elements (robot arm/picker)
params element_type=1,start_address=1,quantity=1,media_home=/root/tapes
# Type 2: Storage Elements (tape slots)
params element_type=2,start_address=1000,quantity=216,media_home=/root/tapes
# Type 3: Import/Export Elements (CAP)
params element_type=3,start_address=10,quantity=20,media_home=/root/tapes
# Type 4: Add Data Transfer devices (drives)
params element_type=4,start_address=500,quantity=3,media_home=/root/tapes
params element_type=4,address=500,tid=1,lun=1
params element_type=4,address=500,tid=1,lun=2
params element_type=4,address=500,tid=1,lun=3
tgt-1.0.80/conf/examples/targets.conf.vtl.MSL2024 0000664 0000000 0000000 00000005477 13747533545 0021264 0 ustar 00root root 0000000 0000000 # Virtual tape library example for an HP MSL-2024 tape library
#
# In this case, tapes are stored in the directory /root/tapes
# size is in MB (1 GB in this case)
# using the command "tgtimg --op=new --device-type=tape --barcode="A00000001" --size=10240 --type=data --file=A00000001"
#
# The tapes can be added after startup with
# "tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 4 --params element_type=2,address=1000,barcode=A0000001,sides=1"
# for slot 0 (is nr 1000)
#
# Please note that an MSL-2024 has no IMPORT/EXPORT elements (type 3)
include /etc/tgt/temp/*.conf
default-driver iscsi
allow-in-use yes
#
# We need a backing store, although the tape drive will be empty,
# so we create a dummy tape "notape" in directory /root/tapes
# with the command "tgtimg --op=new --device-type=tape --barcode="" --size=1 --type=clean --file=notape"
#
lun 1
device-type tape
removable 1
vendor_id "HP "
product_id "Ultrium 3-SCSI"
product_rev "D21W"
scsi_sn "HU012345AB"
scsi_id "HP LTO3 ULTRIUM"
#
# For the tape changer we need also a backing store, this can be a file containing zeros, like this:
# "dd if=/dev/zero of=$HOME/smc bs=1k count=1"
#
lun 4
device-type changer
removable 1
vendor_id "HP "
product_id "MSL G3 Series "
product_rev "3.00"
scsi_sn "ABC01234G3"
# Dummy 'page 0'
mode_page "0:0:0"
# Page 0x02: Disconnect/Reconnect SPC-3
mode_page "0x02:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0"
# Page 0x1a: Power Condition SPC-3
mode_page "0x1a:0:18:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0"
# Page 0x1c: Informational Exceptions Control SPC-3
mode_page "0x1c:0:10:8:0:0:0:0:0:0:0:0:0"
# Page 0x1d: Element Address Assignment SMC-3 7.3.4
mode_page "0x1d:0:0x12:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0"
# Page 0x1e: Transport Geometry Parameters SMC-3 7.3.5
mode_page "0x1e:0:2:0:0"
# Page 0x1f: Device Capabilities SMC-3 7.3.2
# Page 0x1f/Subpage 0x41: Extended Device Capabilities SMC-3 7.3.3
mode_page "0x1f:0:0x12:0x0f:7:0x0f:0x0f:0x0f:0x0f:0:0:0:0:0x0f:0x0f:0x0f:0x0f:0:0:0:0"
# Type 1: Medium Transport Elements (robot arm/picker)
params element_type=1,start_address=1,quantity=1,media_home=/root/tapes
# Type 2: Storage Elements (tape slots)
params element_type=2,start_address=1000,quantity=24,media_home=/root/tapes
# Type 4: Add Data Transfer devices (drives)
params element_type=4,start_address=2,quantity=1,media_home=/root/tapes
params element_type=4,address=2,tid=1,lun=1
tgt-1.0.80/conf/targets.conf 0000664 0000000 0000000 00000000307 13747533545 0015663 0 ustar 00root root 0000000 0000000 # Empty targets configuration file -- please see the package
# documentation directory for an example.
#
# You can drop individual config snippets into /etc/tgt/conf.d
include /etc/tgt/conf.d/*.conf
tgt-1.0.80/doc/ 0000775 0000000 0000000 00000000000 13747533545 0013163 5 ustar 00root root 0000000 0000000 tgt-1.0.80/doc/Makefile 0000664 0000000 0000000 00000006076 13747533545 0014634 0 ustar 00root root 0000000 0000000 mandir ?= $(PREFIX)/share/man
docdir ?= $(PREFIX)/share/doc/tgt
MANPAGES = manpages/tgtadm.8 manpages/tgt-admin.8 manpages/tgtimg.8 \
manpages/tgt-setup-lun.8 manpages/tgtd.8 \
manpages/targets.conf.5
DOCS = README.iscsi README.iser \
README.lu_configuration README.mmc tmf.txt \
README.rbd
XSLTPROC = /usr/bin/xsltproc
XMLMAN = manpages/tgtd.8 manpages/tgtadm.8 manpages/tgtimg.8 \
manpages/tgt-admin.8 manpages/targets.conf.5 \
manpages/tgt-setup-lun.8
XMLHTML = htmlpages/tgtd.8.html htmlpages/tgtadm.8.html \
htmlpages/tgtimg.8.html htmlpages/tgt-admin.8.html \
htmlpages/targets.conf.5.html htmlpages/tgt-setup-lun.8.html
.PHONY:all
all: xmlman xmlhtml
.PHONY: install
install: $(MANPAGES) $(DOCS)
install -d -m 755 $(DESTDIR)$(mandir)/man8 $(DESTDIR)$(mandir)/man5
install -m 644 $(filter %.8,$(MANPAGES)) $(DESTDIR)$(mandir)/man8
install -m 644 $(filter %.5,$(MANPAGES)) $(DESTDIR)$(mandir)/man5
install -d -m 755 $(DESTDIR)$(docdir)
install -m 644 $(DOCS) $(DESTDIR)$(docdir)
install -d -m 755 $(DESTDIR)$(docdir)/html
install -m 644 $(XMLHTML) $(DESTDIR)$(docdir)/html
.PHONY: clean
clean:
rm -f $(XMLMAN) $(XMLHTML)
-rm -f manpages htmlpages
manpages/tgtd.8: tgtd.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
htmlpages/tgtd.8.html: tgtd.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
manpages/tgtadm.8: tgtadm.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
htmlpages/tgtadm.8.html: tgtadm.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
manpages/tgt-admin.8: tgt-admin.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
htmlpages/tgt-admin.8.html: tgt-admin.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
manpages/tgtimg.8: tgtimg.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
htmlpages/tgtimg.8.html: tgtimg.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
manpages/targets.conf.5: targets.conf.5.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
htmlpages/targets.conf.5.html: targets.conf.5.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
manpages/tgt-setup-lun.8: tgt-setup-lun.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
htmlpages/tgt-setup-lun.8.html: tgt-setup-lun.8.xml
-test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
xmlman: $(XMLMAN)
xmlhtml: $(XMLHTML)
tgt-1.0.80/doc/README.dvdjukebox 0000775 0000000 0000000 00000006273 13747533545 0016222 0 ustar 00root root 0000000 0000000 #!/bin/sh
#
# This is a script to set up a SCSI DVD JUKEBOX for all your ISO images.
#
# This script should be run from a directory that contains ISO images
# with the extension .iso .
# When run, the script will create an iSCSI target containing one DVD drive
# and one media changer. The DVD drive will not have a disk mounted but
# the medium changer will have one slot for each ISO image found.
# Use the 'mtx' tool to load/unload media.
#
# By default, any ISO images found will be populated with a barcode of the
# form DVD_%04d and a volume_tag based on the filename.
#
# CON: This is the connection index for TGTD. This is needed if you run
# multiple instances of TGTD on the same host. Each instance must use a unique
# CON value.
#
# TARGETNAME: The iSCSI name to use for the target.
#
# TARGETPORT: The TCP port that this target will be hosted from.
CON=1
TARGETNAME=iqn.ronnie.iso:distros
TARGETPORT=3261
nc -z 127.0.0.1 $TARGETPORT || {
echo "Starting iSCSI target on port $TARGETPORT"
tgtd -C $CON --iscsi portal=0.0.0.0:$TARGETPORT
sleep 3
}
tgtadm -C $CON --op delete --mode target --tid 1 --force 2>/dev/null
tgtadm -C $CON --op new --mode target --tid 1 -T $TARGETNAME
# Create a DVD drive
tgtadm -C $CON --op new --mode logicalunit --tid 1 --lun 1 -Y cd
tgtadm -C $CON --op update --mode logicalunit --tid 1 --lun 1 --params vendor_id=STGT_DVD,product_id=DVD101,product_rev=0010,scsi_sn=STGTDVD01,removable=1
# We need a backend store file for the media changer
SMC=`pwd`/smc-$CON
if [ ! -f $SMC ]; then
dd if=/dev/zero of=$SMC bs=1k count=1
fi
# Create the SMC device and give it a nice name
tgtadm -C $CON --mode logicalunit --op new --tid 1 --lun 2 -b $SMC --device-type=changer
tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params vendor_id=STK,product_id=L700,product_rev=0010,scsi_sn=XYZZY_0,removable=1
# Add a Data Transfer devices (1 drive)
tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,start_address=1,quantity=1
# Specify that the DVD above (LUN 1) is the data transfer device we created
tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,address=1,tid=1,lun=1
# Medium Transport Elements (robot arm / picker)
tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=1,start_address=16,quantity=1
# define path to virtual media
VTL=`pwd`/vtl
mkdir -p ${VTL} 2>/dev/null
rm ${VTL}/*
tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params media_home=${VTL}
NUM_ELEM=`ls *.iso | wc -l`
echo "Creating $NUM_ELEM storage elements for media"
# Storage Elements - starting at addr 1024
tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,start_address=1024,quantity=$NUM_ELEM
# Populate the slots
SLOT=0
ls *.iso | sed -e "s/.iso$//" | while read ISO; do
BARCODE=`echo $SLOT | awk '{printf "DVD_%04d", $1}'`
ln `pwd`/${ISO}.iso ${VTL}/${BARCODE}
tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=`expr "1024" "+" "$SLOT"`,barcode="${BARCODE}",volume_tag="${ISO}",sides=1
SLOT=`expr "$SLOT" "+" "1"`
done
tgtadm -C $CON --op bind --mode target --tid 1 -I ALL
tgtadm -C $CON --op show --mode target
tgt-1.0.80/doc/README.glfs 0000664 0000000 0000000 00000004436 13747533545 0015004 0 ustar 00root root 0000000 0000000 The 'glfs' backing-store driver provides block access to a file within the
gluster distributed file system (www.gluster.org). The file represents a
LUN visible to the initiator (the file may be a regular file in gluster's
underlying XFS filesystem, or a gluster "block device"). This configuration
gives gluster support for any access method supported by the target driver,
such as iSCSI.
The backing-store driver uses the interfaces to gluster in libgfapi.so.
This file is part of the glusterfs-api package which can be downloaded from
www.gluster.org.
To build the glfs backing store driver, set the GLFS_BS environment
variable (export GLFS_BD=1) before running Make.
When a LUN on a target is created with backing store of type glfs (--bstype
glfs), a handle to gluster is created. The handle is destroyed when the
target is closed. The handle represents a particular volume on a gluster
server and a particular file representing the LUN.
To set the gluster volume and file, --backing-store takes the form:
volume@hostname:filename
For example, if the volume name was vol1 , and the host gprfs010, and file
name was "disk1", you would set --backing-store to:
--backing-store="vol1@gprfs010:disk1"
For each CDB, the driver issues an appropriate gluster api call against the
handle. For example, WRITE_16 becomes glfs_pwrite(), SYNCHRONIZE_CACHE
becomes glfs_fdatasync() and UNMAP becomes glfs_discard().
Each call is synchronous, meaning the thread will wait for a response from
gluster before returning to the caller. The libgfapi interfaces support
asynchronous calls, and an asynchronous version of the driver has been
tested. For more information on the asynchronous version please contact
dlambrig@redhat.com.
If the backing store driver was not used, the linux target driver could
still write data to gluster by loopback mounting the gluster file system.
The backing-store would be a file within the file system. Gluster uses FUSE
to forward I/O from the kernel to it's user space daemon. The overhead
incured by FUSE and the kernel is removed when the backing store driver and
libgfapi package is used.
The libgfapi interfaces supports logs. You can use the --bsopts option to
set the logfile and loglevel.
--bsops="logfile=glfs.log;loglevel=3"
tgt-1.0.80/doc/README.iscsi 0000664 0000000 0000000 00000031357 13747533545 0015165 0 ustar 00root root 0000000 0000000 Preface
-------------
This show a simple example to set up some targets.
Starting the daemon
-------------
The iSCSI target driver works with the 2.6.X kernels.
First, you need to compile the source code:
host:~/tgt/usr$ make
Try the following commands:
host:~/tgt$ su
host:~/tgt# ./usr/tgtd
Configuration
-------------
Everyting is configured via the tgtadm management tool.
The following example creates a target with id 1 (the iqn is
iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz) and adds a
logical unit (backed by /dev/hdc1) with lun 1.
Let's create one target device.
host:~/tgt$ su
host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode target --tid 1 -T iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
You can get the current configuration:
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
Account information:
ACL information:
The controller device for management with lun 0 was created
automatically. You can't remove it.
Now it's time to add a logical unit to the target:
host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 1 -b /dev/hdc1
You can get the current configuration:
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: disk
SCSI ID: deadbeaf1:1
SCSI SN: beaf11
Size: 55G
Backing store: /dev/hdc1
Account information:
ACL information:
If you don't need to configure this target any more, enable the target
to accept any initiators:
host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: disk
SCSI ID: deadbeaf1:1
SCSI SN: beaf11
Size: 55G
Backing store: /dev/hdc1
Account information:
ACL information:
ALL
Note "ACL information" section. "ALL" means that this target accepts
any initiators. The ACL feature also provides the access control based
on initiators' addresses.
First, let's remove "ALL" option:
host:~/tgt# ./usr/tgtadm --lld iscsi --op unbind --mode target --tid 1 -I ALL
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: disk
SCSI ID: deadbeaf1:1
SCSI SN: beaf11
Size: 55G
Backing store: /dev/hdc1
Account information:
ACL information:
Here are some examples:
host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode target --tid 1 -I 192.0.2.229
host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode target --tid 1 -I 198.51.100.0/24
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: disk
SCSI ID: deadbeaf1:1
SCSI SN: beaf11
Size: 55G
Backing store: /dev/hdc1
Account information:
ACL information:
192.0.2.229
198.51.100.0/24
You can add lots of logical units:
host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 2 -b /dev/hdd1
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: disk
SCSI ID: deadbeaf1:1
SCSI SN: beaf11
Size: 55G
Backing store: /dev/hdc1
LUN: 2
Type: disk
SCSI ID: deadbeaf1:2
SCSI SN: beaf12
Size: 55G
Backing store: /dev/hdd1
Account information:
ACL information:
You can get iSCSI parameters of the target:
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target --tid 1
MaxRecvDataSegmentLength=8192
MaxXmitDataSegmentLength=8192
HeaderDigest=None
DataDigest=None
InitialR2T=Yes
MaxOutstandingR2T=1
ImmediateData=Yes
FirstBurstLength=65536
MaxBurstLength=262144
DataPDUInOrder=Yes
DataSequenceInOrder=Yes
ErrorRecoveryLevel=0
IFMarker=No
OFMarker=No
DefaultTime2Wait=2
DefaultTime2Retain=20
OFMarkInt=Reject
IFMarkInt=Reject
MaxConnections=1
You can change iSCSI parameters like the following (e.g. set
MaxRecvDataSegmentLength to 16384):
host:~/tgt# ./usr/tgtadm --lld iscsi --mode target --op update --tid 1 --name MaxRecvDataSegmentLength --value 16384
You can get iSCSI parameters again to see its change:
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target --tid 1
MaxRecvDataSegmentLength=16384
MaxXmitDataSegmentLength=8192
HeaderDigest=None
DataDigest=None
InitialR2T=Yes
MaxOutstandingR2T=1
ImmediateData=Yes
FirstBurstLength=65536
MaxBurstLength=262144
DataPDUInOrder=Yes
DataSequenceInOrder=Yes
ErrorRecoveryLevel=0
IFMarker=No
OFMarker=No
DefaultTime2Wait=2
DefaultTime2Retain=20
OFMarkInt=Reject
IFMarkInt=Reject
MaxConnections=1
The following is another example to enable header digest:
host:~/tgt# ./usr/tgtadm --lld iscsi --mode target --op update --tid 1 --name HeaderDigest --value CRC32C
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target --tid 1
MaxRecvDataSegmentLength=16384
MaxXmitDataSegmentLength=8192
HeaderDigest=CRC32C
DataDigest=None
InitialR2T=Yes
MaxOutstandingR2T=1
ImmediateData=Yes
FirstBurstLength=65536
MaxBurstLength=262144
DataPDUInOrder=Yes
DataSequenceInOrder=Yes
ErrorRecoveryLevel=0
IFMarker=No
OFMarker=No
DefaultTime2Wait=2
DefaultTime2Retain=20
OFMarkInt=Reject
IFMarkInt=Reject
MaxConnections=1
The target accepts CRC32C and None. Currently, there is no way to
configure a target to accept only CRC32C.
Authentication
-------------
Let's create a new account:
host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode account --user fujita --password tomo
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode account
Account list:
fujita
You can assign this account to any targets:
host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode account --tid 1 --user fujita
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: disk
SCSI ID: deadbeaf1:1
SCSI SN: beaf11
Size: 55G
Backing store: /dev/hdc1
Account information:
fujita
ACL information:
ALL
You can set up an outgoing account. First, you need to create a new
account like the previous example:
host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode account --user hoge --password deadbeaf
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode account
Account list:
hoge
fujita
host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode account --tid 1 --user hoge --outgoing
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: disk
SCSI ID: deadbeaf1:1
SCSI SN: beaf11
Size: 55G
Backing store: /dev/hdc1
Account information:
fujita
hoge (outgoing)
ACL information:
ALL
Initiator Information
-------------
After the target accepts initiators, the system information would be
something like the followings:
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
I_T nexus: 2
Initiator: iqn.1987-05.com.cisco:01.4438aca09387
Connection: 0
IP Address: 192.0.2.115
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: disk
SCSI ID: deadbeaf1:1
SCSI SN: beaf11
Size: 55G
Backing store: /dev/hdc1
Account information:
ACL information:
ALL
One initiator (192.0.2.113) logs in now. Let's try again:
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
I_T nexus: 2
Initiator: iqn.1987-05.com.cisco:01.4438aca09387
Connection: 0
IP Address: 192.0.2.115
I_T nexus: 3
Initiator: iqn.1991-05.com.microsoft:kernel
Connection: 1
IP Address: 192.0.2.113
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: disk
SCSI ID: deadbeaf1:1
SCSI SN: beaf11
Size: 55G
Backing store: /dev/hdc1
Account information:
ACL information:
ALL
Now we have two initiators. You can see the parameters that the target
and initiator negotiated (use the values follows "I_T nexus:"):
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode session --tid 1 --sid 3
MaxRecvDataSegmentLength=8192
MaxXmitDataSegmentLength=8192
HeaderDigest=None
DataDigest=None
InitialR2T=Yes
MaxOutstandingR2T=1
ImmediateData=Yes
FirstBurstLength=65536
MaxBurstLength=262144
DataPDUInOrder=Yes
DataSequenceInOrder=Yes
ErrorRecoveryLevel=0
IFMarker=No
OFMarker=No
DefaultTime2Wait=2
DefaultTime2Retain=20
OFMarkInt=Reject
IFMarkInt=Reject
MaxConnections=1
Performance tips
-------------
If you need multiple logical units, you had better create multiple
targets including one logical unit instead of creating one target
including multiple logical units.
Shutdown
-------------
host:~/tgt# killall -9 tgtd
We will support better methods later.
iSNS
-------------
iSNS support is still experimental.
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode sys
iSNS:
iSNS=Off
iSNSServerIP=
iSNSServerPort=3205
iSNSAccessControl=Off
host:~/tgt# ./usr/tgtadm --op update --mode sys --name iSNSServerIP -v 192.0.2.113
host:~/tgt# ./usr/tgtadm --op update --mode sys --name iSNS -v On
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode sys
iSNS:
iSNS=On
iSNSServerIP=192.0.2.113
iSNSServerPort=3205
iSNSAccessControl=Off
Now you are ready to add targets. Now there are some limitations:
- once you enable iSNS, you can't disable it.
- you need to enable iSNS before adding targets.
tgt-1.0.80/doc/README.iser 0000664 0000000 0000000 00000043320 13747533545 0015006 0 ustar 00root root 0000000 0000000 *** iSCSI Extensions for RDMA (iSER) in tgt ***
Copyright (C) 2007 Pete Wyckoff
Copyright (C) 2011 Alexander Nezhinsky
1. Background
1.1. Standards (iSCSI, iSER)
The IETF standards track RFC 5046 extends the iSCSI protocol to work
on RDMA-capable networks as well as on traditional TCP/IP:
Internet Small Computer System Interface (iSCSI) Extensions
for Remote Direct Memory Access (RDMA), Mike Ko, October 2007.
It is available online:
http://tools.ietf.org/html/rfc5046
RDMA stands for Remote Direct Memory Access, a way of accessing memory
of a remote node directly through the network without involving the
processor of that remote node. Many network devices implement some
form of RDMA. Two of the more popular network devices are InfiniBand
(IB) and iWARP. IB uses its own physical and network layer, while
iWARP sits on top of TCP/IP (or SCTP).
Using these devices requires a new application programming interface
(API). The Linux kernel has many components of the OpenFabrics
software stack, including APIs for access from user space and drivers
for some popular RDMA-capable NICs, including IB cards with the
chipset from Mellanox and QLogic, and iWARP cards from NetEffect,
Chelsio, and Ammasso. Most Linux distributions ship the user space
libraries for device access and RDMA connection management.
There is an ongoing activity, which is still in progress, intended
to improve upon RFC 5046 and address some existing issues. The text
of the latest proposal is available online (note, though, that it
may become outdated quickly):
http://tools.ietf.org/html/draft-ietf-storm-iser-01
1.2. iSER in tgtd
tgtd is a user space target that supports multiple transports,
including iSCSI/TCP and iSER on RDMA devices.
The original iSER code was written in early 2007 by researchers at
the Ohio Supercomputer Center:
Dennis Dalessandro
Ananth Devulapalli
Pete Wyckoff
The authors wanted to use a faster transport to test the capabilities
of an object-based storage device (OSD) emulator. A report describing
this implementation and some performance results appears in IEEE
conference proceedings as:
Dennis Dalessandro, Ananth Devulapalli and Pete Wyckoff, "iSER
Storage Target for Object-based Storage Devices", Proceedings
of MSST'07, SNAPI Workshop, San Diego, CA, September 2007.
and is available at:
http://www.osc.edu/~pw/papers/iser-snapi07.pdf
Slides of the talk with more results and analysis are available at:
http://www.osc.edu/~pw/papers/wyckoff-iser-snapi07-talk.pdf
The original code lived in iscsi/iscsi_rdma.c, with a few places
in iscsi/iscsid.c. RDMA transport was added and some more functions
where TCP and RDMA behaved differently were virtualized.
There was a bug that resulted in occasional data corruption.
The new implementation was written by Alexander Nezhinsky
. It defines iSER as a separate transport
(and not as a sub-transport of iSCSI/TCP).
One of the main differences between iSCSI/TCP and iSER is that the
former enjoys the stream semantics of TCP and may work in a synchronous
manner, while the latter's flow is intrinsically asynchronous and
message based. Implementing a synchronous flow within an asynchronous
framework is relatively natural, while fitting an asynchronous flow
within a synchronous framework is usually met with a few obstacles
resulting in a sub-optimal design.
The main reason to define iser as a separate transport (which is
an example of such obstacle) was to decouple rx/tx flow from using
EPOLLIN/EPOLLOUT events originally used to poll TCP sockets. See
"Event Management" section below for details.
Although one day we may return to a common tcp/rdma transport, for
now a separate transport LLD (named "iser") is defined.
Other changes include avoiding memory copies, using a memory pool
shared between connections with "patient" memory allocation mechanism,
etc.
Source-wise, a new header "iser.h" is created, "iscsi_rdma.c"
is replaced by "iser.c". File iser_text.c contains the iscsi-text
processing code replicated from iscsid.c. This is done because the
functions there are not general enough, and rely on specifics of
iscsi/tcp structs. This file will hopefully be removed in the future.
2. Design
2.1. General Notes
In general, a SCSI system includes two components, an initiator and
a target. The initiator submits commands and awaits responses. The
target services commands from initiators and returns responses. Data
may flow from the initiator, from the client, or both (bidirectional).
The iSER specification requires all data transfers to be started
by the target, regardless of direction. In a read operation, the
target uses RDMA Write to move data to the initiator, while a write
operation uses RDMA Read to fetch data from the initiator.
2.2. Memory registration
One of the most severe stumbling blocks in moving any application to
take advantage of RDMA features is memory registration. Before using
RDMA, both the sending and receiving buffers must be registered with
the operating system. This operation ensures that the underlying
hardware pages will not be modified during the transfer, and provides
the physical addresses of the buffers to the network card. However,
the process itself is time consuming, and CPU intensive. Previous
investigations have shown that for InfiniBand, the throughput drops
by up to 40% when memory registration and deregistration are included
in the critical path.
This iSER implementation uses pre-registered buffers for RDMA
operations. In general such a scheme is difficult to justify due
to the large per-connection resource requirements. However, in
this application it may be appropriate. Since the target always
initiates RDMA operations and never advertises RDMA buffers, it can
securely use one pool of buffers for multiple clients and can manage
its memory resources explicitly. Also, the architecture of the code
is such that the iSCSI layer dictates incoming and outgoing buffer
locations to the storage device layer, so supplying a registered
buffer is relatively easy.
2.3. Event management
As mentioned above, there is a mismatch between what the iscsid
framework assumes and what the RDMA notification interface provides.
The existing TCP-based iSCSI target code has one file descriptor
per connection and it is driven by readability or writeability of
the socket. A single poll system call returns which sockets can be
serviced, driving the TCP code to read or write as appropriate.
The RDMA interface is also represented by a single file descriptor
created by the driver responsible for the hardware. This file
descriptor readability may be used by requesting interrupts from the
network card on work request completions, after a sufficiently long
period of quiescence. Furter completions can be polled and retrieved
without re-arming the interrupts. Beside this first difference,
the RDMA device file descriptor can not and should not be polled
for writability, as any messages or RDMA transfer requests may be
issued assynhcronously.
Moreover, the existing sockets-based code goes beyond this and
changes the bitmask of requested events to control its code flow.
For instance, after it finishes sending a response, it will modify the
bitmask to only look for readability. Even if the socket is writeable,
there is no data to write, hence polling for that status is not useful.
The code also disables new message arrival during command execution
as a sort of exclusion facility, again by modifying the bitmask.
As it can not be done with the RDMA interface, the original code had
to maintain an active list of tasks having data to write and to drive
a progress engine to service them. The progress was tracked by a
counter, and the tgtd event loop checked this counter and called into
the iSER-specific while the counter is still non-zero. This scheme
was quite unnatural and error-prone.
The new implementation issues all SEND requests
asynchronously. Besides, it relies heavily upon the scheduled events
that are injected into the event loop with no dependence on file
descriptors. It schedules such events to poll for new RDMA completion
events, in hope that new ones are ready. If no event arrives after
a certain number of polls then interrupts are requested and further
progress will be driven through the file-based event mechanism.
Note that only the first event is signal in this manner and while
new completions are constantly arriving, they will be retrieved by
polling only.
Other internal events of the same kind (like tasks requesting a
send, commands that are ready for submition etc.) are grouped on
appropriate lists and special events are scheduled for them. This
allows to process few tasks in a batched manner in order to optimise
RDMA and other operations, if possible.
2.4. RDMA-only mode
The code implies RDMA-only mode of work. This means the "first
burst" including immediate data should be disabled, so that the
entire data transfer is performed using RDMA. This mode is perhaps
the most suitable one for iser in the majority of work scenarios.
The only concern is about relatively small WRITE I/Os, which may
enjoy theoretically lower latencies using IB SEND instead of RDMA-RD.
Implementing this mode is meanwhile precluded because it would lead to
multiple buffers per iSER task (e.g. ImmediateData buffer received
with the command PDU, and the rest of the data retrieved using
RDMA-RD), which is not supported by the existing tgt backing stores.
The RDMA-only mode is achieved by setting:
target->session_param[ISCSI_PARAM_INITIAL_R2T_EN].val = 1;
target->session_param[ISCSI_PARAM_IMM_DATA_EN].val = 0;
which is hardcoded in iser_target_create().
2.5. Padding
The iSCSI specification clearly states that all segments in the
protocol data unit (PDU) must be individually padded to four-byte
boundaries. However, the iSER specification remains mute on the
subject of padding. It is clear from an implementation perspective
that padding data segments is both unnecessary and would add
considerable overhead to implement. (Possibly a memory copy or extra
SG entry on the initiator when sending directly from user memory.)
RDMA is used to move all data, with byte granularity provided by
the network. The need for padding in the TCP case was motivated by
the optional marker support to work around the limitations of the
streaming mode of TCP. IB and iWARP are message-based networks and
would never need markers. And finally, the Linux initiator does not
add padding either.
3. Using iSER
3.1. Building tgtd
Compile tgtd with:
make ISCSI_RDMA=1
As iSCSI support is compiled in by default, there is no need to specify
additional symbols for it.
For build purposes, you'll need to have two libraries installed
on your system: libibverbs-devel and librdmacm-devel.
To run iser, you'lll need the binary form of these libraries,
libibverbs.so and librdmacm.so along with device specific user
space library such as libmlx4 for Mellanox ConnectX HCAs.
If they are installed in the normal system paths (/usr/include and
/usr/lib or /usr/lib64), they will be found automatically. Otherwise,
edit CFLAGS and LIBS in usr/Makefile under ISER to specify the paths
by hand, e.g., for a /usr/local install, it should look like:
ifneq ($(ISCSI_RDMA),)
CFLAGS += -DISCSI_RDMA -I/usr/local/include
TGTD_OBJS += iscsi/iser.o
LIBS += -L/usr/local/lib -libverbs -lrdmacm
endif
If these libraries are not in the normal system paths, you may
possibly also have to set, e.g., LD_LIBRARY_PATH=/usr/local/lib in
your environment to find the shared libraries at runtime.
The libraries mentioned is this section have pre-set packages provided
the by various distributions, e.g RPMs for Fedora, RedHat and Suse,
or DEBs for Debian and Ubuntu.
3.2. Running tgtd
Start the daemon (as root):
./tgtd
It will send messages to syslog. You can add "-d 1" to turn on debug
messages. Debug messages can be also turned on and off during run
time using the following commnds:
./tgtadm --mode system --op update --name debug --value on
./tgtadm --mode system --op update --name debug --value off
The target will listen on all TCP interfaces (as usual), as well
as all RDMA devices. Both use the same default iSCSI port, 3260.
Clients on TCP or RDMA will connect to the same tgtd instance.
When using iser, you need to make sure the kernel IB/RDMA stack
is up and running.
It's recommended to use the rping tool provided by librdmacm-utils
for quick RDMA connectivity testing between the initiator and the
target nodes.
3.3. Configuring tgtd
Configure the running target with one or more devices, using the
tgtadm program you just built (also as root). Full information is
available in doc/README.iscsi. The difference is only in the name of
LLD which should be "iser".
Here is a quick-start example:
./tgtadm --lld iser --mode target \
--op new --tid 1 --targetname "iqn.$(hostname).t1"
./tgtadm --lld iser --mode target \
--op bind --tid 1 --initiator-address ALL
./tgtadm --lld iser --mode logicalunit \
--op new --tid 1 --lun 1 \ --backing-store /dev/sde
--bstype rdwr
3.4. Initiator side
To make your initiator use RDMA, make sure the "ib_iser" module is
loaded in your kernel. Then do discovery as usual, over TCP:
iscsiadm -m discovery -t sendtargets -p $targetip
where $targetip is the ethernet address of your IPoIB device.
Discovery traffic will use IPoIB, but login and full feature phase
will use RDMA natively.
Then do something like the following to change the transport type:
iscsiadm -m node -p $targetip -T $targetname --op update \
-n node.transport_name -v iser
Next, login as usual:
iscsiadm -m node -p $targetip -T $targetname --login
And access the new block device, e.g. /dev/sdb.
Note that separate iscsi and iser transports mean that you should
know which targets are configured as iser and which as iscsi/tcp.
If you try to login to a target configured as iser over tcp, this
will fail. And vice versa, trying to login to a target configured as
iscsi/tcp over iser will not succeed as well.
Because an iscsi target has no means for reporting its RDMA
capabilities you have to try to login over iser to every target
reported by SendTargets. If it fails and you still want to access
the target over tcp, then change the transport name back to "tcp"
and try to login again.
Some distributions include a script named "iscsi_discovery", which
accomplishes just this. If you wish to login either over iser or
over tcp:
iscsi_discovery $targetip -t iser
This will login changing transports if necessary. Then, if succesful,
it will logout leaving the target's record with the appropriate
transport setting.
If you are interested only in iser targets, then add "-f", forcing
the transport to be iser. Note also that the port can be specified
explicitely:
iscsi_discovery $targetip -p $targetport -t iser -f
This will cancel login retry over tcp in case of the initial failure.
4. Errata
4.1. Pre-2.6.21 mthca driver bug
There is a major bug in the mthca driver in linux kernels
before 2.6.21. This includes the popular rhel5 kernels, such as
2.6.18-8.1.6.el5 and possibly later. The critical commit is:
608d8268be392444f825b4fc8fc7c8b509627129 IB/mthca: Fix data
corruption after FMR unmap on Sinai
If you use single-port memfree cards, SCSI read operations will
frequently result in randomly corrupted memory, leading to bad
application data or unexplainable kernel crashes. Older kernels
are also missing some nice iSCSI changes that avoids crashes in some
situations where the target goes away. Stock kernel.org linux after
2.6.21 have been tested and are known to work.
4.2. Bidirectional commands
The Linux kernel iSER initiator is currently lacking support for
bidirectional transfers, and for extended command descriptors (CDBs).
Progress toward adding this is being made, with patches frequently
appearing on the relevant mailing lists.
4.3. ZBVA
The Linux kernel iSER initiator uses a different header structure on
its packets than is in the iSER specification. This is described in
an InfiniBand document and is required for that network, which only
supports for Zero-Based Virtual Addressing (ZBVA). If you are using
a non-IB initiator that doesn't need this header extension, it won't
work with tgtd. There may be some way to negotiate the header format.
Using iWARP hardware devices with the Linux kernel iSER initiator
also will not work due to its reliance on fast memory registration
(FMR), an InfiniBand-only feature.
4.4. MaxOutstandingUnexpectedPDUs
The current code sizes its per-connection resource consumption based
on negotiatied parameters. However, the Linux iSER initiator does
not support negotiation of MaxOutstandingUnexpectedPDUs, so that
value is hard-coded in the target.
4.5. TargetRecvDataSegmentLength
Also, open-iscsi is hard-coded with a very small value of
TargetRecvDataSegmentLength, so even though the target would be willing
to accept a larger size, it cannot. This may limit performance of
small transfers on high-speed networks: transfers bigger than 8 kB,
but not large enough to amortize a round-trip for RDMA setup.
4.6. Multiple devices
The iser code has been successfully tested with multiple Infiniband
devices.
4.7. SCSI command size
A single buffer per SCSI command limitation has another implication
(except the "RDMA-only mode", see above). The RDMA buffers pool is
currently created with buffers of 512KB each (see "Memory registration"
section above). This is suitable to work with most of the linux
initiators, which split all transfers into SCSI commands of up to
128KB, 256KB or 512KB (depending on the system). Initiators that issue
explicit SCSI commands with the size greater than 512KB will be unable
to work with the current iser implementation. Once multiple buffers
are supported by the backing stores this limitation can be eliminated
in a relatively simple manner.
tgt-1.0.80/doc/README.lu_configuration 0000664 0000000 0000000 00000011410 13747533545 0017406 0 ustar 00root root 0000000 0000000 Preface
--------------
This show a simple example to set up some logical units (lu).
Please refer to README.iscsi on instructions to create logical unit(s).
tgtadm options
--------------
You are able to modify some logical unit parameters as well as
modify behaviour of SCSI Sense op code.
NOTE: It is not recommended to change these parameters after the
target/logical unit has been enabled to accept initiators.
It is currently possible to change/modify the following:
Vendor Identification
Product Identification
Product Revision
Format of returned 'sense data'
Define if the lu is capable of supporting removable media
Define/set if the lu is online / offline.
Params are passed using the 'tgtadm' utility:
Format of options are:
vendor_id="string"
product_id="string"
product_rev="string"
removable=<0|1> - 0 = non-removable, 1 = removable media
sense_format=<0|1> - 0 = Clasic sense format, 1 = Support descriptor format
online=<0|1> - 0 == Unit offline, 1 == Unit Online
The options are passed to the logical unit via the "--params" switch to tgtadm
e.g.
tgtadm --lld iscsi --mode logicalunit --op update --tid --lun \
--params vendor_id=QUANTUM,product_id=HD100,product_rev=0010
tgtadm --lld iscsi --mode logicalunit --op update --tid --lun \
--params removable=1,sense_format=1,online=1
Or it can be performed in one go:
tgtadm --lld iscsi --mode logicalunit --op update --tid --lun \
--params vendor_id=QUANTUM,product_id=HD100,product_rev=0010,scsi_sn=FRED00,removable=1,sense_format=0,online=1
# sg_inq -v /dev/sg5
inquiry cdb: 12 00 00 00 24 00
standard INQUIRY:
inquiry cdb: 12 00 00 00 42 00
PQual=0 Device_type=0 RMB=1 version=0x05 [SPC-3]
[AERC=0] [TrmTsk=1] NormACA=0 HiSUP=0 Resp_data_format=2
SCCS=0 ACC=0 TGPS=0 3PC=0 Protect=0 BQue=0
EncServ=0 MultiP=0 [MChngr=0] [ACKREQQ=0] Addr16=0
[RelAdr=0] WBus16=0 Sync=0 Linked=0 [TranDis=0] CmdQue=1
Clocking=0x0 QAS=0 IUS=0
length=66 (0x42) Peripheral device type: disk
Vendor identification: QUANTUM
Product identification: HD100
Product revision level: 0010
inquiry cdb: 12 01 00 00 fc 00
inquiry: requested 252 bytes but got 7 bytes
inquiry cdb: 12 01 80 00 fc 00
inquiry: requested 252 bytes but got 12 bytes
Unit serial number: FRED00
As can be seen from above 'sg_inq' output, the RMB (removable) bit is set to 1.
The Unit serial number page updated with 'FRED00'
Mode Page Creation
------------------
Create mode page '2', subpage 0 and 14 bytes of data.
tgtadm --mode logicalunit --op update --tid 1 --lun 2 \
--params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0
Create mode page '3', subpage 0 and 22 bytes of data.
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \
--params mode_page=3:0:22:0:0:0:0:0:0:0:0:1:0:2:0:0:0:0:0:0:0:0:13:0:0
Create mode page '10', subpage 0 and 10 bytes of data.
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \
--params mode_page=10:0:10:2:0:0:0:0:0:0:0:2:0
Create mode page '0x1c', subpage 0 and 10 bytes of data.
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \
--params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0
SMC unique options
------------------
--params have several unique key=value pairs ontop of all other modules.
- element_type=<1|2|3|4>
- start_address=Number between 1 & 65535
- quantity=Number between 1 & 65535
- sides=1|2
- address=Number between 1 & 65535
- barcode="Char string up to 10 chars"
- tid=
- lun=
- media_home=/path/to/virtual/media
The 'barcode' of media is appended to this path specified in media_home and
it is this file which is used for the backing store when reading/writing to
DATA TRANSFER DEVICE at address 'tid=x lun=x'
Several of these parameters 'work together'
e.g. To add 'quantity' slots as 'element_type' starting at 'start_address'
- element_type=<1|2|3|4>
- start_address=Number between 1 & 65535
- quantity=Number between 1 & 65535
Note: start_address + quantity should not overlap with any other slots..
While 'configuring slot 'address' of 'element_type':
- Set barcode of meda (occupy slot)
- If element type is DATA TRANSFER DEVICE, then define TID & LUN of device.
- element_type=<1|2|3|4>
- address=Number between 1 & 65535
- barcode="String up to 10 chars"
- sides=<1|2>
- tid=
- lun=
It is the responsibility of the user not to configure overlapping slots
of differing types.
Slot types:
1 -> Medium Transport (picker arm)
2 -> Storage Element
3 -> Import/Export Element
4 -> Data Transfer device (CD drive, tape drive, MO drive etc)
Please refer to scripts/tgt-core-test for a working example.
tgt-1.0.80/doc/README.mmc 0000664 0000000 0000000 00000023076 13747533545 0014626 0 ustar 00root root 0000000 0000000 The MMC emulation in TGTD can emulate a DVD device capable of writing/burning
to emulated blank disks.
The emulation supports three types of behaviour (profiles) which are
automatically selected by TGTD at runtime.
These three types are :
NO_PROFILE : This is the behaviour used when the device is OFFLINE or when
there is no backing store file present. In this profile the emulation will
behave as if there is no media (disk) inserted in the drive tray.
DVD-ROM : This is used if the device is ONLINE and also if the backing store
file exists and contains data (i.e. the file size is >0). In this mode
TGTD will behave as if a read-only DVD-ROM disk is inserted in the tray.
DVD+R : This is used if the device is ONLINE and the backing store file exists
and is empty (i.e. the file size is == 0). In this mode the emulation
will behave as if there is a blank, writeable, DVD+R disk in the tray.
In this mode you can burn an ISO image onto the device.
Once the burning process has finished and the application issues the
CLOSE TRACK command to finalize the track, the emulation will morph
from DVD+R and into DVD-ROM so that subsequent access to the drive
will behave as if there is now a DVD-ROM disk in the tray.
Compatibility :
The MMC commandset for writeable media is extensive. This emulation currently
only implements a small subset of the full MMC specification.
In particular, during development the windows application DVD-Decrypter
was used to view which commands and subcommands were required to successfully
burn a disk.
This emulation supports all commands that that application uses when
accessing a DVD drive to identify and write to a blank disk.
Other applications may use a different subset of MMC to perform the same
operations in which case it may or may not work.
It is the intent that the emulation will be enhanced to provide compatibility
with all DVD burning applications on all important platforms.
The emulation is also compatible with Linux:dvdrecorder but you must
specify the command arguments -dao -ignsize -overburn
Example:
dvdrecord -dao -ignsize -overburn dev=/dev/sg3 ./IMAGE.iso
Please report incompatibilities with other burning applications other than
Windows/DVD-Decrypter to ronniesahlberg@gmail.com and I will try to
enhance the emulation to also support your application of choice.
I can however only test with Windows free-ware or linux applications.
#
# Example 1: How to create a MMC device that contains an empty writeable
# DVD+R disk which can be written to using DVD-Decrypter
#
# Create a target
tgtadm --lld iscsi --mode target --op new --tid 1 -T iqn.2007-03:virtual-dvd:`hostname`
# Create an empty file that will represent a writeable DVD+R disk
rm -rf /tmp/empty-disk.iso
tgtimg --op new --device-type cd --type dvd+r --file /tmp/empty-disk.iso
# Create LUN - CD/ROM
tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 1 -b /tmp/empty-disk.iso --device-type=cd
# Give it a nice name
SN=XYZ123
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 --params vendor_id=STGT_DVD,product_id=DVD101,product_rev=0010,scsi_sn=$SN,removable=1
# Allow ALL initiators to connect to this target
tgtadm --lld iscsi --mode target --op bind --tid 1 -I ALL
# Show all our good work.
tgtadm --lld iscsi --mode target --op show
#
# Example 2: How to mark the device "offline" meaning the device will show up
# as "no disk in tray"
#
tgtadm --tid 1 --lun 1 --op update --mode logicalunit --name online --value No
#
# Example 3: While the device is "offline" we can swap the disk in the tray,
# by just pointing the device to a different backing store file.
# In this example we just swap it to another blank DVD+R disk.
#
rm -rf /tmp/empty-disk2.iso
tgtimg --op new --device-type cd --type dvd+r --file /tmp/empty-disk2.iso
tgtadm --tid 1 --lun 1 --op update --mode logicalunit --name path --value /tmp/empty-disk2.iso
# note: when we updated the device to a new bakend storage file the device
# automatically goes online again with the new media present.
#
# Example 4: How to create a DVD jukebox with 8 disk trays
#
# Create a target
tgtadm --lld iscsi --mode target --op new --tid 1 -T iqn.2007-03:virtual-dvd:`hostname`
# Create a DVD drive and give it a nice name
# The dvd starts out without a backing store file, i.e. no disk loaded
tgtadm --op new --mode logicalunit --tid 1 --lun 1 -Y cd
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 --params vendor_id=STGT_DVD,product_id=DVD101,product_rev=0010,scsi_sn=STGTDVD01,removable=1
# We need a backend store file for the media changer
if [ ! -f $HOME/smc ]; then
dd if=/dev/zero of=$HOME/smc bs=1k count=1
fi
# Create the SMC device and give it a nice name
tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 2 -b $HOME/smc --device-type=changer
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params vendor_id=STK,product_id=L700,product_rev=0010,scsi_sn=XYZZY_0,removable=1
# Add a Data Transfer devices (1 drive)
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,start_address=1,quantity=1
# Specify that the DVD above (LUN 1) is the data transfer device we created
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,address=1,tid=1,lun=1
# Medium Transport Elements (robot arm / picker)
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=1,start_address=16,quantity=1
# define path to virtual media
VTL=${HOME}/vtl
mkdir -p ${VTL} 2>/dev/null
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params media_home=${VTL}
# Storage Elements - 8 starting at addr 1024
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,start_address=1024,quantity=8
# Add 'media' to slots 1 and 2 and leave the other 6 slots empty
# slot 1
tgtimg --op new --device-type cd --type dvd+r --file ${VTL}/DISK_001
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=1024,barcode=DISK_001,sides=1
# slot 2
tgtimg --op new --device-type cd --type dvd+r --file ${VTL}/DISK_002
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=1025,barcode=DISK_002,sides=1
# Allow ALL initiators to connect to this target
tgtadm --lld iscsi --mode target --op bind --tid 1 -I ALL
# Show all our good work.
tgtadm --lld iscsi --mode target --op show
Now you should be able to access the devices and try it out :
tgtadm --lld iscsi --mode target --op show
Target 1: iqn.2007-03:virtual-dvd:ronnie
System information:
Driver: iscsi
State: ready
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0 MB
Online: Yes
Removable media: No
Backing store: No backing store
LUN: 1
Type: cd/dvd
SCSI ID: deadbeaf1:1
SCSI SN: STGTDVD0
Size: 0 MB
Online: No
Removable media: Yes
Backing store: No backing store
LUN: 2
Type: changer
SCSI ID: deadbeaf1:2
SCSI SN: XYZZY_0
Size: 0 MB
Online: Yes
Removable media: Yes
Backing store: /root/smc
Account information:
ACL information:
ALL
You can use the 'mtx' command under linux to manage the jukebox (man mtx)
mtx -f /dev/sg4 status
Storage Changer /dev/sg4:1 Drives, 8 Slots ( 0 Import/Export )
Data Transfer Element 0:Empty
Storage Element 1:Full :VolumeTag=DISK_001
Storage Element 2:Full :VolumeTag=DISK_002
Storage Element 3:Empty:VolumeTag=
Storage Element 4:Empty:VolumeTag=
Storage Element 5:Empty:VolumeTag=
Storage Element 6:Empty:VolumeTag=
Storage Element 7:Empty:VolumeTag=
Storage Element 8:Empty:VolumeTag=
Now load the disk in slot 1 :
mtx -f /dev/sg4 load 1
mtx -f /dev/sg4 status
Storage Changer /dev/sg4:1 Drives, 8 Slots ( 0 Import/Export )
Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = DISK_00
Storage Element 1:Empty:VolumeTag=
Storage Element 2:Full :VolumeTag=DISK_002
Storage Element 3:Empty:VolumeTag=
Storage Element 4:Empty:VolumeTag=
Storage Element 5:Empty:VolumeTag=
Storage Element 6:Empty:VolumeTag=
Storage Element 7:Empty:VolumeTag=
Storage Element 8:Empty:VolumeTag=
And if we look at the devices again we see the DVD drive is now online and has
a disk loaded :
tgtadm --lld iscsi --mode target --op show
Target 1: iqn.2007-03:virtual-dvd:ronnie System information:
Driver: iscsi
State: ready
I_T nexus information:
I_T nexus: 1
Initiator: iqn.1993-08.org.debian:ronnie
Connection: 0
IP Address: 127.0.0.1
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0 MB
Online: Yes
Removable media: No
Backing store: No backing store
LUN: 1
Type: cd/dvd
SCSI ID: deadbeaf1:1
SCSI SN: STGTDVD0
Size: 0 MB
Online: Yes
Removable media: Yes
Backing store: /root/vtl/DISK_001
LUN: 2
Type: changer
SCSI ID: deadbeaf1:2
SCSI SN: XYZZY_0
Size: 0 MB
Online: Yes
Removable media: Yes
Backing store: /root/smc
Account information:
ACL information:
ALL
Have fun building big DVD jukeboxes !!!
tgt-1.0.80/doc/README.passthrough 0000664 0000000 0000000 00000015005 13747533545 0016412 0 ustar 00root root 0000000 0000000 Preface
-------------
This documents an example to set up tgt targets utilising existing SCSI
targets on the host utilizing tgt passthrough module and /dev/sg paths.
The hardware to be 'exported' via the SCSI target framework is an
IBM 3573-TL library with a single LTO4 drive.
Physical tape library connected and configured to host:
# lsscsi -g
[0:0:0:0] disk MAXTOR ATLAS10K4_36SCA DFM0 /dev/sda /dev/sg0
[0:0:6:0] process PE/PV 1x3 SCSI BP 1.1 - /dev/sg1
[2:0:1:0] tape IBM ULT3580-TD4 8192 /dev/st1 /dev/sg4
[2:0:1:1] mediumx IBM 3573-TL 6.50 - /dev/sg5
Please refer to the README.iscsi or README.iser for instructions
specific to setting up the transport layer specific section.
Many of the examples below are using the iSCSI lld (low Level) transport.
Starting the daemon
-------------
Please refer to 'Starting the daemon' in the README.iscsi or README.iser.
on instructions for correctly starting the daemon for your transport of choice.
Configuration
-------------
Everyting is configured via the tgtadm management tool.
Please refer to "Configuration" in README.iscsi or README.iser on how
to configure your target for the transport of choice.
Return to here for further instructions on setting up each logical unit using the
passthrough module.
An example to setup the target (target ID 1) for iSCSI
host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode target --tid 1 \
-T iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz
To display your target configuration:
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
Account information:
ACL information:
The controller device for management with lun 0 was created
automatically. You can't remove it.
Now it's time to add a logical unit to the target:
(The tape drive connected via /dev/sg4 - refer to 'lsscsi' output above)
The important flags are:
- Specify device type as passthruough
"--device-type=pt"
- Backing store type is the '/dev/sg' paths
"--bstype=sg"
- Backing store path to use
"-b /dev/sg4"
host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode logicalunit \
--tid 1 --lun 1 --bstype=sg --device-type=pt -b /dev/sg4
To display the current configuration:
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: (null)
SCSI ID: IET 00010002
SCSI SN: beaf12
Size: 0 MB
Online: Yes
Removable media: No
Backing store type: sg
Backing store path: /dev/sg4
Backing store flags:
Account information:
ACL information:
To add another logical unit to this target:
host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode logicalunit \
--tid 1 --lun 2 --bstype=sg --device-type=pt -b /dev/sg5
If you don't need to configure this target any more, enable the target
to accept any initiators:
host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL
Note "ACL information" section. "ALL" means that this target accepts
any initiators. The ACL feature also provides the access control based
on initiators' addresses.
For further instructions on ACL and account setup, please refer to the README.iscsi
As above, use the '--op show' option to display your current setup.
host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target
Target 1: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz
System information:
Driver: iscsi
Status: running
I_T nexus information:
LUN information:
LUN: 0
Type: controller
SCSI ID: deadbeaf1:0
SCSI SN: beaf10
Size: 0
Backing store: No backing store
LUN: 1
Type: (null)
SCSI ID: IET 00010002
SCSI SN: beaf12
Size: 0 MB
Online: Yes
Removable media: No
Backing store type: sg
Backing store path: /dev/sg4
Backing store flags:
LUN: 2
Type: (null)
SCSI ID: IET 00010002
SCSI SN: beaf12
Size: 0 MB
Online: Yes
Removable media: No
Backing store type: sg
Backing store path: /dev/sg5
Backing store flags:
Account information:
ACL information:
ALL
iSCSI Initiator Information
-------------
After the target accepts initiators, the system information would be
something like the following:
Linux open-iscsi initiator
hostb:~ # iscsiadm -m discovery -t sendtargets -p 10.251.60.20
10.251.60.20:3260,1 iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz
hostb:~ # iscsiadm -m node -T iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz \
-p 10.251.60.20 --login
Logging in to [iface: default, target: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz, portal: 10.251.60.20,3260]
Login to [iface: default, target: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz, portal: 10.251.60.20,3260]: successful
hostb:~ # lsscsi -g
[0:0:0:0] cd/dvd TSSTcorp DVD-ROM TS-L333A D100 /dev/sr0 /dev/sg0
[2:0:0:0] disk SEAGATE ST3400755SS NS25 /dev/sda /dev/sg1
[2:0:1:0] disk SEAGATE ST3400755SS NS25 /dev/sdb /dev/sg2
[3:0:0:0] disk DGC DISK 0429 /dev/sdc /dev/sg3
[3:0:1:0] disk DGC DISK 0429 /dev/sdd /dev/sg4
[11:0:0:0] storage IET Controller 0001 - /dev/sg5
[11:0:0:1] tape IBM ULT3580-TD4 8192 /dev/st0 /dev/sg6
[11:0:0:2] mediumx IBM 3573-TL 6.50 - /dev/sg7
Shutdown the target cleanly
---------------------------
host:~/tgt# tgtadm --op unbind --mode target --tid 1 -I ALL
host:~/tgt# tgtadm --op delete --mode conn --tid 1
host:~/tgt# tgtadm --op delete --mode target --tid 1
host:~/tgt# tgtadm --op delete --mode system
tgt-1.0.80/doc/README.rbd 0000664 0000000 0000000 00000004507 13747533545 0014617 0 ustar 00root root 0000000 0000000 The 'rbd' backing-store driver provides access to Ceph (http://ceph.com)
'RADOS block devices (rbd)" through tgtd.
Ceph is a distributed storage system that provides clustered redundant
object storage; one of the ways to use such storage is through the
abstraction of a "block device". rbd devices can be mapped through a
kernel driver and show up as block devices directly, or they can be used
through userland libraries to provide backing store for things like QEMU
virtual disks.
The bs_rbd backing-store driver allows tgtd to use rbd images as backing
storage without requiring the 'rbd' kernel block device driver, and so
avoids kernel version dependencies and the extra context switching between
kernel and userland. It also allows for userland caching of block images,
managed by librbd.
To use bs_rbd, select --bstype rbd, and set --backing-store to an rbd
image specifier. This is of the form
[poolname/]imagename[@snapname]
where poolname is the RADOS pool containing the rbd image (defaults to
"rbd"), imagename is the image you want to export, and snapname is the
optional (readonly) snapshot of the image imagename. (Obviously when
a snapshot is exported, writes are not supported.)
You may also want to use the --bsopts parameter to pass Ceph-specific
parameters to bs_rbd. Accepted keywords are
conf=
This sets the path to the ceph.conf file for the cluster that
stgt should connect to for this target/lun creation (luns may
use different cluster connections).
id=
This sets the id portion of the ceph "client name" (which is
"type.id"). Type is fixed at "client", and the default name is
"client.admin", but you probably want this to set id to
something like "tgt" so that the name of the ceph client is
"client.tgt". This lets you control permissions differently
for the tgt client compared to others, and sets the default log path, etc.
See the Ceph documentation regarding client names.
cluster=
This sets the Ceph cluster name, if you have multiple clusters or
if your cluster name is anything other than "ceph".
This is in turn used by librados to find the conf file and key files.
To specify multiple options, separate them with ';', and since you are,
make sure to quote the option string to protect the semicolon from
the shell:
--bsopts "conf=/usr/local/ceph/ceph.conf;id=tgt"
tgt-1.0.80/doc/README.sbcjukebox 0000775 0000000 0000000 00000010201 13747533545 0016176 0 ustar 00root root 0000000 0000000 #!/bin/sh
# EXAMPLE of sbc jukebox
#
# This is a simple script to create and drive a sbcjukebox that contains
# the $NUMSLOT most recent snapshots of a production SBC LUN.
# It requires that you store the LUN files on a filesystem that can create
# file level cow snapshots using "cp --reflink".
# On linux this is supported on BTRFS and possibly other filesystems too.
#
# It creates a new target with one SBC decice and a mediachanger.
#
#
# TID is the target id to use
# TNAME is the target iqn name to use
# FILE is the backing store file for a production LUN. This file must reside on
# a cow snapshot capable filesystem.
# SNAPDIR is where we keep the SMC media file as well as where all snapshots are
# kept. This must be the same filesystem as FILE
# NUMSLOTS is how many old snapshots we want to keep.
# READONLYSNAPS is whether or not the snapshot device is readonly or not
# sbcjukebox.sh start
# to create the target and populate the storage elements.
#
# sbcjukebox.sh list
# to show which snapshots are available
#
# sbcjukebox.sh snapshot
# to take a new snapshot of the production LUN
#
TID=2
TNAME="iqn.ronnie.test-snapshot"
FILE="/data/RHEL6/RHEL6-1.img"
SNAPDIR="${FILE}.snapshots"
NUMSLOTS=8
READONLYSNAPS="yes"
[ -d "$SNAPDIR" ] || mkdir "$SNAPDIR"
usage() {
echo "sbcjukebox {start|list|snapshot|usage}"
}
prune_old_snapshots() {
ls $SNAPDIR | egrep -v "^smc$" | sort -n -r | tail -n +${NUMSLOTS} | while read OLD; do
echo "Deleting old snapshot $OLD"
rm -f ${SNAPDIR}/${OLD}
done
}
populate_elements() {
ELEMENT=1024
SNAPSHOTS=`ls $SNAPDIR | egrep -v "^smc$" | sort -n | head -n ${NUMSLOTS}`
for SNAP in $SNAPSHOTS; do
tgtadm --mode logicalunit --op update --tid $TID --lun 2 --params element_type=2,address=$ELEMENT,barcode=$SNAP,sides=1
ELEMENT=`expr "$ELEMENT" "+" "1"`
done
LAST=`expr "1024" "+" "$NUMSLOTS"`
while [ $ELEMENT -lt $LAST ]; do
tgtadm --mode logicalunit --op update --tid $TID --lun 2 --params element_type=2,address=$ELEMENT,clear_slot=1
ELEMENT=`expr "$ELEMENT" "+" "1"`
done
tgtadm --mode logicalunit --op update --tid $TID --lun 2 --params element_type=2,address=`expr "$LAST" "-" "1"`,clear_slot=1
}
snapshot() {
SNAP=`date +"%y%m%d%H%M"`
echo Create snapshot $SNAP
cp --reflink $FILE $SNAPDIR/$SNAP
prune_old_snapshots
populate_elements
}
list() {
echo Snapshots:
ls $SNAPDIR | egrep -v "^smc$"
}
start() {
#create a target
tgtadm --lld iscsi --op new --mode target --tid $TID -T $TNAME
# create the data transfer device
tgtadm --op new --mode logicalunit --tid $TID --lun 1 -Y disk
tgtadm --op update --mode logicalunit --tid $TID --lun 1 --params vendor_id=STGT,product_id=SNAPSHOT_SBC,product_rev=0010
[ "$READONLYSNAPS" != "yes" ] || {
tgtadm --op update --mode logicalunit --tid $TID --lun 1 --params readonly=1
}
# create backing store for SMC
[ -f "${SNAPDIR}/smc" ] || dd if=/dev/zero of="${SNAPDIR}/smc" bs=1k count=1
# create the media changer
tgtadm --op new --mode logicalunit --tid $TID --lun 2 -b "${SNAPDIR}/smc" --device-type=changer
tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params vendor_id=STGT,product_id=SNAPSHOT_SMC,product_rev=0010,removable=1
# Add a Data Transfer devices (1 drive)
tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params element_type=4,start_address=1,quantity=1
# Specify that the DISK above (LUN 1) is the data transfer device
tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params element_type=4,address=1,tid=$TID,lun=1
# Medium Transport Elements (robot arm / picker)
tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params element_type=1,start_address=16,quantity=1
# define path to virtual media
tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params media_home=${SNAPDIR}
# create the storage elements
tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params element_type=2,start_address=1024,quantity=$NUMSLOTS
prune_old_snapshots
populate_elements
tgtadm --lld iscsi --op bind --mode target --tid $TID -I ALL
tgtadm --lld iscsi --op show --mode target
}
case "$1" in
start)
start
;;
snapshot)
snapshot
;;
list)
list
;;
*)
usage
;;
esac
tgt-1.0.80/doc/README.sheepdog 0000664 0000000 0000000 00000007577 13747533545 0015660 0 ustar 00root root 0000000 0000000 Sheepdog is a distributed storage system for QEMU, iSCSI clients and
RESTful services. It provides highly available block level storage
volumes that can be attached to QEMU-based virtual machines. The
volumes can also be attached to other virtual machines and operating
systems run on baremetal hardware if they support iSCSI
protocol. Sheepdog scales to several hundreds nodes, and supports
advanced volume management features such as snapshot, cloning, and
thin provisioning. Stuff like volumes, snapshots, QEMU's vm-state
(from live snapshot), even ISO files can be stored in the Sheepdog
cluster.
With tgt, sheepdog's virtual disks can be used by iSCSI
initiators. Below is a brief description of setup.
1. Install and launch tgt
$ git clone https://github.com/fujita/tgt.git
$ cd tgt
$ make
# make install
2. Create a sheepdog VDI
There is no special procedures in this step.
$ dog vdi create tgt0 100G
Of course you can use existing VDIs as iSCSI logical units.
3. Setup iSCSI target provided by tgt
One logical unit corresponds to one VDI of sheepdog. In this step, we
create iSCSI target and logical unit which can be seen by iSCSI
initiator.
# tgtd
# tgtadm --op new --mode target --tid 1 --lld iscsi --targetname iqn.2013-10.org.sheepdog-project
# tgtadm --op new --mode lu --tid 1 --lun 2 --bstype sheepdog --backing-store unix:/sheep_store/sock:tgt0
# tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL
The most important parameter is --backing-store which is required by
tgtadm when we create the logical unit in the target (the third line
of the above commands). With this parameter, we tell the tgtd
process how to connect to the sheep process, which VDI we use as the
logical unit, which snapshot ID of the VDI we use. The form of the
--backing-store option is like this:
unix:path_of_unix_domain_socket:vdi
unix:path_of_unix_domain_socket:vdi:tag
unix:path_of_unix_domain_socket:vdi:snapid
tcp:host:port:vdi
tcp:host:port:vdi:tag
tcp:host:port:vdi:snapid
We use a special case of the first one in the above command
sequence. It means "connect to sheep process via unix domain socket
protocol, path of unix domain socket is /sheep_store/sock, use VDI
named tgt0 as a backing store of newly created logical unit".
You can also specify sheep which is running on remote host via
TCP. But if tgtd process is running on a same host of sheep process,
there is no benefit of using TCP. Unix domain socket can provide
faster connection than TCP.
You can also specify tag or ID of snapshots via suffix of the
--backing-store option.
4. Setup iSCSI session (example of the open-iscsi initiator on Linux)
After setting up iSCSI target, you can use the VDIs from any virtual
machines and operating systems which supports iSCSI initiator. Many of
popular hypervisors and operating systems support it (e.g. VMware ESX
Family, Linux, Windows, etc). In this example, the way of Linux +
open-iscsi is described.
At first, you have to install open-iscsi ( http://www.open-iscsi.org/
) and launch it. Major linux distros provide their open-iscsi
package. Below is a way of installation in Debian and Ubuntu based
systems.
# apt-get install open-iscsi
# /etc/init.d/open-iscsi start
Next, we need to let iscsid discover and login to the target we've
already created in the above sequence. If the initiator is running
on different host from the target, you have to change the IP
addresses in the below commands.
# iscsiadm -m discovery -t st -p 127.0.0.1
# iscsiadm -m node --targetname iqn.2013-10.org.sheepdog-project --portal 127.0.0.1:3260 --login
New device files, e.g. /dev/sdb, will be created on your system after
login completion. Now your system can use the speepdog VDIs like
ordinal HDDs.
ToDo
Currently, iSCSI multipath is not supported by sheepdog. It will be
implemented in v0.9.0.
The latest version of this guide can be found here:
https://github.com/sheepdog/sheepdog/wiki/General-protocol-support-%28iSCSI-and-NBD%29
tgt-1.0.80/doc/README.ssc 0000664 0000000 0000000 00000011226 13747533545 0014634 0 ustar 00root root 0000000 0000000 Introduction
-------------
The SSC emulation in TGTD can emulate tape drives by using files. This
virtualization technology is known as a virtual tape library (VTL).
Solaris tips
-------------
If one wants to get VTL working on a Solaris initiator (and this
target of course under Linux) then it is rather simple to get a tape
drive working. Getting the changer device working under Solaris
(OpenSolaris) take a bit more effort. This file describes the way to
do it.
Although most of it is probably well know to Solaris fans, it took me
a bit of time to figure out to get the VTL target working fine under
OpenSolaris. For those who want to give it a try, here are the
commands:
For normal disk and cd targets no files have to be changed. All you
need is the following:
# svcadm enable iscsi_initiator
and check if it is online:
# svcs iscsi_initiator
the state should be online.
Now we add the discovery address to the initiator:
# iscsiadm add discovery-address
Now we configure the discovery mode to sendtargets:
# iscsiadm modify discovery -t enable
Check the target:
# iscsiadm list target -S
And create the device files:
# devfsadm -i iscsi
Tape devices will be shown properly, but not the changer device, this
takes a bit more work. Two files need to be changed, and a reboot is
needed to set things up properly.
In the file /etc/driver_aliases the two lines
sgen "scsa,08.bfcp"
sgen "scsa,08.bvhci"
need to be replaced with
sgen "scsiclass,08"
The file /kernel/drv/sgen.conf, which is basically all commented out
needs to have the following lines:
device-type-config-list="changer","sequential";
inquiry-config-list= "*", "*";
name="sgen" class="scsi" target=0 lun=4;
name="sgen" class="scsi" target=1 lun=4;
name="sgen" class="scsi" target=2 lun=4;
name="sgen" class="scsi" target=3 lun=4;
name="sgen" class="scsi" target=4 lun=4;
name="sgen" class="scsi" target=5 lun=4;
name="sgen" class="scsi" target=6 lun=4;
name="sgen" class="scsi" target=7 lun=4;
name="sgen" class="scsi" target=8 lun=4;
name="sgen" class="scsi" target=9 lun=4;
name="sgen" class="scsi" target=10 lun=4;
name="sgen" class="scsi" target=11 lun=4;
name="sgen" class="scsi" target=12 lun=4;
name="sgen" class="scsi" target=13 lun=4;
name="sgen" class="scsi" target=14 lun=4;
name="sgen" class="scsi" target=15 lun=4;
For the last bit it should be noted that my changer was set to lun 4,
and since I don't know which target I put all target numbers in (0-15).
After these modifications the best is to reboot the machine, and do
the iscsi commands above, and he presto it works:
In my case, three tape drives and one changer:
root@solar:/kernel/drv# iscsiadm list target -S
Target: iqn.2008-09.com.example:server.tape
Alias: -
TPGT: 1
ISID: 4000002a0000
Connections: 1
LUN: 4
Vendor: STK
Product: L700
OS Device Name: /dev/scsi/changer/c0t0d0
LUN: 3
Vendor: HP
Product: LTO3 ULTRIUM
OS Device Name: /dev/rmt/2n
LUN: 2
Vendor: HP
Product: LTO3 ULTRIUM
OS Device Name: /dev/rmt/1n
LUN: 1
Vendor: HP
Product: LTO3 ULTRIUM
OS Device Name: /dev/rmt/0n
root@solar:/kernel/drv# mtx -f /dev/scsi/changer/c0t0d0 status
Storage Changer /dev/scsi/changer/c0t0d0:3 Drives, 29 Slots ( 5 Import/Export )
Data Transfer Element 0:Empty
Data Transfer Element 1:Empty
Data Transfer Element 2:Empty
Storage Element 1:Full :VolumeTag=A0000001
Storage Element 2:Full :VolumeTag=A0000002
Storage Element 3:Full :VolumeTag=A0000003
Storage Element 4:Full :VolumeTag=A0000004
Storage Element 5:Full :VolumeTag=A0000005
Storage Element 6:Full :VolumeTag=A0000006
Storage Element 7:Full :VolumeTag=A0000007
Storage Element 8:Full :VolumeTag=A0000008
Storage Element 9:Empty:VolumeTag=
Storage Element 10:Empty:VolumeTag=
Storage Element 11:Empty:VolumeTag=
Storage Element 12:Empty:VolumeTag=
Storage Element 13:Empty:VolumeTag=
Storage Element 14:Empty:VolumeTag=
Storage Element 15:Empty:VolumeTag=
Storage Element 16:Empty:VolumeTag=
Storage Element 17:Empty:VolumeTag=
Storage Element 18:Empty:VolumeTag=
Storage Element 19:Empty:VolumeTag=
Storage Element 20:Empty:VolumeTag=
Storage Element 21:Empty:VolumeTag=
Storage Element 22:Empty:VolumeTag=
Storage Element 23:Empty:VolumeTag=
Storage Element 24:Full :VolumeTag=CLN00001
Storage Element 25 IMPORT/EXPORT:Empty:VolumeTag=
Storage Element 26 IMPORT/EXPORT:Empty:VolumeTag=
Storage Element 27 IMPORT/EXPORT:Empty:VolumeTag=
Storage Element 28 IMPORT/EXPORT:Empty:VolumeTag=
Storage Element 29 IMPORT/EXPORT:Empty:VolumeTag=
tgt-1.0.80/doc/README.vtl 0000775 0000000 0000000 00000007233 13747533545 0014657 0 ustar 00root root 0000000 0000000 #!/bin/sh
# This is a simple script to aid in setting up a virtual tape library
# (VTL) using STGT
#
# You can tweak the settings for how many tape drives and how many
# and how big tapes the library should have using the variables below
# Connection number. You can use multiple concurrent TGTD instances as
# long as you give them unique CN numbers and they listen on different
# portals
CN=1
# Listen to port 3260 on wildcard for both ipv4 and ipv6
PORTAL=:3260
# iSCSI name for this target
TARGET=iqn.2012-08.ronnie.vtl
# Number of TAPE Drives for this virtual tape library
NUM_DRIVES=2
# Number of tapes
NUM_TAPES=8
# Tape size in MB
TAPE_SIZE=1024
# Directory where the tape library state and media will be stored
VTLDIR=$HOME/vtl
# Create the directory to store the tapes
if [ ! -d $VTLDIR ]; then
echo Creating directory for VTL : $VTLDIR
mkdir -p $VTLDIR
fi
# make sure we have a backing store for the media changer
if [ ! -f $VTLDIR/smc ]; then
echo Creating a backing store for the media changer : $VTLDIR/smc
dd if=/dev/zero of=$VTLDIR/smc bs=1k count=1 2>/dev/null
fi
# Create the tapes
seq 1 $NUM_TAPES | while read IDX; do
TAPE=`echo $IDX | awk '{printf "%08d", $1}'`
if [ ! -f $VTLDIR/$TAPE ]; then
echo Creating blank tape : $TAPE
tgtimg --op new --device-type tape --barcode $TAPE --size $TAPE_SIZE --type data --file $VTLDIR/$TAPE
fi
done
# Restart tgtd
echo Restart TGTD
tgt-admin -C $CN --update ALL -c /dev/null -f
tgtadm -C $CN --op delete --mode system
tgtd -C $CN --iscsi portal=$PORTAL
sleep 1
echo Create target
tgtadm -C $CN --lld iscsi --op new --mode target --tid 1 -T $TARGET
# Create the tape drives
echo Create tape drives
seq 1 $NUM_DRIVES | while read IDX; do
TAPE=`echo $IDX | awk '{printf "%08d", $1}'`
echo Create tape drive $IDX
tgtadm -C $CN --lld iscsi --mode logicalunit --op new --tid 1 --lun $IDX -b $VTLDIR/$TAPE --device-type=tape
tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $IDX --params online=0
done
# Create the changer
echo Create media changer device
LUN=`expr "$NUM_DRIVES" "+" "1"`
tgtadm -C $CN --lld iscsi --mode logicalunit --op new --tid 1 --lun $LUN -b $VTLDIR/smc --device-type=changer
tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params media_home=$VTLDIR
# Add data transfer devices (one for each tape drive)
echo Adding data transfer devices
tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=4,start_address=1,quantity=$NUM_DRIVES
seq 1 $NUM_DRIVES | while read IDX; do
echo Adding data transfer device for drive $IDX
tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=4,address=$IDX,tid=1,lun=$IDX
done
# Add medium transport elements (robot arm / picker)
echo Adding medium transport elements
tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=1,start_address=$LUN,quantity=1
# Storage elements
echo Adding storage elements
STORAGE=`expr "$LUN" "+" "1"`
tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=2,start_address=$STORAGE,quantity=$NUM_TAPES
# Add media to the storage slots
seq 1 $NUM_TAPES | while read IDX; do
TAPE=`echo $IDX | awk '{printf "%08d", $1}'`
echo Loading $TAPE into storage slot $STORAGE
tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=2,address=$STORAGE,barcode=$TAPE,sides=1
STORAGE=`expr "$STORAGE" "+" "1"`
done
# Allow anyone to access this target and the VTL
tgtadm -C $CN --lld iscsi --op bind --mode target --tid 1 -I ALL
# show all our good work
tgtadm -C $CN --lld iscsi --op show --mode target
tgt-1.0.80/doc/targets.conf.5.xml 0000664 0000000 0000000 00000032347 13747533545 0016456 0 ustar 00root root 0000000 0000000
targets.conf
5
targets.conf
Linux SCSI Target Configuration File
DESCRIPTION
tgt-admin uses /etc/tgt/targets.conf to initialize tgtd
configuration, by default. Its layout uses a HTML-like structure, with a
hierarchy of nested tags to define targets and LUNs.
CONFIGURATION FILE SYNTAX
Global directives configure options that are
global to tgtd's configuration, as well as defining exported
targets, via target sections.
Target sections contain directives specific to one target
device. They define each target's exported LUNs via "backing-store" and
"direct-store" directives, as well as other target-wide options.
LUN directives ("backing-store" and "direct-store") may contain
options specific to a single exported logical unit.
GLOBAL DIRECTIVES
With the exception of target directives, each of these should occur at most once.
Defines a the start of a target definition. IQN is an ISCSI Qualified
Name such as "iqn.2001-04.com.example:storage1".
Within this block should be target-level directives, as
documented below.
The target definition ends with "</target>"
Instead of specifying a driver type for each target,
default-driver defines a default low-level driver for all
exported targets. It may be overriden on a per-target
basis.
Valid lld values are "iscsi" or
"iser". Since iscsi is assumed if this directive
is not present, it is only generally needed if iser
is the desired default.
Include the configuration from another configuration
file. Wildcards are allowed, so an example like "include
/etc/tgt/xen/*.conf" is allowed.
Errors from tgtadm will be ignored. Default is no.
Define a different local socket key for communicating with
tgtd. Defaults to 0. This is only generally useful if
multiple instances of tgtd are in use.
Define the address of the iSNS server in IPv4 dotted-quad
address format.
Required when using iSNS, otherwise ignored.
Set iSNS access control. Valid values are "On" or "Off".
Required when using iSNS, otherwise ignored.
Set a different iSNS server port. Default is 3205.
Optional when using iSNS, otherwise ignored.
Enable iSNS. Only valid value is "On". Default is off.
Define iscsi incoming discovery authentication setting. If no
value is given, no authentication is performed.
Define iscsi outgoing discovery authentication setting. If no
value is given, no authentication is performed.
TARGET-LEVEL DIRECTIVES
Each target may export multiple block devices, or logical units
(LUNs). For the purposes of LUN numbering, backing-store
directives are processed before direct-store directives.
Defines a logical unit (LUN) exported by the
target. This may specify either a regular file, or a block
device.
Defines a direct mapped logical unit (LUN) with the same properties
as the physical device (such as VENDOR_ID, SERIAL_NUM, etc.)
Define the low-level driver to use for this target, either
"iscsi" or "iser" (without quotes). This overrides the
"default-driver" global directive.
Allows connections only from the specified IP
address. Defaults to ALL if no initiator-address directive is specified.
Allows connections only from the specified initiator name.
Define iscsi incoming authentication setting. If no
"incominguser" is specified, it is not used. This
directive may be used multiple times per target.
Define iscsi outgoing authentication setting. If no
"outgoinguser" is specified, it is not used. This
directive may be used multiple times per target.
Define the tid of the controller. Default is next
available integer.
LUN-LEVEL DIRECTIVES
All of these may be listed at the target level (and apply to all
LUNs) or within an individual LUN's definition, if it is defined
using the container-style, multi-line definition, rather than
single-line definition format:
<backing-store /dev/sdb1>
# LUN directives go here
</backing-store>
<direct-store /dev/sdb1>
# LUN directives go here
</direct-store>
It is recommended to use either single-line or container-style
LUN definitions within a target. Mixing styles can cause parser
errors.
"on" or "off", default on.
Specify the block size for this LUN.
Specify the Logical blocks per physical block
exponent. By default TGTD will set the lbppbe to automatically
match the underlying filesystem. Use this parameter to override
that setting.
This is an internal option that should not be
set directly.
Specify the lowest aligned logical block address.
This is an internal option that should not be set directly.
Specify the optimal transfer granularity, to be reflected
in the Block Limits VPD.
This is an internal option that should not be set directly.
Specify the optimal transfer length, to be reflected
in the Block Limits VPD.
This is an internal option that should not be set directly.
Pass additional parameters to tgtadm.
CONFIGURATION FILE EXAMPLE
Example configuration file:
<target iqn.2007-04.com.example:san.monitoring>
backing-store /dev/san/monitoring
# if no "incominguser" is specified, it is not used
incominguser backup secretpass12
# defaults to ALL if no "initiator-address" is specified
initiator-address 192.168.1.2
</target>
<target iqn.2007-02.com.example:san.xen1>
backing-store /dev/san/xen1-disk1 # LUN1
direct-store /dev/san/xen1-disk2 # LUN2
initiator-address 192.168.1.2 # Allowed IP
initiator-address 192.168.5.6 # Allowed IP
incominguser user1 secretpass12
incominguser user2 secretpass23
outgoinguser userA secretpassA
</target>
<target iqn.2007-02.com.example:san.xen2>
backing-store /dev/san/xen2
</target>
<target iqn.2007-06.com.example:san.vmware1>
backing-store /dev/san/vmware1
</target>
FILES
Configuration file for tgt-admin.
Example configuration file for tgt-admin.
SEE ALSO
tgtd(8), tgtadm(8), tgtimg(8), tgt-setup-lun(8).
REPORTING BUGS
Report bugs to <stgt@vger.kernel.org>
tgt-1.0.80/doc/tgt-admin.8.xml 0000664 0000000 0000000 00000016214 13747533545 0015743 0 ustar 00root root 0000000 0000000
tgt-admin
8
tgt-admin
Linux SCSI Target Configuration Tool
tgt-admin
-e --execute
--delete <value>
--offline <value>
--ready <value>
-s --show
-C --control-port <NNNN>
-c --conf <file>
--ignore-errors
-f --force
-p --pretend
--dump
-v --verbose
-h --help
DESCRIPTION
tgt-admin is a utility which allows a persistent configuration of targets and luns.
It uses tgtadm commands to create, delete and show targets.
OPTIONS
Read /etc/tgt/targets.conf and execute tgtadm commands.
If the target already exists, it won't be processed.
See --update.
Delete all or selected targets.
The target will be deleted only if it's not used (no initiator
is connected to it).
If you want to delete targets which are in use, you have to add
the "--force" flag.
Example usage:
--delete ALL - delete all targets
--delete tid=4 - delete target 4
(target with tid 4)
--delete iqn.2008-08.com.example:some.target
- delete this target
Put all or selected targets in offline state.
Example usage:
--offline ALL - offline all targets
--offline tid=4 - offline target 4
(target with tid 4)
--offline iqn.2008-08.com.example:some.target
- offline this target
Put all or selected targets in ready state.
Example usage:
--ready ALL - ready all targets
--ready tid=4 - ready target 4
(target with tid 4)
--ready iqn.2008-08.com.example:some.target
- ready this target
Update all or selected targets.
The target will be updated only if it's not used (no initiator
is connected to it).
If you want to update targets which are in use, you have to add
"--force" flag.
Example usage:
--update ALL - ready all targets
--update tid=4 - ready target 4
(target with tid 4)
--update iqn.2008-08.com.example:some.target
- update this target
Show all the targets.
It is possible to run multiple concurrent instances of
tgtd on a host. This argument is used to control which
instance the tgt-admin command will operate on.
Specify an alternative configuration file instead of
/etc/tgt/targets.conf, which is the default.
Continue even if tgtadm exits with non-zero code.
Force some operations even if the target is in use.
Only print tgtadm options which would be used; don't execute any
actions.
Assumes -v.
Dump current tgtd configuration. Note: does not include detailed
parameters, like write caching.
Increase verbosity (show tgtadm commands used).
Display a list of available options and exit.
FILES
Configuration file for tgt-admin.
Example configuration file for tgt-admin.
SEE ALSO
targets.conf(5), tgtd(8), tgtadm(8), tgtimg(8), tgt-setup-lun(8).
REPORTING BUGS
Report bugs to <stgt@vger.kernel.org>
tgt-1.0.80/doc/tgt-setup-lun.8.xml 0000664 0000000 0000000 00000005535 13747533545 0016613 0 ustar 00root root 0000000 0000000
tgt-setup-lun
8
tgt-setup-lun
Helper script that creates a target, adds a device to the target, and defines
initiators that can connect to the target
tgt-setup-lun
-d <device>
-n <targetname>
-b <bsname>
-t <transport>
-C <control_port>
initiator_IP
-h
DESCRIPTION
Creates a target according to the supplied target_name. tgtd must
be running. The format of the default IQN generated from the target name is:
"iqn.2001-04.com.<hostname>-<target_name>", or a
complete IQN may be given.
The target name must be unique.
The script then adds the requested device to the target. If specific IP
addresses are defined, it adds them to the list of allowed initiators
for that target. If no IP addresses is defined, it defines that the
target accepts any initiator.
EXAMPLES
Create a target that uses /dev/sdb1 and allows connections only from
192.168.10.81:
tgt-setup-lun -d /dev/sdb1 -n my_target 192.168.10.81
Create a target that uses /dev/sdb1 and allows connections only from
192.168.10.81 and 192.168.10.82:
tgt-setup-lun -d /dev/sdb1 -n my_target 192.168.10.81 192.168.10.82
Create a target that uses /dev/sdb1 and allows connections from any
initiator:
tgt-setup-lun -d /dev/sdb1 -n my_target
Display help:
tgt-setup-lun -h
AUTHOR
Written by Erez Zilber
SEE ALSO
targets.conf(5), tgtd(8), tgtadm(8), tgtimg(8), tgt-admin(8).
REPORTING BUGS
Report bugs to <stgt@vger.kernel.org>
COPYRIGHT
Copyright © Voltaire Ltd. 2008.
tgt-1.0.80/doc/tgtadm.8.xml 0000664 0000000 0000000 00000076476 13747533545 0015357 0 ustar 00root root 0000000 0000000
tgtadm
8
tgtadm
Linux SCSI Target Administration Utility
tgtadm [OPTIONS]...
-C --control-port <port>
-L --lld <driver>
-o --op <operation>
-m --mode <mode>
-t --tid <id>
-T --targetname <targetname>
-y --blocksize <size>
-Y --device-type <type>
-l --lun <lun>
-b --backing-store <path>
-f --bsoflags {direct|sync}
-S --bsopts {backing-store opt string}
-E --bstype <type>
-I --initiator-address <address>
-Q --initiator-name <name>
-n --name <parameter>
-v --value <value>
-P --params <param=value[,param=value...]>
-F --force
-h --help
DESCRIPTION
tgtadm is used to monitor and modify everything about Linux SCSI target
software: targets, volumes, etc.
OPTIONS
It is possible to run multiple concurrent instances of tgtd
on a host. This argument is used to control which instance
the tgtadm command will operate on.
Block devices are created with a default block size of 512 bytes.
This argument can be used to create block devices with different
block sizes.
Example:
tgtadm --lld iscsi --mode logicalunit --op new \
--tid <TID> --lun <LUN> \
-b <backing-file> --blocksize=4096
When creating a LUN, this parameter specifies the type of device
to create. Default is disk.
Possible device-types are:
disk : emulate a disk device
tape : emulate a tape reader
ssc : same as tape
cd : emulate a DVD drive
changer : emulate a media changer device
pt : passthrough type to export a /dev/sg device
When creating a LUN, this parameter specifies the type of backend storage
to to use.
Possible backend types are:
rdwr : Use normal file I/O. This is the default for disk devices
aio : Use Asynchronous I/O
rbd : Use Ceph's distributed-storage RADOS Block Device
sg : Special backend type for passthrough devices
ssc : Special backend type for tape emulation
Add a new target with <id> and <name>.
Delete specific target with <id>. The target must have no active I_T nexus.
Delete specific target forcibly with <id>.
Show all the targets.
Show target parameters of a target with <id>.
Add a new logical unit with <lun> to specific target with <id>. The
logical unit is offered to the initiators. <path> must be block device
files (including LVM and RAID devices) or regular files, or an RBD image or
snapshot name for --bstype rbd. lun0 is reserved for a special device
automatically created.
Example:
If tgt is compiled with the bs_rbd backing store for
Ceph RBD images (see tgtadm --mode system --op show to
verify), set up a target mapping the rbd image named
"rbdimage", and pass options to bs_rbd:
tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \
--lun 1 --bstype=rbd --backing-store=rbdimage \
--bsopts="conf=/etc/ceph/ceph.conf;id=tgt"
Delete specific logical unit with <lun> that the target with <id> has.
Add the address to the access lists of the target with <id>. Initiators
with the address can access to the target. 'ALL' is a special address
to allow all initiators to access to a target.
Add the initiator's name to the access lists of the target with <id>.
Initiators with the names can access to the target.
Delete the address from the access lists of the target with <id>.
Delete the initiator's name from the access lists of the target with <id>.
Change the value of <parameter> of the target with <id> to <value>.
Sets/changes the value of one or more target parameters.
Sets/changes the value of one or more logical unit parameters.
Start the specified lld without restarting the tgtd process.
Can be used to initialize lld driver in case required modules were loaded
after tgtd was already executed.
Display a list of available options and exits.
LUN PARAMETERS
These parameters are only applicable for "--mode logicalunit".
This parameter sets the Vendor Identification string that a LUN
will report in INQURY data.
This parameter sets the Product Identification string that a LUN
will report in INQURY data.
This parameter sets the Product Revision string that a LUN
will report in INQURY data.
Example:
tgtadm --lld iscsi --mode logicalunit --op update \
--tid <TID> --lun <LUN> \
--params vendor_id=TGTD,product_id=VirtualHD,product_rev=0103
This can be used to override/change the default setting for
the removable flag. Disk devices default to non-removable while
DVD and TAPE devices default to removable.
This flag controls the format of sense data that the device will
return. 0 = Clasic sense format, 1 = Support descriptor format.
This controls whether a device is online or not.
Devices default to be online when created but can be brought offline
using this parameter. Behaviour of offline devices depend on device
type. An MMC/DVD device that is offline will report that there is
no disk in the unit but the actual MMC/DVD unit itself can still
be communicated with. All other device types will fail all I/O
with a sense code of Not Ready.
Example:
tgtadm --lld iscsi --mode logicalunit --op update \
--tid 1 --lun 1 \
--params removable=1,sense_format=1,online=1
This parameter is used to set specific mode pages for the device
and the mode page contents. Most devices default to reasonable
default mode pages automatically when the LUN is created, but this
allows special settings.
Examples:
Create mode page '2', subpage 0 and 14 bytes of data.
This is Disconnect-Reconnect mode page.
tgtadm --mode logicalunit --op update --tid 1 --lun 2 \
--params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0
Create mode page '10', subpage 0 and 10 bytes of data.
This is Control Extension mode page.
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \
--params mode_page=10:0:10:2:0:0:0:0:0:0:0:2:0
Create mode page '0x1c', subpage 0 and 10 bytes of data.
This is Informational Exceptions Control mode page.
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \
--params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0
This sets the read-only flag of a LUN. A read-only LUN will
refuse any attempts to write data to it.
This parameter only applies to DISK devices.
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 \
--params readonly=1
This controls the provisioning for the LUN. A thin-provisioned
LUN is represented as a sparse file.
TGTD supports provisioning type 2 for sparse files.
When initiators use the SCSI UNMAP command TGTD will release
the affected areas back to the filesystem using
FALLOC_FL_PUNCH_HOLE.
This parameter only applies to DISK devices.
Thin-provisioning only works for LUNs stored on filesystems
that support FALLOC_FL_PUNCH_HOLE.
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 \
--params thin_provisioning=1
SMC SPECIFIC LUN PARAMETERS
These parameters are only applicable for luns that are of type "changer"
i.e. the media changer device for a DVD Jukebox or a Virtual Tape Library.
This controls which type of element a certain slot in the
jukebox/vtl is.
Slot types:
1 -> Medium Transport (picker arm)
2 -> Storage Element
3 -> Import/Export Element
4 -> Data Transfer device (CD drive, tape drive, MO drive etc)
This is used to create/operate on a single slot.
Address specifies the slot on which to perform the operation.
This is used to create/operate on an entire range of slots at once.
Start_address specifies the first address to operate on and
quantity specifies the number of consequtive slots.
When creating storage elements, i.e. "element_type=2", this
parameter specifies if the media has 1 or two sides to hold data.
This option is used to clear a storage element and remove any
media that may be present. Once this command completes
the storage element will show up as "Empty".
This is used to assign a barcode to an element. Barcodes are limited
to 10 characters in tgtd.
This is used to assign a volume tag to SMC storage elements.
If no volume tag is specified tgtd will use fall back to the
barcode. The volume tag can be up to 32 characters.
This parameter specifies a directory where all virtual media
for the dvd/tape device elements are stored.
To assign a media image file to a storage element slot,
you assign "barcode" to be the name of the image file in
the "media_home" directory.
Example: How to create a DVD jukebox with eight disk trays and
two empty DVD-R disks.
# Create a target
tgtadm --lld iscsi --mode target --op new --tid 1 --targetname iqn.2007-03:virtual-dvd:`hostname`
# Create a DVD drive and give it a nice name
# The dvd starts out without a backing store file, i.e. no disk loaded
tgtadm --op new --mode logicalunit --tid 1 --lun 1 --device-type cd
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 --params vendor_id=STGT_DVD,product_id=DVD101,product_rev=0010,scsi_sn=STGTDVD01,removable=1
# We need a backend store file for the media changer
if [ ! -f $HOME/smc ]; then
dd if=/dev/zero of=$HOME/smc bs=1k count=1
fi
# Create the SMC device and give it a nice name
tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 2 --backing-store $HOME/smc --device-type changer
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params vendor_id=STK,product_id=L700,product_rev=0010,scsi_sn=XYZZY_0,removable=1
# Add a Data Transfer devices (1 drive)
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,start_address=1,quantity=1
# Specify that the DVD above (LUN 1) is the data transfer device we created
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,address=1,tid=1,lun=1
# Medium Transport Elements (robot arm / picker)
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=1,start_address=16,quantity=1
# define path to virtual media
VTL=${HOME}/vtl
mkdir -p ${VTL}
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params media_home=${VTL}
# Storage Elements - 8 starting at addr 1024
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,start_address=1024,quantity=8
# Add 'media' to slots 1 and 2 and leave the other 6 slots empty
# slot 1
# Create empty writeable virtual DVD-R media
tgtimg --op new --device-type cd --type dvd+r --file ${VTL}/DISK_001
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=1024,barcode=DISK_001,volume_tag="A blank disk",sides=1
# slot 2
tgtimg --op new --device-type cd --type dvd+r --file ${VTL}/DISK_002
tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=1025,barcode=DISK_002,volume_tag="My second blank disk",sides=1
# Allow ALL initiators to connect to this target
tgtadm --lld iscsi --mode target --op bind --tid 1 --initiator-address ALL
# Show all our good work.
tgtadm --lld iscsi --mode target --op show
Passthrough devices
In addition to device emulation TGTD also supports utilizing existing SG devices on the host and exporting these through a special passthrough device type.
This specifies that an SG devices is used.
This specifies that passthrough device type is used.
This specifies which device to export through TGTD.
This argument is used when creating a LUN to specify extra flags
to use when opening the backing file.
O_DIRECT is specified by "direct" and O_SYNC by "sync".
Example:
Make /dev/sg4 available to initiators connecting to TGTD.
tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 1 --bstype=sg --device-type=pt --backing-store=/dev/sg4
Example:
Open the backing file with O_SYNC.
tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 1 --bsoflags="sync" --backing-store=/data/100m_image.raw
Header Digest and Data Digest
Header and data digests can be set on a per target parameter.
TGTD supports two modes, None and CRC32C.
When the digest is set to None, TDTD will negotiate that digests
will not be used, and when CRC32C is set, TGTD will force the
connection to use digest.
Viewing the current settings
This command is used to view the current settings for header/data
digest.
tgtadm --op show --mode target --tid 1
...
HeaderDigest=None
DataDigest=None
...
Setting digest
Set header digest to CRC32C:
tgtadm --op update --mode target --tid 1 -n HeaderDigest -v CRC32C
Set data digest to None:
tgtadm --op update --mode target --tid 1 -n DataDigest -v None
CHAP Authentication
CHAP authentication is supported to require authentication before
an initiator is allowed to log in and access devices.
CHAP main-phase authentication is set on the target level.
To set up CHAP authentication we first need to create an account
and its associated password, then we bind the account to one or more
targets.
Setting CHAP on a target
These two commands create a user account and binds it to target 1.
tgtadm --lld iscsi --op new --mode account --user ronnie --password password
tgtadm --lld iscsi --op bind --mode account --tid 1 --user ronnie
List all accounts
This command is used to list all accounts that have been created.
tgtadm --lld iscsi --op show --mode account
Account list:
ronnie
Show if a target requires authentication
When listing the targets, each target that has authantication enabled
will contain a listing of all accoutns bound to that target.
tgtadm --lld iscsi --op show --mode target
Target 1: iqn.ronnie.test
...
Account information:
ronnie
...
NOP-OUT Probes
TGTD can send NOP-OUT probes to connected initiators to determine when
an initiator is dead and then automatically clear and tear down the
TCP connection. This can either be set as a global default from the
tgtd command-line or it can be set for individual targets using the
tgtadm command.
Check the current NOP-OUT setting
The tgtadm command is used to view the current setting for if/when
to send NOP-OUT probes to connected initiators.
If the target is configured to send NOP-OUT probes this will show up
as two parameter lines in the target printout. If the target is not
configured to send NOP-OUT these lines will not be printed at all.
tgtadm --lld iscsi --op show --mode target
Target 1: iqn.ronnie.test
System information:
Driver: iscsi
State: ready
Nop interval: 5
Nop count: 5
I_T nexus information:
Setting NOP-OUT for a target
The tgtadm command is used to change the NOP-OUT settings.
tgtadm --op update --mode target --tid 1 -n nop_count -v 5
tgtadm --op update --mode target --tid 1 -n nop_interval -v 5
iSCSI PORTALS
iSCSI portals can be viewed, added and removed at runtime.
List portals
This command is used to list the current iSCSI portals defined on the target:
tgtadm --lld iscsi --op show --mode portal
Portal: 10.1.1.101:3260,1
Portal: 127.0.0.1:3260,1
Add portal
This command is used to add a portal to the target :
tgtadm --lld iscsi --op new --mode portal --param portal=10.1.1.101:3260
Remove portal
This command is used to remove a portal from the target :
tgtadm --lld iscsi --op delete --mode portal --param portal=10.1.1.101:3260
iSCSI CONNECTIONS
iSCSI connections can be viewed and forced closed at runtime.
List all active connections for a target
This command is used to list the all the active iSCSI connections to the target with connection id, initiator name and ip address for the initiator :
tgtadm --lld iscsi --op show --mode conn --tid 1
Session: 2
Connection: 0
Initiator: iqn.2008-11.org.linux-kvm:
IP Address: 127.0.0.1
Close an existing connection
This command is used to close an iSCSI connection. Note that forcibly closing iSCSI connections can lead to data-loss.
tgtadm --lld iscsi --op delete --mode conn --tid 1 --sid 2 --cid 0
Online/Offline Status
Tgtd LUNs can be in online or offline status. LUNs that are Offline behave slightly different
depending on the device type. Offline devices behave as if there is no media available and any
operations that access media will fail with an check-condition error.
Devices can not be set to Offline mode while there are "PREVENT ALLOW MEDIUM REMOVAL"
locks on the device. Similarly media in Online devices can not be software ejected while there are such locks on the device (the 'eject' command will fail).
Show Online/Offline status
Finding the Online/Offline status of a LUN is done through the tgtd command. If "Prevent removal"
is "Yes" this indicates that an application holds a "prevent media removal" lock on the device.
tgtadm --lld iscsi --mode target --op show
...
LUN: 2
Type: cd/dvd
SCSI ID: IET 00010002
SCSI SN: beaf12
Size: 3432 MB, Block size: 1
Online: Yes
Removable media: Yes
Prevent removal: Yes
...
Changing a LUN to Offline
A LUN is changed to Offline status using the tgtadm command.
When devices are set Offline these devices will behave as if there is no media
loaded into the drive.
Change a LUN to become offline. (no disk in the drive)
tgtadm --tid 1 --lun 2 --op update --mode logicalunit -P Online=No
iSNS PARAMETERS
iSNS configuration for a target is by using the tgtadm command.
This specifies the IP address of the iSNS server. TGTD only
supprots one iSNS server.
Example:
tgtadm --op update --mode sys --name iSNSServerIP --value 192.168.11.133
This setting enables(on)/disables(off) iSNS.
Example:
tgtadm --op update --mode sys --name iSNS --value On
This setting specifies the port to use for iSNS.
Example:
tgtadm --op update --mode sys --name iSNSServerPort --value 3205
Enable/disable access control for iSNS.
Example:
tgtadm --op update --mode sys --name iSNSAccessControl --value Off
SEE ALSO
tgtd(8), tgt-admin(8), tgtimg(8), tgt-setup-lun(8).
REPORTING BUGS
Report bugs to <stgt@vger.kernel.org>
tgt-1.0.80/doc/tgtd.8.xml 0000664 0000000 0000000 00000013463 13747533545 0015024 0 ustar 00root root 0000000 0000000
tgtd
8
tgtd
The SCSI Target Daemon
tgtd
tgtd
-C --control-port <INTEGER>
-d --debug <INTEGER>
-f --foregound
-h --help
--iscsi <...>
DESCRIPTION
Tgtd is a SCSI Target daemon. It can be used to provide SCSI target
service to a network. The most common service is iSCSI but other services are also
supported.
Device types
Tgtd provides support for both emulated and passthrough of real devices.
Tgtd can emulate the following types of devices:
disk : Normal disk device. Emulates a SCSI harddisk.
tape : Tape device. Emulates a SCSI tape drive.
cd : CD device. Emulates a SCSI DVD burner.
changer : Media changer. Emulate the changer device for a virtual tape library or DVD jukebox.
OPTIONS
-d --debug <INTEGER>
Set to non-zero value to activate additional debugging messages to
be logged.
-f --foreground
Run the daemon in the foreground.
-h --help
Print help text to the screen.
-C --control-port <INTEGER>
This comamnd is used to specify the control port to use for
this instance of tgtd. This allows to run multiple instances of
TGTD on a host.
TGTADM has a matching argument to control which instance to connect
to.
--iscsi <...>
ISCSI specific options.
See the ISCSI section below for options specific to this frontend.
ISCSI options
These parameters apply only to the iSCSI frontend.
portal=<ip-address[:port]>
This option is used to bind tgtd to a specific ip-address/portal and/or
port. By default tgtd will bind to port 3260 on the wildcard address.
The ip-address part (before the ":") can be missing to designate the
wildcard address with a none-default port.
Example: to bind tgtd to a specific address and port
tgtd --iscsi portal=192.0.2.1:3260
Example: to bind tgtd to any address but a none-default port
tgtd --iscsi portal=:3251
nop_interval=<integer>
This sets the default interval for sending NOP-OUT to probe for
connected initiators.
This parameter only controls the default value for targets.
Individual targets can be controlled using tgtadm.
The default value is 0 which means that the feature is disabled
TGTD will not send any NOP-OUT probes.
nop_count=<integer>
This sets the default value for after how many failed probes TGTD
will consider the initiator dead and tear down the session.
This parameter only controls the default value for targets.
Individual targets can be controlled using tgtadm.
The default value is 0.
Example: send NOP-OUT every 5 seconds and abort the session after
6 failures.
tgtd --iscsi portal=192.0.2.1:3260,nop_interval=5,nop_count=6
ENVIRONMENT VARIABLES
TGT_IPC_SOCKET=<path>
When set tgtd and tgtadm will use the specified path as the
IPC socket instead of the default '/var/run/tgtd/socket.0'
SEE ALSO
targets.conf(5), tgtadm(8), tgt-admin(8), tgtimg(8), tgt-setup-lun(8).
COPYRIGHT/LICENSE
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see http://www.gnu.org/licenses/.
tgt-1.0.80/doc/tgtimg.8.xml 0000664 0000000 0000000 00000013253 13747533545 0015352 0 ustar 00root root 0000000 0000000
tgtimg
8
tgtimg
Linux SCSI Target Framework Image File Utility
tgtimg
-o --op <operation>
-Y --device-type <device-type>
-b --barcode <barcode>
-s --size <size>
-t --type <media-type>
-f --file <path>
-T --thin-provisioning
tgtimg --help
DESCRIPTION
Tgtimg is a utility to create and manage the image files used by the
TGTD device emulation.
This command is used to initialize device image files with the additional
metadata, such as barcode, list of blocks, total size, etc that
the TGTD emulation needs.
OPTIONS
Show a help screen and exit.
Operation. Is either new to create a new image file or show to
show the content of an existing image file.
This specifies the type of image file.
Supported device types are :
cd : to create media for a dvd device
disk : to create media for a disk device
tape : to create media for a tape device
When creating a new image, this specifies the type of media to
emulate. The available mediatypes depend on the type of the
device.
Supported media types for cd devices are :
dvd+r : create a blank writeable DVD+R disk
Supported media types for disk devices are :
disk : create an empty disk
Supported media types for tape devices are :
data : create a normal data tape
clean : create a cleaning tape
worm : create a worm
When creating a new image, this argument specifies the barcode
to use with this image file. Backup application software often
uses barcodes to identify specific tapes. When emulating a VTL,
make sure that all tape images use unique barcodes.
When creating a new image, this specifies the size in megabytes
for the virtual tape image.
The filename of the image file.
This argument makes the allocation of the image format use
thin-provisioning. This means that the file created will be a
sparse file that will allocate blocks from the filesystem on demand.
Be careful when using thin-provisioning. If the filesystem
fills up a iSCSI write to a thin-provisioned LUN
can fail. Initiators generally do not handle "out of space" errors
gracefully.
Thin-provisioning uses FALLOC_FL_PUNCH_HOLE which is only available
on some linux filesystems. Thin-provisioning can only be used for
DISK images.
EXAMPLES
To create a new 100MByte disk
tgtimg --op new --device-type disk --type disk --size 100 --file /data/hd001.raw
To create a new tape image
tgtimg --op new --device-type tape --barcode 12345 --size 100 --type data --file /data/tape001.img
To view the content of an existing image
tgtimg --op show --device-type tape --file /data/tape001.img
To create a new blank DVD+R image
tgtimg --op new --device-type cd --type dvd+r --file /data/dvd001.iso
SEE ALSO
tgtd(8), tgtadm(8), tgt-admin(8), tgt-setup-lun(8).
REPORTING BUGS
Report bugs to <stgt@vger.kernel.org>
tgt-1.0.80/doc/tmf.txt 0000664 0000000 0000000 00000004327 13747533545 0014520 0 ustar 00root root 0000000 0000000 The tgt Task Management Functions (TMF) works in the followings:
- When LLDs queue scsi commands to tgt (scsi_tgt_queue_command), they
need to specify unique 'tag' for each command.
- LLDs call 'int scsi_tgt_tsk_mgmt_request(struct Scsi_Host *host, int,
u64 tag, struct scsi_lun *lun, void *data)'.
- int (* tsk_mgmt_response)(u64 data, int result) is added to
scsi_host_template.
The tag arg in scsi_tgt_queue_command is used only for ABORT_TASK to
identify a single command. Initiators send a command with a unique
tag so LLDs simply call scsi_tgt_queue_command() with it.
With FCP and iSCSI, tag is 32-bit, however, unfortunately, SRP uses
64-bit tag. So we need 64-bit for it.
When an initiator sends a task management request, the LLD calls
scsi_tgt_tsk_mgmt_request. the LLD can use whatever it wants for the
data arg. The data arg is used later as the arg in the
tsk_mgmt_response callback.
tgt core just sends the task management request to user space
(by using TGT_KEVENT_TSK_MGMT_REQ).
In the case of ABORT_TASK, tgtd finds a single command to abort and
sends TGT_UEVENT_CMD_RSP and TGT_UEVENT_TSK_MGMT_RSP events.
tgt core calls eh_abort_handler for TGT_UEVENT_CMD_RSP and then
tsk_mgmt_response for TGT_UEVENT_TSK_MGMT_RSP.
If tgtd fails to find a command to abort, it sends only
TGT_UEVENT_TSK_MGMT_RSP event (no TGT_UEVENT_CMD_RSP event).
In the case of the rests task management functions (like
ABORT_TASK_SET), tgt needs to abort multiple commands. Thus, tgtd
finds multiple commands to abort and sends multiple TGT_UEVENT_CMD_RSP
events and a single TGT_UEVENT_TSK_MGMT_RSP event. tgt core calls
eh_abort_handler multiple times and tsk_mgmt_response once.
eh_abort_handler enables LLDs to safely free resource related with a
command to abort.
Note that when tgtd finds that a TGT_KEVENT_TSK_MGMT_REQ event tries
to abort commands between in TGT_UEVENT_CMD_RSP and
TGT_KEVENT_CMD_DONE states (that is, tgt calls something like
bio_map_user for the commands), tgtd gives up. After all, we cannot
abort such commands. For example, possibly, The commands already touch
page cache. In this case, tgtd waits for the completion of the commands
(TGT_KEVENT_CMD_DONE) and sends TGT_UEVENT_TSK_MGMT_RSP event with an
appropriate result value.
tgt-1.0.80/scripts/ 0000775 0000000 0000000 00000000000 13747533545 0014105 5 ustar 00root root 0000000 0000000 tgt-1.0.80/scripts/Makefile 0000664 0000000 0000000 00000000344 13747533545 0015546 0 ustar 00root root 0000000 0000000 sbindir ?= $(PREFIX)/sbin
SCRIPTS += tgt-setup-lun tgt-admin
.PHONY: all
all:
.PHONY: install
install: $(SCRIPTS)
install -d -m 755 $(DESTDIR)$(sbindir)
install -m 755 $(SCRIPTS) $(DESTDIR)$(sbindir)
.PHONY: clean
clean:
tgt-1.0.80/scripts/build-pkg.sh 0000775 0000000 0000000 00000005105 13747533545 0016323 0 ustar 00root root 0000000 0000000 #!/bin/bash
#
# Copyright (C) 2012 Roi Dayan
#
TARGET=$1
usage() {
echo "Usage: `basename $0` [rpm|deb]"
exit 1
}
if [ "$TARGET" != "rpm" -a "$TARGET" != "deb" ]; then
usage
fi
DIR=$(cd `dirname $0`; pwd)
BASE=`cd $DIR/.. ; pwd`
_TOP="$BASE/pkg"
SPEC="tgtd.spec"
LOG=/tmp/`basename $0`-$$.log
# get branch name
branch=`git branch | grep '^*' | sed 's/^..\(.*\)/\1/'`
# get version tag
version=`git describe --tags --abbrev=0 | sed "s/^v//g"`
# release is number of commits since the version tag
release=`git describe --tags | cut -d- -f2 | tr - _`
if [ "$version" = "$release" ]; then
# no commits and release can't be empty
release=0
fi
if [ "$branch" != "master" ]; then
# if not under master branch include hash tag
hash=`git rev-parse HEAD | cut -c 1-6`
release="$release.$hash"
fi
echo "Building version: $version-$release"
cp_src() {
local dest=$1
cp -a conf $dest
cp -a doc $dest
cp -a scripts $dest
cp -a usr $dest
cp -a README $dest
cp -a Makefile $dest
}
check() {
local rc=$?
local msg="$1"
if (( rc )) ; then
echo $msg
exit 1
fi
}
build_rpm() {
name=scsi-target-utils-$version-$release
TARBALL=$name.tgz
SRPM=$_TOP/SRPMS/$name.src.rpm
echo "Creating rpm build dirs under $_TOP"
mkdir -p $_TOP/{RPMS,SRPMS,SOURCES,BUILD,SPECS,tmp}
mkdir -p $_TOP/tmp/$name
cp_src $_TOP/tmp/$name
echo "Creating tgz $TARBALL"
tar -czf $_TOP/SOURCES/$TARBALL -C $_TOP/tmp $name
echo "Creating rpm"
sed -r "s/^Version:(\s*).*/Version:\1$version/;s/^Release:(\s*).*/Release:\1$release/" scripts/$SPEC > $_TOP/SPECS/$SPEC
rpmbuild -bs --define="_topdir $_TOP" $_TOP/SPECS/$SPEC
check "Failed to create source rpm."
rpmbuild -bb --define="_topdir $_TOP" $_TOP/SPECS/$SPEC > $LOG 2>&1
check "Failed to build rpm. LOG: $LOG"
# display created rpm files
grep ^Wrote $LOG
rm -fr $LOG
}
build_deb() {
if ! which debuild >/dev/null 2>&1 ; then
echo "Missing debuild. Please install devscripts package."
exit 1
fi
name=tgt_$version
TARBALL=$name.orig.tar.gz
echo "Building under $_TOP/$name"
mkdir -p $_TOP/$name
cp_src $_TOP/$name
tar -czf $_TOP/$TARBALL -C $_TOP $name
mkdir -p $_TOP/$name/debian
cp -a scripts/deb/* $_TOP/$name/debian
cd $_TOP/$name
sed -i -r "s/^tgt \(([0-9.-]+)\) (.*)/tgt \($version-$release\) \2/" debian/changelog
debuild -uc -us
check "Failed building deb package."
cd ../..
ls -l $_TOP/$name*.deb
}
cd $BASE
build_$TARGET
echo "Done."
tgt-1.0.80/scripts/checkarch.sh 0000775 0000000 0000000 00000000414 13747533545 0016356 0 ustar 00root root 0000000 0000000 #!/bin/sh
arch=`gcc -dumpmachine`
case $arch in
`echo $arch | grep x86_64`)
echo -m64
;;
`echo $arch | grep "i[3-6]86"`)
echo -m32
;;
*)
echo '
Failed to parse your architecture.
Please run
$ make check32
or
$ make check64
manually.
'
exit 1
;;
esac
tgt-1.0.80/scripts/checkpatch.pl 0000775 0000000 0000000 00000276404 13747533545 0016557 0 ustar 00root root 0000000 0000000 #!/usr/bin/perl -w
# (c) 2001, Dave Jones. (the file handling bit)
# (c) 2005, Joel Schopp (the ugly bit)
# (c) 2007,2008, Andy Whitcroft (new conditions, test suite)
# (c) 2008-2010 Andy Whitcroft
# Licensed under the terms of the GNU GPL License version 2
use strict;
my $P = $0;
$P =~ s@.*/@@g;
my $V = '0.32';
use Getopt::Long qw(:config no_auto_abbrev);
my $quiet = 0;
my $tree = 0;
my $chk_signoff = 1;
my $chk_patch = 1;
my $tst_only;
my $emacs = 0;
my $terse = 0;
my $file = 0;
my $check = 0;
my $summary = 1;
my $mailback = 0;
my $summary_file = 0;
my $show_types = 0;
my $root;
my %debug;
my %ignore_type = ();
my @ignore = ();
my $help = 0;
my $configuration_file = ".checkpatch.conf";
sub help {
my ($exitcode) = @_;
print << "EOM";
Usage: $P [OPTION]... [FILE]...
Version: $V (modified for stgt)
Options:
-q, --quiet quiet
--tree run with a kernel tree
--no-signoff do not check for 'Signed-off-by' line
--patch treat FILE as patchfile (default)
--emacs emacs compile window format
-t, --terse one line per report
-f, --file treat FILE as regular source file
--subjective, --strict enable more subjective tests
--ignore TYPE(,TYPE2...) ignore various comma separated message types
--show-types show the message "types" in the output
--root=PATH PATH to the kernel tree root
--no-summary suppress the per-file summary
--mailback only produce a report in case of warnings/errors
--summary-file include the filename in summary
--debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of
'values', 'possible', 'type', and 'attr' (default
is all off)
--test-only=WORD report only warnings/errors containing WORD
literally
-h, --help, --version display this help and exit
When FILE is - read standard input.
EOM
exit($exitcode);
}
my $conf = which_conf($configuration_file);
if (-f $conf) {
my @conf_args;
open(my $conffile, '<', "$conf")
or warn "$P: Can't find a readable $configuration_file file $!\n";
while (<$conffile>) {
my $line = $_;
$line =~ s/\s*\n?$//g;
$line =~ s/^\s*//g;
$line =~ s/\s+/ /g;
next if ($line =~ m/^\s*#/);
next if ($line =~ m/^\s*$/);
my @words = split(" ", $line);
foreach my $word (@words) {
last if ($word =~ m/^#/);
push (@conf_args, $word);
}
}
close($conffile);
unshift(@ARGV, @conf_args) if @conf_args;
}
GetOptions(
'q|quiet+' => \$quiet,
'tree+' => \$tree,
'signoff!' => \$chk_signoff,
'patch!' => \$chk_patch,
'emacs!' => \$emacs,
't|terse!' => \$terse,
'f|file!' => \$file,
'subjective!' => \$check,
'strict!' => \$check,
'ignore=s' => \@ignore,
'show-types!' => \$show_types,
'root=s' => \$root,
'summary!' => \$summary,
'mailback!' => \$mailback,
'summary-file!' => \$summary_file,
'debug=s' => \%debug,
'test-only=s' => \$tst_only,
'h|help' => \$help,
'version' => \$help
) or help(1);
help(0) if ($help);
my $exit = 0;
if ($#ARGV < 0) {
print "$P: no input files\n";
exit(1);
}
@ignore = split(/,/, join(',',@ignore));
foreach my $word (@ignore) {
$word =~ s/\s*\n?$//g;
$word =~ s/^\s*//g;
$word =~ s/\s+/ /g;
$word =~ tr/[a-z]/[A-Z]/;
next if ($word =~ m/^\s*#/);
next if ($word =~ m/^\s*$/);
$ignore_type{$word}++;
}
my $dbg_values = 0;
my $dbg_possible = 0;
my $dbg_type = 0;
my $dbg_attr = 0;
for my $key (keys %debug) {
## no critic
eval "\${dbg_$key} = '$debug{$key}';";
die "$@" if ($@);
}
my $rpt_cleaners = 0;
if ($terse) {
$emacs = 1;
$quiet++;
}
if ($tree) {
if (defined $root) {
if (!top_of_kernel_tree($root)) {
die "$P: $root: --root does not point at a valid tree\n";
}
} else {
if (top_of_kernel_tree('.')) {
$root = '.';
} elsif ($0 =~ m@(.*)/scripts/[^/]*$@ &&
top_of_kernel_tree($1)) {
$root = $1;
}
}
if (!defined $root) {
print "Must be run from the top-level dir. of a kernel tree\n";
exit(2);
}
}
my $emitted_corrupt = 0;
our $Ident = qr{
[A-Za-z_][A-Za-z\d_]*
(?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)*
}x;
our $Storage = qr{extern|static|asmlinkage};
our $Sparse = qr{
__user|
__kernel|
__force|
__iomem|
__must_check|
__init_refok|
__kprobes|
__ref|
__rcu
}x;
# Notes to $Attribute:
# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check
our $Attribute = qr{
const|
__percpu|
__nocast|
__safe|
__bitwise__|
__packed__|
__packed2__|
__naked|
__maybe_unused|
__always_unused|
__noreturn|
__used|
__cold|
__noclone|
__deprecated|
__read_mostly|
__kprobes|
__(?:mem|cpu|dev|)(?:initdata|initconst|init\b)|
____cacheline_aligned|
____cacheline_aligned_in_smp|
____cacheline_internodealigned_in_smp|
__weak
}x;
our $Modifier;
our $Inline = qr{inline|__always_inline|noinline};
our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]};
our $Lval = qr{$Ident(?:$Member)*};
our $Constant = qr{(?i:(?:[0-9]+|0x[0-9a-f]+)[ul]*)};
our $Assignment = qr{(?:\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=)};
our $Compare = qr{<=|>=|==|!=|<|>};
our $Operators = qr{
<=|>=|==|!=|
=>|->|<<|>>|<|>|!|~|
&&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%
}x;
our $NonptrType;
our $Type;
our $Declare;
our $NON_ASCII_UTF8 = qr{
[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
}x;
our $UTF8 = qr{
[\x09\x0A\x0D\x20-\x7E] # ASCII
| $NON_ASCII_UTF8
}x;
our $typeTypedefs = qr{(?x:
(?:__)?(?:u|s|be|le)(?:8|16|32|64)|
atomic_t
)};
our $logFunctions = qr{(?x:
printk(?:_ratelimited|_once|)|
[a-z0-9]+_(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)|
WARN(?:_RATELIMIT|_ONCE|)|
panic|
MODULE_[A-Z_]+
)};
our $signature_tags = qr{(?xi:
Signed-off-by:|
Acked-by:|
Tested-by:|
Reviewed-by:|
Reported-by:|
To:|
Cc:
)};
our @typeList = (
qr{void},
qr{(?:unsigned\s+)?char},
qr{(?:unsigned\s+)?short},
qr{(?:unsigned\s+)?int},
qr{(?:unsigned\s+)?long},
qr{(?:unsigned\s+)?long\s+int},
qr{(?:unsigned\s+)?long\s+long},
qr{(?:unsigned\s+)?long\s+long\s+int},
qr{unsigned},
qr{float},
qr{double},
qr{bool},
qr{struct\s+$Ident},
qr{union\s+$Ident},
qr{enum\s+$Ident},
qr{${Ident}_t},
qr{${Ident}_handler},
qr{${Ident}_handler_fn},
);
our @modifierList = (
qr{fastcall},
);
our $allowed_asm_includes = qr{(?x:
irq|
memory
)};
# memory.h: ARM has a custom one
sub build_types {
my $mods = "(?x: \n" . join("|\n ", @modifierList) . "\n)";
my $all = "(?x: \n" . join("|\n ", @typeList) . "\n)";
$Modifier = qr{(?:$Attribute|$Sparse|$mods)};
$NonptrType = qr{
(?:$Modifier\s+|const\s+)*
(?:
(?:typeof|__typeof__)\s*\([^\)]*\)|
(?:$typeTypedefs\b)|
(?:${all}\b)
)
(?:\s+$Modifier|\s+const)*
}x;
$Type = qr{
$NonptrType
(?:(?:\s|\*|\[\])+\s*const|(?:\s|\*|\[\])+|(?:\s*\[\s*\])+)?
(?:\s+$Inline|\s+$Modifier)*
}x;
$Declare = qr{(?:$Storage\s+)?$Type};
}
build_types();
our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*};
# Using $balanced_parens, $LvalOrFunc, or $FuncArg
# requires at least perl version v5.10.0
# Any use must be runtime checked with $^V
our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/;
our $LvalOrFunc = qr{($Lval)\s*($balanced_parens{0,1})\s*};
our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant)};
sub deparenthesize {
my ($string) = @_;
return "" if (!defined($string));
$string =~ s@^\s*\(\s*@@g;
$string =~ s@\s*\)\s*$@@g;
$string =~ s@\s+@ @g;
return $string;
}
$chk_signoff = 0 if ($file);
my @dep_includes = ();
my @dep_functions = ();
my $removal = "Documentation/feature-removal-schedule.txt";
if ($tree && -f "$root/$removal") {
open(my $REMOVE, '<', "$root/$removal") ||
die "$P: $removal: open failed - $!\n";
while (<$REMOVE>) {
if (/^Check:\s+(.*\S)/) {
for my $entry (split(/[, ]+/, $1)) {
if ($entry =~ m@include/(.*)@) {
push(@dep_includes, $1);
} elsif ($entry !~ m@/@) {
push(@dep_functions, $entry);
}
}
}
}
close($REMOVE);
}
my @rawlines = ();
my @lines = ();
my $vname;
for my $filename (@ARGV) {
my $FILE;
if ($file) {
open($FILE, '-|', "diff -u /dev/null $filename") ||
die "$P: $filename: diff failed - $!\n";
} elsif ($filename eq '-') {
open($FILE, '<&STDIN');
} else {
open($FILE, '<', "$filename") ||
die "$P: $filename: open failed - $!\n";
}
if ($filename eq '-') {
$vname = 'Your patch';
} else {
$vname = $filename;
}
while (<$FILE>) {
chomp;
push(@rawlines, $_);
}
close($FILE);
if (!process($filename)) {
$exit = 1;
}
@rawlines = ();
@lines = ();
}
exit($exit);
sub top_of_kernel_tree {
my ($root) = @_;
my @tree_check = (
"COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile",
"README", "Documentation", "arch", "include", "drivers",
"fs", "init", "ipc", "kernel", "lib", "scripts",
);
foreach my $check (@tree_check) {
if (! -e $root . '/' . $check) {
return 0;
}
}
return 1;
}
sub parse_email {
my ($formatted_email) = @_;
my $name = "";
my $address = "";
my $comment = "";
if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) {
$name = $1;
$address = $2;
$comment = $3 if defined $3;
} elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) {
$address = $1;
$comment = $2 if defined $2;
} elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) {
$address = $1;
$comment = $2 if defined $2;
$formatted_email =~ s/$address.*$//;
$name = $formatted_email;
$name =~ s/^\s+|\s+$//g;
$name =~ s/^\"|\"$//g;
# If there's a name left after stripping spaces and
# leading quotes, and the address doesn't have both
# leading and trailing angle brackets, the address
# is invalid. ie:
# "joe smith joe@smith.com" bad
# "joe smith ]+>$/) {
$name = "";
$address = "";
$comment = "";
}
}
$name =~ s/^\s+|\s+$//g;
$name =~ s/^\"|\"$//g;
$address =~ s/^\s+|\s+$//g;
$address =~ s/^\<|\>$//g;
if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
$name =~ s/(?";
}
return $formatted_email;
}
sub which_conf {
my ($conf) = @_;
foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
if (-e "$path/$conf") {
return "$path/$conf";
}
}
return "";
}
sub expand_tabs {
my ($str) = @_;
my $res = '';
my $n = 0;
for my $c (split(//, $str)) {
if ($c eq "\t") {
$res .= ' ';
$n++;
for (; ($n % 8) != 0; $n++) {
$res .= ' ';
}
next;
}
$res .= $c;
$n++;
}
return $res;
}
sub copy_spacing {
(my $res = shift) =~ tr/\t/ /c;
return $res;
}
sub line_stats {
my ($line) = @_;
# Drop the diff line leader and expand tabs
$line =~ s/^.//;
$line = expand_tabs($line);
# Pick the indent from the front of the line.
my ($white) = ($line =~ /^(\s*)/);
return (length($line), length($white));
}
my $sanitise_quote = '';
sub sanitise_line_reset {
my ($in_comment) = @_;
if ($in_comment) {
$sanitise_quote = '*/';
} else {
$sanitise_quote = '';
}
}
sub sanitise_line {
my ($line) = @_;
my $res = '';
my $l = '';
my $qlen = 0;
my $off = 0;
my $c;
# Always copy over the diff marker.
$res = substr($line, 0, 1);
for ($off = 1; $off < length($line); $off++) {
$c = substr($line, $off, 1);
# Comments we are wacking completly including the begin
# and end, all to $;.
if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') {
$sanitise_quote = '*/';
substr($res, $off, 2, "$;$;");
$off++;
next;
}
if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') {
$sanitise_quote = '';
substr($res, $off, 2, "$;$;");
$off++;
next;
}
if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') {
$sanitise_quote = '//';
substr($res, $off, 2, $sanitise_quote);
$off++;
next;
}
# A \ in a string means ignore the next character.
if (($sanitise_quote eq "'" || $sanitise_quote eq '"') &&
$c eq "\\") {
substr($res, $off, 2, 'XX');
$off++;
next;
}
# Regular quotes.
if ($c eq "'" || $c eq '"') {
if ($sanitise_quote eq '') {
$sanitise_quote = $c;
substr($res, $off, 1, $c);
next;
} elsif ($sanitise_quote eq $c) {
$sanitise_quote = '';
}
}
#print "c<$c> SQ<$sanitise_quote>\n";
if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") {
substr($res, $off, 1, $;);
} elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") {
substr($res, $off, 1, $;);
} elsif ($off != 0 && $sanitise_quote && $c ne "\t") {
substr($res, $off, 1, 'X');
} else {
substr($res, $off, 1, $c);
}
}
if ($sanitise_quote eq '//') {
$sanitise_quote = '';
}
# The pathname on a #include may be surrounded by '<' and '>'.
if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) {
my $clean = 'X' x length($1);
$res =~ s@\<.*\>@<$clean>@;
# The whole of a #error is a string.
} elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) {
my $clean = 'X' x length($1);
$res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@;
}
return $res;
}
sub ctx_statement_block {
my ($linenr, $remain, $off) = @_;
my $line = $linenr - 1;
my $blk = '';
my $soff = $off;
my $coff = $off - 1;
my $coff_set = 0;
my $loff = 0;
my $type = '';
my $level = 0;
my @stack = ();
my $p;
my $c;
my $len = 0;
my $remainder;
while (1) {
@stack = (['', 0]) if ($#stack == -1);
#warn "CSB: blk<$blk> remain<$remain>\n";
# If we are about to drop off the end, pull in more
# context.
if ($off >= $len) {
for (; $remain > 0; $line++) {
last if (!defined $lines[$line]);
next if ($lines[$line] =~ /^-/);
$remain--;
$loff = $len;
$blk .= $lines[$line] . "\n";
$len = length($blk);
$line++;
last;
}
# Bail if there is no further context.
#warn "CSB: blk<$blk> off<$off> len<$len>\n";
if ($off >= $len) {
last;
}
if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) {
$level++;
$type = '#';
}
}
$p = $c;
$c = substr($blk, $off, 1);
$remainder = substr($blk, $off);
#warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n";
# Handle nested #if/#else.
if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) {
push(@stack, [ $type, $level ]);
} elsif ($remainder =~ /^#\s*(?:else|elif)\b/) {
($type, $level) = @{$stack[$#stack - 1]};
} elsif ($remainder =~ /^#\s*endif\b/) {
($type, $level) = @{pop(@stack)};
}
# Statement ends at the ';' or a close '}' at the
# outermost level.
if ($level == 0 && $c eq ';') {
last;
}
# An else is really a conditional as long as its not else if
if ($level == 0 && $coff_set == 0 &&
(!defined($p) || $p =~ /(?:\s|\}|\+)/) &&
$remainder =~ /^(else)(?:\s|{)/ &&
$remainder !~ /^else\s+if\b/) {
$coff = $off + length($1) - 1;
$coff_set = 1;
#warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n";
#warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n";
}
if (($type eq '' || $type eq '(') && $c eq '(') {
$level++;
$type = '(';
}
if ($type eq '(' && $c eq ')') {
$level--;
$type = ($level != 0)? '(' : '';
if ($level == 0 && $coff < $soff) {
$coff = $off;
$coff_set = 1;
#warn "CSB: mark coff<$coff>\n";
}
}
if (($type eq '' || $type eq '{') && $c eq '{') {
$level++;
$type = '{';
}
if ($type eq '{' && $c eq '}') {
$level--;
$type = ($level != 0)? '{' : '';
if ($level == 0) {
if (substr($blk, $off + 1, 1) eq ';') {
$off++;
}
last;
}
}
# Preprocessor commands end at the newline unless escaped.
if ($type eq '#' && $c eq "\n" && $p ne "\\") {
$level--;
$type = '';
$off++;
last;
}
$off++;
}
# We are truly at the end, so shuffle to the next line.
if ($off == $len) {
$loff = $len + 1;
$line++;
$remain--;
}
my $statement = substr($blk, $soff, $off - $soff + 1);
my $condition = substr($blk, $soff, $coff - $soff + 1);
#warn "STATEMENT<$statement>\n";
#warn "CONDITION<$condition>\n";
#print "coff<$coff> soff<$off> loff<$loff>\n";
return ($statement, $condition,
$line, $remain + 1, $off - $loff + 1, $level);
}
sub statement_lines {
my ($stmt) = @_;
# Strip the diff line prefixes and rip blank lines at start and end.
$stmt =~ s/(^|\n)./$1/g;
$stmt =~ s/^\s*//;
$stmt =~ s/\s*$//;
my @stmt_lines = ($stmt =~ /\n/g);
return $#stmt_lines + 2;
}
sub statement_rawlines {
my ($stmt) = @_;
my @stmt_lines = ($stmt =~ /\n/g);
return $#stmt_lines + 2;
}
sub statement_block_size {
my ($stmt) = @_;
$stmt =~ s/(^|\n)./$1/g;
$stmt =~ s/^\s*{//;
$stmt =~ s/}\s*$//;
$stmt =~ s/^\s*//;
$stmt =~ s/\s*$//;
my @stmt_lines = ($stmt =~ /\n/g);
my @stmt_statements = ($stmt =~ /;/g);
my $stmt_lines = $#stmt_lines + 2;
my $stmt_statements = $#stmt_statements + 1;
if ($stmt_lines > $stmt_statements) {
return $stmt_lines;
} else {
return $stmt_statements;
}
}
sub ctx_statement_full {
my ($linenr, $remain, $off) = @_;
my ($statement, $condition, $level);
my (@chunks);
# Grab the first conditional/block pair.
($statement, $condition, $linenr, $remain, $off, $level) =
ctx_statement_block($linenr, $remain, $off);
#print "F: c<$condition> s<$statement> remain<$remain>\n";
push(@chunks, [ $condition, $statement ]);
if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) {
return ($level, $linenr, @chunks);
}
# Pull in the following conditional/block pairs and see if they
# could continue the statement.
for (;;) {
($statement, $condition, $linenr, $remain, $off, $level) =
ctx_statement_block($linenr, $remain, $off);
#print "C: c<$condition> s<$statement> remain<$remain>\n";
last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s));
#print "C: push\n";
push(@chunks, [ $condition, $statement ]);
}
return ($level, $linenr, @chunks);
}
sub ctx_block_get {
my ($linenr, $remain, $outer, $open, $close, $off) = @_;
my $line;
my $start = $linenr - 1;
my $blk = '';
my @o;
my @c;
my @res = ();
my $level = 0;
my @stack = ($level);
for ($line = $start; $remain > 0; $line++) {
next if ($rawlines[$line] =~ /^-/);
$remain--;
$blk .= $rawlines[$line];
# Handle nested #if/#else.
if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) {
push(@stack, $level);
} elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) {
$level = $stack[$#stack - 1];
} elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) {
$level = pop(@stack);
}
foreach my $c (split(//, $lines[$line])) {
##print "C<$c>L<$level><$open$close>O<$off>\n";
if ($off > 0) {
$off--;
next;
}
if ($c eq $close && $level > 0) {
$level--;
last if ($level == 0);
} elsif ($c eq $open) {
$level++;
}
}
if (!$outer || $level <= 1) {
push(@res, $rawlines[$line]);
}
last if ($level == 0);
}
return ($level, @res);
}
sub ctx_block_outer {
my ($linenr, $remain) = @_;
my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0);
return @r;
}
sub ctx_block {
my ($linenr, $remain) = @_;
my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0);
return @r;
}
sub ctx_statement {
my ($linenr, $remain, $off) = @_;
my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off);
return @r;
}
sub ctx_block_level {
my ($linenr, $remain) = @_;
return ctx_block_get($linenr, $remain, 0, '{', '}', 0);
}
sub ctx_statement_level {
my ($linenr, $remain, $off) = @_;
return ctx_block_get($linenr, $remain, 0, '(', ')', $off);
}
sub ctx_locate_comment {
my ($first_line, $end_line) = @_;
# Catch a comment on the end of the line itself.
my ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@);
return $current_comment if (defined $current_comment);
# Look through the context and try and figure out if there is a
# comment.
my $in_comment = 0;
$current_comment = '';
for (my $linenr = $first_line; $linenr < $end_line; $linenr++) {
my $line = $rawlines[$linenr - 1];
#warn " $line\n";
if ($linenr == $first_line and $line =~ m@^.\s*\*@) {
$in_comment = 1;
}
if ($line =~ m@/\*@) {
$in_comment = 1;
}
if (!$in_comment && $current_comment ne '') {
$current_comment = '';
}
$current_comment .= $line . "\n" if ($in_comment);
if ($line =~ m@\*/@) {
$in_comment = 0;
}
}
chomp($current_comment);
return($current_comment);
}
sub ctx_has_comment {
my ($first_line, $end_line) = @_;
my $cmt = ctx_locate_comment($first_line, $end_line);
##print "LINE: $rawlines[$end_line - 1 ]\n";
##print "CMMT: $cmt\n";
return ($cmt ne '');
}
sub raw_line {
my ($linenr, $cnt) = @_;
my $offset = $linenr - 1;
$cnt++;
my $line;
while ($cnt) {
$line = $rawlines[$offset++];
next if (defined($line) && $line =~ /^-/);
$cnt--;
}
return $line;
}
sub cat_vet {
my ($vet) = @_;
my ($res, $coded);
$res = '';
while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) {
$res .= $1;
if ($2 ne '') {
$coded = sprintf("^%c", unpack('C', $2) + 64);
$res .= $coded;
}
}
$res =~ s/$/\$/;
return $res;
}
my $av_preprocessor = 0;
my $av_pending;
my @av_paren_type;
my $av_pend_colon;
sub annotate_reset {
$av_preprocessor = 0;
$av_pending = '_';
@av_paren_type = ('E');
$av_pend_colon = 'O';
}
sub annotate_values {
my ($stream, $type) = @_;
my $res;
my $var = '_' x length($stream);
my $cur = $stream;
print "$stream\n" if ($dbg_values > 1);
while (length($cur)) {
@av_paren_type = ('E') if ($#av_paren_type < 0);
print " <" . join('', @av_paren_type) .
"> <$type> <$av_pending>" if ($dbg_values > 1);
if ($cur =~ /^(\s+)/o) {
print "WS($1)\n" if ($dbg_values > 1);
if ($1 =~ /\n/ && $av_preprocessor) {
$type = pop(@av_paren_type);
$av_preprocessor = 0;
}
} elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') {
print "CAST($1)\n" if ($dbg_values > 1);
push(@av_paren_type, $type);
$type = 'c';
} elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) {
print "DECLARE($1)\n" if ($dbg_values > 1);
$type = 'T';
} elsif ($cur =~ /^($Modifier)\s*/) {
print "MODIFIER($1)\n" if ($dbg_values > 1);
$type = 'T';
} elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) {
print "DEFINE($1,$2)\n" if ($dbg_values > 1);
$av_preprocessor = 1;
push(@av_paren_type, $type);
if ($2 ne '') {
$av_pending = 'N';
}
$type = 'E';
} elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) {
print "UNDEF($1)\n" if ($dbg_values > 1);
$av_preprocessor = 1;
push(@av_paren_type, $type);
} elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) {
print "PRE_START($1)\n" if ($dbg_values > 1);
$av_preprocessor = 1;
push(@av_paren_type, $type);
push(@av_paren_type, $type);
$type = 'E';
} elsif ($cur =~ /^(\#\s*(?:else|elif))/o) {
print "PRE_RESTART($1)\n" if ($dbg_values > 1);
$av_preprocessor = 1;
push(@av_paren_type, $av_paren_type[$#av_paren_type]);
$type = 'E';
} elsif ($cur =~ /^(\#\s*(?:endif))/o) {
print "PRE_END($1)\n" if ($dbg_values > 1);
$av_preprocessor = 1;
# Assume all arms of the conditional end as this
# one does, and continue as if the #endif was not here.
pop(@av_paren_type);
push(@av_paren_type, $type);
$type = 'E';
} elsif ($cur =~ /^(\\\n)/o) {
print "PRECONT($1)\n" if ($dbg_values > 1);
} elsif ($cur =~ /^(__attribute__)\s*\(?/o) {
print "ATTR($1)\n" if ($dbg_values > 1);
$av_pending = $type;
$type = 'N';
} elsif ($cur =~ /^(sizeof)\s*(\()?/o) {
print "SIZEOF($1)\n" if ($dbg_values > 1);
if (defined $2) {
$av_pending = 'V';
}
$type = 'N';
} elsif ($cur =~ /^(if|while|for)\b/o) {
print "COND($1)\n" if ($dbg_values > 1);
$av_pending = 'E';
$type = 'N';
} elsif ($cur =~/^(case)/o) {
print "CASE($1)\n" if ($dbg_values > 1);
$av_pend_colon = 'C';
$type = 'N';
} elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) {
print "KEYWORD($1)\n" if ($dbg_values > 1);
$type = 'N';
} elsif ($cur =~ /^(\()/o) {
print "PAREN('$1')\n" if ($dbg_values > 1);
push(@av_paren_type, $av_pending);
$av_pending = '_';
$type = 'N';
} elsif ($cur =~ /^(\))/o) {
my $new_type = pop(@av_paren_type);
if ($new_type ne '_') {
$type = $new_type;
print "PAREN('$1') -> $type\n"
if ($dbg_values > 1);
} else {
print "PAREN('$1')\n" if ($dbg_values > 1);
}
} elsif ($cur =~ /^($Ident)\s*\(/o) {
print "FUNC($1)\n" if ($dbg_values > 1);
$type = 'V';
$av_pending = 'V';
} elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) {
if (defined $2 && $type eq 'C' || $type eq 'T') {
$av_pend_colon = 'B';
} elsif ($type eq 'E') {
$av_pend_colon = 'L';
}
print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1);
$type = 'V';
} elsif ($cur =~ /^($Ident|$Constant)/o) {
print "IDENT($1)\n" if ($dbg_values > 1);
$type = 'V';
} elsif ($cur =~ /^($Assignment)/o) {
print "ASSIGN($1)\n" if ($dbg_values > 1);
$type = 'N';
} elsif ($cur =~/^(;|{|})/) {
print "END($1)\n" if ($dbg_values > 1);
$type = 'E';
$av_pend_colon = 'O';
} elsif ($cur =~/^(,)/) {
print "COMMA($1)\n" if ($dbg_values > 1);
$type = 'C';
} elsif ($cur =~ /^(\?)/o) {
print "QUESTION($1)\n" if ($dbg_values > 1);
$type = 'N';
} elsif ($cur =~ /^(:)/o) {
print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1);
substr($var, length($res), 1, $av_pend_colon);
if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') {
$type = 'E';
} else {
$type = 'N';
}
$av_pend_colon = 'O';
} elsif ($cur =~ /^(\[)/o) {
print "CLOSE($1)\n" if ($dbg_values > 1);
$type = 'N';
} elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) {
my $variant;
print "OPV($1)\n" if ($dbg_values > 1);
if ($type eq 'V') {
$variant = 'B';
} else {
$variant = 'U';
}
substr($var, length($res), 1, $variant);
$type = 'N';
} elsif ($cur =~ /^($Operators)/o) {
print "OP($1)\n" if ($dbg_values > 1);
if ($1 ne '++' && $1 ne '--') {
$type = 'N';
}
} elsif ($cur =~ /(^.)/o) {
print "C($1)\n" if ($dbg_values > 1);
}
if (defined $1) {
$cur = substr($cur, length($1));
$res .= $type x length($1);
}
}
return ($res, $var);
}
sub possible {
my ($possible, $line) = @_;
my $notPermitted = qr{(?:
^(?:
$Modifier|
$Storage|
$Type|
DEFINE_\S+
)$|
^(?:
goto|
return|
case|
else|
asm|__asm__|
do|
\#|
\#\#|
)(?:\s|$)|
^(?:typedef|struct|enum)\b
)}x;
warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2);
if ($possible !~ $notPermitted) {
# Check for modifiers.
$possible =~ s/\s*$Storage\s*//g;
$possible =~ s/\s*$Sparse\s*//g;
if ($possible =~ /^\s*$/) {
} elsif ($possible =~ /\s/) {
$possible =~ s/\s*$Type\s*//g;
for my $modifier (split(' ', $possible)) {
if ($modifier !~ $notPermitted) {
warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible);
push(@modifierList, $modifier);
}
}
} else {
warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible);
push(@typeList, $possible);
}
build_types();
} else {
warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1);
}
}
my $prefix = '';
sub show_type {
return !defined $ignore_type{$_[0]};
}
sub report {
if (!show_type($_[1]) ||
(defined $tst_only && $_[2] !~ /\Q$tst_only\E/)) {
return 0;
}
my $line;
if ($show_types) {
$line = "$prefix$_[0]:$_[1]: $_[2]\n";
} else {
$line = "$prefix$_[0]: $_[2]\n";
}
$line = (split('\n', $line))[0] . "\n" if ($terse);
push(our @report, $line);
return 1;
}
sub report_dump {
our @report;
}
sub ERROR {
if (report("ERROR", $_[0], $_[1])) {
our $clean = 0;
our $cnt_error++;
}
}
sub WARN {
if (report("WARNING", $_[0], $_[1])) {
our $clean = 0;
our $cnt_warn++;
}
}
sub CHK {
if ($check && report("CHECK", $_[0], $_[1])) {
our $clean = 0;
our $cnt_chk++;
}
}
sub check_absolute_file {
my ($absolute, $herecurr) = @_;
my $file = $absolute;
##print "absolute<$absolute>\n";
# See if any suffix of this path is a path within the tree.
while ($file =~ s@^[^/]*/@@) {
if (-f "$root/$file") {
##print "file<$file>\n";
last;
}
}
if (! -f _) {
return 0;
}
# It is, so see if the prefix is acceptable.
my $prefix = $absolute;
substr($prefix, -length($file)) = '';
##print "prefix<$prefix>\n";
if ($prefix ne ".../") {
WARN("USE_RELATIVE_PATH",
"use relative pathname instead of absolute in changelog text\n" . $herecurr);
}
}
sub pos_last_openparen {
my ($line) = @_;
my $pos = 0;
my $opens = $line =~ tr/\(/\(/;
my $closes = $line =~ tr/\)/\)/;
my $last_openparen = 0;
if (($opens == 0) || ($closes >= $opens)) {
return -1;
}
my $len = length($line);
for ($pos = 0; $pos < $len; $pos++) {
my $string = substr($line, $pos);
if ($string =~ /^($FuncArg|$balanced_parens)/) {
$pos += length($1) - 1;
} elsif (substr($line, $pos, 1) eq '(') {
$last_openparen = $pos;
} elsif (index($string, '(') == -1) {
last;
}
}
return $last_openparen + 1;
}
sub process {
my $filename = shift;
my $linenr=0;
my $prevline="";
my $prevrawline="";
my $stashline="";
my $stashrawline="";
my $length;
my $indent;
my $previndent=0;
my $stashindent=0;
our $clean = 1;
my $signoff = 0;
my $is_patch = 0;
my $in_header_lines = 1;
my $in_commit_log = 0; #Scanning lines before patch
our @report = ();
our $cnt_lines = 0;
our $cnt_error = 0;
our $cnt_warn = 0;
our $cnt_chk = 0;
# Trace the real file/line as we go.
my $realfile = '';
my $realline = 0;
my $realcnt = 0;
my $here = '';
my $in_comment = 0;
my $comment_edge = 0;
my $first_line = 0;
my $p1_prefix = '';
my $prev_values = 'E';
# suppression flags
my %suppress_ifbraces;
my %suppress_whiletrailers;
my %suppress_export;
my $suppress_statement = 0;
# Pre-scan the patch sanitizing the lines.
# Pre-scan the patch looking for any __setup documentation.
#
my @setup_docs = ();
my $setup_docs = 0;
sanitise_line_reset();
my $line;
foreach my $rawline (@rawlines) {
$linenr++;
$line = $rawline;
if ($rawline=~/^\+\+\+\s+(\S+)/) {
$setup_docs = 0;
if ($1 =~ m@Documentation/kernel-parameters.txt$@) {
$setup_docs = 1;
}
#next;
}
if ($rawline=~/^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) {
$realline=$1-1;
if (defined $2) {
$realcnt=$3+1;
} else {
$realcnt=1+1;
}
$in_comment = 0;
# Guestimate if this is a continuing comment. Run
# the context looking for a comment "edge". If this
# edge is a close comment then we must be in a comment
# at context start.
my $edge;
my $cnt = $realcnt;
for (my $ln = $linenr + 1; $cnt > 0; $ln++) {
next if (defined $rawlines[$ln - 1] &&
$rawlines[$ln - 1] =~ /^-/);
$cnt--;
#print "RAW<$rawlines[$ln - 1]>\n";
last if (!defined $rawlines[$ln - 1]);
if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ &&
$rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) {
($edge) = $1;
last;
}
}
if (defined $edge && $edge eq '*/') {
$in_comment = 1;
}
# Guestimate if this is a continuing comment. If this
# is the start of a diff block and this line starts
# ' *' then it is very likely a comment.
if (!defined $edge &&
$rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@)
{
$in_comment = 1;
}
##print "COMMENT:$in_comment edge<$edge> $rawline\n";
sanitise_line_reset($in_comment);
} elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) {
# Standardise the strings and chars within the input to
# simplify matching -- only bother with positive lines.
$line = sanitise_line($rawline);
}
push(@lines, $line);
if ($realcnt > 1) {
$realcnt-- if ($line =~ /^(?:\+| |$)/);
} else {
$realcnt = 0;
}
#print "==>$rawline\n";
#print "-->$line\n";
if ($setup_docs && $line =~ /^\+/) {
push(@setup_docs, $line);
}
}
$prefix = '';
$realcnt = 0;
$linenr = 0;
foreach my $line (@lines) {
$linenr++;
my $rawline = $rawlines[$linenr - 1];
#extract the line range in the file after the patch is applied
if ($line=~/^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) {
$is_patch = 1;
$first_line = $linenr + 1;
$realline=$1-1;
if (defined $2) {
$realcnt=$3+1;
} else {
$realcnt=1+1;
}
annotate_reset();
$prev_values = 'E';
%suppress_ifbraces = ();
%suppress_whiletrailers = ();
%suppress_export = ();
$suppress_statement = 0;
next;
# track the line number as we move through the hunk, note that
# new versions of GNU diff omit the leading space on completely
# blank context lines so we need to count that too.
} elsif ($line =~ /^( |\+|$)/) {
$realline++;
$realcnt-- if ($realcnt != 0);
# Measure the line length and indent.
($length, $indent) = line_stats($rawline);
# Track the previous line.
($prevline, $stashline) = ($stashline, $line);
($previndent, $stashindent) = ($stashindent, $indent);
($prevrawline, $stashrawline) = ($stashrawline, $rawline);
#warn "line<$line>\n";
} elsif ($realcnt == 1) {
$realcnt--;
}
my $hunk_line = ($realcnt != 0);
#make up the handle for any error we report on this line
$prefix = "$filename:$realline: " if ($emacs && $file);
$prefix = "$filename:$linenr: " if ($emacs && !$file);
$here = "#$linenr: " if (!$file);
$here = "#$realline: " if ($file);
# extract the filename as it passes
if ($line =~ /^diff --git.*?(\S+)$/) {
$realfile = $1;
$realfile =~ s@^([^/]*)/@@;
$in_commit_log = 0;
} elsif ($line =~ /^\+\+\+\s+(\S+)/) {
$realfile = $1;
$realfile =~ s@^([^/]*)/@@;
$in_commit_log = 0;
$p1_prefix = $1;
if (!$file && $tree && $p1_prefix ne '' &&
-e "$root/$p1_prefix") {
WARN("PATCH_PREFIX",
"patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n");
}
if ($realfile =~ m@^include/asm/@) {
ERROR("MODIFIED_INCLUDE_ASM",
"do not modify files in include/asm, change architecture specific files in include/asm-\n" . "$here$rawline\n");
}
next;
}
$here .= "FILE: $realfile:$realline:" if ($realcnt != 0);
my $hereline = "$here\n$rawline\n";
my $herecurr = "$here\n$rawline\n";
my $hereprev = "$here\n$prevrawline\n$rawline\n";
$cnt_lines++ if ($realcnt != 0);
# Check for incorrect file permissions
if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) {
my $permhere = $here . "FILE: $realfile\n";
if ($realfile =~ /(Makefile|Kconfig|\.c|\.h|\.S|\.tmpl)$/) {
ERROR("EXECUTE_PERMISSIONS",
"do not set execute permissions for source files\n" . $permhere);
}
}
# Check the patch for a signoff:
if ($line =~ /^\s*signed-off-by:/i) {
$signoff++;
$in_commit_log = 0;
}
# Check signature styles
if (!$in_header_lines &&
$line =~ /^(\s*)($signature_tags)(\s*)(.*)/) {
my $space_before = $1;
my $sign_off = $2;
my $space_after = $3;
my $email = $4;
my $ucfirst_sign_off = ucfirst(lc($sign_off));
if (defined $space_before && $space_before ne "") {
WARN("BAD_SIGN_OFF",
"Do not use whitespace before $ucfirst_sign_off\n" . $herecurr);
}
if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) {
WARN("BAD_SIGN_OFF",
"'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr);
}
if (!defined $space_after || $space_after ne " ") {
WARN("BAD_SIGN_OFF",
"Use a single space after $ucfirst_sign_off\n" . $herecurr);
}
my ($email_name, $email_address, $comment) = parse_email($email);
my $suggested_email = format_email(($email_name, $email_address));
if ($suggested_email eq "") {
ERROR("BAD_SIGN_OFF",
"Unrecognized email address: '$email'\n" . $herecurr);
} else {
my $dequoted = $suggested_email;
$dequoted =~ s/^"//;
$dequoted =~ s/" ;
# Don't force email to have quotes
# Allow just an angle bracketed address
if ("$dequoted$comment" ne $email &&
"<$email_address>$comment" ne $email &&
"$suggested_email$comment" ne $email) {
WARN("BAD_SIGN_OFF",
"email address '$email' might be better as '$suggested_email$comment'\n" . $herecurr);
}
}
}
# Check for wrappage within a valid hunk of the file
if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) {
ERROR("CORRUPTED_PATCH",
"patch seems to be corrupt (line wrapped?)\n" .
$herecurr) if (!$emitted_corrupt++);
}
# Check for absolute kernel paths.
if ($tree) {
while ($line =~ m{(?:^|\s)(/\S*)}g) {
my $file = $1;
if ($file =~ m{^(.*?)(?::\d+)+:?$} &&
check_absolute_file($1, $herecurr)) {
#
} else {
check_absolute_file($file, $herecurr);
}
}
}
# UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php
if (($realfile =~ /^$/ || $line =~ /^\+/) &&
$rawline !~ m/^$UTF8*$/) {
my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/);
my $blank = copy_spacing($rawline);
my $ptr = substr($blank, 0, length($utf8_prefix)) . "^";
my $hereptr = "$hereline$ptr\n";
CHK("INVALID_UTF8",
"Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr);
}
# Check if it's the start of a commit log
# (not a header line and we haven't seen the patch filename)
if ($in_header_lines && $realfile =~ /^$/ &&
$rawline !~ /^(commit\b|from\b|[\w-]+:).+$/i) {
$in_header_lines = 0;
$in_commit_log = 1;
}
# Still not yet in a patch, check for any UTF-8
if ($in_commit_log && $realfile =~ /^$/ &&
$rawline =~ /$NON_ASCII_UTF8/) {
CHK("UTF8_BEFORE_PATCH",
"8-bit UTF-8 used in possible commit log\n" . $herecurr);
}
# ignore non-hunk lines and lines being removed
next if (!$hunk_line || $line =~ /^-/);
#trailing whitespace
if ($line =~ /^\+.*\015/) {
my $herevet = "$here\n" . cat_vet($rawline) . "\n";
ERROR("DOS_LINE_ENDINGS",
"DOS line endings\n" . $herevet);
} elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) {
my $herevet = "$here\n" . cat_vet($rawline) . "\n";
ERROR("TRAILING_WHITESPACE",
"trailing whitespace\n" . $herevet);
$rpt_cleaners = 1;
}
# check for Kconfig help text having a real description
# Only applies when adding the entry originally, after that we do not have
# sufficient context to determine whether it is indeed long enough.
if ($realfile =~ /Kconfig/ &&
$line =~ /.\s*config\s+/) {
my $length = 0;
my $cnt = $realcnt;
my $ln = $linenr + 1;
my $f;
my $is_start = 0;
my $is_end = 0;
for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) {
$f = $lines[$ln - 1];
$cnt-- if ($lines[$ln - 1] !~ /^-/);
$is_end = $lines[$ln - 1] =~ /^\+/;
next if ($f =~ /^-/);
if ($lines[$ln - 1] =~ /.\s*(?:bool|tristate)\s*\"/) {
$is_start = 1;
} elsif ($lines[$ln - 1] =~ /.\s*(?:---)?help(?:---)?$/) {
$length = -1;
}
$f =~ s/^.//;
$f =~ s/#.*//;
$f =~ s/^\s+//;
next if ($f =~ /^$/);
if ($f =~ /^\s*config\s/) {
$is_end = 1;
last;
}
$length++;
}
WARN("CONFIG_DESCRIPTION",
"please write a paragraph that describes the config symbol fully\n" . $herecurr) if ($is_start && $is_end && $length < 4);
#print "is_start<$is_start> is_end<$is_end> length<$length>\n";
}
if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) &&
($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) {
my $flag = $1;
my $replacement = {
'EXTRA_AFLAGS' => 'asflags-y',
'EXTRA_CFLAGS' => 'ccflags-y',
'EXTRA_CPPFLAGS' => 'cppflags-y',
'EXTRA_LDFLAGS' => 'ldflags-y',
};
WARN("DEPRECATED_VARIABLE",
"Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag});
}
# check we are in a valid source file if not then ignore this hunk
next if ($realfile !~ /\.(h|c|s|S|pl|sh)$/);
#80 column limit
if ($line =~ /^\+/ && $prevrawline !~ /\/\*\*/ &&
$rawline !~ /^.\s*\*\s*\@$Ident\s/ &&
!($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(KERN_\S+\s*|[^"]*))?"[X\t]*"\s*(?:|,|\)\s*;)\s*$/ ||
$line =~ /^\+\s*"[^"]*"\s*(?:\s*|,|\)\s*;)\s*$/) &&
$length > 80)
{
WARN("LONG_LINE",
"line over 80 characters\n" . $herecurr);
}
# Check for user-visible strings broken across lines, which breaks the ability
# to grep for the string. Limited to strings used as parameters (those
# following an open parenthesis), which almost completely eliminates false
# positives, as well as warning only once per parameter rather than once per
# line of the string. Make an exception when the previous string ends in a
# newline (multiple lines in one string constant) or \n\t (common in inline
# assembly to indent the instruction on the following line).
if ($line =~ /^\+\s*"/ &&
$prevline =~ /"\s*$/ &&
$prevline =~ /\(/ &&
$prevrawline !~ /\\n(?:\\t)*"\s*$/) {
WARN("SPLIT_STRING",
"quoted string split across lines\n" . $hereprev);
}
# check for spaces before a quoted newline
if ($rawline =~ /^.*\".*\s\\n/) {
WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE",
"unnecessary whitespace before a quoted newline\n" . $herecurr);
}
# check for adding lines without a newline.
if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) {
WARN("MISSING_EOF_NEWLINE",
"adding a line without newline at end of file\n" . $herecurr);
}
# Blackfin: use hi/lo macros
if ($realfile =~ m@arch/blackfin/.*\.S$@) {
if ($line =~ /\.[lL][[:space:]]*=.*&[[:space:]]*0x[fF][fF][fF][fF]/) {
my $herevet = "$here\n" . cat_vet($line) . "\n";
ERROR("LO_MACRO",
"use the LO() macro, not (... & 0xFFFF)\n" . $herevet);
}
if ($line =~ /\.[hH][[:space:]]*=.*>>[[:space:]]*16/) {
my $herevet = "$here\n" . cat_vet($line) . "\n";
ERROR("HI_MACRO",
"use the HI() macro, not (... >> 16)\n" . $herevet);
}
}
# check we are in a valid source file C or perl if not then ignore this hunk
next if ($realfile !~ /\.(h|c|pl)$/);
# at the beginning of a line any tabs must come first and anything
# more than 8 must use tabs.
if ($rawline =~ /^\+\s* \t\s*\S/ ||
$rawline =~ /^\+\s* \s*/) {
my $herevet = "$here\n" . cat_vet($rawline) . "\n";
ERROR("CODE_INDENT",
"code indent should use tabs where possible\n" . $herevet);
$rpt_cleaners = 1;
}
# check for space before tabs.
if ($rawline =~ /^\+/ && $rawline =~ / \t/) {
my $herevet = "$here\n" . cat_vet($rawline) . "\n";
WARN("SPACE_BEFORE_TAB",
"please, no space before tabs\n" . $herevet);
}
# check for && or || at the start of a line
if ($rawline =~ /^\+\s*(&&|\|\|)/) {
CHK("LOGICAL_CONTINUATIONS",
"Logical continuations should be on the previous line\n" . $hereprev);
}
# check multi-line statement indentation matches previous line
if ($^V && $^V ge 5.10.0 &&
$prevline =~ /^\+(\t*)(if \(|$Ident\().*(\&\&|\|\||,)\s*$/) {
$prevline =~ /^\+(\t*)(.*)$/;
my $oldindent = $1;
my $rest = $2;
my $pos = pos_last_openparen($rest);
if ($pos >= 0) {
$line =~ /^\+([ \t]*)/;
my $newindent = $1;
my $goodtabindent = $oldindent .
"\t" x ($pos / 8) .
" " x ($pos % 8);
my $goodspaceindent = $oldindent . " " x $pos;
if ($newindent ne $goodtabindent &&
$newindent ne $goodspaceindent) {
CHK("PARENTHESIS_ALIGNMENT",
"Alignment should match open parenthesis\n" . $hereprev);
}
}
}
if ($line =~ /^\+.*\*[ \t]*\)[ \t]+/) {
CHK("SPACING",
"No space is necessary after a cast\n" . $hereprev);
}
# check for spaces at the beginning of a line.
# Exceptions:
# 1) within comments
# 2) indented preprocessor commands
# 3) hanging labels
if ($rawline =~ /^\+ / && $line !~ /\+ *(?:$;|#|$Ident:)/) {
my $herevet = "$here\n" . cat_vet($rawline) . "\n";
WARN("LEADING_SPACE",
"please, no spaces at the start of a line\n" . $herevet);
}
# check we are in a valid C source file if not then ignore this hunk
next if ($realfile !~ /\.(h|c)$/);
# check for RCS/CVS revision markers
if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) {
WARN("CVS_KEYWORD",
"CVS style keyword markers, these will _not_ be updated\n". $herecurr);
}
# Blackfin: don't use __builtin_bfin_[cs]sync
if ($line =~ /__builtin_bfin_csync/) {
my $herevet = "$here\n" . cat_vet($line) . "\n";
ERROR("CSYNC",
"use the CSYNC() macro in asm/blackfin.h\n" . $herevet);
}
if ($line =~ /__builtin_bfin_ssync/) {
my $herevet = "$here\n" . cat_vet($line) . "\n";
ERROR("SSYNC",
"use the SSYNC() macro in asm/blackfin.h\n" . $herevet);
}
# Check for potential 'bare' types
my ($stat, $cond, $line_nr_next, $remain_next, $off_next,
$realline_next);
#print "LINE<$line>\n";
if ($linenr >= $suppress_statement &&
$realcnt && $line =~ /.\s*\S/) {
($stat, $cond, $line_nr_next, $remain_next, $off_next) =
ctx_statement_block($linenr, $realcnt, 0);
$stat =~ s/\n./\n /g;
$cond =~ s/\n./\n /g;
#print "linenr<$linenr> <$stat>\n";
# If this statement has no statement boundaries within
# it there is no point in retrying a statement scan
# until we hit end of it.
my $frag = $stat; $frag =~ s/;+\s*$//;
if ($frag !~ /(?:{|;)/) {
#print "skip<$line_nr_next>\n";
$suppress_statement = $line_nr_next;
}
# Find the real next line.
$realline_next = $line_nr_next;
if (defined $realline_next &&
(!defined $lines[$realline_next - 1] ||
substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) {
$realline_next++;
}
my $s = $stat;
$s =~ s/{.*$//s;
# Ignore goto labels.
if ($s =~ /$Ident:\*$/s) {
# Ignore functions being called
} elsif ($s =~ /^.\s*$Ident\s*\(/s) {
} elsif ($s =~ /^.\s*else\b/s) {
# declarations always start with types
} elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) {
my $type = $1;
$type =~ s/\s+/ /g;
possible($type, "A:" . $s);
# definitions in global scope can only start with types
} elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) {
possible($1, "B:" . $s);
}
# any (foo ... *) is a pointer cast, and foo is a type
while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) {
possible($1, "C:" . $s);
}
# Check for any sort of function declaration.
# int foo(something bar, other baz);
# void (*store_gdt)(x86_descr_ptr *);
if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) {
my ($name_len) = length($1);
my $ctx = $s;
substr($ctx, 0, $name_len + 1, '');
$ctx =~ s/\)[^\)]*$//;
for my $arg (split(/\s*,\s*/, $ctx)) {
if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) {
possible($1, "D:" . $s);
}
}
}
}
#
# Checks which may be anchored in the context.
#
# Check for switch () and associated case and default
# statements should be at the same indent.
if ($line=~/\bswitch\s*\(.*\)/) {
my $err = '';
my $sep = '';
my @ctx = ctx_block_outer($linenr, $realcnt);
shift(@ctx);
for my $ctx (@ctx) {
my ($clen, $cindent) = line_stats($ctx);
if ($ctx =~ /^\+\s*(case\s+|default:)/ &&
$indent != $cindent) {
$err .= "$sep$ctx\n";
$sep = '';
} else {
$sep = "[...]\n";
}
}
if ($err ne '') {
ERROR("SWITCH_CASE_INDENT_LEVEL",
"switch and case should be at the same indent\n$hereline$err");
}
}
# if/while/etc brace do not go on next line, unless defining a do while loop,
# or if that brace on the next line is for something else
if ($line =~ /(.*)\b((?:if|while|for|switch)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) {
my $pre_ctx = "$1$2";
my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0);
if ($line =~ /^\+\t{6,}/) {
WARN("DEEP_INDENTATION",
"Too many leading tabs - consider code refactoring\n" . $herecurr);
}
my $ctx_cnt = $realcnt - $#ctx - 1;
my $ctx = join("\n", @ctx);
my $ctx_ln = $linenr;
my $ctx_skip = $realcnt;
while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt &&
defined $lines[$ctx_ln - 1] &&
$lines[$ctx_ln - 1] =~ /^-/)) {
##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n";
$ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/);
$ctx_ln++;
}
#print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n";
#print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n";
if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln -1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) {
ERROR("OPEN_BRACE",
"that open brace { should be on the previous line\n" .
"$here\n$ctx\n$rawlines[$ctx_ln - 1]\n");
}
if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ &&
$ctx =~ /\)\s*\;\s*$/ &&
defined $lines[$ctx_ln - 1])
{
my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]);
if ($nindent > $indent) {
WARN("TRAILING_SEMICOLON",
"trailing semicolon indicates no statements, indent implies otherwise\n" .
"$here\n$ctx\n$rawlines[$ctx_ln - 1]\n");
}
}
}
# Check relative indent for conditionals and blocks.
if ($line =~ /\b(?:(?:if|while|for)\s*\(|do\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) {
($stat, $cond, $line_nr_next, $remain_next, $off_next) =
ctx_statement_block($linenr, $realcnt, 0)
if (!defined $stat);
my ($s, $c) = ($stat, $cond);
substr($s, 0, length($c), '');
# Make sure we remove the line prefixes as we have
# none on the first line, and are going to readd them
# where necessary.
$s =~ s/\n./\n/gs;
# Find out how long the conditional actually is.
my @newlines = ($c =~ /\n/gs);
my $cond_lines = 1 + $#newlines;
# We want to check the first line inside the block
# starting at the end of the conditional, so remove:
# 1) any blank line termination
# 2) any opening brace { on end of the line
# 3) any do (...) {
my $continuation = 0;
my $check = 0;
$s =~ s/^.*\bdo\b//;
$s =~ s/^\s*{//;
if ($s =~ s/^\s*\\//) {
$continuation = 1;
}
if ($s =~ s/^\s*?\n//) {
$check = 1;
$cond_lines++;
}
# Also ignore a loop construct at the end of a
# preprocessor statement.
if (($prevline =~ /^.\s*#\s*define\s/ ||
$prevline =~ /\\\s*$/) && $continuation == 0) {
$check = 0;
}
my $cond_ptr = -1;
$continuation = 0;
while ($cond_ptr != $cond_lines) {
$cond_ptr = $cond_lines;
# If we see an #else/#elif then the code
# is not linear.
if ($s =~ /^\s*\#\s*(?:else|elif)/) {
$check = 0;
}
# Ignore:
# 1) blank lines, they should be at 0,
# 2) preprocessor lines, and
# 3) labels.
if ($continuation ||
$s =~ /^\s*?\n/ ||
$s =~ /^\s*#\s*?/ ||
$s =~ /^\s*$Ident\s*:/) {
$continuation = ($s =~ /^.*?\\\n/) ? 1 : 0;
if ($s =~ s/^.*?\n//) {
$cond_lines++;
}
}
}
my (undef, $sindent) = line_stats("+" . $s);
my $stat_real = raw_line($linenr, $cond_lines);
# Check if either of these lines are modified, else
# this is not this patch's fault.
if (!defined($stat_real) ||
$stat !~ /^\+/ && $stat_real !~ /^\+/) {
$check = 0;
}
if (defined($stat_real) && $cond_lines > 1) {
$stat_real = "[...]\n$stat_real";
}
#print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n";
if ($check && (($sindent % 8) != 0 ||
($sindent <= $indent && $s ne ''))) {
WARN("SUSPECT_CODE_INDENT",
"suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n");
}
}
# Track the 'values' across context and added lines.
my $opline = $line; $opline =~ s/^./ /;
my ($curr_values, $curr_vars) =
annotate_values($opline . "\n", $prev_values);
$curr_values = $prev_values . $curr_values;
if ($dbg_values) {
my $outline = $opline; $outline =~ s/\t/ /g;
print "$linenr > .$outline\n";
print "$linenr > $curr_values\n";
print "$linenr > $curr_vars\n";
}
$prev_values = substr($curr_values, -1);
#ignore lines not being added
if ($line=~/^[^\+]/) {next;}
# TEST: allow direct testing of the type matcher.
if ($dbg_type) {
if ($line =~ /^.\s*$Declare\s*$/) {
ERROR("TEST_TYPE",
"TEST: is type\n" . $herecurr);
} elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) {
ERROR("TEST_NOT_TYPE",
"TEST: is not type ($1 is)\n". $herecurr);
}
next;
}
# TEST: allow direct testing of the attribute matcher.
if ($dbg_attr) {
if ($line =~ /^.\s*$Modifier\s*$/) {
ERROR("TEST_ATTR",
"TEST: is attr\n" . $herecurr);
} elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) {
ERROR("TEST_NOT_ATTR",
"TEST: is not attr ($1 is)\n". $herecurr);
}
next;
}
# check for initialisation to aggregates open brace on the next line
if ($line =~ /^.\s*{/ &&
$prevline =~ /(?:^|[^=])=\s*$/) {
ERROR("OPEN_BRACE",
"that open brace { should be on the previous line\n" . $hereprev);
}
#
# Checks which are anchored on the added line.
#
# check for malformed paths in #include statements (uses RAW line)
if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) {
my $path = $1;
if ($path =~ m{//}) {
ERROR("MALFORMED_INCLUDE",
"malformed #include filename\n" .
$herecurr);
}
}
# no C99 // comments
if ($line =~ m{//}) {
ERROR("C99_COMMENTS",
"do not use C99 // comments\n" . $herecurr);
}
# Remove C99 comments.
$line =~ s@//.*@@;
$opline =~ s@//.*@@;
# EXPORT_SYMBOL should immediately follow the thing it is exporting, consider
# the whole statement.
#print "APW <$lines[$realline_next - 1]>\n";
if (defined $realline_next &&
exists $lines[$realline_next - 1] &&
!defined $suppress_export{$realline_next} &&
($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/ ||
$lines[$realline_next - 1] =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) {
# Handle definitions which produce identifiers with
# a prefix:
# XXX(foo);
# EXPORT_SYMBOL(something_foo);
my $name = $1;
if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ &&
$name =~ /^${Ident}_$2/) {
#print "FOO C name<$name>\n";
$suppress_export{$realline_next} = 1;
} elsif ($stat !~ /(?:
\n.}\s*$|
^.DEFINE_$Ident\(\Q$name\E\)|
^.DECLARE_$Ident\(\Q$name\E\)|
^.LIST_HEAD\(\Q$name\E\)|
^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(|
\b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\()
)/x) {
#print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n";
$suppress_export{$realline_next} = 2;
} else {
$suppress_export{$realline_next} = 1;
}
}
if (!defined $suppress_export{$linenr} &&
$prevline =~ /^.\s*$/ &&
($line =~ /EXPORT_SYMBOL.*\((.*)\)/ ||
$line =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) {
#print "FOO B <$lines[$linenr - 1]>\n";
$suppress_export{$linenr} = 2;
}
if (defined $suppress_export{$linenr} &&
$suppress_export{$linenr} == 2) {
WARN("EXPORT_SYMBOL",
"EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr);
}
# check for global initialisers.
if ($line =~ /^.$Type\s*$Ident\s*(?:\s+$Modifier)*\s*=\s*(0|NULL|false)\s*;/) {
ERROR("GLOBAL_INITIALISERS",
"do not initialise globals to 0 or NULL\n" .
$herecurr);
}
# check for static initialisers.
if ($line =~ /\bstatic\s.*=\s*(0|NULL|false)\s*;/) {
ERROR("INITIALISED_STATIC",
"do not initialise statics to 0 or NULL\n" .
$herecurr);
}
# check for static const char * arrays.
if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) {
WARN("STATIC_CONST_CHAR_ARRAY",
"static const char * array should probably be static const char * const\n" .
$herecurr);
}
# check for static char foo[] = "bar" declarations.
if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) {
WARN("STATIC_CONST_CHAR_ARRAY",
"static char array declaration should probably be static const char\n" .
$herecurr);
}
# check for declarations of struct pci_device_id
if ($line =~ /\bstruct\s+pci_device_id\s+\w+\s*\[\s*\]\s*\=\s*\{/) {
WARN("DEFINE_PCI_DEVICE_TABLE",
"Use DEFINE_PCI_DEVICE_TABLE for struct pci_device_id\n" . $herecurr);
}
# check for new typedefs, only function parameters and sparse annotations
# make sense.
if ($line =~ /\btypedef\s/ &&
$line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ &&
$line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ &&
$line !~ /\b$typeTypedefs\b/ &&
$line !~ /\b__bitwise(?:__|)\b/) {
WARN("NEW_TYPEDEFS",
"do not add new typedefs\n" . $herecurr);
}
# * goes on variable not on type
# (char*[ const])
while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) {
#print "AA<$1>\n";
my ($from, $to) = ($2, $2);
# Should start with a space.
$to =~ s/^(\S)/ $1/;
# Should not end with a space.
$to =~ s/\s+$//;
# '*'s should not have spaces between.
while ($to =~ s/\*\s+\*/\*\*/) {
}
#print "from<$from> to<$to>\n";
if ($from ne $to) {
ERROR("POINTER_LOCATION",
"\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr);
}
}
while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) {
#print "BB<$1>\n";
my ($from, $to, $ident) = ($2, $2, $3);
# Should start with a space.
$to =~ s/^(\S)/ $1/;
# Should not end with a space.
$to =~ s/\s+$//;
# '*'s should not have spaces between.
while ($to =~ s/\*\s+\*/\*\*/) {
}
# Modifiers should have spaces.
$to =~ s/(\b$Modifier$)/$1 /;
#print "from<$from> to<$to> ident<$ident>\n";
if ($from ne $to && $ident !~ /^$Modifier$/) {
ERROR("POINTER_LOCATION",
"\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr);
}
}
# # no BUG() or BUG_ON()
# if ($line =~ /\b(BUG|BUG_ON)\b/) {
# print "Try to use WARN_ON & Recovery code rather than BUG() or BUG_ON()\n";
# print "$herecurr";
# $clean = 0;
# }
if ($line =~ /\bLINUX_VERSION_CODE\b/) {
WARN("LINUX_VERSION_CODE",
"LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr);
}
# check for uses of printk_ratelimit
if ($line =~ /\bprintk_ratelimit\s*\(/) {
WARN("PRINTK_RATELIMITED",
"Prefer printk_ratelimited or pr__ratelimited to printk_ratelimit\n" . $herecurr);
}
# printk should use KERN_* levels. Note that follow on printk's on the
# same line do not need a level, so we use the current block context
# to try and find and validate the current printk. In summary the current
# printk includes all preceding printk's which have no newline on the end.
# we assume the first bad printk is the one to report.
if ($line =~ /\bprintk\((?!KERN_)\s*"/) {
my $ok = 0;
for (my $ln = $linenr - 1; $ln >= $first_line; $ln--) {
#print "CHECK<$lines[$ln - 1]\n";
# we have a preceding printk if it ends
# with "\n" ignore it, else it is to blame
if ($lines[$ln - 1] =~ m{\bprintk\(}) {
if ($rawlines[$ln - 1] !~ m{\\n"}) {
$ok = 1;
}
last;
}
}
if ($ok == 0) {
WARN("PRINTK_WITHOUT_KERN_LEVEL",
"printk() should include KERN_ facility level\n" . $herecurr);
}
}
if ($line =~ /\bprintk\s*\(\s*KERN_([A-Z]+)/) {
my $orig = $1;
my $level = lc($orig);
$level = "warn" if ($level eq "warning");
WARN("PREFER_PR_LEVEL",
"Prefer pr_$level(... to printk(KERN_$1, ...\n" . $herecurr);
}
if ($line =~ /\bpr_warning\s*\(/) {
WARN("PREFER_PR_LEVEL",
"Prefer pr_warn(... to pr_warning(...\n" . $herecurr);
}
# function brace can't be on same line, except for #defines of do while,
# or if closed on same line
if (($line=~/$Type\s*$Ident\(.*\).*\s\{/) and
!($line=~/\#\s*define.*do\s\{/) and !($line=~/\}/)) {
ERROR("OPEN_BRACE",
"open brace '{' following function declarations go on the next line\n" . $herecurr);
}
# open braces for enum, union and struct go on the same line.
if ($line =~ /^.\s*{/ &&
$prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) {
ERROR("OPEN_BRACE",
"open brace '{' following $1 go on the same line\n" . $hereprev);
}
# missing space after union, struct or enum definition
if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?(?:\s+$Ident)?[=\{]/) {
WARN("SPACING",
"missing space after $1 definition\n" . $herecurr);
}
# check for spacing round square brackets; allowed:
# 1. with a type on the left -- int [] a;
# 2. at the beginning of a line for slice initialisers -- [0...10] = 5,
# 3. inside a curly brace -- = { [0...10] = 5 }
while ($line =~ /(.*?\s)\[/g) {
my ($where, $prefix) = ($-[1], $1);
if ($prefix !~ /$Type\s+$/ &&
($where != 0 || $prefix !~ /^.\s+$/) &&
$prefix !~ /[{,]\s+$/) {
ERROR("BRACKET_SPACE",
"space prohibited before open square bracket '['\n" . $herecurr);
}
}
# check for spaces between functions and their parentheses.
while ($line =~ /($Ident)\s+\(/g) {
my $name = $1;
my $ctx_before = substr($line, 0, $-[1]);
my $ctx = "$ctx_before$name";
# Ignore those directives where spaces _are_ permitted.
if ($name =~ /^(?:
if|for|while|switch|return|case|
volatile|__volatile__|
__attribute__|format|__extension__|
asm|__asm__)$/x)
{
# cpp #define statements have non-optional spaces, ie
# if there is a space between the name and the open
# parenthesis it is simply not a parameter group.
} elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) {
# cpp #elif statement condition may start with a (
} elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) {
# If this whole things ends with a type its most
# likely a typedef for a function.
} elsif ($ctx =~ /$Type$/) {
} else {
WARN("SPACING",
"space prohibited between function name and open parenthesis '('\n" . $herecurr);
}
}
# check for whitespace before a non-naked semicolon
if ($line =~ /^\+.*\S\s+;/) {
CHK("SPACING",
"space prohibited before semicolon\n" . $herecurr);
}
# Check operator spacing.
if (!($line=~/\#\s*include/)) {
my $ops = qr{
<<=|>>=|<=|>=|==|!=|
\+=|-=|\*=|\/=|%=|\^=|\|=|&=|
=>|->|<<|>>|<|>|=|!|~|
&&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%|
\?|:
}x;
my @elements = split(/($ops|;)/, $opline);
my $off = 0;
my $blank = copy_spacing($opline);
for (my $n = 0; $n < $#elements; $n += 2) {
$off += length($elements[$n]);
# Pick up the preceding and succeeding characters.
my $ca = substr($opline, 0, $off);
my $cc = '';
if (length($opline) >= ($off + length($elements[$n + 1]))) {
$cc = substr($opline, $off + length($elements[$n + 1]));
}
my $cb = "$ca$;$cc";
my $a = '';
$a = 'V' if ($elements[$n] ne '');
$a = 'W' if ($elements[$n] =~ /\s$/);
$a = 'C' if ($elements[$n] =~ /$;$/);
$a = 'B' if ($elements[$n] =~ /(\[|\()$/);
$a = 'O' if ($elements[$n] eq '');
$a = 'E' if ($ca =~ /^\s*$/);
my $op = $elements[$n + 1];
my $c = '';
if (defined $elements[$n + 2]) {
$c = 'V' if ($elements[$n + 2] ne '');
$c = 'W' if ($elements[$n + 2] =~ /^\s/);
$c = 'C' if ($elements[$n + 2] =~ /^$;/);
$c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/);
$c = 'O' if ($elements[$n + 2] eq '');
$c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/);
} else {
$c = 'E';
}
my $ctx = "${a}x${c}";
my $at = "(ctx:$ctx)";
my $ptr = substr($blank, 0, $off) . "^";
my $hereptr = "$hereline$ptr\n";
# Pull out the value of this operator.
my $op_type = substr($curr_values, $off + 1, 1);
# Get the full operator variant.
my $opv = $op . substr($curr_vars, $off, 1);
# Ignore operators passed as parameters.
if ($op_type ne 'V' &&
$ca =~ /\s$/ && $cc =~ /^\s*,/) {
# # Ignore comments
# } elsif ($op =~ /^$;+$/) {
# ; should have either the end of line or a space or \ after it
} elsif ($op eq ';') {
if ($ctx !~ /.x[WEBC]/ &&
$cc !~ /^\\/ && $cc !~ /^;/) {
ERROR("SPACING",
"space required after that '$op' $at\n" . $hereptr);
}
# // is a comment
} elsif ($op eq '//') {
# No spaces for:
# ->
# : when part of a bitfield
} elsif ($op eq '->' || $opv eq ':B') {
if ($ctx =~ /Wx.|.xW/) {
ERROR("SPACING",
"spaces prohibited around that '$op' $at\n" . $hereptr);
}
# , must have a space on the right.
} elsif ($op eq ',') {
if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) {
ERROR("SPACING",
"space required after that '$op' $at\n" . $hereptr);
}
# '*' as part of a type definition -- reported already.
} elsif ($opv eq '*_') {
#warn "'*' is part of type\n";
# unary operators should have a space before and
# none after. May be left adjacent to another
# unary operator, or a cast
} elsif ($op eq '!' || $op eq '~' ||
$opv eq '*U' || $opv eq '-U' ||
$opv eq '&U' || $opv eq '&&U') {
if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) {
ERROR("SPACING",
"space required before that '$op' $at\n" . $hereptr);
}
if ($op eq '*' && $cc =~/\s*$Modifier\b/) {
# A unary '*' may be const
} elsif ($ctx =~ /.xW/) {
ERROR("SPACING",
"space prohibited after that '$op' $at\n" . $hereptr);
}
# unary ++ and unary -- are allowed no space on one side.
} elsif ($op eq '++' or $op eq '--') {
if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) {
ERROR("SPACING",
"space required one side of that '$op' $at\n" . $hereptr);
}
if ($ctx =~ /Wx[BE]/ ||
($ctx =~ /Wx./ && $cc =~ /^;/)) {
ERROR("SPACING",
"space prohibited before that '$op' $at\n" . $hereptr);
}
if ($ctx =~ /ExW/) {
ERROR("SPACING",
"space prohibited after that '$op' $at\n" . $hereptr);
}
# << and >> may either have or not have spaces both sides
} elsif ($op eq '<<' or $op eq '>>' or
$op eq '&' or $op eq '^' or $op eq '|' or
$op eq '+' or $op eq '-' or
$op eq '*' or $op eq '/' or
$op eq '%')
{
if ($ctx =~ /Wx[^WCE]|[^WCE]xW/) {
ERROR("SPACING",
"need consistent spacing around '$op' $at\n" .
$hereptr);
}
# A colon needs no spaces before when it is
# terminating a case value or a label.
} elsif ($opv eq ':C' || $opv eq ':L') {
if ($ctx =~ /Wx./) {
ERROR("SPACING",
"space prohibited before that '$op' $at\n" . $hereptr);
}
# All the others need spaces both sides.
} elsif ($ctx !~ /[EWC]x[CWE]/) {
my $ok = 0;
# Ignore email addresses
if (($op eq '<' &&
$cc =~ /^\S+\@\S+>/) ||
($op eq '>' &&
$ca =~ /<\S+\@\S+$/))
{
$ok = 1;
}
# Ignore ?:
if (($opv eq ':O' && $ca =~ /\?$/) ||
($op eq '?' && $cc =~ /^:/)) {
$ok = 1;
}
if ($ok == 0) {
ERROR("SPACING",
"spaces required around that '$op' $at\n" . $hereptr);
}
}
$off += length($elements[$n + 1]);
}
}
# check for multiple assignments
if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) {
CHK("MULTIPLE_ASSIGNMENTS",
"multiple assignments should be avoided\n" . $herecurr);
}
## # check for multiple declarations, allowing for a function declaration
## # continuation.
## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ &&
## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) {
##
## # Remove any bracketed sections to ensure we do not
## # falsly report the parameters of functions.
## my $ln = $line;
## while ($ln =~ s/\([^\(\)]*\)//g) {
## }
## if ($ln =~ /,/) {
## WARN("MULTIPLE_DECLARATION",
## "declaring multiple variables together should be avoided\n" . $herecurr);
## }
## }
#need space before brace following if, while, etc
if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\){/) ||
$line =~ /do\{/) {
ERROR("SPACING",
"space required before the open brace '{'\n" . $herecurr);
}
# closing brace should have a space following it when it has anything
# on the line
if ($line =~ /}(?!(?:,|;|\)))\S/) {
ERROR("SPACING",
"space required after that close brace '}'\n" . $herecurr);
}
# check spacing on square brackets
if ($line =~ /\[\s/ && $line !~ /\[\s*$/) {
ERROR("SPACING",
"space prohibited after that open square bracket '['\n" . $herecurr);
}
if ($line =~ /\s\]/) {
ERROR("SPACING",
"space prohibited before that close square bracket ']'\n" . $herecurr);
}
# check spacing on parentheses
if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ &&
$line !~ /for\s*\(\s+;/) {
ERROR("SPACING",
"space prohibited after that open parenthesis '('\n" . $herecurr);
}
if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ &&
$line !~ /for\s*\(.*;\s+\)/ &&
$line !~ /:\s+\)/) {
ERROR("SPACING",
"space prohibited before that close parenthesis ')'\n" . $herecurr);
}
#goto labels aren't indented, allow a single space however
if ($line=~/^.\s+[A-Za-z\d_]+:(?![0-9]+)/ and
!($line=~/^. [A-Za-z\d_]+:/) and !($line=~/^.\s+default:/)) {
WARN("INDENTED_LABEL",
"labels should not be indented\n" . $herecurr);
}
# Return is not a function.
if (defined($stat) && $stat =~ /^.\s*return(\s*)(\(.*);/s) {
my $spacing = $1;
my $value = $2;
# Flatten any parentheses
$value =~ s/\(/ \(/g;
$value =~ s/\)/\) /g;
while ($value =~ s/\[[^\[\]]*\]/1/ ||
$value !~ /(?:$Ident|-?$Constant)\s*
$Compare\s*
(?:$Ident|-?$Constant)/x &&
$value =~ s/\([^\(\)]*\)/1/) {
}
#print "value<$value>\n";
if ($value =~ /^\s*(?:$Ident|-?$Constant)\s*$/) {
ERROR("RETURN_PARENTHESES",
"return is not a function, parentheses are not required\n" . $herecurr);
} elsif ($spacing !~ /\s+/) {
ERROR("SPACING",
"space required before the open parenthesis '('\n" . $herecurr);
}
}
# Return of what appears to be an errno should normally be -'ve
if ($line =~ /^.\s*return\s*(E[A-Z]*)\s*;/) {
my $name = $1;
if ($name ne 'EOF' && $name ne 'ERROR') {
WARN("USE_NEGATIVE_ERRNO",
"return of an errno should typically be -ve (return -$1)\n" . $herecurr);
}
}
# Need a space before open parenthesis after if, while etc
if ($line=~/\b(if|while|for|switch)\(/) {
ERROR("SPACING", "space required before the open parenthesis '('\n" . $herecurr);
}
# Check for illegal assignment in if conditional -- and check for trailing
# statements after the conditional.
if ($line =~ /do\s*(?!{)/) {
($stat, $cond, $line_nr_next, $remain_next, $off_next) =
ctx_statement_block($linenr, $realcnt, 0)
if (!defined $stat);
my ($stat_next) = ctx_statement_block($line_nr_next,
$remain_next, $off_next);
$stat_next =~ s/\n./\n /g;
##print "stat<$stat> stat_next<$stat_next>\n";
if ($stat_next =~ /^\s*while\b/) {
# If the statement carries leading newlines,
# then count those as offsets.
my ($whitespace) =
($stat_next =~ /^((?:\s*\n[+-])*\s*)/s);
my $offset =
statement_rawlines($whitespace) - 1;
$suppress_whiletrailers{$line_nr_next +
$offset} = 1;
}
}
if (!defined $suppress_whiletrailers{$linenr} &&
$line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) {
my ($s, $c) = ($stat, $cond);
if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) {
ERROR("ASSIGN_IN_IF",
"do not use assignment in if condition\n" . $herecurr);
}
# Find out what is on the end of the line after the
# conditional.
substr($s, 0, length($c), '');
$s =~ s/\n.*//g;
$s =~ s/$;//g; # Remove any comments
if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ &&
$c !~ /}\s*while\s*/)
{
# Find out how long the conditional actually is.
my @newlines = ($c =~ /\n/gs);
my $cond_lines = 1 + $#newlines;
my $stat_real = '';
$stat_real = raw_line($linenr, $cond_lines)
. "\n" if ($cond_lines);
if (defined($stat_real) && $cond_lines > 1) {
$stat_real = "[...]\n$stat_real";
}
ERROR("TRAILING_STATEMENTS",
"trailing statements should be on next line\n" . $herecurr . $stat_real);
}
}
# Check for bitwise tests written as boolean
if ($line =~ /
(?:
(?:\[|\(|\&\&|\|\|)
\s*0[xX][0-9]+\s*
(?:\&\&|\|\|)
|
(?:\&\&|\|\|)
\s*0[xX][0-9]+\s*
(?:\&\&|\|\||\)|\])
)/x)
{
WARN("HEXADECIMAL_BOOLEAN_TEST",
"boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr);
}
# if and else should not have general statements after it
if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) {
my $s = $1;
$s =~ s/$;//g; # Remove any comments
if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) {
ERROR("TRAILING_STATEMENTS",
"trailing statements should be on next line\n" . $herecurr);
}
}
# if should not continue a brace
if ($line =~ /}\s*if\b/) {
ERROR("TRAILING_STATEMENTS",
"trailing statements should be on next line\n" .
$herecurr);
}
# case and default should not have general statements after them
if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g &&
$line !~ /\G(?:
(?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$|
\s*return\s+
)/xg)
{
ERROR("TRAILING_STATEMENTS",
"trailing statements should be on next line\n" . $herecurr);
}
# Check for }else {, these must be at the same
# indent level to be relevant to each other.
if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ and
$previndent == $indent) {
ERROR("ELSE_AFTER_BRACE",
"else should follow close brace '}'\n" . $hereprev);
}
if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ and
$previndent == $indent) {
my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0);
# Find out what is on the end of the line after the
# conditional.
substr($s, 0, length($c), '');
$s =~ s/\n.*//g;
if ($s =~ /^\s*;/) {
ERROR("WHILE_AFTER_BRACE",
"while should follow close brace '}'\n" . $hereprev);
}
}
#studly caps, commented out until figure out how to distinguish between use of existing and adding new
# if (($line=~/[\w_][a-z\d]+[A-Z]/) and !($line=~/print/)) {
# print "No studly caps, use _\n";
# print "$herecurr";
# $clean = 0;
# }
#no spaces allowed after \ in define
if ($line=~/\#\s*define.*\\\s$/) {
WARN("WHITESPACE_AFTER_LINE_CONTINUATION",
"Whitepspace after \\ makes next lines useless\n" . $herecurr);
}
#warn if is #included and is available (uses RAW line)
if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\}) {
my $file = "$1.h";
my $checkfile = "include/linux/$file";
if (-f "$root/$checkfile" &&
$realfile ne $checkfile &&
$1 !~ /$allowed_asm_includes/)
{
if ($realfile =~ m{^arch/}) {
CHK("ARCH_INCLUDE_LINUX",
"Consider using #include instead of \n" . $herecurr);
} else {
WARN("INCLUDE_LINUX",
"Use #include instead of \n" . $herecurr);
}
}
}
# multi-statement macros should be enclosed in a do while loop, grab the
# first statement and ensure its the whole macro if its not enclosed
# in a known good container
if ($realfile !~ m@/vmlinux.lds.h$@ &&
$line =~ /^.\s*\#\s*define\s*$Ident(\()?/) {
my $ln = $linenr;
my $cnt = $realcnt;
my ($off, $dstat, $dcond, $rest);
my $ctx = '';
($dstat, $dcond, $ln, $cnt, $off) =
ctx_statement_block($linenr, $realcnt, 0);
$ctx = $dstat;
#print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n";
#print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n";
$dstat =~ s/^.\s*\#\s*define\s+$Ident(?:\([^\)]*\))?\s*//;
$dstat =~ s/$;//g;
$dstat =~ s/\\\n.//g;
$dstat =~ s/^\s*//s;
$dstat =~ s/\s*$//s;
# Flatten any parentheses and braces
while ($dstat =~ s/\([^\(\)]*\)/1/ ||
$dstat =~ s/\{[^\{\}]*\}/1/ ||
$dstat =~ s/\[[^\[\]]*\]/1/)
{
}
# Flatten any obvious string concatentation.
while ($dstat =~ s/("X*")\s*$Ident/$1/ ||
$dstat =~ s/$Ident\s*("X*")/$1/)
{
}
my $exceptions = qr{
$Declare|
module_param_named|
MODULE_PARAM_DESC|
DECLARE_PER_CPU|
DEFINE_PER_CPU|
__typeof__\(|
union|
struct|
\.$Ident\s*=\s*|
^\"|\"$
}x;
#print "REST<$rest> dstat<$dstat> ctx<$ctx>\n";
if ($dstat ne '' &&
$dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(),
$dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo();
$dstat !~ /^[!~-]?(?:$Ident|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo
$dstat !~ /^'X'$/ && # character constants
$dstat !~ /$exceptions/ &&
$dstat !~ /^\.$Ident\s*=/ && # .foo =
$dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...)
$dstat !~ /^for\s*$Constant$/ && # for (...)
$dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar()
$dstat !~ /^do\s*{/ && # do {...
$dstat !~ /^\(\{/) # ({...
{
$ctx =~ s/\n*$//;
my $herectx = $here . "\n";
my $cnt = statement_rawlines($ctx);
for (my $n = 0; $n < $cnt; $n++) {
$herectx .= raw_line($linenr, $n) . "\n";
}
if ($dstat =~ /;/) {
ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE",
"Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx");
} else {
ERROR("COMPLEX_MACRO",
"Macros with complex values should be enclosed in parenthesis\n" . "$herectx");
}
}
}
# make sure symbols are always wrapped with VMLINUX_SYMBOL() ...
# all assignments may have only one of the following with an assignment:
# .
# ALIGN(...)
# VMLINUX_SYMBOL(...)
if ($realfile eq 'vmlinux.lds.h' && $line =~ /(?:(?:^|\s)$Ident\s*=|=\s*$Ident(?:\s|$))/) {
WARN("MISSING_VMLINUX_SYMBOL",
"vmlinux.lds.h needs VMLINUX_SYMBOL() around C-visible symbols\n" . $herecurr);
}
# check for redundant bracing round if etc
if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) {
my ($level, $endln, @chunks) =
ctx_statement_full($linenr, $realcnt, 1);
#print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n";
#print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n";
if ($#chunks > 0 && $level == 0) {
my @allowed = ();
my $allow = 0;
my $seen = 0;
my $herectx = $here . "\n";
my $ln = $linenr - 1;
for my $chunk (@chunks) {
my ($cond, $block) = @{$chunk};
# If the condition carries leading newlines, then count those as offsets.
my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s);
my $offset = statement_rawlines($whitespace) - 1;
$allowed[$allow] = 0;
#print "COND<$cond> whitespace<$whitespace> offset<$offset>\n";
# We have looked at and allowed this specific line.
$suppress_ifbraces{$ln + $offset} = 1;
$herectx .= "$rawlines[$ln + $offset]\n[...]\n";
$ln += statement_rawlines($block) - 1;
substr($block, 0, length($cond), '');
$seen++ if ($block =~ /^\s*{/);
#print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n";
if (statement_lines($cond) > 1) {
#print "APW: ALLOWED: cond<$cond>\n";
$allowed[$allow] = 1;
}
if ($block =~/\b(?:if|for|while)\b/) {
#print "APW: ALLOWED: block<$block>\n";
$allowed[$allow] = 1;
}
if (statement_block_size($block) > 1) {
#print "APW: ALLOWED: lines block<$block>\n";
$allowed[$allow] = 1;
}
$allow++;
}
if ($seen) {
my $sum_allowed = 0;
foreach (@allowed) {
$sum_allowed += $_;
}
if ($sum_allowed == 0) {
WARN("BRACES",
"braces {} are not necessary for any arm of this statement\n" . $herectx);
} elsif ($sum_allowed != $allow &&
$seen != $allow) {
CHK("BRACES",
"braces {} should be used on all arms of this statement\n" . $herectx);
}
}
}
}
if (!defined $suppress_ifbraces{$linenr - 1} &&
$line =~ /\b(if|while|for|else)\b/) {
my $allowed = 0;
# Check the pre-context.
if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) {
#print "APW: ALLOWED: pre<$1>\n";
$allowed = 1;
}
my ($level, $endln, @chunks) =
ctx_statement_full($linenr, $realcnt, $-[0]);
# Check the condition.
my ($cond, $block) = @{$chunks[0]};
#print "CHECKING<$linenr> cond<$cond> block<$block>\n";
if (defined $cond) {
substr($block, 0, length($cond), '');
}
if (statement_lines($cond) > 1) {
#print "APW: ALLOWED: cond<$cond>\n";
$allowed = 1;
}
if ($block =~/\b(?:if|for|while)\b/) {
#print "APW: ALLOWED: block<$block>\n";
$allowed = 1;
}
if (statement_block_size($block) > 1) {
#print "APW: ALLOWED: lines block<$block>\n";
$allowed = 1;
}
# Check the post-context.
if (defined $chunks[1]) {
my ($cond, $block) = @{$chunks[1]};
if (defined $cond) {
substr($block, 0, length($cond), '');
}
if ($block =~ /^\s*\{/) {
#print "APW: ALLOWED: chunk-1 block<$block>\n";
$allowed = 1;
}
}
if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) {
my $herectx = $here . "\n";
my $cnt = statement_rawlines($block);
for (my $n = 0; $n < $cnt; $n++) {
$herectx .= raw_line($linenr, $n) . "\n";
}
WARN("BRACES",
"braces {} are not necessary for single statement blocks\n" . $herectx);
}
}
# don't include deprecated include files (uses RAW line)
for my $inc (@dep_includes) {
if ($rawline =~ m@^.\s*\#\s*include\s*\<$inc>@) {
ERROR("DEPRECATED_INCLUDE",
"Don't use <$inc>: see Documentation/feature-removal-schedule.txt\n" . $herecurr);
}
}
# don't use deprecated functions
for my $func (@dep_functions) {
if ($line =~ /\b$func\b/) {
ERROR("DEPRECATED_FUNCTION",
"Don't use $func(): see Documentation/feature-removal-schedule.txt\n" . $herecurr);
}
}
# no volatiles please
my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b};
if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) {
WARN("VOLATILE",
"Use of volatile is usually wrong: see Documentation/volatile-considered-harmful.txt\n" . $herecurr);
}
# warn about #if 0
if ($line =~ /^.\s*\#\s*if\s+0\b/) {
CHK("REDUNDANT_CODE",
"if this code is redundant consider removing it\n" .
$herecurr);
}
# check for needless kfree() checks
if ($prevline =~ /\bif\s*\(([^\)]*)\)/) {
my $expr = $1;
if ($line =~ /\bkfree\(\Q$expr\E\);/) {
WARN("NEEDLESS_KFREE",
"kfree(NULL) is safe this check is probably not required\n" . $hereprev);
}
}
# check for needless usb_free_urb() checks
if ($prevline =~ /\bif\s*\(([^\)]*)\)/) {
my $expr = $1;
if ($line =~ /\busb_free_urb\(\Q$expr\E\);/) {
WARN("NEEDLESS_USB_FREE_URB",
"usb_free_urb(NULL) is safe this check is probably not required\n" . $hereprev);
}
}
# prefer usleep_range over udelay
if ($line =~ /\budelay\s*\(\s*(\w+)\s*\)/) {
# ignore udelay's < 10, however
if (! (($1 =~ /(\d+)/) && ($1 < 10)) ) {
CHK("USLEEP_RANGE",
"usleep_range is preferred over udelay; see Documentation/timers/timers-howto.txt\n" . $line);
}
}
# warn about unexpectedly long msleep's
if ($line =~ /\bmsleep\s*\((\d+)\);/) {
if ($1 < 20) {
WARN("MSLEEP",
"msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt\n" . $line);
}
}
# warn about #ifdefs in C files
# if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) {
# print "#ifdef in C files should be avoided\n";
# print "$herecurr";
# $clean = 0;
# }
# warn about spacing in #ifdefs
if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) {
ERROR("SPACING",
"exactly one space required after that #$1\n" . $herecurr);
}
# check for spinlock_t definitions without a comment.
if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ ||
$line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) {
my $which = $1;
if (!ctx_has_comment($first_line, $linenr)) {
CHK("UNCOMMENTED_DEFINITION",
"$1 definition without comment\n" . $herecurr);
}
}
# check for memory barriers without a comment.
if ($line =~ /\b(mb|rmb|wmb|read_barrier_depends|smp_mb|smp_rmb|smp_wmb|smp_read_barrier_depends)\(/) {
if (!ctx_has_comment($first_line, $linenr)) {
CHK("MEMORY_BARRIER",
"memory barrier without comment\n" . $herecurr);
}
}
# check of hardware specific defines
if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) {
CHK("ARCH_DEFINES",
"architecture specific defines should be avoided\n" . $herecurr);
}
# Check that the storage class is at the beginning of a declaration
if ($line =~ /\b$Storage\b/ && $line !~ /^.\s*$Storage\b/) {
WARN("STORAGE_CLASS",
"storage class should be at the beginning of the declaration\n" . $herecurr)
}
# check the location of the inline attribute, that it is between
# storage class and type.
if ($line =~ /\b$Type\s+$Inline\b/ ||
$line =~ /\b$Inline\s+$Storage\b/) {
ERROR("INLINE_LOCATION",
"inline keyword should sit between storage class and type\n" . $herecurr);
}
# Check for __inline__ and __inline, prefer inline
if ($line =~ /\b(__inline__|__inline)\b/) {
WARN("INLINE",
"plain inline is preferred over $1\n" . $herecurr);
}
# Check for __attribute__ packed, prefer __packed
if ($line =~ /\b__attribute__\s*\(\s*\(.*\bpacked\b/) {
WARN("PREFER_PACKED",
"__packed is preferred over __attribute__((packed))\n" . $herecurr);
}
# Check for __attribute__ aligned, prefer __aligned
if ($line =~ /\b__attribute__\s*\(\s*\(.*aligned/) {
WARN("PREFER_ALIGNED",
"__aligned(size) is preferred over __attribute__((aligned(size)))\n" . $herecurr);
}
# Check for __attribute__ format(printf, prefer __printf
if ($line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf/) {
WARN("PREFER_PRINTF",
"__printf(string-index, first-to-check) is preferred over __attribute__((format(printf, string-index, first-to-check)))\n" . $herecurr);
}
# Check for __attribute__ format(scanf, prefer __scanf
if ($line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\b/) {
WARN("PREFER_SCANF",
"__scanf(string-index, first-to-check) is preferred over __attribute__((format(scanf, string-index, first-to-check)))\n" . $herecurr);
}
# check for sizeof(&)
if ($line =~ /\bsizeof\s*\(\s*\&/) {
WARN("SIZEOF_ADDRESS",
"sizeof(& should be avoided\n" . $herecurr);
}
# check for line continuations in quoted strings with odd counts of "
if ($rawline =~ /\\$/ && $rawline =~ tr/"/"/ % 2) {
WARN("LINE_CONTINUATIONS",
"Avoid line continuations in quoted strings\n" . $herecurr);
}
# Check for misused memsets
if ($^V && $^V ge 5.10.0 &&
defined $stat &&
$stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/s) {
my $ms_addr = $2;
my $ms_val = $7;
my $ms_size = $12;
if ($ms_size =~ /^(0x|)0$/i) {
ERROR("MEMSET",
"memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n");
} elsif ($ms_size =~ /^(0x|)1$/i) {
WARN("MEMSET",
"single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n");
}
}
# typecasts on min/max could be min_t/max_t
if ($^V && $^V ge 5.10.0 &&
defined $stat &&
$stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) {
if (defined $2 || defined $7) {
my $call = $1;
my $cast1 = deparenthesize($2);
my $arg1 = $3;
my $cast2 = deparenthesize($7);
my $arg2 = $8;
my $cast;
if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) {
$cast = "$cast1 or $cast2";
} elsif ($cast1 ne "") {
$cast = $cast1;
} else {
$cast = $cast2;
}
WARN("MINMAX",
"$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n");
}
}
# check for new externs in .c files.
if ($realfile =~ /\.c$/ && defined $stat &&
$stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s)
{
my $function_name = $1;
my $paren_space = $2;
my $s = $stat;
if (defined $cond) {
substr($s, 0, length($cond), '');
}
if ($s =~ /^\s*;/ &&
$function_name ne 'uninitialized_var')
{
WARN("AVOID_EXTERNS",
"externs should be avoided in .c files\n" . $herecurr);
}
if ($paren_space =~ /\n/) {
WARN("FUNCTION_ARGUMENTS",
"arguments for function declarations should follow identifier\n" . $herecurr);
}
} elsif ($realfile =~ /\.c$/ && defined $stat &&
$stat =~ /^.\s*extern\s+/)
{
WARN("AVOID_EXTERNS",
"externs should be avoided in .c files\n" . $herecurr);
}
# checks for new __setup's
if ($rawline =~ /\b__setup\("([^"]*)"/) {
my $name = $1;
if (!grep(/$name/, @setup_docs)) {
CHK("UNDOCUMENTED_SETUP",
"__setup appears un-documented -- check Documentation/kernel-parameters.txt\n" . $herecurr);
}
}
# check for pointless casting of kmalloc return
if ($line =~ /\*\s*\)\s*[kv][czm]alloc(_node){0,1}\b/) {
WARN("UNNECESSARY_CASTS",
"unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr);
}
# check for multiple semicolons
if ($line =~ /;\s*;\s*$/) {
WARN("ONE_SEMICOLON",
"Statements terminations use 1 semicolon\n" . $herecurr);
}
# check for gcc specific __FUNCTION__
if ($line =~ /__FUNCTION__/) {
WARN("USE_FUNC",
"__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr);
}
# check for use of yield()
if ($line =~ /\byield\s*\(\s*\)/) {
WARN("YIELD",
"Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr);
}
# check for semaphores initialized locked
if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) {
WARN("CONSIDER_COMPLETION",
"consider using a completion\n" . $herecurr);
}
# recommend kstrto* over simple_strto* and strict_strto*
if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) {
WARN("CONSIDER_KSTRTO",
"$1 is obsolete, use k$3 instead\n" . $herecurr);
}
# check for __initcall(), use device_initcall() explicitly please
if ($line =~ /^.\s*__initcall\s*\(/) {
WARN("USE_DEVICE_INITCALL",
"please use device_initcall() instead of __initcall()\n" . $herecurr);
}
# check for various ops structs, ensure they are const.
my $struct_ops = qr{acpi_dock_ops|
address_space_operations|
backlight_ops|
block_device_operations|
dentry_operations|
dev_pm_ops|
dma_map_ops|
extent_io_ops|
file_lock_operations|
file_operations|
hv_ops|
ide_dma_ops|
intel_dvo_dev_ops|
item_operations|
iwl_ops|
kgdb_arch|
kgdb_io|
kset_uevent_ops|
lock_manager_operations|
microcode_ops|
mtrr_ops|
neigh_ops|
nlmsvc_binding|
pci_raw_ops|
pipe_buf_operations|
platform_hibernation_ops|
platform_suspend_ops|
proto_ops|
rpc_pipe_ops|
seq_operations|
snd_ac97_build_ops|
soc_pcmcia_socket_ops|
stacktrace_ops|
sysfs_ops|
tty_operations|
usb_mon_operations|
wd_ops}x;
if ($line !~ /\bconst\b/ &&
$line =~ /\bstruct\s+($struct_ops)\b/) {
WARN("CONST_STRUCT",
"struct $1 should normally be const\n" .
$herecurr);
}
# use of NR_CPUS is usually wrong
# ignore definitions of NR_CPUS and usage to define arrays as likely right
if ($line =~ /\bNR_CPUS\b/ &&
$line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ &&
$line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ &&
$line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ &&
$line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ &&
$line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/)
{
WARN("NR_CPUS",
"usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr);
}
# check for %L{u,d,i} in strings
my $string;
while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) {
$string = substr($rawline, $-[1], $+[1] - $-[1]);
$string =~ s/%%/__/g;
if ($string =~ /(?mutex.\n" . $herecurr);
}
}
if ($line =~ /debugfs_create_file.*S_IWUGO/ ||
$line =~ /DEVICE_ATTR.*S_IWUGO/ ) {
WARN("EXPORTED_WORLD_WRITABLE",
"Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr);
}
}
# If we have no input at all, then there is nothing to report on
# so just keep quiet.
if ($#rawlines == -1) {
exit(0);
}
# In mailback mode only produce a report in the negative, for
# things that appear to be patches.
if ($mailback && ($clean == 1 || !$is_patch)) {
exit(0);
}
# This is not a patch, and we are are in 'no-patch' mode so
# just keep quiet.
if (!$chk_patch && !$is_patch) {
exit(0);
}
if (!$is_patch) {
ERROR("NOT_UNIFIED_DIFF",
"Does not appear to be a unified-diff format patch\n");
}
if ($is_patch && $chk_signoff && $signoff == 0) {
ERROR("MISSING_SIGN_OFF",
"Missing Signed-off-by: line(s)\n");
}
print report_dump();
if ($summary && !($clean == 1 && $quiet == 1)) {
print "$filename " if ($summary_file);
print "total: $cnt_error errors, $cnt_warn warnings, " .
(($check)? "$cnt_chk checks, " : "") .
"$cnt_lines lines checked\n";
print "\n" if ($quiet == 0);
}
if ($quiet == 0) {
if ($^V lt 5.10.0) {
print("NOTE: perl $^V is not modern enough to detect all possible issues.\n");
print("An upgrade to at least perl v5.10.0 is suggested.\n\n");
}
# If there were whitespace errors which cleanpatch can fix
# then suggest that.
if ($rpt_cleaners) {
print "NOTE: whitespace errors detected, you may wish to use scripts/cleanpatch or\n";
print " scripts/cleanfile\n\n";
$rpt_cleaners = 0;
}
}
if ($quiet == 0 && keys %ignore_type) {
print "NOTE: Ignored message types:";
foreach my $ignore (sort keys %ignore_type) {
print " $ignore";
}
print "\n\n";
}
if ($clean == 1 && $quiet == 0) {
print "$vname has no obvious style problems and is ready for submission.\n"
}
if ($clean == 0 && $quiet == 0) {
print << "EOM";
$vname has style problems, please review.
If any of these errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
EOM
}
return $clean;
}
tgt-1.0.80/scripts/deb/ 0000775 0000000 0000000 00000000000 13747533545 0014637 5 ustar 00root root 0000000 0000000 tgt-1.0.80/scripts/deb/changelog 0000664 0000000 0000000 00000000261 13747533545 0016510 0 ustar 00root root 0000000 0000000 tgt (1.0.37-1) UNRELEASED; urgency=low
* Non-maintainer upload.
*
* Initial release. (Closes: #XXXXXX)
-- Roi Dayan Thu, 18 Jul 2013 09:31:00 +0300
tgt-1.0.80/scripts/deb/compat 0000664 0000000 0000000 00000000002 13747533545 0016035 0 ustar 00root root 0000000 0000000 8
tgt-1.0.80/scripts/deb/control 0000664 0000000 0000000 00000001120 13747533545 0016234 0 ustar 00root root 0000000 0000000 Source: tgt
Maintainer: FUJITA Tomonori
Section: net
Priority: optional
Standards-Version: 3.9.1
Build-Depends: debhelper (>= 8), dpkg-dev (>= 1.13.19), bash-completion,
librdmacm-dev, libibverbs-dev, xsltproc, docbook-xsl
Homepage: http://stgt.berlios.de/
Package: tgt
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, librdmacm1, libconfig-general-perl
Description: The SCSI target daemon and utility programs
The SCSI target package contains the daemon and tools to setup a SCSI targets.
Currently, software iSCSI targets are supported.
tgt-1.0.80/scripts/deb/copyright 0000664 0000000 0000000 00000001223 13747533545 0016570 0 ustar 00root root 0000000 0000000 License:
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see http://www.gnu.org/licenses/.
tgt-1.0.80/scripts/deb/init 0000775 0000000 0000000 00000007326 13747533545 0015540 0 ustar 00root root 0000000 0000000 #!/bin/sh
# This is an example init.d script for stopping/starting/reconfiguring tgtd.
### BEGIN INIT INFO
# Provides: tgtd
# Required-Start: $network
# Required-Stop: $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Starts and stops the generic storage target daemon
# Description: tgtd provides the SCSI and software transport target state machine daemon.
### END INIT INFO
TGTD_CONFIG=/etc/tgt/targets.conf
TASK=$1
start()
{
echo "Starting target framework daemon"
# Start tgtd first.
tgtd &>/dev/null
RETVAL=$?
if [ "$RETVAL" -ne 0 ] ; then
echo "Could not start tgtd (is tgtd already running?)"
exit 1
fi
sleep 1
# Put tgtd into "offline" state until all the targets are configured.
# We don't want initiators to (re)connect and fail the connection
# if it's not ready.
tgtadm --op update --mode sys --name State -v offline
# Configure the targets.
tgt-admin -e -c $TGTD_CONFIG
# Put tgtd into "ready" state.
tgtadm --op update --mode sys --name State -v ready
}
stop()
{
if [ -n "$RUNLEVEL" ] && [ "$RUNLEVEL" -eq 0 -o "$RUNLEVEL" -eq 6 ] ; then
forcedstop
fi
echo "Stopping target framework daemon"
# Remove all targets. It only removes targets which are not in use.
tgt-admin --update ALL -c /dev/null &>/dev/null
# tgtd will exit if all targets were removed
tgtadm --op delete --mode system &>/dev/null
RETVAL=$?
if [ "$RETVAL" -eq 107 ] ; then
echo "tgtd is not running"
[ "$TASK" != "restart" ] && exit 1
elif [ "$RETVAL" -ne 0 ] ; then
echo "Some initiators are still connected - could not stop tgtd"
exit 2
fi
echo -n
}
forcedstop()
{
# NOTE: Forced shutdown of the iscsi target may cause data corruption
# for initiators that are connected.
echo "Force-stopping target framework daemon"
# Offline everything first. May be needed if we're rebooting, but
# expect the initiators to reconnect cleanly when we boot again
# (i.e. we don't want them to reconnect to a tgtd which is still
# working, but the target is gone).
tgtadm --op update --mode sys --name State -v offline &>/dev/null
RETVAL=$?
if [ "$RETVAL" -eq 107 ] ; then
echo "tgtd is not running"
[ "$TASK" != "restart" ] && exit 1
else
tgt-admin --offline ALL
# Remove all targets, even if they are still in use.
tgt-admin --update ALL -c /dev/null -f
# It will shut down tgtd only after all targets were removed.
tgtadm --op delete --mode system
RETVAL=$?
if [ "$RETVAL" -ne 0 ] ; then
echo "Failed to shutdown tgtd"
exit 1
fi
fi
echo -n
}
reload()
{
echo "Updating target framework daemon configuration"
# Update configuration for targets. Only targets which
# are not in use will be updated.
tgt-admin --update ALL -c $TGTD_CONFIG &>/dev/null
RETVAL=$?
if [ "$RETVAL" -eq 107 ] ; then
echo "tgtd is not running"
exit 1
fi
}
forcedreload()
{
echo "Force-updating target framework daemon configuration"
# Update configuration for targets, even those in use.
tgt-admin --update ALL -f -c $TGTD_CONFIG &>/dev/null
RETVAL=$?
if [ "$RETVAL" -eq 107 ] ; then
echo "tgtd is not running"
exit 1
fi
}
status()
{
# Don't name this script "tgtd"...
TGTD_PROC=$(ps -C tgtd | grep -c tgtd)
if [ "$TGTD_PROC" -eq 2 ] ; then
echo "tgtd is running. Run 'tgt-admin -s' to see detailed target info."
else
echo "tgtd is NOT running."
fi
}
case $1 in
start)
start
;;
stop)
stop
;;
forcedstop)
forcedstop
;;
restart)
TASK=restart
stop && start
;;
forcedrestart)
TASK=restart
forcedstop && start
;;
reload)
reload
;;
force-reload)
forcedreload
;;
status)
status
;;
*)
echo "Usage: $0 {start|stop|forcedstop|restart|forcedrestart|reload|forcedreload|status}"
exit 2
;;
esac
tgt-1.0.80/scripts/deb/patches/ 0000775 0000000 0000000 00000000000 13747533545 0016266 5 ustar 00root root 0000000 0000000 tgt-1.0.80/scripts/deb/patches/0001-Use-local-docbook-for-generating-docs.patch 0000664 0000000 0000000 00000007416 13747533545 0026772 0 ustar 00root root 0000000 0000000 From 1edc4f6428dfdbd86a2d47177148e12fd617c3b1 Mon Sep 17 00:00:00 2001
From: Roi Dayan
Date: Thu, 10 Apr 2014 17:36:45 +0300
Subject: [PATCH] Use local docbook for generating docs
Signed-off-by: Roi Dayan
---
doc/Makefile | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/doc/Makefile b/doc/Makefile
index 0516dc8..e56695d 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -34,40 +34,40 @@ clean:
-rm -f manpages htmlpages
manpages/tgtd.8: tgtd.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $<
htmlpages/tgtd.8.html: tgtd.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $<
manpages/tgtadm.8: tgtadm.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $<
htmlpages/tgtadm.8.html: tgtadm.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $<
manpages/tgt-admin.8: tgt-admin.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $<
htmlpages/tgt-admin.8.html: tgt-admin.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $<
manpages/tgtimg.8: tgtimg.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $<
htmlpages/tgtimg.8.html: tgtimg.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $<
manpages/targets.conf.5: targets.conf.5.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $<
htmlpages/targets.conf.5.html: targets.conf.5.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $<
manpages/tgt-setup-lun.8: tgt-setup-lun.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $<
htmlpages/tgt-setup-lun.8.html: tgt-setup-lun.8.xml
- -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $<
+ -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $<
xmlman: $(XMLMAN)
--
1.8.1.2
tgt-1.0.80/scripts/deb/rules 0000775 0000000 0000000 00000000331 13747533545 0015714 0 ustar 00root root 0000000 0000000 #!/usr/bin/make -f
DEB_MAKE_ENVVARS += ISCSI_RDMA=1
%:
dh $@ --with bash-completion
override_dh_auto_build:
dh_auto_build -- $(DEB_MAKE_ENVVARS)
override_dh_auto_install:
dh_auto_install -- $(DEB_MAKE_ENVVARS)
tgt-1.0.80/scripts/deb/source/ 0000775 0000000 0000000 00000000000 13747533545 0016137 5 ustar 00root root 0000000 0000000 tgt-1.0.80/scripts/deb/source/format 0000664 0000000 0000000 00000000014 13747533545 0017345 0 ustar 00root root 0000000 0000000 3.0 (quilt)
tgt-1.0.80/scripts/deb/tgt.bash-completion 0000664 0000000 0000000 00000000033 13747533545 0020437 0 ustar 00root root 0000000 0000000 scripts/tgt.bashcomp.sh tgt tgt-1.0.80/scripts/initd.sample 0000775 0000000 0000000 00000007307 13747533545 0016431 0 ustar 00root root 0000000 0000000 #!/bin/sh
# This is an example init.d script for stopping/starting/reconfiguring tgtd.
# chkconfig: 345 20 80
#
### BEGIN INIT INFO
# Provides: tgtd
# Required-Start: $network
# Required-Stop: $network
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Short-Description: Starts and stops the generic storage target daemon
# Description: tgtd provides the SCSI and software transport target state machine daemon.
### END INIT INFO
TGTD_CONFIG=/etc/tgt/targets.conf
TASK=$1
start()
{
echo "Starting target framework daemon"
# Start tgtd first.
tgtd &>/dev/null
RETVAL=$?
if [ "$RETVAL" -ne 0 ] ; then
echo "Could not start tgtd (is tgtd already running?)"
exit 1
fi
# Put tgtd into "offline" state until all the targets are configured.
# We don't want initiators to (re)connect and fail the connection
# if it's not ready.
tgtadm --op update --mode sys --name State -v offline
# Configure the targets.
tgt-admin -e -c $TGTD_CONFIG
# Put tgtd into "ready" state.
tgtadm --op update --mode sys --name State -v ready
}
stop()
{
if [ "$RUNLEVEL" == 0 -o "$RUNLEVEL" == 6 ] ; then
forcedstop
fi
echo "Stopping target framework daemon"
# Remove all targets. It only removes targets which are not in use.
tgt-admin --update ALL -c /dev/null &>/dev/null
# tgtd will exit if all targets were removed
tgtadm --op delete --mode system &>/dev/null
RETVAL=$?
if [ "$RETVAL" -eq 107 ] ; then
echo "tgtd is not running"
[ "$TASK" != "restart" ] && exit 1
elif [ "$RETVAL" -ne 0 ] ; then
echo "Some initiators are still connected - could not stop tgtd"
exit 2
fi
echo -n
}
forcedstop()
{
# NOTE: Forced shutdown of the iscsi target may cause data corruption
# for initiators that are connected.
echo "Force-stopping target framework daemon"
# Offline everything first. May be needed if we're rebooting, but
# expect the initiators to reconnect cleanly when we boot again
# (i.e. we don't want them to reconnect to a tgtd which is still
# working, but the target is gone).
tgtadm --op update --mode sys --name State -v offline &>/dev/null
RETVAL=$?
if [ "$RETVAL" -eq 107 ] ; then
echo "tgtd is not running"
[ "$TASK" != "restart" ] && exit 1
else
tgt-admin --offline ALL
# Remove all targets, even if they are still in use.
tgt-admin --update ALL -c /dev/null -f
# It will shut down tgtd only after all targets were removed.
tgtadm --op delete --mode system
RETVAL=$?
if [ "$RETVAL" -ne 0 ] ; then
echo "Failed to shutdown tgtd"
exit 1
fi
fi
echo -n
}
reload()
{
echo "Updating target framework daemon configuration"
# Update configuration for targets. Only targets which
# are not in use will be updated.
tgt-admin --update ALL -c $TGTD_CONFIG &>/dev/null
RETVAL=$?
if [ "$RETVAL" -eq 107 ] ; then
echo "tgtd is not running"
exit 1
fi
}
forcedreload()
{
echo "Force-updating target framework daemon configuration"
# Update configuration for targets, even those in use.
tgt-admin --update ALL -f -c $TGTD_CONFIG &>/dev/null
RETVAL=$?
if [ "$RETVAL" -eq 107 ] ; then
echo "tgtd is not running"
exit 1
fi
}
status()
{
# Don't name this script "tgtd"...
TGTD_PROC=$(ps -C tgtd | grep -c tgtd)
if [ "$TGTD_PROC" -eq 2 ] ; then
echo "tgtd is running. Run 'tgt-admin -s' to see detailed target info."
else
echo "tgtd is NOT running."
fi
}
case $1 in
start)
start
;;
stop)
stop
;;
forcedstop)
forcedstop
;;
restart)
TASK=restart
stop && start
;;
forcedrestart)
TASK=restart
forcedstop && start
;;
reload)
reload
;;
forcedreload)
forcedreload
;;
status)
status
;;
*)
echo "Usage: $0 {start|stop|forcedstop|restart|forcedrestart|reload|forcedreload|status}"
exit 2
;;
esac
tgt-1.0.80/scripts/tgt-admin 0000664 0000000 0000000 00000123523 13747533545 0015722 0 ustar 00root root 0000000 0000000 #!/usr/bin/perl
#
# This tools parses /etc/tgt/targets.conf file and configures tgt
#
# Author: Tomasz Chmielewski
# License: GPLv2
#
use strict;
use Config::General qw(ParseConfig);
use Getopt::Long;
# Our config file
my $configfile = "/etc/tgt/targets.conf";
sub usage {
print < delete all or selected targets
(see "--delete help" for more info)
--offline put all or selected targets in offline state
(see "--offline help" for more info)
--ready put all or selected targets in ready state
(see "--ready help" for more info)
--update update configuration for all or selected targets
(see "--update help" for more info)
-s, --show show all the targets
-C, --control-port specify the control port to connect to
-c, --conf specify an alternative configuration file
--ignore-errors continue even if tgtadm exits with non-zero code
-f, --force force some operations even if the target is in use
-p, --pretend only print tgtadm options
--dump dump current tgtd configuration (note: does not
include detailed parameters, like write caching)
-v, --verbose increase verbosity (show tgtadm commands)
-h, --help show this help
EOF
exit;
}
my %conf;
my $param = $ARGV[0];
my $execute = 0;
my $delete = 0;
my $offline = 0;
my $ready = 0;
my $update = 0;
my $show = 0;
my $alternate_conf="0";
my $ignore_errors = 0;
my $force = 0;
my $pretend = 0;
my $dump = 0;
my $verbose = 0;
my $help = 0;
my $control_port=0;
Getopt::Long::Configure("no_ignore_case");
my $result = GetOptions (
"e|execute" => \$execute,
"delete=s" => \$delete,
"offline=s" => \$offline,
"ready=s" => \$ready,
"update=s" => \$update,
"s|show" => \$show,
"c|conf=s" => \$alternate_conf,
"ignore-errors" => \$ignore_errors,
"f|force" => \$force,
"p|pretend" => \$pretend,
"dump" => \$dump,
"v|verbose" => \$verbose,
"h|help" => \$help,
"C|control-port=s" => \$control_port,
);
if (($help == 1) || ($param eq undef)) {
usage;
}
# Show all the targets and exit
if ($show == 1) {
execute("tgtadm -C $control_port --op show --mode target");
exit;
}
# Some variables/arrays/hashes we will use globally
my %tgtadm_output;
my %tgtadm_output_tid;
my %tgtadm_output_name;
my @largest_tid;
my $next_tid;
my %existing_accounts;
# Look up if iSNS is already on
sub check_isns {
# We need to run as root
if ( $> ) {
die("You must be root to run this program.\n");
}
my @show_sys = `tgtadm -C $control_port --op show --mode sys`;
foreach my $sys_line (@show_sys) {
if ($sys_line =~ m/iSNS=On/) {
return 1;
}
}
# iSNS is not enabled if we're here
return 0;
}
# Look up which targets are configured
sub process_targets {
# We need to run as root
if ( $> ) {
die("You must be root to run this program.\n");
}
my @show_target = `tgtadm -C $control_port --op show --mode target`;
my $tid;
my $targetname;
# Here, we create hashes of target names (all target data) and target tids
foreach my $show_target_line (@show_target) {
if ( $show_target_line =~ m/^Target (\d*): (.+)/ ) {
$tid = $1;
$targetname = $2;
$tgtadm_output{$targetname} = $show_target_line;
$tgtadm_output_tid{$targetname} = $tid;
$tgtadm_output_name{$tid} = $targetname;
} else {
$tgtadm_output{$targetname} .= $show_target_line;
}
}
# What is the largest tid?
my @tids = values %tgtadm_output_tid;
@largest_tid = sort { $a <=> $b } @tids;
$next_tid = $largest_tid[$#largest_tid];
}
sub process_accounts {
# We need to run as root
if ( $> ) {
die("You must be root to run this program.\n");
}
my @show_account = `tgtadm -C $control_port --op show --mode account`;
# Here, we create a hash of accounts
foreach my $show_account_line (@show_account) {
if ( $show_account_line =~ m/^\s+(.*?)$/ ) {
$existing_accounts{$1} = 1;
}
}
}
# Parse config file(s)
sub parse_configs {
# Parse the config
if ($alternate_conf ne 0) {
# Check if alternative configuration file exists
if (-e "$alternate_conf") {
execute("# Using $alternate_conf as configuration file\n");
%conf = ParseConfig(-ConfigFile => "$alternate_conf", -UseApacheInclude => 1, -IncludeDirectories => 1, -IncludeGlob => 1, -MergeDuplicateBlocks => 1);
}
else {
die("Config file $alternate_conf not found. Exiting...\n");
}
} else {
# Parse the config file with Config::General
if (-e "$configfile") {
%conf = ParseConfig(-ConfigFile => "$configfile", -UseApacheInclude => 1, -IncludeDirectories => 1, -IncludeGlob => 1, -MergeDuplicateBlocks => 1);
} else {
die("Config file $configfile not found. Exiting...\n");
}
}
}
# Add targets, if they are not configured already
my $default_driver;
my $target;
my $option;
my $value;
my $lun;
sub add_targets {
my $single_target = $_[0];
my $configured = $_[1];
my $connected = $_[2];
my $in_configfile = $_[3];
my $isns_enabled = check_isns;
foreach my $k (sort keys %conf) {
if ($k eq "default-driver") {
if (not length ref($conf{$k})) {
$default_driver = $conf{$k};
} else {
print "Multiple default-driver definitions are not allowed!\n";
print "Check your config file for errors.\n";
exit 1;
}
} elsif ($k eq "ignore-errors") {
if ($conf{$k} eq "yes") {
$ignore_errors = 1;
}
} elsif (($k eq "iSNSAccessControl") || ($k eq "iSNSServerIP") ||
($k eq "iSNSServerPort")) {
if ($isns_enabled eq 0) {
check_if_hash_array($conf{$k}, $k);
execute("tgtadm -C $control_port --op update --mode sys --name $k -v $conf{$k}");
}
} elsif (($k eq "incomingdiscoveryuser") || ($k eq "outgoingdiscoveryuser")) {
my @userpass = split(/ /, $conf{$k});
check_value($userpass[1]);
# Only delete or create account if it doesn't already exist
if (! exists $existing_accounts{$userpass[0]} ) {
execute("tgtadm -C $control_port --mode account --op new --user=$userpass[0] --password=$userpass[1]");
$existing_accounts{$userpass[0]} = 1;
}
my $extra_str = "";
if ($k eq "outgoingdiscoveryuser") {
$extra_str = " --outgoing";
}
execute("tgtadm -C $control_port --mode account --op bind --user=$userpass[0] $extra_str");
}
}
foreach my $k (sort keys %conf) {
if ($k eq "iSNS") {
if ($isns_enabled eq 0) {
check_if_hash_array($conf{$k}, $k);
execute("tgtadm -C $control_port --op update --mode sys --name $k -v $conf{$k}");
} else {
execute("# iSNS already enabled");
}
} elsif ($k eq "control-port") {
if ($control_port eq 0) {
$control_port = $conf{$k};
} else {
print "Multiple control-port definitions are not allowed!\n";
print "Check your config file for errors.\n";
exit 1;
}
}
}
# If $default_driver is empty, default to iscsi
if (not defined $default_driver) {
execute("# default-driver not defined, defaulting to iscsi.\n");
$default_driver = "iscsi";
}
foreach my $k (sort keys %conf) {
if ($k eq "target") {
foreach my $k2 (sort keys %{$conf{$k}}) {
# Do we run update or execute?
if (length $single_target) {
if ($single_target ne $k2) {
next;
} else {
$target = $single_target;
}
} else {
$target = $k2;
}
my $in_use = 0;
if (length $single_target) {
$in_use = main_delete($target);
}
my $allowall = 1;
if ((not defined $tgtadm_output{$k2}) ||
($update ne 0 && $in_use == 0) ||
($update ne 0 && $in_use == 1 && $pretend == 1 && $force == 1))
{
# We have to find available tid
if ($in_configfile == 1 && $configured == 0 && $pretend == 0) {
my $maxtid = find_max_tid();
$next_tid = $maxtid + 1;
} elsif (length $single_target && $configured == 1) {
$next_tid = $tgtadm_output_tid{$target};
} else {
$next_tid = $next_tid + 1;
}
# Before we add a target, we need to know its type
# and other parameters which can be specified globally
my %target_options;
my $target_options_ref;
my $data_key;
foreach my $k3 (sort keys %{$conf{$k}{$k2}}) {
$lun = 1;
$option = $k3;
$value = $conf{$k}{$k2}{$k3};
check_value($value);
$target_options{$option} = $value;
$target_options_ref = \%target_options;
$data_key = make_key($target_options_ref, "lun", "allow-in-use");
}
if (defined $target_options{"controller_tid"}) {
$next_tid = $target_options{"controller_tid"};
}
if (not defined $target_options{"driver"}) {
$target_options{"driver"} = $default_driver;
}
my $driver = $target_options{"driver"};
execute("# Adding target: $target");
execute("tgtadm -C $control_port --lld $driver --op new --mode target --tid $next_tid -T $target");
foreach my $k3 (sort keys %{$conf{$k}{$k2}}) {
$option = $k3;
$value = $conf{$k}{$k2}{$k3};
check_value($value);
process_options($target_options_ref,$data_key);
# If there was no option called "initiator-address", it means
# we want to allow ALL initiators for this target
if ($option eq "initiator-address") {
$allowall = 0;
}
}
if ($allowall == 1) {
execute("tgtadm -C $control_port --lld $driver --op bind --mode target --tid $next_tid -I ALL");
}
} else {
if (not length $configured || $in_use eq 1) {
execute("# Target $target already exists!");
}
}
$tgtadm_output_tid{$target} = $next_tid;
}
if (length $single_target && $in_configfile == 0 && $configured == 0) {
print "Target $single_target is currently not configured\n";
print "and does not exist in the config file - can't continue!\n";
exit 1;
}
execute();
}
}
}
# Pre-parse the config and get some values we need
sub make_key {
my $target_options_ref = shift;
my @actions = @_;
my %data_key;
foreach my $action (@actions) {
if (ref $$target_options_ref{'backing-store'} eq "HASH") {
foreach my $testlun (keys %{$$target_options_ref{'backing-store'}}) {
$data_key{$testlun}{$action} = $$target_options_ref{'backing-store'}{$testlun}{$action};
}
}
if (ref $$target_options_ref{'direct-store'} eq "HASH") {
foreach my $testlun (keys %{$$target_options_ref{'direct-store'}}) {
$data_key{$testlun}{$action} = $$target_options_ref{'direct-store'}{$testlun}{$action};
}
}
}
return \%data_key;
}
# Some options can be specified only once
sub check_if_hash_array {
my $check = $_[0];
my $definition = $_[1];
if (ref($check) eq 'ARRAY' || ref($check) eq "HASH") {
if ($option) {
print "Multiple '$definition' definitions in '$option' not allowed!\n";
print "Check your config file for errors (target: $target).\n";
} else {
print "Multiple '$definition' definitions not allowed!\n";
print "Check your config file for errors.\n";
}
exit 1;
}
}
# Force an array if we just have one command
sub force_array {
unless (ref($value) eq 'ARRAY') {
$value = [ $value ];
}
}
# If we start any external command, we want to know if it exists
sub check_exe {
my $command = $_[0];
my $option = $_[1];
my @path = split(":", $ENV{PATH});
my $exists = 0;
foreach my $path (@path) {
if ( -x "$path/$command" && -f "$path/$command" ) { $exists = 1 }
}
if ($exists == 0) {
if ($command eq "sg_inq") {
print "Command '$command' (needed by '$option') is not in your path - can't continue!\n";
exit 1;
} elsif ($command eq "lsof") {
execute("# Command '$command' is not in your path.");
execute("# Can't reliably check if device is not in use.");
return 1;
}
}
}
# Apply additional parameters
sub add_params {
my $param = shift;
my $param_value = shift;
my $lun = shift;
my $driver = shift;
if ($param eq "write-cache") {
if ($param_value eq "off") {
return("tgtadm -C $control_port --lld $driver --op update --mode logicalunit --tid $next_tid --lun=$lun --params mode_page=8:0:18:0x10:0:0xff:0xff:0:0:0xff:0xff:0xff:0xff:0x80:0x14:0:0:0:0:0:0");
} elsif ($param_value eq "on" || not length $param_value) {
return("# Write cache is enabled (default) for lun $lun.");
} else {
return("# WARNING! Unknown value ($param_value) to write-cache! Accepted values are \"on\" and \"off\".");
}
}
if ($param eq "scsi_id" ||
$param eq "scsi_sn" ||
$param eq "vendor_id" ||
$param eq "product_id" ||
$param eq "product_rev" ||
$param eq "sense_format" ||
$param eq "removable" ||
$param eq "online" ||
$param eq "path" ||
$param eq "mode_page" ||
$param eq "lbppbe" ||
$param eq "la_lba" ||
$param eq "optimal_xfer_gran" ||
$param eq "optimal_xfer_len" ||
$param eq "readonly") {
return("tgtadm -C $control_port --lld $driver --op update --mode logicalunit --tid $next_tid --lun=$lun --params $param=\"$param_value\"");
}
if ($param eq "params") {
return("tgtadm -C $control_port --lld $driver --op update --mode logicalunit --tid $next_tid --lun=$lun --params \"$param_value\"");
}
}
# Find next available LUN
sub find_next_lun {
my $backing_store = $_[0];
my $data_key_ref = $_[1];
my $lun_collision = 0;
my $lun_is_free = 0;
my $found_lun = 1;
while ($lun_is_free == 0) {
foreach my $testlun (keys %$data_key_ref) {
foreach my $testlun2 (values %{$$data_key_ref{$testlun}}) {
if ($found_lun eq $testlun2) {
$lun_collision = 1;
}
}
}
if ($lun_collision == 0) {
$lun_is_free = 1;
} else {
$found_lun += 1;
}
$lun_collision = 0;
}
$$data_key_ref{$backing_store}{'lun'} = $found_lun;
return $found_lun;
}
# Add backing or direct store
sub add_backing_direct {
my $backing_store = $_[0];
my $target_options_ref = $_[1];
my $data_key_ref = $_[2];
my $direct_store = $_[3];
my $driver = $$target_options_ref{"driver"};
my $bstype = $$target_options_ref{"bs-type"};
# Is the device in use?
my $can_alloc = 1;
if ($bstype !~ "rbd" && $force != 1 && $$target_options_ref{'allow-in-use'} ne "yes") {
$can_alloc = check_device($backing_store,$data_key_ref);
}
if ($can_alloc == 1 &&
($bstype =~ "glfs" || $bstype =~ "rbd" || (-e $backing_store && ! -d $backing_store))) {
my @exec_commands;
my $device_type;
my $bs_type;
my $bsopts;
my $bsoflags;
my $block_size;
my %luns;
my @added_luns;
# Find out LUNs which are "reserved" in the config file
if (ref $value eq "HASH") {
if (length $$data_key_ref{$backing_store}{'lun'}) {
$lun = $$data_key_ref{$backing_store}{'lun'};
} else {
# Find an available lun if it wasn't specified
$lun = find_next_lun($backing_store,$data_key_ref);
}
}
# Process parameters for each lun / backing store
if (ref $value eq "HASH") {
my %params_added;
my @mode_page;
my @params;
foreach my $store (keys %$value) {
# lun
if (length $$target_options_ref{"lun"}) {
check_if_hash_array($$target_options_ref{"lun"}, "lun");
$lun = $$target_options_ref{"lun"};
}
if (ref $$value{$store} eq "HASH" && $store eq $backing_store) {
foreach my $store_option (keys %{$$value{$store}}) {
my $result = $$value{$store}{$store_option};
check_value($result);
if ($store_option ne "mode_page" && $store_option ne "params")
{ check_if_hash_array($result,$store_option) }
# write-cache can be set globally per target and overridden per lun,
# so we treat it differently
if ($store_option ne "mode_page" && $store_option ne "write-cache"
&& $store_option ne "params") {
my $exec_command = add_params($store_option, $result, $lun, $driver);
push(@exec_commands, $exec_command);
$params_added{$store_option} = 1;
}
if ($store_option eq "write-cache") {
my $exec_command = add_params($store_option, $result, $lun, $driver);
$params_added{write_cache} = 1;
push(@exec_commands, $exec_command);
}
if ($store_option eq "device-type") {
$device_type = $result;
$params_added{$store_option} = 1;
}
if ($store_option eq "bs-type") {
$bs_type = $result;
$params_added{$store_option} = 1;
}
if ($store_option eq "bsopts") {
$bsopts = $result;
$params_added{$store_option} = 1;
}
if ($store_option eq "bsoflags") {
$bsoflags = $result;
$params_added{$store_option} = 1;
}
if ($store_option eq "block-size") {
$block_size = $result;
$params_added{$store_option} = 1;
}
if ($store_option eq "mode_page") {
unless (ref($result) eq 'ARRAY') {
$result = [ $result ];
}
@mode_page = @$result;
foreach my $mode_page (@mode_page) {
my $exec_command = add_params($store_option, $mode_page, $lun, $driver);
push(@exec_commands, $exec_command);
}
}
if ($store_option eq "params") {
unless (ref($result) eq 'ARRAY') {
$result = [ $result ];
}
@params = @$result;
foreach my $param (@params) {
my $exec_command = add_params($store_option, $param, $lun, $driver);
push(@exec_commands, $exec_command);
}
}
}
}
}
# Used only if lun is a direct-store
my $sg_inq;
my $sg_readcap;
my %direct_params;
if ($direct_store == 1) {
$sg_inq=`sg_inq "$backing_store"`;
if ($sg_inq=~m {
Vendor\ identification:\s+?(.*?)\n
\s+Product\ identification:\s+(.*?)\n
\s+Product\ revision\ level:\s+(.*?)\n
(?:\s+Unit\ serial\ number:\s+(.*?)\n)?
}xs ) {
# If they were not defined globally for a target,
# add them now
if (not length $$target_options_ref{vendor_id}) {
$direct_params{vendor_id} = $1;
}
if (not length $$target_options_ref{product_id}) {
$direct_params{product_id} = $2;
}
if (not length $$target_options_ref{product_rev}) {
$direct_params{product_rev} = $3;
}
if (not length $$target_options_ref{scsi_sn}) {
$direct_params{scsi_sn} = $4;
}
}
$sg_inq=`sg_inq -p 0xb0 "$backing_store"`;
if ($sg_inq=~m {
Optimal\ transfer\ length\ granularity:\s+?(\d+).*?\n
\s+Maximum\ transfer\ length:\s+?(\d+).*?\n
\s+Optimal\ transfer\ length:\s+?(\d+).*?\n
}xs ) {
if (not length $$target_options_ref{optimal_xfer_gran}) {
$direct_params{optimal_xfer_gran} = $1;
}
# $2 unused
if (not length $$target_options_ref{optimal_xfer_len}) {
$direct_params{optimal_xfer_len} = $3;
}
}
$sg_readcap=`sg_readcap -16 "$backing_store"`;
if ($sg_readcap=~ m {
Logical\ block\ length=(\d+).*?\n
\s+Logical\ blocks\ per\ physical\ block\ exponent=(\d+).*?\n
\s+Lowest\ aligned\ logical\ block\ address=(\d+).*?\n
}xs) {
if (not length $block_size) {
$block_size = $1;
}
if (not length $$target_options_ref{lbppbe}) {
$direct_params{lbppbe} = $2;
}
if (not length $$target_options_ref{la_lba}) {
$direct_params{la_lba} = $3;
}
}
}
# Add these parameters if they were not overwritten in the config file
my @opts = ("scsi_id", "sense_format", "removable", "online", "path", "readonly");
foreach my $single_opt (@opts) {
check_if_hash_array($$target_options_ref{$single_opt},$single_opt);
if ($params_added{$single_opt} ne 1 && length $$target_options_ref{$single_opt}) {
my $exec_command = add_params($single_opt, $$target_options_ref{$single_opt}, $lun, $driver);
push(@exec_commands, $exec_command);
$params_added{$single_opt} = 1;
}
}
# These options can be fetched by sg_inq for direct-store
my @opts = ("vendor_id", "product_id", "product_rev", "scsi_sn",
"lbppbe", "la_lba", "optimal_xfer_gran", "optimal_xfer_len");
foreach my $single_opt (@opts) {
check_if_hash_array($$target_options_ref{$single_opt},$single_opt);
my $this_opt;
if (length $$target_options_ref{$single_opt}) {
$this_opt = $$target_options_ref{$single_opt};
} elsif (length $direct_params{$single_opt}) {
$this_opt = $direct_params{$single_opt};
}
if ($params_added{$single_opt} ne 1 && length $this_opt) {
my $exec_command = add_params($single_opt, $this_opt, $lun, $driver);
push(@exec_commands, $exec_command);
$params_added{$single_opt} = 1;
}
}
# write-cache
if ($params_added{write_cache} ne 1) {
my $exec_command = add_params("write-cache", $$target_options_ref{"write-cache"}, $lun, $driver);
push(@exec_commands, $exec_command);
$params_added{write_cache} = 1;
}
# mode_page
unless (ref($$target_options_ref{mode_page}) eq 'ARRAY') {
$$target_options_ref{mode_page} = [ $$target_options_ref{mode_page} ];
}
foreach my $mode_page (@{$$target_options_ref{"mode_page"}}) {
if (length $mode_page) {
my $exec_command = add_params("mode_page", $mode_page, $lun, $driver);
push(@exec_commands, $exec_command);
}
}
# params
unless (ref($$target_options_ref{params}) eq 'ARRAY') {
$$target_options_ref{params} = [ $$target_options_ref{params} ];
}
foreach my $param (@{$$target_options_ref{"params"}}) {
if (length $param) {
my $exec_command = add_params("params", $param, $lun, $driver);
push(@exec_commands, $exec_command);
}
}
# device-type
if ($params_added{"device-type"} ne 1) {
check_if_hash_array($$target_options_ref{"device-type"}, "device-type");
$device_type = $$target_options_ref{"device-type"};
}
# bs-type
if ($params_added{"bs-type"} ne 1) {
check_if_hash_array($$target_options_ref{"bs-type"}, "bs-type");
$bs_type = $$target_options_ref{"bs-type"};
}
# bsopts
if ($params_added{"bsopts"} ne 1) {
check_if_hash_array($$target_options_ref{"bsopts"}, "bsopts");
$bsopts = $$target_options_ref{"bsopts"};
}
# bsoflags
if ($params_added{"bsoflags"} ne 1) {
check_if_hash_array($$target_options_ref{"bsoflags"}, "bsoflags");
$bsoflags = $$target_options_ref{"bsoflags"};
}
} else {
print "If you got here, this means your config file is not supported.\n";
print "Please report it to stgt mailing list and attach your config files.\n";
exit 1;
}
# Execute commands for a given LUN
if (length $device_type) { $device_type = "--device-type $device_type" };
if (length $bs_type) { $bs_type = "--bstype $bs_type" };
if (length $bsopts) { $bsopts = "--bsopts $bsopts" };
if (length $bsoflags) { $bsoflags = "--bsoflags $bsoflags" };
if (length $block_size) { $block_size = "--blocksize $block_size" };
execute("tgtadm -C $control_port --lld $driver --op new --mode logicalunit --tid $next_tid --lun $lun -b \"$backing_store\" $device_type $bs_type $bsopts $bsoflags $block_size");
# Commands should be executed in order
my @execute_last;
foreach my $exec_command (@exec_commands) {
if (length $exec_command) {
if ($exec_command =~ m/params mode_page/) {
execute($exec_command);
} else {
push(@execute_last, $exec_command);
}
}
}
foreach my $exec_command (@execute_last) {
execute($exec_command);
}
$lun += 1;
return $lun;
} elsif ($can_alloc == 0) {
execute("# Skipping device $backing_store - it is in use.");
execute("# You can override it with --force or 'allow-in-use yes' config option.");
execute("# Note - do so only if you know what you're doing, you may damage your data.");
} else {
execute("# Skipping device: $backing_store");
execute("# $backing_store does not exist - please check the configuration file");
}
}
# Process options from the config file
sub process_options {
my $target_options_ref = $_[0];
my $data_key_ref = $_[1];
my $driver = $$target_options_ref{"driver"};
if ($option eq "backing-store" || $option eq "direct-store") {
my $direct_store = 0;
if ($option eq "direct-store") {
check_exe("sg_inq", "option direct-store");
$direct_store = 1;
}
# We want to make everything a hash to use it
# in the same way later on
unless (ref($value)) {
$value = { $value }
}
my %arrvalue;
if (ref($value) eq "ARRAY") {
foreach my $backing_store (@$value) {
$arrvalue{$backing_store} = 1;
}
$value = \%arrvalue;
}
if (ref($value) eq "HASH") {
foreach my $backing_store (sort keys %$value) {
if ($backing_store =~ m/HASH/) {
print "\nYour config file is not supported. See targets.conf.example for details.\n";
exit 1;
}
}
foreach my $backing_store (sort keys %$value) {
add_backing_direct($backing_store,$target_options_ref,$data_key_ref,$direct_store);
}
}
}
if ($option eq "incominguser") {
# if we have one command, force it to be an array anyway
force_array();
my @value_arr = @$value;
foreach my $incominguser (@value_arr) {
my @userpass = split(/ /, $incominguser);
check_value($userpass[1]);
# Only delete or create account if it doesn't already exist
if (! exists $existing_accounts{$userpass[0]} ) {
execute("tgtadm -C $control_port --lld $driver --mode account --op new --user=$userpass[0] --password=$userpass[1]");
$existing_accounts{$userpass[0]} = 1;
}
execute("tgtadm -C $control_port --lld $driver --mode account --op bind --tid=$next_tid --user=$userpass[0]");
}
}
if ($option eq "outgoinguser") {
# if we have one command, force it to be an array anyway
force_array();
if (length @$value[1]) {
execute("# Warning: only one $option is allowed. Will only use the first one.");
}
my @userpass = split(/ /, @$value[0]);
check_value($userpass[1]);
# Only delete or create account if it doesn't already exist
if (! exists $existing_accounts{$userpass[0]} ) {
execute("tgtadm -C $control_port --lld $driver --mode account --op new --user=$userpass[0] --password=$userpass[1]");
$existing_accounts{$userpass[0]} = 1;
}
execute("tgtadm -C $control_port --lld $driver --mode account --op bind --tid=$next_tid --user=$userpass[0] --outgoing");
}
if ($option eq "initiator-address") {
# if we have one command, force it to be an array anyway
force_array();
my @value_arr = @$value;
foreach my $initiator_address (@value_arr) {
check_value($initiator_address);
execute("tgtadm -C $control_port --lld $driver --op bind --mode target --tid $next_tid -I $initiator_address");
}
}
if ($option eq "initiator-name") {
# if we have one command, force it to be an array anyway
force_array();
my @value_arr = @$value;
foreach my $initiator_name (@value_arr) {
check_value($initiator_name);
execute("tgtadm -C $control_port --lld $driver --op bind --mode target --tid $next_tid -Q $initiator_name");
}
}
if ($option =~ m/^MaxRecvDataSegmentLength$|
^MaxXmitDataSegmentLength$|
^HeaderDigest$|
^DataDigest$|
^InitialR2T$|
^MaxOutstandingR2T$|
^ImmediateData$|
^FirstBurstLength$|
^MaxBurstLength$|
^DataPDUInOrder$|
^DataSequenceInOrder$|
^ErrorRecoveryLevel$|
^IFMarker$|
^OFMarker$|
^DefaultTime2Wait$|
^DefaultTime2Retain$|
^OFMarkInt$|
^IFMarkInt$|
^MaxConnections$|
^state/x) {
# if we have one command, force it to be an array anyway
force_array();
if (length @$value[1]) {
execute("# Warning: only one $option is allowed. Will only use the first one.");
}
check_value($option);
execute("tgtadm -C $control_port --lld $driver --mode target --op update --tid $next_tid --name $option --value $$value[0]");
}
}
# If the target is configured, but not present in the config file,
# try to remove it
sub remove_targets {
process_targets;
my @all_targets = keys %tgtadm_output_tid;
foreach my $existing_target (@all_targets) {
my $dontremove = 0;
my $k2;
foreach my $k (sort keys %conf) {
if ( $k eq "target" ) {
foreach $k2 (sort keys %{$conf{$k}}) {
if ( $k2 eq $existing_target ) {
$dontremove = 1;
}
}
if ( $dontremove == 0 ) {
# Remove the target
main_delete($existing_target);
}
}
}
}
}
# Dump current tgtd configuration
sub dump_config {
process_targets;
my @all_targets = keys %tgtadm_output_tid;
# If all targets use the same driver, use it only once in the config
my $skip_driver = 0;
my @drivers_combined;
foreach my $current_target (@all_targets) {
my $driver = show_target_info($current_target, "driver");
push (@drivers_combined, $driver);
}
my %drivers_uniq;
@drivers_uniq{@drivers_combined} = ();
my @drivers_combined_uniq = sort keys %drivers_uniq;
if (scalar @drivers_combined_uniq == 1) {
print "default-driver $drivers_combined_uniq[0]\n\n";
}
# Print everything else in the config
foreach my $current_target (@all_targets) {
my $target_name = show_target_info($current_target, "target_name");
print "\n";
if (scalar @drivers_combined_uniq gt 1) {
my $driver = show_target_info($current_target, "driver");
print "\tdriver $driver\n";
}
my @backing_stores = show_target_info($current_target, "backing_stores");
foreach my $backing_store (@backing_stores) {
print "\tbacking-store $backing_store\n";
}
my @account_information = show_target_info($current_target, "account_information");
foreach my $account (@account_information) {
if ($account =~ /(.+)\ \(outgoing\)/) {
print "\toutgoinguser $1 PLEASE_CORRECT_THE_PASSWORD\n";
} elsif (length $account) {
print "\tincominguser $account PLEASE_CORRECT_THE_PASSWORD\n";
}
}
my @acl_information = show_target_info($current_target, "acl_information");
if (scalar(@acl_information) != 1 || $acl_information[0] ne "ALL") {
foreach my $ini_address (@acl_information) {
# determine here if acl is an IP or IQN
if ( $ini_address =~ /^[0-9,.E]+$/ ) {
print "\tinitiator-address $ini_address\n";
} else {
print "\tinitiator-name $ini_address\n";
}
}
}
print "\n\n";
}
}
# Offline or ready targets
sub ready_offline_targets {
my $var = $_[0]; # This variable is either "offline" or "ready"
my $off_ready;
if ($ready eq 0) {
$off_ready = $offline
} elsif ($offline eq 0) {
$off_ready = $ready
} else {
print "Invalid value (you can't use both ready and offline)!\n";
exit 1;
}
if ($off_ready eq "help") {
print < $var all or selected targets
Example usage:
--$var help - display this help
--$var ALL - $var all targets
--$var tid=4 - $var target 4 (target with tid 4)
--$var iqn.2008-08.com.example:some.target - $var this target
EOF
} elsif ($off_ready eq "ALL") {
process_targets;
# Run over all targets and offline/ready them
my @all_targets = keys %tgtadm_output_tid;
foreach my $existing_target (@all_targets) {
execute("tgtadm -C $control_port --op update --mode target --tid=$tgtadm_output_tid{$existing_target} -n state -v $var");
}
} elsif ($off_ready =~ m/tid=(.+)/) {
process_targets;
execute("tgtadm -C $control_port --op update --mode target --tid=$1 -n state -v $var");
} else {
process_targets;
if (length $tgtadm_output_tid{$off_ready}) {
execute("tgtadm -C $control_port --op update --mode target --tid=$tgtadm_output_tid{$off_ready} --name=\"$off_ready\" -n state -v $var");
} else {
print "There is no target with name \"$off_ready\", can't $var it!\n";
exit 1;
}
}
}
# Show info for a given target
sub show_target_info {
my $existing_target = $_[0];
my $task = $_[1];
# Returns target information
if ($task eq "target_name") {
if ($tgtadm_output{$existing_target} =~ m/^Target (\d*): (.+)/ ) {
return $2;
}
# Returns driver information
} elsif ($task eq "driver") {
if ($tgtadm_output{$existing_target} =~ m/\s+Driver: (.+)/ ) {
return $1;
}
# Returns backing store
} elsif ($task eq "backing_stores") {
if ($tgtadm_output{$existing_target} =~ m/\s+Backing store path: (?!None)(.+)/ ) {
my @backing_stores = $tgtadm_output{$existing_target} =~ m{\s+Backing store path: (?!None\n)(.+)}g;
return @backing_stores;
}
return;
# Returns account information:
} elsif ($task eq "account_information") {
if ($tgtadm_output{$existing_target} =~ m{
\s+Account\ information:\n(.*)\n\s+ACL\ information:
}xs
) {
my @accounts = split(/\n/, $1);
my @account_information;
foreach my $user (@accounts) {
my @var = split(/^\s+/, $user);
push(@account_information, $var[1]);
}
return @account_information;
}
# Returns ACL information
} elsif ($task eq "acl_information") {
if ($tgtadm_output{$existing_target} =~ m{
\s+ACL\ information:\n(.*)
}xs
) {
my @ini_addresses = split(/\n/, $1);
my @acls;
foreach my $ini_address (@ini_addresses) {
my @var = split(/^\s+/, $ini_address);
push(@acls, $var[1]);
}
return @acls;
}
# Returns sessions
} elsif ($task eq "sessions") {
my %sessions;
if ($tgtadm_output{$existing_target} =~ m{
\s+I_T\ nexus\ information:\n(.*)LUN\ information:
}xs
) {
my @var = split(/\n/, $1);
my $sid;
my $cid;
foreach my $line (@var) {
if ($line =~ m/\s+I_T nexus:\ (.+)/) {
$sid = $1;
} else {
if ($line =~ m/\s+Connection:\ (.+)/) {
$cid = $1;
$sessions{$sid} = $cid;
}
}
}
}
return %sessions;
}
}
# Main subroutine for deleting targets
sub main_delete {
my $current_target = $_[0];
my $current_tid = $_[1];
my $configured = check_configured($current_target);
my $del_upd_text;
# Check if the target has initiators connected
if ($tgtadm_output{$current_target} =~ m/\s+Connection:/) {
if ($force == 1) {
execute("# Removing target: $current_target");
# Remove ACLs first
my @acl_info = show_target_info($current_target, "acl_information");
foreach my $acl (@acl_info) {
execute("tgtadm -C $control_port --op unbind --mode target --tid $tgtadm_output_tid{$current_target} -I $acl");
}
# Now, remove all sessions / connections from that tid
my %sessions = show_target_info($current_target, "sessions");
foreach my $sid (keys %sessions) {
foreach my $cid ($sessions{$sid}) {
execute("tgtadm -C $control_port --op delete --mode conn --tid $tgtadm_output_tid{$current_target} --sid $sid --cid $cid");
}
}
execute("tgtadm -C $control_port --mode target --op delete --tid=$tgtadm_output_tid{$current_target}");
} else {
if ($update ne 0) {
$del_upd_text = "updated";
} else {
$del_upd_text = "deleted";
}
execute("# Target with tid $tgtadm_output_tid{$current_target} ($current_target) is in use, it won't be $del_upd_text.");
return 1;
}
} elsif (length $tgtadm_output_tid{$current_target}) {
execute("# Removing target: $current_target");
execute("tgtadm -C $control_port --mode target --op delete --tid=$tgtadm_output_tid{$current_target}");
} else {
if (length $current_tid) {
execute("# Target with tid $current_tid does not exist!");
} else {
execute("# Target with name $current_target does not exist!");
}
}
if ($configured ne 0) {
execute();
}
return 0;
}
# Delete the targets
sub delete_targets {
if ($delete eq "help") {
print < delete all or selected targets
The target will be deleted only if it's not used
(no initiator is connected to it).
If you want to delete targets which are in use,
you have to add "--force" flag.
Example usage:
--delete help - display this help
--delete ALL - delete all targets
--delete tid=4 - delete target 4 (target with tid 4)
--delete iqn.2008-08.com.example:some.target - delete this target
EOF
exit;
} elsif ($delete eq "ALL") {
process_targets;
# Run over all targets and delete them
my @all_targets = keys %tgtadm_output_tid;
foreach my $current_target (@all_targets) {
main_delete($current_target);
}
} elsif ($delete =~ m/^tid=(.+)/) {
# Delete by tid
process_targets;
my $current_target = $tgtadm_output_name{$1};
main_delete($current_target, $1);
} else {
# Delete by name
process_targets;
my $current_target = $delete;
main_delete($current_target);
}
}
# Update targets
sub update_targets {
if ($update eq "help") {
print < update all or selected targets
The target will be updated only if it's not used
(no initiator is connected to it).
If you want to update targets which are in use,
you have to add "--force" flag.
Example usage:
--update help - display this help
--update ALL - update all targets
--update tid=4 - update target 4 (target with tid 4)
--update iqn.2008-08.com.example:some.target - update this target
EOF
exit;
} elsif ($update eq "ALL") {
# Run over all targets and delete them if they are not in use
parse_configs;
process_targets;
my @targets_combined = combine_targets();
foreach my $current_target (@targets_combined) {
my $configured = check_configured($current_target);
my $connected = check_connected($current_target);
my $in_configfile = check_in_configfile($current_target);
combine_targets();
if (($in_configfile == 0) && ($configured == 1)) {
# Delete the target if it's not in the config file
main_delete($current_target);
} else {
add_targets($current_target, $configured, $connected, $in_configfile);
}
}
} elsif ($update =~ m/^tid=(.+)/) {
# Update by tid
parse_configs;
process_targets;
my $current_target = $tgtadm_output_name{$1};
my $configured = check_configured($current_target);
my $connected = check_connected($current_target);
my $in_configfile = check_in_configfile($current_target);
if (($in_configfile == 0) && ($configured == 1)) {
# Delete the target if it's not in the config file
main_delete($current_target);
} elsif ($configured == 1) {
add_targets($current_target, $configured, $connected, $in_configfile);
} else {
print "There is no target with tid $1, can't continue!\n";
exit 1;
}
} else {
# Update by name
parse_configs;
process_targets;
my $current_target = $update;
my $configured = check_configured($current_target);
my $connected = check_connected($current_target);
my $in_configfile = check_in_configfile($current_target);
if ($in_configfile == 0 && $configured == 1) {
# Delete the target if it's not in the config file
main_delete($current_target);
} else {
add_targets($current_target, $configured, $connected, $in_configfile);
}
}
}
# Find the biggest tid
sub find_max_tid {
my @all_targets = keys %tgtadm_output_tid;
my $maxtid = 0;
foreach my $var (@all_targets) {
if ($tgtadm_output_tid{$var} > $maxtid) {
$maxtid = $tgtadm_output_tid{$var};
}
}
return $maxtid;
}
# Combine targets from the config file and currently configured targets
sub combine_targets {
my @targets_in_configfile;
my @all_targets = keys %tgtadm_output_tid;
my @targets_combined;
# Make an array of targets in the config file
foreach my $k (sort keys %conf) {
if ( $k eq "target" ) {
foreach my $k2 (sort keys %{$conf{$k}}) {
push(@targets_in_configfile, $k2)
}
}
}
# Use only unique elements from both arrays
foreach my $current_target (@all_targets) {
push (@targets_combined, $current_target) unless grep { $_ eq $current_target } @targets_in_configfile;
}
@targets_combined = (@targets_combined, @targets_in_configfile);
return @targets_combined;
}
# Check if a value is correct
sub check_value {
if ( not defined $_[0] or not length $_[0] ) {
print "\nOption $option has a missing value!\n";
print "Check your config file for errors (target: $target)\n";
exit 1;
}
}
# Check if the target is in the config file
sub check_in_configfile {
my $current_target = $_[0];
my $result;
foreach my $k (sort keys %conf) {
if ( $k eq "target" ) {
foreach my $k2 (sort keys %{$conf{$k}}) {
if ($k2 eq $current_target) {
return 1;
}
}
# If we're here, we didn't find a match
return 0;
}
}
}
# Check if the target is configured in tgtd
sub check_configured {
my $current_target = $_[0];
if (length $tgtadm_output_tid{$current_target}) {
return 1;
} else {
return 0;
}
}
# Check if any initiators are connected to the target
sub check_connected {
my $current_target = $_[0];
if ($tgtadm_output{$current_target} =~ m/\s+Connection:/) {
return 1;
} else {
return 0;
}
}
# Check if a device can be allocated
# Device can be used "by system" (i.e. mounted, used as swap, as a part of
# a RAID array etc.) or "by user" - i.e., already by tgtd, or someone doing:
# dd if=/dev/1st_device of=/dev/2nd_device
# We shouldn't allow a device to be used more than one time, as it could
# cause corruption when written several times. Unless the user really wants to.
sub check_device {
my $backing_store = $_[0];
my $data_key_ref = $_[1];
# If allow-in-use is "yes", there is no need to do
# farther tests
if ($$data_key_ref{$backing_store}{'allow-in-use'} eq "yes") {
return 1;
}
if (! -e $backing_store) {
return 1;
}
# Check if the system uses this device
use Fcntl qw(O_RDONLY O_EXCL);
use Errno;
sysopen(FH, $backing_store, O_RDONLY | O_EXCL);
if ($!{EBUSY}) {
execute("# Device $backing_store is used by the system (mounted, used by swap?).");
return 0;
}
close(FH);
# Check if userspace uses this device
my $lsof_check = check_exe("lsof");
if ($lsof_check ne 1) {
system("lsof \"$backing_store\" >/dev/null 2>&1");
my $exit_value = $? >> 8;
if ($exit_value eq 0) {
execute("# Device $backing_store is used (already tgtd target?).");
execute("# Run 'lsof $backing_store' to see the details.");
return 0;
}
}
return 1;
}
# Execute or just print (or both) everything we start or would start
sub execute {
if ($pretend == 0) {
my $args = "@_";
if ($verbose == 1) {
print "$args\n";
}
# Don't try to execute if it's a comment
my @execargs = split(/#/, $args);
if ($execargs[0] ne undef) {
system($args);
# If non-zero exit code was return, exit
my $exit_value = $? >> 8;
if (($exit_value != 0) && ($ignore_errors == 0)) {
print "Command:\n\t$args\nexited with code: $exit_value.\n";
exit $exit_value;
}
}
} elsif ($pretend == 1) {
print "@_\n";
}
}
if ($execute == 1) {
parse_configs;
process_targets;
process_accounts;
add_targets;
remove_targets;
} elsif ($delete ne 0) {
delete_targets;
} elsif ($update ne 0) {
process_accounts;
update_targets;
} elsif ($dump == 1) {
dump_config;
} elsif ($offline ne 0) {
ready_offline_targets("offline");
} elsif ($ready ne 0) {
ready_offline_targets("ready");
} else {
print "No action specified.\n";
}
tgt-1.0.80/scripts/tgt-core-test 0000775 0000000 0000000 00000016707 13747533545 0016547 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Parent directory for data files..
HOME=/d/01
# Start tgtd if not running..
P=`ps -ef|grep -v grep|grep tgtd|wc -l`
if [ "X"$P == "X0" ]; then
tgtd -d 1
sleep 1
fi
if [ ! -d $HOME ]; then
mkdir -p $HOME
fi
if [ ! -f $HOME/hd_block ]; then
dd if=/dev/zero of=$HOME/hd_block bs=1M count=8
fi
set -x
###############################################################################
# Set up SBC HDD device
###############################################################################
TID=1
# Create Target ID 1..
tgtadm --lld iscsi --mode target --op new --tid $TID \
-T iqn.2007-03:marks-vtl_tgt:`hostname`
sleep 1
# Create first LUN - Disk
LUN=1
tgtadm --lld iscsi --mode logicalunit --op new --tid $TID --lun $LUN -b $HOME/hd_block --device-type=disk
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params scsi_sn=FRED00,scsi_id=Fred
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params vendor_id=QUANTUM,product_id=HD100,product_rev=0010,removable=1,sense_format=0
#### Set up mode pages ####
# First try a couple of attempts with incorrect data..
# i.e. Expect the first two to fail!
# - Length too long & Incorrect value (300) as one if the params...
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=2:0:14:0x80:0x80:0:0xa:0:300:0:0:0:0:0:0:0:0:3
# - Length too short...
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0
#
# From here on - should work OK..
#
# Vendor Uniq - Mode page 0..
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0:0:0
# Disconnect page
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0
# Format mode page
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=3:0:22:0:0:0:0:0:0:0:0:1:0:2:0:0:0:0:0:0:0:0:13:0:0
# Geo mode page
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=4:0:22:0:0:0:0x40:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0x3a:0x98:0:0
# Caching Page
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=8:0:18:0x14:0:0xff:0xff:0:0:0xff:0xff:0xff:0xff:0x80:0x14:0:0:0:0:0:0
# ctrl mode page
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=10:0:10:2:0:0:0:0:0:0:0:2:0
# Informational Exceptions Control Mode Page
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0
for LUN in 2 3 4; do
if [ ! -f $HOME/cdrom$LUN ]; then
dd if=/dev/zero of=$HOME/cdrom$LUN bs=1M count=8
fi
# Create LUN - CD/ROM
tgtadm --lld iscsi --mode logicalunit --op new --tid $TID --lun $LUN -b $HOME/cdrom$LUN --device-type=cd
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params vendor_id=VirtualCD,product_id=CD101,product_rev=0010,scsi_sn=XYZZY1$LUN,removable=1
# Vendor Uniq - Mode page 0..
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0:0:0
# ctrl mode page
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=10:0:10:2:0:0:0:0:0:0:0:2:0
# Informational Exceptions Control Mode Page
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0
done
###############################################################################
# Set up SMC Medium Changer
###############################################################################
LUN=5
if [ ! -f $HOME/smc ]; then
dd if=/dev/zero of=$HOME/smc bs=1k count=1
fi
tgtadm --lld iscsi --mode logicalunit --op new --tid $TID --lun $LUN \
-b $HOME/smc --device-type=changer
#### Set up mode pages ####
# From smc3-06.pdf
# Page 0x02: Disconnect/Reconnect SPC-3
# Page 0x0a: Control SPC-3
# Page 0x18: Protocol Specific LUN SPC-3
# Page 0x19: Protocol Specific Port SPC-3
# Page 0x1a: Power Condition SPC-3
# Page 0x1c: Informational Exceptions Control SPC-3
# Page 0x1d: Element Address Assignment SMC-3 7.3.4
# Page 0x1e: Transport Geometry Parameters SMC-3 7.3.5
# Page 0x1f: Device Capabilities SMC-3 7.3.2
# Page 0x1f/Subpage 0x41: Extended Device Capabilities SMC-3 7.3.3
# Dummy 'page 0'
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0:0:0
# Disconnect/Reconnect
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0
# Power Condition
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0x1a:0:18:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
# Informational Exceptions Control Mode Page
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0
# Element Address Assignment - Setup afterwards.
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0x1d:0:0x12:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
# Transport Geometry Parameters
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0x1e:0:2:0:0
# Device Capabilities
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params mode_page=0x1f:0:0x12:0x0f:7:0x0f:0x0f:0x0f:0x0f:0:0:0:0:0x0f:0x0f:0x0f:0x0f:0:0:0:0
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params vendor_id=STK,product_id=L700,product_rev=0010,scsi_sn=XYZZY_0,removable=1
## Add Data Transfer devices (3 drives)
# Define slot address for devices.
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params element_type=4,start_address=1,quantity=3
# Now define which device at each address.
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params element_type=4,address=1,tid=1,lun=2
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params element_type=4,address=2,tid=1,lun=3
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params element_type=4,address=3,tid=1,lun=4
# Medium Transport Elements (robot arm / picker)
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params element_type=1,start_address=16,quantity=1
## Storage Elements - 8 starting at addr 1024
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params element_type=2,start_address=1024,quantity=8
# Add 'media' to slots
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params element_type=2,address=1024,barcode=ABC123,sides=1
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params element_type=2,address=1026,barcode=ULT001L3,sides=1
# Import/Export Elements - 5 starting at addr 32
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params element_type=3,start_address=32,quantity=5
# define path to virtual media
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params media_home=/d/01/vtl
# Dump the list of configured slots to syslog...
tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \
--params dump=1
# Allow ALL initiators to connect to this target
tgtadm --lld iscsi --mode target --op bind --tid $TID -I ALL
# Show all our good work.
tgtadm --lld iscsi --mode target --op show
tgt-1.0.80/scripts/tgt-setup-lun 0000775 0000000 0000000 00000016061 13747533545 0016567 0 ustar 00root root 0000000 0000000 #!/bin/bash
# LUN assignment script
#
# Copyright (C) 2007 Erez Zilber
# Copyright (C) 2012 Roi Dayan
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
usage()
{
name=$(basename $0);
echo "usage:";
echo -e "\t$name -n tgt_name -d dev -b bs_name -B block_size -t transport -C control_port [initiator_IP1 initiator_IP2 ...]";
echo "defaults:";
echo -e "\tbacking store: rdwr";
echo -e "\ttransport: iscsi";
echo -e "\tinitiator: ALL";
echo -e "\tcontrol port: -";
echo "examples:";
echo -e "\t$name -n tgt-1 -d /dev/sdb1 192.168.1.2";
echo -e "\t$name -n tgt-2 -d /tmp/null -b null -t iser";
echo -e "\t$name -n tgt-3 -d ~/disk3.bin -b rdwr 192.168.1.2 192.168.1.3";
echo -e "WARNING:"
echo -e "\tPlease remember to set blocksize to 4096 for newer Advanced Format (4K) disks larger than 2TB. Otherwise, your existing filesystems will not be readable."
}
verify_params()
{
if ! [ "$dev" -o "$bs_type" == "null" ]; then
echo "Error: a device is mandatory";
exit 1;
else
return;
fi
# Make sure that the device exists
if ! [ -b $dev -o -f $dev -o -c $dev ]; then
echo "Error: $dev is not a device";
exit 1;
fi
if ! [ "$tgt_name" ]; then
echo "Error: target name is mandatory";
exit 1;
fi
if ! [ "$lld_name" ]; then
echo "Error: lld name empty";
exit 1;
fi
}
find_vacant_tgt_id()
{
id_list=$($TGTADM --lld $lld_name --op show --mode target | grep Target | cut -d" " -f2 | sed s/://)
next_vacant_id=1
for id in $id_list; do
if (($id > $next_vacant_id)); then
break;
else
next_vacant_id=$((next_vacant_id+1))
fi
done
return $next_vacant_id
}
find_vacant_lun()
{
tid=$1
tgt_found=0
next_vacant_lun=0
tmp_file=/tmp/target_list.txt
$TGTADM --lld $lld_name --op show --mode target > $tmp_file
while read line; do
# Check if we finished going over this target
if ((tgt_found == 1 && $(echo $line | grep -c "^Target") == 1)); then
break
fi
# Check if we found the requested target
if (($(echo $line | grep -c "Target $tid:") == 1)); then
tgt_found=1
continue
fi
if ((tgt_found == 1 && $(echo $line | grep -c "LUN:") == 1)); then
curr_lun=$(echo $line | cut -d" " -f2)
if (($curr_lun > $next_vacant_lun)); then
break
else
next_vacant_lun=$((next_vacant_lun+1))
fi
fi
done < $tmp_file
rm -f $tmp_file
if ((tgt_found == 0)); then
echo "Error: could not find a LUN for target $tid"
return -1
fi
return $next_vacant_lun
}
get_acl() {
tid=$1
tgt_found=0
acl_found=0
next_vacant_lun=0
tmp_file=/tmp/target_list.txt
local _acl=""
$TGTADM --lld $lld_name --op show --mode target > $tmp_file
while read line; do
# Check if we finished going over this target
if ((tgt_found == 1 && $(echo $line | grep -c "^Target") == 1)); then
break
fi
# Check if we found the requested target
if (($(echo $line | grep -c "Target $tid:") == 1)); then
tgt_found=1
continue
fi
if ((tgt_found == 1 && $(echo $line | grep -c "ACL information") == 1)); then
acl_found=1
continue
fi
if ((tgt_found == 1 && acl_found == 1)); then
curr_acl=$(echo $line)
_acl=`echo $_acl $curr_acl`
fi
done < $tmp_file
rm -f $tmp_file
echo $_acl
}
err_exit()
{
if ((new_tgt == 0)); then
exit 1
fi
echo "Deleting new target, tid=$tid"
$TGTADM --lld $lld_name --op delete --mode target --tid $tid
res=$?
if [ $res -ne 0 ]; then
echo "Error: failed to delete target, tid=$tid"
fi
exit 1
}
check_if_tgt_exists()
{
tgt_list=$($TGTADM --lld $lld_name --op show --mode target | grep Target | cut -d" " -f3)
for curr_tgt in $tgt_list; do
if [ $tgt_name = $curr_tgt ]; then
return 1
fi
done
return 0
}
if [ $# -eq 0 ]; then
usage
exit 1
fi
TGTADM="tgtadm"
lld_name="iscsi"
control_port=""
while getopts "d:n:b:B:t:h:C:" opt
do
case ${opt} in
d)
dev=$OPTARG;;
n)
tgt_name=$OPTARG;;
b)
bs_type=$OPTARG;;
B)
block_size=$OPTARG;;
t)
lld_name=$OPTARG;;
C)
control_port=$OPTARG;;
h*)
usage
exit 1
esac
done
shift $(($OPTIND - 1))
initiators=$*
verify_params
# Check if tgtd is running (we should have 2 daemons)
tgtd_count=`pidof tgtd | wc -w`
if [ $tgtd_count -lt 1 ]; then
echo "tgtd is not running"
echo "Exiting..."
exit 1
fi
if [ -n "$control_port" ]; then
echo "Control port: $control_port"
TGTADM="$TGTADM -C $control_port"
fi
echo "Using transport: $lld_name"
if ! [[ $tgt_name =~ ^iqn ]]; then
tgt_name="iqn.2001-04.com.$(hostname -s)-$tgt_name"
fi
# Make sure that a target with the same name doesn't exist
check_if_tgt_exists
if [ $? -eq 1 ]; then
echo "Error: target named $tgt_name already exists"
read -p "Add a new lun to the existing target? (yes/NO): " add_lun
if [ "$add_lun" != "yes" ]; then
exit 1
fi
tid=$($TGTADM --lld $lld_name --op show --mode target | grep $tgt_name | cut -d" " -f2)
tid=${tid%:}
new_tgt=0
else
find_vacant_tgt_id
tid=$?
# Create the new target
echo "Creating new target (name=$tgt_name, tid=$tid)"
$TGTADM --lld $lld_name --op new --mode target --tid $tid -T $tgt_name
res=$?
if [ $res -ne 0 ]; then
echo "Error: failed to create target (name=$tgt_name, tid=$tid)"
exit 1
fi
new_tgt=1
fi
find_vacant_lun $tid
lun=$?
# Add a logical unit to the target
echo "Adding a logical unit ($dev) to target, tid=$tid"
if [ $bs_type ]; then
echo "Setting backing store type: $bs_type"
bs_opt="-E $bs_type"
fi
if [ $block_size ]; then
blksize_opt="--blocksize $block_size"
fi
$TGTADM --lld $lld_name --op new --mode logicalunit --tid $tid --lun $lun -b $dev $bs_opt $blksize_opt
res=$?
if [ $res -ne 0 ]; then
echo "Error: failed to add a logical unit ($dev) to target, tid=$tid"
err_exit
fi
# Define which initiators can use this target
tgt_acl=`get_acl $tid`
if test "$initiators" ; then
# Allow access only for specific initiators
echo "Accepting connections from $initiators"
for initiator in $initiators; do
# Check if record already exists
if ((`echo $tgt_acl | grep -c "\b$initiator\b"` == 1)) ; then
continue
fi
$TGTADM --lld $lld_name --op bind --mode target --tid $tid -I $initiator
res=$?
if [ $res -ne 0 ]; then
echo "Error: could not assign initiator $initiator to the target"
fi
done
else
# Check if record already exists
if ((`echo $tgt_acl | grep -c "\bALL\b"` == 1)) ; then
exit 0
fi
# Allow access for everyone
echo "Accepting connections from all initiators"
$TGTADM --lld $lld_name --op bind --mode target --tid $tid -I ALL
res=$?
if [ $res -ne 0 ]; then
echo "Error: failed to set access for all initiators"
err_exit
fi
fi
tgt-1.0.80/scripts/tgt.bashcomp.sh 0000664 0000000 0000000 00000014254 13747533545 0017040 0 ustar 00root root 0000000 0000000 # list available target names
_tgt_targets() {
COMPREPLY=( $(compgen -W "$(tgt-admin --show|\
grep Target | cut -d' ' -f3\
) ALL help" -- "${cur}") )
}
have tgtd &&
_tgtd() {
local optslist split=false
local cur prev
COMPREPLY=()
cur=$(_get_cword "=")
prev="${COMP_WORDS[COMP_CWORD-1]}"
_expand || return 0
optslist='
--foreground -f
--control-port -C
--nr_iothreads -t
--debug -d
--version -V
--help -h
'
_split_longopt && split=true
case "${prev}" in
--nr_iothreads|-t|\
--control-port|-C|\
--debug|-d)
return 0;;
esac
$split && return 0
case "${cur}" in
-*)
COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") )
return 0;;
*)
_filedir
return 0;;
esac
} &&
complete -F _tgtd ${nospace} tgtd
have tgtimg &&
_tgtimg() {
local optslist split=false
local cur prev
COMPREPLY=()
cur=$(_get_cword "=")
prev="${COMP_WORDS[COMP_CWORD-1]}"
_expand || return 0
optslist='
--help -h
--op -o
--device-type -Y
--barcode -b
--size -s
--type -t
--file -f
--thin-provisioning -T
'
_split_longopt && split=true
case "${prev}" in
--op|-o)
COMPREPLY=( $(compgen -W "new show" -- "${cur}") )
return 0;;
--device-type|-Y)
COMPREPLY=( $(compgen -W "cd disk tape" -- "${cur}") )
return 0;;
--type|-t)
COMPREPLY=( $(compgen -W "dvd+r disk data clean worm" -- "${cur}") )
return 0;;
--file|-f)
_filedir
return 0;;
--barcode|-b|\
--size|-s)
return 0;;
esac
$split && return 0
case "${cur}" in
-*)
COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") )
return 0;;
*)
_filedir
return 0;;
esac
} &&
complete -F _tgtimg ${nospace} tgtimg
have tgtadm &&
_tgtadm() {
local optslist split=false
local cur prev
COMPREPLY=()
cur=$(_get_cword "=")
prev="${COMP_WORDS[COMP_CWORD-1]}"
_expand || return 0
optslist='
--debug -d
--help -h
--version -V
--lld -L
--op -o
--mode -m
--tid -t
--sid -s
--cid -c
--lun -l
--name -n
--value -v
--backing-store -b
--bstype -E
--bsoflags -f
--blocksize -y
--targetname -T
--initiator-address -I
--initiator-name -Q
--user -u
--password -p
--host -H
--force -F
--params -P
--bus -B
--device-type -Y
--outgoing -O
--control-port -C
'
_split_longopt && split=true
case "${prev}" in
--lld|-L)
COMPREPLY=( $(compgen -W "iscsi iser" -- "${cur}") )
return 0;;
--op|-o)
COMPREPLY=( $(compgen -W "new delete bind unbind show
update stat start stop" -- "${cur}") )
return 0;;
--mode|-m)
COMPREPLY=( $(compgen -W "system sys target tgt logicalunit lu
portal pt session sess connection conn account lld" -- "${cur}") )
return 0;;
--bstype|-E)
COMPREPLY=( $(compgen -W "rdwr aio rbd sg ssc" -- "${cur}") )
return 0;;
--bsoflags|-f)
COMPREPLY=( $(compgen -W "direct sync" -- "${cur}") )
return 0;;
--device-type|-Y)
COMPREPLY=( $(compgen -W "disk tape cd changer osd ssc pt" -- "${cur}") )
return 0;;
--targetname|-T)
_tgt_targets
return 0;;
--backing-store|-b)
_filedir
return 0;;
--blocksize|-y|\
--bus|-B|\
--cid|-c|\
--control-port|-C|\
--host|-H|\
--initiator-address|-I|\
--initiator-name|-Q|\
--lun|-l|\
--name|-n|\
--params|-P|\
--password|-p|\
--sid|-s|\
--tid|-t|\
--user|-u|\
--value|-v)
return 0;;
esac
$split && return 0
case "${cur}" in
-*)
COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") )
return 0;;
*)
_filedir
return 0;;
esac
} &&
complete -F _tgtadm ${nospace} tgtadm
have tgt-admin &&
_tgt_admin() {
local optslist split=false
local cur prev
COMPREPLY=()
cur=$(_get_cword "=")
prev="${COMP_WORDS[COMP_CWORD-1]}"
_expand || return 0
optslist='
--execute -e
--delete
--offline
--ready
--update
--show -s
--conf -c
--ignore-errors
--force -f
--pretend -p
--dump
--verbose -v
--help -h
--control-port -C
'
_split_longopt && split=true
case "${prev}" in
--delete|-d|\
--offline|\
--ready|\
--update)
_tgt_targets
return 0;;
--conf|-c)
_filedir
return 0;;
--control-port|-C)
return 0;;
esac
$split && return 0
case "${cur}" in
-*)
COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") )
return 0;;
*)
_filedir
return 0;;
esac
} &&
complete -F _tgt_admin ${nospace} tgt-admin
have tgt-setup-lun &&
_tgt_setup_lun() {
local optslist split=false
local cur prev
COMPREPLY=()
cur=$(_get_cword "=")
prev="${COMP_WORDS[COMP_CWORD-1]}"
_expand || return 0
optslist='
-d
-n
-b
-t
-C
-h
'
_split_longopt && split=true
case "${prev}" in
-n)
_tgt_targets
return 0;;
-d)
_filedir
return 0;;
esac
$split && return 0
case "${cur}" in
-*)
COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") )
return 0;;
*)
_filedir
return 0;;
esac
} &&
complete -F _tgt_setup_lun ${nospace} tgt-setup-lun
tgt-1.0.80/scripts/tgtd.service 0000664 0000000 0000000 00000001674 13747533545 0016441 0 ustar 00root root 0000000 0000000 [Unit]
Description=iSCSI target framework daemon
Documentation=man: tgtd(8)
After=network.target
# On systems without virtual consoles, don't start any getty. Note
# that serial gettys are covered by serial-getty@.service, not this
# unit.
ConditionPathExists=/etc/tgt/targets.conf
[Service]
Type=forking
Environment=TGTD_CONFIG=/etc/tgt/targets.conf
ExecStart=/usr/sbin/tgtd
ExecStartPost=/usr/sbin/tgtadm --op update --mode sys --name State -v offline
ExecStartPost=/usr/sbin/tgtadm --op update --mode sys --name State -v ready
ExecStartPost=/usr/sbin/tgt-admin -e -c ${TGTD_CONFIG}
ExecReload=/usr/sbin/tgt-admin --update ALL -f -c ${TGTD_CONFIG}
ExecStop=/usr/sbin/tgtadm --op update --mode sys --name State -v offline
ExecStop=/usr/sbin/tgt-admin --offline ALL
ExecStop=/usr/sbin/tgt-admin --update ALL -c /dev/null -f
ExecStop=/usr/sbin/tgtadm --op delete --mode system
# Exit code: 107 tgtd not running
[Install]
WantedBy=multi-user.target
tgt-1.0.80/scripts/tgtd.spec 0000664 0000000 0000000 00000005356 13747533545 0015734 0 ustar 00root root 0000000 0000000 Name: scsi-target-utils
Version: 1.0.24
Release: 2%{?dist}
Summary: The SCSI target daemon and utility programs
Packager: Roi Dayan
Group: System Environment/Daemons
License: GPLv2
URL: http://stgt.sourceforge.net/
Source0: %{name}-%{version}-%{release}.tgz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires: pkgconfig libibverbs-devel librdmacm-devel libxslt libaio-devel
%if %{defined suse_version}
BuildRequires: docbook-xsl-stylesheets
Requires: aaa_base
%else
BuildRequires: docbook-style-xsl
Requires(post): chkconfig
Requires(preun): chkconfig
Requires(preun): initscripts
%endif
Requires: lsof sg3_utils
ExcludeArch: s390 s390x
%description
The SCSI target package contains the daemon and tools to setup a SCSI targets.
Currently, software iSCSI targets are supported.
%prep
%setup -q -n %{name}-%{version}-%{release}
%build
%{__make} %{?_smp_mflags} ISCSI_RDMA=1
%install
%{__rm} -rf %{buildroot}
%{__install} -d %{buildroot}%{_sbindir}
%{__install} -d %{buildroot}%{_mandir}/man5
%{__install} -d %{buildroot}%{_mandir}/man8
%{__install} -d %{buildroot}%{_initrddir}
%{__install} -d %{buildroot}/etc/bash_completion.d
%{__install} -d %{buildroot}/etc/tgt
%{__install} -p -m 0755 scripts/tgt-setup-lun %{buildroot}%{_sbindir}
%{__install} -p -m 0755 scripts/initd.sample %{buildroot}%{_initrddir}/tgtd
%{__install} -p -m 0755 scripts/tgt-admin %{buildroot}/%{_sbindir}/tgt-admin
%{__install} -p -m 0644 scripts/tgt.bashcomp.sh %{buildroot}/etc/bash_completion.d/tgt
%{__install} -p -m 0644 doc/manpages/targets.conf.5 %{buildroot}/%{_mandir}/man5
%{__install} -p -m 0644 doc/manpages/tgtadm.8 %{buildroot}/%{_mandir}/man8
%{__install} -p -m 0644 doc/manpages/tgt-admin.8 %{buildroot}/%{_mandir}/man8
%{__install} -p -m 0644 doc/manpages/tgt-setup-lun.8 %{buildroot}/%{_mandir}/man8
%{__install} -p -m 0644 doc/manpages/tgtimg.8 %{buildroot}/%{_mandir}/man8
%{__install} -p -m 0600 conf/targets.conf %{buildroot}/etc/tgt
pushd usr
%{__make} install DESTDIR=%{buildroot} sbindir=%{_sbindir}
%post
/sbin/chkconfig --add tgtd
%postun
if [ "$1" = "1" ] ; then
/sbin/service tgtd condrestart > /dev/null 2>&1 || :
fi
%preun
if [ "$1" = "0" ] ; then
/sbin/chkconfig tgtd stop > /dev/null 2>&1
/sbin/chkconfig --del tgtd
fi
%clean
%{__rm} -rf %{buildroot}
%files
%defattr(-, root, root, -)
%doc README doc/README.iscsi doc/README.iser doc/README.lu_configuration doc/README.mmc
%{_sbindir}/tgtd
%{_sbindir}/tgtadm
%{_sbindir}/tgt-setup-lun
%{_sbindir}/tgt-admin
%{_sbindir}/tgtimg
%{_mandir}/man5/*
%{_mandir}/man8/*
%{_initrddir}/tgtd
/etc/bash_completion.d/tgt
%attr(0600,root,root) %config(noreplace) /etc/tgt/targets.conf
tgt-1.0.80/usr/ 0000775 0000000 0000000 00000000000 13747533545 0013227 5 ustar 00root root 0000000 0000000 tgt-1.0.80/usr/Makefile 0000664 0000000 0000000 00000005152 13747533545 0014672 0 ustar 00root root 0000000 0000000 sbindir ?= $(PREFIX)/sbin
libdir ?= $(PREFIX)/lib/tgt
ifneq ($(shell test -e /usr/include/linux/signalfd.h && echo 1),)
CFLAGS += -DUSE_SIGNALFD
endif
ifneq ($(shell test -n $(shell find /usr/include -name "timerfd.h" | head -n1) && echo 1),)
CFLAGS += -DUSE_TIMERFD
endif
TGTD_OBJS += $(addprefix iscsi/, conn.o param.o session.o \
iscsid.o target.o chap.o sha1.o md5.o transport.o iscsi_tcp.o \
isns.o)
ifneq ($(CEPH_RBD),)
MODULES += bs_rbd.so
endif
ifneq ($(GLFS_BD),)
MODULES += bs_glfs.so
endif
ifneq ($(SD_NOTIFY),)
CFLAGS += -DUSE_SYSTEMD
endif
ifneq ($(shell test -e /usr/include/sys/eventfd.h && test -e /usr/include/libaio.h && echo 1),)
CFLAGS += -DUSE_EVENTFD
TGTD_OBJS += bs_aio.o
LIBS += -laio
endif
ifneq ($(ISCSI_RDMA),)
TGTD_OBJS += iscsi/iser.o iscsi/iser_text.o
LIBS += -libverbs -lrdmacm
endif
INCLUDES += -I.
CFLAGS += -D_GNU_SOURCE
CFLAGS += $(INCLUDES)
ifneq ($(DEBUG),)
CFLAGS += -g -O0 -ggdb -rdynamic
else
CFLAGS += -g -O2 -fno-strict-aliasing
endif
CFLAGS += -Wall -Wstrict-prototypes -Werror -fPIC
CFLAGS += -DTGT_VERSION=\"$(VERSION)$(EXTRAVERSION)\"
CFLAGS += -DBSDIR=\"$(DESTDIR)$(libdir)/backing-store\"
LIBS += -lpthread -ldl
ifneq ($(SD_NOTIFY),)
LIBS += -lsystemd
endif
PROGRAMS += tgtd tgtadm tgtimg
TGTD_OBJS += tgtd.o mgmt.o target.o scsi.o log.o driver.o util.o work.o \
concat_buf.o parser.o spc.o sbc.o mmc.o osd.o scc.o smc.o \
ssc.o libssc.o bs_rdwr.o bs_ssc.o \
bs_null.o bs_sg.o bs.o libcrc32c.o bs_sheepdog.o
TGTD_DEP = $(TGTD_OBJS:.o=.d)
LDFLAGS = -Wl,-E,-rpath=$(libdir)
.PHONY:all
all: $(PROGRAMS) $(MODULES)
tgtd: $(TGTD_OBJS)
echo $(CC) $^ -o $@ $(LIBS)
$(CC) $^ -o $@ $(LDFLAGS) $(LIBS)
-include $(TGTD_DEP)
TGTADM_OBJS = tgtadm.o concat_buf.o
TGTADM_DEP = $(TGTADM_OBJS:.o=.d)
tgtadm: $(TGTADM_OBJS)
$(CC) $^ -o $@
-include $(TGTADM_DEP)
TGTIMG_OBJS = tgtimg.o libssc.o libcrc32c.o
TGTIMG_DEP = $(TGTIMG_OBJS:.o=.d)
tgtimg: $(TGTIMG_OBJS)
$(CC) $^ -o $@
-include $(TGTIMG_DEP)
%.o: %.c
$(CC) -c $(CFLAGS) $*.c -o $*.o
@$(CC) -MM $(CFLAGS) -MF $*.d -MT $*.o $*.c
%.so: %.c
$(CC) -shared $(CFLAGS) $*.c -o $*.so
bs_rbd.so: bs_rbd.c
$(CC) -shared $(CFLAGS) bs_rbd.c -o bs_rbd.so -lrados -lrbd
bs_glfs.so: bs_glfs.c
$(CC) -I/usr/include/glusterfs/api -shared $(CFLAGS) bs_glfs.c -o bs_glfs.so -lgfapi
.PHONY: install
install: $(PROGRAMS) $(MODULES)
install -d -m 755 $(DESTDIR)$(sbindir)
install -m 755 $(PROGRAMS) $(DESTDIR)$(sbindir)
ifneq ($(MODULES),)
install -d -m 755 $(DESTDIR)$(libdir)/backing-store
install -m 755 $(MODULES) $(DESTDIR)$(libdir)/backing-store
endif
.PHONY: clean
clean:
rm -f *.[od] *.so $(PROGRAMS) iscsi/*.[od] ibmvio/*.[od] fc/*.[od]
tgt-1.0.80/usr/be_byteshift.h 0000664 0000000 0000000 00000003377 13747533545 0016061 0 ustar 00root root 0000000 0000000 #ifndef _LINUX_UNALIGNED_BE_BYTESHIFT_H
#define _LINUX_UNALIGNED_BE_BYTESHIFT_H
static inline uint16_t __get_unaligned_be16(const uint8_t *p)
{
return p[0] << 8 | p[1];
}
static inline uint32_t __get_unaligned_be32(const uint8_t *p)
{
return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
}
static inline uint64_t __get_unaligned_be64(const uint8_t *p)
{
return (uint64_t)__get_unaligned_be32(p) << 32 |
__get_unaligned_be32(p + 4);
}
static inline void __put_unaligned_be16(uint16_t val, uint8_t *p)
{
*p++ = val >> 8;
*p++ = val;
}
static inline void __put_unaligned_be32(uint32_t val, uint8_t *p)
{
__put_unaligned_be16(val >> 16, p);
__put_unaligned_be16(val, p + 2);
}
static inline void __put_unaligned_be64(uint64_t val, uint8_t *p)
{
__put_unaligned_be32(val >> 32, p);
__put_unaligned_be32(val, p + 4);
}
static inline uint16_t get_unaligned_be16(const void *p)
{
return __get_unaligned_be16((const uint8_t *)p);
}
static inline uint32_t get_unaligned_be24(const uint8_t *p)
{
return p[0] << 16 | p[1] << 8 | p[2];
}
static inline uint32_t get_unaligned_be32(const void *p)
{
return __get_unaligned_be32((const uint8_t *)p);
}
static inline uint64_t get_unaligned_be64(const void *p)
{
return __get_unaligned_be64((const uint8_t *)p);
}
static inline void put_unaligned_be16(uint16_t val, void *p)
{
__put_unaligned_be16(val, p);
}
static inline void put_unaligned_be24(uint32_t val, void *p)
{
((uint8_t *)p)[0] = (val >> 16) & 0xff;
((uint8_t *)p)[1] = (val >> 8) & 0xff;
((uint8_t *)p)[2] = val & 0xff;
}
static inline void put_unaligned_be32(uint32_t val, void *p)
{
__put_unaligned_be32(val, p);
}
static inline void put_unaligned_be64(uint64_t val, void *p)
{
__put_unaligned_be64(val, p);
}
#endif /* _LINUX_UNALIGNED_BE_BYTESHIFT_H */
tgt-1.0.80/usr/bs.c 0000664 0000000 0000000 00000023766 13747533545 0014015 0 ustar 00root root 0000000 0000000 /*
* backing store routine
*
* Copyright (C) 2007 FUJITA Tomonori
* Copyright (C) 2007 Mike Christie
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "list.h"
#include "tgtd.h"
#include "tgtadm_error.h"
#include "util.h"
#include "bs_thread.h"
#include "scsi.h"
LIST_HEAD(bst_list);
static LIST_HEAD(finished_list);
static pthread_mutex_t finished_lock;
/* used by both bs_rdwr.c and bs_rbd.c */
int nr_iothreads = 16;
static int sig_fd = -1;
static int command_fd[2];
static int done_fd[2];
static pthread_t ack_thread;
/* protected by pipe */
static LIST_HEAD(ack_list);
static pthread_cond_t finished_cond;
void bs_create_opcode_map(struct backingstore_template *bst,
unsigned char *opcodes, int num)
{
int i;
for (i = 0; i < num; i++)
set_bit(opcodes[i], bst->bs_supported_ops);
}
int is_bs_support_opcode(struct backingstore_template *bst, int op)
{
/*
* assumes that this bs doesn't support supported_ops yet so
* returns success for the compatibility.
*/
if (!test_bit(TEST_UNIT_READY, bst->bs_supported_ops))
return 1;
return test_bit(op, bst->bs_supported_ops);
}
int register_backingstore_template(struct backingstore_template *bst)
{
list_add(&bst->backingstore_siblings, &bst_list);
return 0;
}
struct backingstore_template *get_backingstore_template(const char *name)
{
struct backingstore_template *bst;
list_for_each_entry(bst, &bst_list, backingstore_siblings) {
if (!strcmp(name, bst->bs_name))
return bst;
}
return NULL;
}
/* threading helper functions */
static void *bs_thread_ack_fn(void *arg)
{
int command, ret, nr;
struct scsi_cmd *cmd;
retry:
ret = read(command_fd[0], &command, sizeof(command));
if (ret < 0) {
eprintf("ack pthread will be dead, %m\n");
if (errno == EAGAIN || errno == EINTR)
goto retry;
goto out;
}
pthread_mutex_lock(&finished_lock);
while (list_empty(&finished_list))
pthread_cond_wait(&finished_cond, &finished_lock);
while (!list_empty(&finished_list)) {
cmd = list_first_entry(&finished_list,
struct scsi_cmd, bs_list);
dprintf("found %p\n", cmd);
list_del(&cmd->bs_list);
list_add_tail(&cmd->bs_list, &ack_list);
}
pthread_mutex_unlock(&finished_lock);
nr = 1;
rewrite:
ret = write(done_fd[1], &nr, sizeof(nr));
if (ret < 0) {
eprintf("can't ack tgtd, %m\n");
if (errno == EAGAIN || errno == EINTR)
goto rewrite;
goto out;
}
goto retry;
out:
pthread_exit(NULL);
}
static void bs_thread_request_done(int fd, int events, void *data)
{
struct scsi_cmd *cmd;
int nr_events, ret;
ret = read(done_fd[0], &nr_events, sizeof(nr_events));
if (ret < 0) {
eprintf("wrong wakeup\n");
return;
}
while (!list_empty(&ack_list)) {
cmd = list_first_entry(&ack_list,
struct scsi_cmd, bs_list);
dprintf("back to tgtd, %p\n", cmd);
list_del(&cmd->bs_list);
target_cmd_io_done(cmd, scsi_get_result(cmd));
}
rewrite:
ret = write(command_fd[1], &nr_events, sizeof(nr_events));
if (ret < 0) {
eprintf("can't write done, %m\n");
if (errno == EAGAIN || errno == EINTR)
goto rewrite;
return;
}
}
static void bs_sig_request_done(int fd, int events, void *data)
{
int ret;
struct scsi_cmd *cmd;
struct signalfd_siginfo siginfo[16];
LIST_HEAD(list);
ret = read(fd, (char *)siginfo, sizeof(siginfo));
if (ret <= 0) {
return;
}
pthread_mutex_lock(&finished_lock);
list_splice_init(&finished_list, &list);
pthread_mutex_unlock(&finished_lock);
while (!list_empty(&list)) {
cmd = list_first_entry(&list, struct scsi_cmd, bs_list);
list_del(&cmd->bs_list);
target_cmd_io_done(cmd, scsi_get_result(cmd));
}
}
/* Unlock mutex even if thread is cancelled */
static void mutex_cleanup(void *mutex)
{
pthread_mutex_unlock(mutex);
}
static void *bs_thread_worker_fn(void *arg)
{
struct bs_thread_info *info = arg;
struct scsi_cmd *cmd;
sigset_t set;
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, NULL);
while (1) {
pthread_mutex_lock(&info->pending_lock);
pthread_cleanup_push(mutex_cleanup, &info->pending_lock);
while (list_empty(&info->pending_list))
pthread_cond_wait(&info->pending_cond,
&info->pending_lock);
cmd = list_first_entry(&info->pending_list,
struct scsi_cmd, bs_list);
list_del(&cmd->bs_list);
pthread_cleanup_pop(1); /* Unlock pending_lock mutex */
info->request_fn(cmd);
pthread_mutex_lock(&finished_lock);
list_add_tail(&cmd->bs_list, &finished_list);
pthread_mutex_unlock(&finished_lock);
if (sig_fd < 0)
pthread_cond_signal(&finished_cond);
else
kill(getpid(), SIGUSR2);
}
pthread_exit(NULL);
}
static int bs_init_signalfd(void)
{
sigset_t mask;
int ret;
DIR *dir;
dir = opendir(BSDIR);
if (dir == NULL) {
/* not considered an error if there are no modules */
dprintf("could not open backing-store module directory %s\n",
BSDIR);
} else {
struct dirent *dirent;
void *handle;
while ((dirent = readdir(dir))) {
char *soname;
void (*register_bs_module)(void);
if (dirent->d_name[0] == '.') {
continue;
}
ret = asprintf(&soname, "%s/%s", BSDIR,
dirent->d_name);
if (ret == -1) {
eprintf("out of memory\n");
continue;
}
handle = dlopen(soname, RTLD_NOW|RTLD_LOCAL);
if (handle == NULL) {
eprintf("failed to dlopen backing-store "
"module %s error %s \n",
soname, dlerror());
free(soname);
continue;
}
register_bs_module = dlsym(handle, "register_bs_module");
if (register_bs_module == NULL) {
eprintf("could not find register_bs_module "
"symbol in module %s\n",
soname);
free(soname);
continue;
}
register_bs_module();
free(soname);
}
closedir(dir);
}
pthread_mutex_init(&finished_lock, NULL);
sigemptyset(&mask);
sigaddset(&mask, SIGUSR2);
sigprocmask(SIG_BLOCK, &mask, NULL);
sig_fd = __signalfd(-1, &mask, 0);
if (sig_fd < 0)
return 1;
ret = tgt_event_add(sig_fd, EPOLLIN, bs_sig_request_done, NULL);
if (ret < 0) {
close (sig_fd);
sig_fd = -1;
return 1;
}
return 0;
}
static int bs_init_notify_thread(void)
{
int ret;
pthread_cond_init(&finished_cond, NULL);
pthread_mutex_init(&finished_lock, NULL);
ret = pipe(command_fd);
if (ret) {
eprintf("failed to create command pipe, %m\n");
goto destroy_cond_mutex;
}
ret = pipe(done_fd);
if (ret) {
eprintf("failed to done command pipe, %m\n");
goto close_command_fd;
}
ret = tgt_event_add(done_fd[0], EPOLLIN, bs_thread_request_done, NULL);
if (ret) {
eprintf("failed to add epoll event\n");
goto close_done_fd;
}
ret = pthread_create(&ack_thread, NULL, bs_thread_ack_fn, NULL);
if (ret) {
eprintf("failed to create an ack thread, %s\n", strerror(ret));
goto event_del;
}
ret = write(command_fd[1], &ret, sizeof(ret));
if (ret <= 0)
goto event_del;
return 0;
event_del:
tgt_event_del(done_fd[0]);
close_done_fd:
close(done_fd[0]);
close(done_fd[1]);
close_command_fd:
close(command_fd[0]);
close(command_fd[1]);
destroy_cond_mutex:
pthread_cond_destroy(&finished_cond);
pthread_mutex_destroy(&finished_lock);
return 1;
}
int bs_init(void)
{
int ret;
ret = bs_init_signalfd();
if (!ret) {
eprintf("use signalfd notification\n");
return 0;
}
ret = bs_init_notify_thread();
if (!ret) {
eprintf("use pthread notification\n");
return 0;
}
return 1;
}
tgtadm_err bs_thread_open(struct bs_thread_info *info, request_func_t *rfn,
int nr_threads)
{
int i, ret;
info->worker_thread = zalloc(sizeof(pthread_t) * nr_threads);
if (!info->worker_thread)
return TGTADM_NOMEM;
eprintf("%d\n", nr_threads);
info->request_fn = rfn;
INIT_LIST_HEAD(&info->pending_list);
pthread_cond_init(&info->pending_cond, NULL);
pthread_mutex_init(&info->pending_lock, NULL);
for (i = 0; i < nr_threads; i++) {
ret = pthread_create(&info->worker_thread[i], NULL,
bs_thread_worker_fn, info);
if (ret) {
eprintf("failed to create a worker thread, %d %s\n",
i, strerror(ret));
if (ret)
goto destroy_threads;
}
}
info->nr_worker_threads = nr_threads;
return TGTADM_SUCCESS;
destroy_threads:
for (; i > 0; i--) {
if (info->worker_thread[i - 1]) {
pthread_cancel(info->worker_thread[i - 1]);
pthread_join(info->worker_thread[i - 1], NULL);
eprintf("stopped the worker thread %d\n", i - 1);
}
}
pthread_cond_destroy(&info->pending_cond);
pthread_mutex_destroy(&info->pending_lock);
free(info->worker_thread);
return TGTADM_NOMEM;
}
void bs_thread_close(struct bs_thread_info *info)
{
int i;
for (i = 0; i < info->nr_worker_threads && info->worker_thread[i]; i++) {
pthread_cancel(info->worker_thread[i]);
pthread_join(info->worker_thread[i], NULL);
}
pthread_cond_destroy(&info->pending_cond);
pthread_mutex_destroy(&info->pending_lock);
free(info->worker_thread);
}
int bs_thread_cmd_submit(struct scsi_cmd *cmd)
{
struct scsi_lu *lu = cmd->dev;
struct bs_thread_info *info = BS_THREAD_I(lu);
pthread_mutex_lock(&info->pending_lock);
list_add_tail(&cmd->bs_list, &info->pending_list);
pthread_mutex_unlock(&info->pending_lock);
pthread_cond_signal(&info->pending_cond);
set_cmd_async(cmd);
return 0;
}
tgt-1.0.80/usr/bs_aio.c 0000664 0000000 0000000 00000026366 13747533545 0014644 0 ustar 00root root 0000000 0000000 /*
* AIO backing store
*
* Copyright (C) 2006-2007 FUJITA Tomonori
* Copyright (C) 2006-2007 Mike Christie
* Copyright (C) 2011 Alexander Nezhinsky
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "list.h"
#include "util.h"
#include "tgtd.h"
#include "target.h"
#include "scsi.h"
#ifndef O_DIRECT
#define O_DIRECT 040000
#endif
#define AIO_MAX_IODEPTH 128
struct bs_aio_info {
struct list_head dev_list_entry;
io_context_t ctx;
struct list_head cmd_wait_list;
unsigned int nwaiting;
unsigned int npending;
unsigned int iodepth;
int resubmit;
struct scsi_lu *lu;
int evt_fd;
struct iocb iocb_arr[AIO_MAX_IODEPTH];
struct iocb *piocb_arr[AIO_MAX_IODEPTH];
struct io_event io_evts[AIO_MAX_IODEPTH];
};
static struct list_head bs_aio_dev_list = LIST_HEAD_INIT(bs_aio_dev_list);
static inline struct bs_aio_info *BS_AIO_I(struct scsi_lu *lu)
{
return (struct bs_aio_info *) ((char *)lu + sizeof(*lu));
}
static void bs_aio_iocb_prep(struct bs_aio_info *info, int idx,
struct scsi_cmd *cmd)
{
struct iocb *iocb = &info->iocb_arr[idx];
unsigned int scsi_op = (unsigned int)cmd->scb[0];
iocb->data = cmd;
iocb->key = 0;
iocb->aio_reqprio = 0;
iocb->aio_fildes = info->lu->fd;
switch (scsi_op) {
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
iocb->aio_lio_opcode = IO_CMD_PWRITE;
iocb->u.c.buf = scsi_get_out_buffer(cmd);
iocb->u.c.nbytes = scsi_get_out_length(cmd);
dprintf("prep WR cmd:%p op:%x buf:0x%p sz:%lx\n",
cmd, scsi_op, iocb->u.c.buf, iocb->u.c.nbytes);
break;
case READ_6:
case READ_10:
case READ_12:
case READ_16:
iocb->aio_lio_opcode = IO_CMD_PREAD;
iocb->u.c.buf = scsi_get_in_buffer(cmd);
iocb->u.c.nbytes = scsi_get_in_length(cmd);
dprintf("prep RD cmd:%p op:%x buf:0x%p sz:%lx\n",
cmd, scsi_op, iocb->u.c.buf, iocb->u.c.nbytes);
break;
default:
return;
}
iocb->u.c.offset = cmd->offset;
iocb->u.c.flags |= (1 << 0); /* IOCB_FLAG_RESFD - use eventfd file desc. */
iocb->u.c.resfd = info->evt_fd;
}
static int bs_aio_submit_dev_batch(struct bs_aio_info *info)
{
int nsubmit, nsuccess;
struct scsi_cmd *cmd, *next;
int i = 0;
nsubmit = info->iodepth - info->npending; /* max allowed to submit */
if (nsubmit > info->nwaiting)
nsubmit = info->nwaiting;
dprintf("nsubmit:%d waiting:%d pending:%d, tgt:%d lun:%"PRId64 "\n",
nsubmit, info->nwaiting, info->npending,
info->lu->tgt->tid, info->lu->lun);
if (!nsubmit)
return 0;
list_for_each_entry_safe(cmd, next, &info->cmd_wait_list, bs_list) {
bs_aio_iocb_prep(info, i, cmd);
list_del(&cmd->bs_list);
if (++i == nsubmit)
break;
}
nsuccess = io_submit(info->ctx, nsubmit, info->piocb_arr);
if (unlikely(nsuccess < 0)) {
if (nsuccess == -EAGAIN) {
eprintf("delayed submit %d cmds to tgt:%d lun:%"PRId64 "\n",
nsubmit, info->lu->tgt->tid, info->lu->lun);
nsuccess = 0; /* leave the dev pending with all cmds */
}
else {
eprintf("failed to submit %d cmds to tgt:%d lun:%"PRId64
", err: %d\n",
nsubmit, info->lu->tgt->tid,
info->lu->lun, -nsuccess);
for (i = nsubmit - 1; i >= 0; i--) {
cmd = info->iocb_arr[i].data;
clear_cmd_async(cmd);
info->nwaiting--;
if (!info->nwaiting)
list_del(&info->dev_list_entry);
}
return nsuccess;
}
}
if (unlikely(nsuccess < nsubmit)) {
for (i=nsubmit-1; i >= nsuccess; i--) {
cmd = info->iocb_arr[i].data;
list_add(&cmd->bs_list, &info->cmd_wait_list);
}
}
info->npending += nsuccess;
info->nwaiting -= nsuccess;
/* if no cmds remain, remove the dev from the pending list */
if (likely(!info->nwaiting))
list_del(&info->dev_list_entry);
dprintf("submitted %d of %d cmds to tgt:%d lun:%"PRId64
", waiting:%d pending:%d\n",
nsuccess, nsubmit, info->lu->tgt->tid, info->lu->lun,
info->nwaiting, info->npending);
return 0;
}
static int bs_aio_submit_all_devs(void)
{
struct bs_aio_info *dev_info, *next_dev;
int err;
/* pass over all devices having some queued cmds and submit */
list_for_each_entry_safe(dev_info, next_dev, &bs_aio_dev_list, dev_list_entry) {
err = bs_aio_submit_dev_batch(dev_info);
if (unlikely(err))
return err;
}
return 0;
}
static int bs_aio_cmd_submit(struct scsi_cmd *cmd)
{
struct scsi_lu *lu = cmd->dev;
struct bs_aio_info *info = BS_AIO_I(lu);
unsigned int scsi_op = (unsigned int)cmd->scb[0];
switch (scsi_op) {
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
case READ_6:
case READ_10:
case READ_12:
case READ_16:
break;
case WRITE_SAME:
case WRITE_SAME_16:
eprintf("WRITE_SAME not yet supported for AIO backend.\n");
return -1;
case SYNCHRONIZE_CACHE:
case SYNCHRONIZE_CACHE_16:
default:
dprintf("skipped cmd:%p op:%x\n", cmd, scsi_op);
return 0;
}
list_add_tail(&cmd->bs_list, &info->cmd_wait_list);
if (!info->nwaiting)
list_add_tail(&info->dev_list_entry, &bs_aio_dev_list);
info->nwaiting++;
set_cmd_async(cmd);
if (!cmd_not_last(cmd)) /* last cmd in batch */
return bs_aio_submit_all_devs();
if (info->nwaiting == info->iodepth - info->npending)
return bs_aio_submit_dev_batch(info);
return 0;
}
static void bs_aio_complete_one(struct io_event *ep)
{
struct scsi_cmd *cmd = (void *)(unsigned long)ep->data;
uint32_t length;
int result;
switch (cmd->scb[0]) {
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
length = scsi_get_out_length(cmd);
break;
default:
length = scsi_get_in_length(cmd);
break;
}
if (likely(ep->res == length))
result = SAM_STAT_GOOD;
else {
sense_data_build(cmd, MEDIUM_ERROR, 0);
result = SAM_STAT_CHECK_CONDITION;
}
dprintf("cmd: %p\n", cmd);
target_cmd_io_done(cmd, result);
}
static void bs_aio_get_completions(int fd, int events, void *data)
{
struct bs_aio_info *info = data;
int i, ret;
/* read from eventfd returns 8-byte int, fails with the error EINVAL
if the size of the supplied buffer is less than 8 bytes */
uint64_t evts_complete;
unsigned int ncomplete, nevents;
retry_read:
ret = read(info->evt_fd, &evts_complete, sizeof(evts_complete));
if (unlikely(ret < 0)) {
eprintf("failed to read AIO completions, %m\n");
if (errno == EAGAIN || errno == EINTR)
goto retry_read;
return;
}
ncomplete = (unsigned int) evts_complete;
while (ncomplete) {
nevents = min_t(unsigned int, ncomplete, ARRAY_SIZE(info->io_evts));
retry_getevts:
ret = io_getevents(info->ctx, 1, nevents, info->io_evts, NULL);
if (likely(ret > 0)) {
nevents = ret;
info->npending -= nevents;
} else {
if (ret == -EINTR)
goto retry_getevts;
eprintf("io_getevents failed, err:%d\n", -ret);
return;
}
dprintf("got %d ioevents out of %d, pending %d\n",
nevents, ncomplete, info->npending);
for (i = 0; i < nevents; i++)
bs_aio_complete_one(&info->io_evts[i]);
ncomplete -= nevents;
}
if (info->nwaiting) {
dprintf("submit waiting cmds to tgt:%d lun:%"PRId64 "\n",
info->lu->tgt->tid, info->lu->lun);
bs_aio_submit_dev_batch(info);
}
}
static int bs_aio_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size)
{
struct bs_aio_info *info = BS_AIO_I(lu);
int ret, afd;
uint32_t blksize = 0;
info->iodepth = AIO_MAX_IODEPTH;
eprintf("create aio context for tgt:%d lun:%"PRId64 ", max iodepth:%d\n",
info->lu->tgt->tid, info->lu->lun, info->iodepth);
ret = io_setup(info->iodepth, &info->ctx);
if (ret) {
eprintf("failed to create aio context, %m\n");
return -1;
}
afd = eventfd(0, O_NONBLOCK);
if (afd < 0) {
eprintf("failed to create eventfd for tgt:%d lun:%"PRId64 ", %m\n",
info->lu->tgt->tid, info->lu->lun);
ret = afd;
goto close_ctx;
}
dprintf("eventfd:%d for tgt:%d lun:%"PRId64 "\n",
afd, info->lu->tgt->tid, info->lu->lun);
ret = tgt_event_add(afd, EPOLLIN, bs_aio_get_completions, info);
if (ret)
goto close_eventfd;
info->evt_fd = afd;
eprintf("open %s, RW, O_DIRECT for tgt:%d lun:%"PRId64 "\n",
path, info->lu->tgt->tid, info->lu->lun);
*fd = backed_file_open(path, O_RDWR|O_LARGEFILE|O_DIRECT, size,
&blksize);
/* If we get access denied, try opening the file in readonly mode */
if (*fd == -1 && (errno == EACCES || errno == EROFS)) {
eprintf("open %s, READONLY, O_DIRECT for tgt:%d lun:%"PRId64 "\n",
path, info->lu->tgt->tid, info->lu->lun);
*fd = backed_file_open(path, O_RDONLY|O_LARGEFILE|O_DIRECT,
size, &blksize);
lu->attrs.readonly = 1;
}
if (*fd < 0) {
eprintf("failed to open %s, for tgt:%d lun:%"PRId64 ", %m\n",
path, info->lu->tgt->tid, info->lu->lun);
ret = *fd;
goto remove_tgt_evt;
}
eprintf("%s opened successfully for tgt:%d lun:%"PRId64 "\n",
path, info->lu->tgt->tid, info->lu->lun);
if (!lu->attrs.no_auto_lbppbe)
update_lbppbe(lu, blksize);
return 0;
remove_tgt_evt:
tgt_event_del(afd);
close_eventfd:
close(afd);
close_ctx:
io_destroy(info->ctx);
return ret;
}
static void bs_aio_close(struct scsi_lu *lu)
{
close(lu->fd);
}
static tgtadm_err bs_aio_init(struct scsi_lu *lu, char *bsopts)
{
struct bs_aio_info *info = BS_AIO_I(lu);
int i;
memset(info, 0, sizeof(*info));
INIT_LIST_HEAD(&info->dev_list_entry);
INIT_LIST_HEAD(&info->cmd_wait_list);
info->lu = lu;
for (i=0; i < ARRAY_SIZE(info->iocb_arr); i++)
info->piocb_arr[i] = &info->iocb_arr[i];
return TGTADM_SUCCESS;
}
static void bs_aio_exit(struct scsi_lu *lu)
{
struct bs_aio_info *info = BS_AIO_I(lu);
tgt_event_del(info->evt_fd);
close(info->evt_fd);
io_destroy(info->ctx);
}
static struct backingstore_template aio_bst = {
.bs_name = "aio",
.bs_datasize = sizeof(struct bs_aio_info),
.bs_init = bs_aio_init,
.bs_exit = bs_aio_exit,
.bs_open = bs_aio_open,
.bs_close = bs_aio_close,
.bs_cmd_submit = bs_aio_cmd_submit,
};
__attribute__((constructor)) static void register_bs_module(void)
{
unsigned char opcodes[] = {
ALLOW_MEDIUM_REMOVAL,
COMPARE_AND_WRITE,
FORMAT_UNIT,
INQUIRY,
MAINT_PROTOCOL_IN,
MODE_SELECT,
MODE_SELECT_10,
MODE_SENSE,
MODE_SENSE_10,
ORWRITE_16,
PERSISTENT_RESERVE_IN,
PERSISTENT_RESERVE_OUT,
PRE_FETCH_10,
PRE_FETCH_16,
READ_10,
READ_12,
READ_16,
READ_6,
READ_CAPACITY,
RELEASE,
REPORT_LUNS,
REQUEST_SENSE,
RESERVE,
SEND_DIAGNOSTIC,
SERVICE_ACTION_IN,
START_STOP,
SYNCHRONIZE_CACHE,
SYNCHRONIZE_CACHE_16,
TEST_UNIT_READY,
UNMAP,
VERIFY_10,
VERIFY_12,
VERIFY_16,
WRITE_10,
WRITE_12,
WRITE_16,
WRITE_6,
WRITE_VERIFY,
WRITE_VERIFY_12,
WRITE_VERIFY_16
};
bs_create_opcode_map(&aio_bst, opcodes, ARRAY_SIZE(opcodes));
register_backingstore_template(&aio_bst);
}
tgt-1.0.80/usr/bs_aio.h 0000664 0000000 0000000 00000006113 13747533545 0014635 0 ustar 00root root 0000000 0000000 #ifndef __BS_AIO_H
#define __BS_AIO_H
/* this file is a workaround */
/*
* eventfd-aio-test by Davide Libenzi (test app for eventfd hooked into KAIO)
* Copyright (C) 2007 Davide Libenzi
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Davide Libenzi
*
*/
enum {
IOCB_CMD_PREAD = 0,
IOCB_CMD_PWRITE = 1,
IOCB_CMD_FSYNC = 2,
IOCB_CMD_FDSYNC = 3,
/* These two are experimental.
* IOCB_CMD_PREADX = 4,
* IOCB_CMD_POLL = 5,
*/
IOCB_CMD_NOOP = 6,
IOCB_CMD_PREADV = 7,
IOCB_CMD_PWRITEV = 8,
};
#define IOCB_FLAG_RESFD (1 << 0)
#if defined(__LITTLE_ENDIAN)
#define PADDED(x, y) x, y
#elif defined(__BIG_ENDIAN)
#define PADDED(x, y) y, x
#else
#error edit for your odd byteorder.
#endif
typedef unsigned long io_context_t;
struct io_event {
uint64_t data; /* the data field from the iocb */
uint64_t obj; /* what iocb this event came from */
int64_t res; /* result code for this event */
int64_t res2; /* secondary result */
};
struct iocb {
/* these are internal to the kernel/libc. */
uint64_t aio_data; /* data to be returned in event's data */
int32_t PADDED(aio_key, aio_reserved1);
/* the kernel sets aio_key to the req # */
/* common fields */
uint16_t aio_lio_opcode; /* see IOCB_CMD_ above */
int16_t aio_reqprio;
int32_t aio_fildes;
uint64_t aio_buf;
uint64_t aio_nbytes;
int64_t aio_offset;
/* extra parameters */
uint64_t aio_reserved2; /* TODO: use this for a (struct sigevent *) */
/* flags for the "struct iocb" */
int32_t aio_flags;
/*
* if the IOCB_FLAG_RESFD flag of "aio_flags" is set, this is an
* eventfd to signal AIO readiness to
*/
int32_t aio_resfd;
}; /* 64 bytes */
static inline int io_setup(unsigned nr_reqs, io_context_t *ctx)
{
return syscall(__NR_io_setup, nr_reqs, ctx);
}
static inline long io_destroy(io_context_t ctx)
{
return syscall(__NR_io_destroy, ctx);
}
static inline int io_submit(io_context_t ctx, long n, struct iocb **paiocb)
{
return syscall(__NR_io_submit, ctx, n, paiocb);
}
static inline long io_cancel(io_context_t ctx, struct iocb *aiocb,
struct io_event *res)
{
return syscall(__NR_io_cancel, ctx, aiocb, res);
}
static inline long io_getevents(io_context_t ctx, long min_nr, long nr,
struct io_event *events, struct timespec *tmo)
{
return syscall(__NR_io_getevents, ctx, min_nr, nr, events, tmo);
}
static inline int eventfd(int count)
{
return syscall(__NR_eventfd, count);
}
#endif
tgt-1.0.80/usr/bs_glfs.c 0000664 0000000 0000000 00000027143 13747533545 0015021 0 ustar 00root root 0000000 0000000 /*
* Synchronous glfs backing store routines
*
* Modified from bs_rdb.c
* Copyright (C) 2013 Dan Lambright
* Copyright (C) 2006-2007 FUJITA Tomonori
* Copyright (C) 2006-2007 Mike Christie
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#define _XOPEN_SOURCE 600
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "list.h"
#include "util.h"
#include "tgtd.h"
#include "scsi.h"
#include "spc.h"
#include "bs_thread.h"
#include "glfs.h"
struct active_glfs {
char *name;
glfs_t *fs;
glfs_fd_t *gfd;
char *logfile;
int loglevel;
};
#define ALLOWED_BSOFLAGS (O_SYNC | O_DIRECT | O_RDWR | O_LARGEFILE)
#define GLUSTER_PORT 24007
#define GFSP(lu) ((struct active_glfs *) \
((char *)lu + \
sizeof(struct scsi_lu) + \
sizeof(struct bs_thread_info)) \
)
static void set_medium_error(int *result, uint8_t *key, uint16_t *asc)
{
*result = SAM_STAT_CHECK_CONDITION;
*key = MEDIUM_ERROR;
*asc = ASC_READ_ERROR;
}
static int bs_glfs_discard(glfs_fd_t *gfd, off_t offset, size_t len)
{
#ifdef BS_GLFS_DISCARD
return glfs_discard(gfd, offset, len);
#endif
return 0;
}
static void bs_glfs_request(struct scsi_cmd *cmd)
{
glfs_fd_t *gfd = GFSP(cmd->dev)->gfd;
struct scsi_lu *lu = cmd->dev;
int ret;
uint32_t length;
int result = SAM_STAT_GOOD;
uint8_t key;
uint16_t asc;
char *tmpbuf;
size_t blocksize;
uint64_t offset = cmd->offset;
uint32_t tl = cmd->tl;
int do_verify = 0;
int i;
char *ptr;
const char *write_buf = NULL;
ret = length = 0;
key = asc = 0;
switch (cmd->scb[0]) {
case ORWRITE_16:
length = scsi_get_out_length(cmd);
tmpbuf = malloc(length);
if (!tmpbuf) {
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
ret = glfs_pread(gfd, tmpbuf, length, offset, lu->bsoflags);
if (ret != length) {
set_medium_error(&result, &key, &asc);
free(tmpbuf);
break;
}
ptr = scsi_get_out_buffer(cmd);
for (i = 0; i < length; i++)
ptr[i] |= tmpbuf[i];
free(tmpbuf);
write_buf = scsi_get_out_buffer(cmd);
goto write;
case COMPARE_AND_WRITE:
/* Blocks are transferred twice, first the set that
* we compare to the existing data, and second the set
* to write if the compare was successful.
*/
length = scsi_get_out_length(cmd) / 2;
if (length != cmd->tl) {
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_INVALID_FIELD_IN_CDB;
break;
}
tmpbuf = malloc(length);
if (!tmpbuf) {
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
ret = glfs_pread(gfd, tmpbuf, length, offset, SEEK_SET);
if (ret != length) {
set_medium_error(&result, &key, &asc);
free(tmpbuf);
break;
}
if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) {
uint32_t pos = 0;
char *spos = scsi_get_out_buffer(cmd);
char *dpos = tmpbuf;
/*
* Data differed, this is assumed to be 'rare'
* so use a much more expensive byte-by-byte
* comparasion to find out at which offset the
* data differs.
*/
for (pos = 0; pos < length && *spos++ == *dpos++;
pos++)
;
result = SAM_STAT_CHECK_CONDITION;
key = MISCOMPARE;
asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION;
free(tmpbuf);
break;
}
free(tmpbuf);
write_buf = scsi_get_out_buffer(cmd) + length;
goto write;
case SYNCHRONIZE_CACHE:
case SYNCHRONIZE_CACHE_16:
/* TODO */
length = (cmd->scb[0] == SYNCHRONIZE_CACHE) ? 0 : 0;
if (cmd->scb[1] & 0x2) {
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_INVALID_FIELD_IN_CDB;
} else {
glfs_fdatasync(gfd);
}
break;
case WRITE_VERIFY:
case WRITE_VERIFY_12:
case WRITE_VERIFY_16:
do_verify = 1;
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
length = scsi_get_out_length(cmd);
write_buf = scsi_get_out_buffer(cmd);
write:
ret = glfs_pwrite(gfd, write_buf, length, offset, lu->bsoflags);
if (ret == length) {
struct mode_pg *pg;
/*
* it would be better not to access to pg
* directy.
*/
pg = find_mode_page(cmd->dev, 0x08, 0);
if (pg == NULL) {
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_INVALID_FIELD_IN_CDB;
break;
}
if (((cmd->scb[0] != WRITE_6) && (cmd->scb[1] & 0x8)) ||
!(pg->mode_data[0] & 0x04))
glfs_fdatasync(gfd);
} else
set_medium_error(&result, &key, &asc);
if (do_verify)
goto verify;
break;
case WRITE_SAME:
case WRITE_SAME_16:
/* WRITE_SAME used to punch hole in file */
if (cmd->scb[1] & 0x08) {
ret = bs_glfs_discard(gfd, offset, tl);
if (ret != 0) {
eprintf("Failed WRITE_SAME command\n");
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
break;
}
while (tl > 0) {
blocksize = 1 << cmd->dev->blk_shift;
tmpbuf = scsi_get_out_buffer(cmd);
switch (cmd->scb[1] & 0x06) {
case 0x02: /* PBDATA==0 LBDATA==1 */
put_unaligned_be32(offset, tmpbuf);
break;
case 0x04: /* PBDATA==1 LBDATA==0 */
/* physical sector format */
put_unaligned_be64(offset, tmpbuf);
break;
}
ret = glfs_pwrite(gfd, tmpbuf, blocksize,
offset, lu->bsoflags);
if (ret != blocksize)
set_medium_error(&result, &key, &asc);
offset += blocksize;
tl -= blocksize;
}
break;
case READ_6:
case READ_10:
case READ_12:
case READ_16:
length = scsi_get_in_length(cmd);
ret = glfs_pread(gfd, scsi_get_in_buffer(cmd),
length, offset, SEEK_SET);
if (ret != length) {
eprintf("Error on read %x %x", ret, length);
set_medium_error(&result, &key, &asc);
}
break;
case PRE_FETCH_10:
case PRE_FETCH_16:
if (ret != 0)
set_medium_error(&result, &key, &asc);
break;
case VERIFY_10:
case VERIFY_12:
case VERIFY_16:
verify:
length = scsi_get_out_length(cmd);
tmpbuf = malloc(length);
if (!tmpbuf) {
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
ret = glfs_pread(gfd, tmpbuf, length, offset, lu->bsoflags);
if (ret != length)
set_medium_error(&result, &key, &asc);
else if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) {
result = SAM_STAT_CHECK_CONDITION;
key = MISCOMPARE;
asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION;
}
free(tmpbuf);
break;
case UNMAP:
if (!cmd->dev->attrs.thinprovisioning) {
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_INVALID_FIELD_IN_CDB;
break;
}
length = scsi_get_out_length(cmd);
tmpbuf = scsi_get_out_buffer(cmd);
if (length < 8)
break;
length -= 8;
tmpbuf += 8;
while (length >= 16) {
offset = get_unaligned_be64(&tmpbuf[0]);
offset = offset << cmd->dev->blk_shift;
tl = get_unaligned_be32(&tmpbuf[8]);
tl = tl << cmd->dev->blk_shift;
if (offset + tl > cmd->dev->size) {
eprintf("UNMAP beyond EOF\n");
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_LBA_OUT_OF_RANGE;
break;
}
if (tl > 0) {
if (bs_glfs_discard(gfd, offset, tl) != 0) {
eprintf("Failed UNMAP\n");
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
}
length -= 16;
tmpbuf += 16;
}
break;
default:
break;
}
dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length);
scsi_set_result(cmd, result);
if (result != SAM_STAT_GOOD) {
eprintf("io error %p %x %x %d %d %" PRIu64 ", %m\n",
cmd, result, cmd->scb[0], ret, length, offset);
sense_data_build(cmd, key, asc);
}
}
static void parse_imagepath(char *image, char **server, char **vol, char **path)
{
char *origp = strdup(image);
char *p, *sep;
p = origp;
sep = strchr(p, '@');
if (sep == NULL) {
*server = "";
} else {
*sep = '\0';
*server = strdup(p);
p = sep + 1;
}
sep = strchr(p, ':');
if (sep == NULL) {
*vol = "";
} else {
*vol = strdup(sep + 1);
*sep = '\0';
}
/* p points to path\0 */
*path = strdup(p);
free(origp);
}
static int bs_glfs_open(struct scsi_lu *lu, char *image, int *fd,
uint64_t *size)
{
int ret = 0;
char *servername;
char *volname;
char *pathname;
int bsoflags = ALLOWED_BSOFLAGS;
glfs_t *fs = 0;
parse_imagepath(image, &volname, &pathname, &servername);
if (volname && servername && pathname) {
glfs_fd_t *gfd = NULL;
struct stat st;
fs = glfs_new(volname);
if (!fs)
goto fail;
ret = glfs_set_volfile_server(fs, "tcp", servername,
GLUSTER_PORT);
ret = glfs_init(fs);
if (ret)
goto fail;
GFSP(lu)->fs = fs;
if (lu->bsoflags)
bsoflags = lu->bsoflags;
gfd = glfs_open(fs, pathname, bsoflags);
if (gfd == NULL)
goto fail;
ret = glfs_lstat(fs, pathname, &st);
if (ret)
goto fail;
GFSP(lu)->gfd = gfd;
*size = (long) st.st_size;
if (GFSP(lu)->logfile)
glfs_set_logging(fs, GFSP(lu)->logfile,
GFSP(lu)->loglevel);
return 0;
}
fail:
if (fs)
glfs_fini(fs);
return -EIO;
}
static void bs_glfs_close(struct scsi_lu *lu)
{
if (GFSP(lu)->gfd)
glfs_close(GFSP(lu)->gfd);
if (GFSP(lu)->gfd)
glfs_fini(GFSP(lu)->fs);
}
static char *slurp_to_semi(char **p)
{
char *end = index(*p, ';');
char *ret;
int len;
if (end == NULL)
end = *p + strlen(*p);
len = end - *p;
ret = malloc(len + 1);
strncpy(ret, *p, len);
ret[len] = '\0';
*p = end;
/* Jump past the semicolon, if we stopped at one */
if (**p == ';')
*p = end + 1;
return ret;
}
static char *slurp_value(char **p)
{
char *equal = index(*p, '=');
if (equal) {
*p = equal + 1;
return slurp_to_semi(p);
} else {
return NULL;
}
}
static int is_opt(const char *opt, char *p)
{
int ret = 0;
if ((strncmp(p, opt, strlen(opt)) == 0) &&
(p[strlen(opt)] == '=')) {
ret = 1;
}
return ret;
}
static tgtadm_err bs_glfs_init(struct scsi_lu *lu, char *bsopts)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
char *logfile = NULL;
int loglevel = 0;
char *sloglevel;
while (bsopts && strlen(bsopts)) {
if (is_opt("logfile", bsopts))
logfile = slurp_value(&bsopts);
else if (is_opt("loglevel", bsopts)) {
sloglevel = slurp_value(&bsopts);
loglevel = atoi(sloglevel);
}
}
GFSP(lu)->logfile = logfile;
GFSP(lu)->loglevel = loglevel;
return bs_thread_open(info, bs_glfs_request, nr_iothreads);
}
static void bs_glfs_exit(struct scsi_lu *lu)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
if (GFSP(lu)->gfd)
glfs_close(GFSP(lu)->gfd);
if (GFSP(lu)->fs)
glfs_fini(GFSP(lu)->fs);
bs_thread_close(info);
}
static struct backingstore_template glfs_bst = {
.bs_name = "glfs",
.bs_datasize = sizeof(struct active_glfs) +
sizeof(struct bs_thread_info),
.bs_open = bs_glfs_open,
.bs_close = bs_glfs_close,
.bs_init = bs_glfs_init,
.bs_exit = bs_glfs_exit,
.bs_cmd_submit = bs_thread_cmd_submit,
.bs_oflags_supported = ALLOWED_BSOFLAGS
};
void register_bs_module(void)
{
register_backingstore_template(&glfs_bst);
}
tgt-1.0.80/usr/bs_null.c 0000664 0000000 0000000 00000003110 13747533545 0015024 0 ustar 00root root 0000000 0000000 /*
* NULL I/O backing store routine
*
* Copyright (C) 2008 Alexander Nezhinsky
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include "list.h"
#include "tgtd.h"
#include "scsi.h"
#define NULL_BS_DEV_SIZE (1ULL << 40)
int bs_null_cmd_submit(struct scsi_cmd *cmd)
{
scsi_set_result(cmd, SAM_STAT_GOOD);
return 0;
}
static int bs_null_open(struct scsi_lu *lu, char *path,
int *fd, uint64_t *size)
{
*size = NULL_BS_DEV_SIZE;
dprintf("NULL backing store open, size: %" PRIu64 "\n", *size);
return 0;
}
static void bs_null_close(struct scsi_lu *lu)
{
}
static struct backingstore_template null_bst = {
.bs_name = "null",
.bs_datasize = 0,
.bs_open = bs_null_open,
.bs_close = bs_null_close,
.bs_cmd_submit = bs_null_cmd_submit,
};
__attribute__((constructor)) static void bs_null_constructor(void)
{
register_backingstore_template(&null_bst);
}
tgt-1.0.80/usr/bs_rbd.c 0000664 0000000 0000000 00000037534 13747533545 0014642 0 ustar 00root root 0000000 0000000 /*
* Synchronous rbd image backing store routine
*
* modified from bs_rdrw.c:
* Copyright (C) 2006-2007 FUJITA Tomonori
* Copyright (C) 2006-2007 Mike Christie
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#define _XOPEN_SOURCE 600
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "list.h"
#include "util.h"
#include "tgtd.h"
#include "scsi.h"
#include "spc.h"
#include "bs_thread.h"
#include "rados/librados.h"
#include "rbd/librbd.h"
struct active_rbd {
char *poolname;
char *imagename;
char *snapname;
rados_t cluster;
rados_ioctx_t ioctx;
rbd_image_t rbd_image;
};
/* active_rbd is allocated just after the bs_thread_info */
#define RBDP(lu) ((struct active_rbd *) \
((char *)lu + \
sizeof(struct scsi_lu) + \
sizeof(struct bs_thread_info)) \
)
static void parse_imagepath(char *path, char **pool, char **image, char **snap)
{
char *origp = strdup(path);
char *p, *sep;
p = origp;
sep = strchr(p, '/');
if (sep == NULL) {
*pool = "rbd";
} else {
*sep = '\0';
*pool = strdup(p);
p = sep + 1;
}
/* p points to image[@snap] */
sep = strchr(p, '@');
if (sep == NULL) {
*snap = "";
} else {
*snap = strdup(sep + 1);
*sep = '\0';
}
/* p points to image\0 */
*image = strdup(p);
free(origp);
}
static void set_medium_error(int *result, uint8_t *key, uint16_t *asc)
{
*result = SAM_STAT_CHECK_CONDITION;
*key = MEDIUM_ERROR;
*asc = ASC_READ_ERROR;
}
static void bs_sync_sync_range(struct scsi_cmd *cmd, uint32_t length,
int *result, uint8_t *key, uint16_t *asc)
{
int ret;
ret = rbd_flush(RBDP(cmd->dev)->rbd_image);
if (ret)
set_medium_error(result, key, asc);
}
static void bs_rbd_request(struct scsi_cmd *cmd)
{
int ret;
uint32_t length;
int result = SAM_STAT_GOOD;
uint8_t key;
uint16_t asc;
#if 0
/*
* This should go in the sense data on error for COMPARE_AND_WRITE, but
* there doesn't seem to be any attempt to do so...
*/
uint32_t info = 0;
#endif
char *tmpbuf;
size_t blocksize;
uint64_t offset = cmd->offset;
uint32_t tl = cmd->tl;
int do_verify = 0;
int i;
char *ptr;
const char *write_buf = NULL;
ret = length = 0;
key = asc = 0;
struct active_rbd *rbd = RBDP(cmd->dev);
switch (cmd->scb[0]) {
case ORWRITE_16:
length = scsi_get_out_length(cmd);
tmpbuf = malloc(length);
if (!tmpbuf) {
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
ret = rbd_read(rbd->rbd_image, offset, length, tmpbuf);
if (ret != length) {
set_medium_error(&result, &key, &asc);
free(tmpbuf);
break;
}
ptr = scsi_get_out_buffer(cmd);
for (i = 0; i < length; i++)
ptr[i] |= tmpbuf[i];
free(tmpbuf);
write_buf = scsi_get_out_buffer(cmd);
goto write;
case COMPARE_AND_WRITE:
/* Blocks are transferred twice, first the set that
* we compare to the existing data, and second the set
* to write if the compare was successful.
*/
length = scsi_get_out_length(cmd) / 2;
if (length != cmd->tl) {
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_INVALID_FIELD_IN_CDB;
break;
}
tmpbuf = malloc(length);
if (!tmpbuf) {
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
ret = rbd_read(rbd->rbd_image, offset, length, tmpbuf);
if (ret != length) {
set_medium_error(&result, &key, &asc);
free(tmpbuf);
break;
}
if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) {
uint32_t pos = 0;
char *spos = scsi_get_out_buffer(cmd);
char *dpos = tmpbuf;
/*
* Data differed, this is assumed to be 'rare'
* so use a much more expensive byte-by-byte
* comparasion to find out at which offset the
* data differs.
*/
for (pos = 0; pos < length && *spos++ == *dpos++;
pos++)
;
#if 0
/* See comment above at declaration */
info = pos;
#endif
result = SAM_STAT_CHECK_CONDITION;
key = MISCOMPARE;
asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION;
free(tmpbuf);
break;
}
/* no DPO bit (cache retention advice) support */
free(tmpbuf);
write_buf = scsi_get_out_buffer(cmd) + length;
goto write;
case SYNCHRONIZE_CACHE:
case SYNCHRONIZE_CACHE_16:
/* TODO */
length = (cmd->scb[0] == SYNCHRONIZE_CACHE) ? 0 : 0;
if (cmd->scb[1] & 0x2) {
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_INVALID_FIELD_IN_CDB;
} else
bs_sync_sync_range(cmd, length, &result, &key, &asc);
break;
case WRITE_VERIFY:
case WRITE_VERIFY_12:
case WRITE_VERIFY_16:
do_verify = 1;
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
length = scsi_get_out_length(cmd);
write_buf = scsi_get_out_buffer(cmd);
write:
ret = rbd_write(rbd->rbd_image, offset, length, write_buf);
if (ret == length) {
struct mode_pg *pg;
/*
* it would be better not to access to pg
* directy.
*/
pg = find_mode_page(cmd->dev, 0x08, 0);
if (pg == NULL) {
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_INVALID_FIELD_IN_CDB;
break;
}
if (((cmd->scb[0] != WRITE_6) && (cmd->scb[1] & 0x8)) ||
!(pg->mode_data[0] & 0x04))
bs_sync_sync_range(cmd, length, &result, &key,
&asc);
} else
set_medium_error(&result, &key, &asc);
if (do_verify)
goto verify;
break;
case WRITE_SAME:
case WRITE_SAME_16:
/* WRITE_SAME used to punch hole in file */
if (cmd->scb[1] & 0x08) {
ret = rbd_discard(rbd->rbd_image, offset, tl);
if (ret != 0) {
eprintf("Failed to punch hole for WRITE_SAME"
" command\n");
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
break;
}
while (tl > 0) {
blocksize = 1 << cmd->dev->blk_shift;
tmpbuf = scsi_get_out_buffer(cmd);
switch (cmd->scb[1] & 0x06) {
case 0x02: /* PBDATA==0 LBDATA==1 */
put_unaligned_be32(offset, tmpbuf);
break;
case 0x04: /* PBDATA==1 LBDATA==0 */
/* physical sector format */
put_unaligned_be64(offset, tmpbuf);
break;
}
ret = rbd_write(rbd->rbd_image, offset, blocksize,
tmpbuf);
if (ret != blocksize)
set_medium_error(&result, &key, &asc);
offset += blocksize;
tl -= blocksize;
}
break;
case READ_6:
case READ_10:
case READ_12:
case READ_16:
length = scsi_get_in_length(cmd);
ret = rbd_read(rbd->rbd_image, offset, length,
scsi_get_in_buffer(cmd));
if (ret != length)
set_medium_error(&result, &key, &asc);
break;
case PRE_FETCH_10:
case PRE_FETCH_16:
break;
case VERIFY_10:
case VERIFY_12:
case VERIFY_16:
verify:
length = scsi_get_out_length(cmd);
tmpbuf = malloc(length);
if (!tmpbuf) {
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
ret = rbd_read(rbd->rbd_image, offset, length, tmpbuf);
if (ret != length)
set_medium_error(&result, &key, &asc);
else if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) {
result = SAM_STAT_CHECK_CONDITION;
key = MISCOMPARE;
asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION;
}
free(tmpbuf);
break;
case UNMAP:
if (!cmd->dev->attrs.thinprovisioning) {
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_INVALID_FIELD_IN_CDB;
break;
}
length = scsi_get_out_length(cmd);
tmpbuf = scsi_get_out_buffer(cmd);
if (length < 8)
break;
length -= 8;
tmpbuf += 8;
while (length >= 16) {
offset = get_unaligned_be64(&tmpbuf[0]);
offset = offset << cmd->dev->blk_shift;
tl = get_unaligned_be32(&tmpbuf[8]);
tl = tl << cmd->dev->blk_shift;
if (offset + tl > cmd->dev->size) {
eprintf("UNMAP beyond EOF\n");
result = SAM_STAT_CHECK_CONDITION;
key = ILLEGAL_REQUEST;
asc = ASC_LBA_OUT_OF_RANGE;
break;
}
if (tl > 0) {
if (rbd_discard(rbd->rbd_image, offset, tl)
!= 0) {
eprintf("Failed to punch hole for"
" UNMAP at offset:%" PRIu64
" length:%d\n",
offset, tl);
result = SAM_STAT_CHECK_CONDITION;
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
break;
}
}
length -= 16;
tmpbuf += 16;
}
break;
default:
break;
}
dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length);
scsi_set_result(cmd, result);
if (result != SAM_STAT_GOOD) {
eprintf("io error %p %x %d %d %" PRIu64 ", %m\n",
cmd, cmd->scb[0], ret, length, offset);
sense_data_build(cmd, key, asc);
}
}
static int bs_rbd_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size)
{
uint32_t blksize = 0;
int ret;
rbd_image_info_t inf;
char *poolname;
char *imagename;
char *snapname;
struct active_rbd *rbd = RBDP(lu);
parse_imagepath(path, &poolname, &imagename, &snapname);
rbd->poolname = poolname;
rbd->imagename = imagename;
rbd->snapname = snapname;
eprintf("bs_rbd_open: pool: %s image: %s snap: %s\n",
poolname, imagename, snapname);
ret = rados_ioctx_create(rbd->cluster, poolname, &rbd->ioctx);
if (ret < 0) {
eprintf("bs_rbd_open: rados_ioctx_create: %d\n", ret);
return -EIO;
}
ret = rbd_open(rbd->ioctx, imagename, &rbd->rbd_image, snapname);
if (ret < 0) {
eprintf("bs_rbd_open: rbd_open: %d\n", ret);
return ret;
}
if (rbd_stat(rbd->rbd_image, &inf, sizeof(inf)) < 0) {
eprintf("bs_rbd_open: rbd_stat: %d\n", ret);
return ret;
}
*size = inf.size;
blksize = inf.obj_size;
if (!lu->attrs.no_auto_lbppbe)
update_lbppbe(lu, blksize);
return 0;
}
static void bs_rbd_close(struct scsi_lu *lu)
{
struct active_rbd *rbd = RBDP(lu);
if (rbd->rbd_image) {
rbd_close(rbd->rbd_image);
rados_ioctx_destroy(rbd->ioctx);
rbd->rbd_image = rbd->ioctx = NULL;
}
}
// Slurp up and return a copy of everything to the next ';', and update p
static char *slurp_to_semi(char **p)
{
char *end = index(*p, ';');
char *ret;
int len;
if (end == NULL)
end = *p + strlen(*p);
len = end - *p;
ret = malloc(len + 1);
strncpy(ret, *p, len);
ret[len] = '\0';
*p = end;
/* Jump past the semicolon, if we stopped at one */
if (**p == ';')
*p = end + 1;
return ret;
}
static char *slurp_value(char **p)
{
char *equal = index(*p, '=');
if (equal) {
*p = equal + 1;
return slurp_to_semi(p);
} else {
// uh...no?
return NULL;
}
}
static int is_opt(const char *opt, char *p)
{
int ret = 0;
if ((strncmp(p, opt, strlen(opt)) == 0) &&
(p[strlen(opt)] == '=')) {
ret = 1;
}
return ret;
}
static tgtadm_err bs_rbd_init(struct scsi_lu *lu, char *bsopts)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
tgtadm_err ret = TGTADM_UNKNOWN_ERR;
int rados_ret;
struct active_rbd *rbd = RBDP(lu);
char *confname = NULL;
char *clientid = NULL;
char *virsecretuuid = NULL;
char *given_cephx_key = NULL;
char disc_cephx_key[256];
char *clustername = NULL;
char clientid_full[128];
char *ignore = NULL;
dprintf("bs_rbd_init bsopts: \"%s\"\n", bsopts);
// look for conf= or id= or cluster=
while (bsopts && strlen(bsopts)) {
if (is_opt("conf", bsopts))
confname = slurp_value(&bsopts);
else if (is_opt("id", bsopts))
clientid = slurp_value(&bsopts);
else if (is_opt("cluster", bsopts))
clustername = slurp_value(&bsopts);
else if (is_opt("virsecretuuid", bsopts))
virsecretuuid = slurp_value(&bsopts);
else if (is_opt("cephx_key", bsopts))
given_cephx_key = slurp_value(&bsopts);
else {
ignore = slurp_to_semi(&bsopts);
eprintf("bs_rbd: ignoring unknown option \"%s\"\n",
ignore);
free(ignore);
break;
}
}
if (clientid)
eprintf("bs_rbd_init: clientid %s\n", clientid);
if (confname)
eprintf("bs_rbd_init: confname %s\n", confname);
if (clustername)
eprintf("bs_rbd_init: clustername %s\n", clustername);
if (virsecretuuid)
eprintf("bs_rbd_init: virsecretuuid %s\n", virsecretuuid);
if (given_cephx_key)
eprintf("bs_rbd_init: given_cephx_key %s\n", given_cephx_key);
/* virsecretuuid && given_cephx_key are conflicting options. */
if (virsecretuuid && given_cephx_key) {
eprintf("Conflicting options virsecretuuid=[%s] cephx_key=[%s]",
virsecretuuid, given_cephx_key);
goto fail;
}
/* Get stored key from secret uuid. */
if (virsecretuuid) {
char libvir_uuid_file_path_buf[256] = "/etc/libvirt/secrets/";
strcat(libvir_uuid_file_path_buf, virsecretuuid);
strcat(libvir_uuid_file_path_buf, ".base64");
FILE *fp;
fp = fopen(libvir_uuid_file_path_buf , "r");
if (fp == NULL) {
eprintf("bs_rbd_init: Unable to read %s\n",
libvir_uuid_file_path_buf);
goto fail;
}
if (fgets(disc_cephx_key, 256, fp) == NULL) {
eprintf("bs_rbd_init: Unable to read %s\n",
libvir_uuid_file_path_buf);
goto fail;
}
fclose(fp);
strtok(disc_cephx_key, "\n");
eprintf("bs_rbd_init: disc_cephx_key %s\n", disc_cephx_key);
}
eprintf("bs_rbd_init bsopts=%s\n", bsopts);
/*
* clientid may be set by -i/--id. If clustername is set, then
* we use rados_create2, else rados_create
*/
if (clustername) {
/* rados_create2 wants the full client name */
if (clientid)
snprintf(clientid_full, sizeof clientid_full,
"client.%s", clientid);
else /* if not specified, default to client.admin */
snprintf(clientid_full, sizeof clientid_full,
"client.admin");
rados_ret = rados_create2(&rbd->cluster, clustername,
clientid_full, 0);
} else {
rados_ret = rados_create(&rbd->cluster, clientid);
}
if (rados_ret < 0) {
eprintf("bs_rbd_init: rados_create: %d\n", rados_ret);
return ret;
}
/*
* Read config from environment, then conf file(s) which may
* be set by conf=
*/
rados_ret = rados_conf_parse_env(rbd->cluster, NULL);
if (rados_ret < 0) {
eprintf("bs_rbd_init: rados_conf_parse_env: %d\n", rados_ret);
goto fail;
}
rados_ret = rados_conf_read_file(rbd->cluster, confname);
if (rados_ret < 0) {
eprintf("bs_rbd_init: rados_conf_read_file: %d\n", rados_ret);
goto fail;
}
/* Set given key */
if (virsecretuuid) {
if (rados_conf_set(rbd->cluster, "key", disc_cephx_key) < 0) {
eprintf("bs_rbd_init: failed to set cephx_key: %s\n",
disc_cephx_key);
goto fail;
}
}
if (given_cephx_key) {
if (rados_conf_set(rbd->cluster, "key", given_cephx_key) < 0) {
eprintf("bs_rbd_init: failed to set cephx_key: %s\n",
given_cephx_key);
goto fail;
}
}
rados_ret = rados_connect(rbd->cluster);
if (rados_ret < 0) {
eprintf("bs_rbd_init: rados_connect: %d\n", rados_ret);
goto fail;
}
ret = bs_thread_open(info, bs_rbd_request, nr_iothreads);
fail:
if (confname)
free(confname);
if (clientid)
free(clientid);
if (virsecretuuid)
free(virsecretuuid);
if (given_cephx_key)
free(given_cephx_key);
return ret;
}
static void bs_rbd_exit(struct scsi_lu *lu)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
struct active_rbd *rbd = RBDP(lu);
/* do this first to try to be sure there's no outstanding I/O */
bs_thread_close(info);
rados_shutdown(rbd->cluster);
}
static struct backingstore_template rbd_bst = {
.bs_name = "rbd",
.bs_datasize = sizeof(struct bs_thread_info) +
sizeof(struct active_rbd),
.bs_open = bs_rbd_open,
.bs_close = bs_rbd_close,
.bs_init = bs_rbd_init,
.bs_exit = bs_rbd_exit,
.bs_cmd_submit = bs_thread_cmd_submit,
.bs_oflags_supported = O_SYNC | O_DIRECT,
};
void register_bs_module(void)
{
register_backingstore_template(&rbd_bst);
}
tgt-1.0.80/usr/bs_rdwr.c 0000664 0000000 0000000 00000027326 13747533545 0015047 0 ustar 00root root 0000000 0000000 /*
* Synchronous I/O file backing store routine
*
* Copyright (C) 2006-2007 FUJITA Tomonori
* Copyright (C) 2006-2007 Mike Christie
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#define _XOPEN_SOURCE 600
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "list.h"
#include "util.h"
#include "tgtd.h"
#include "scsi.h"
#include "spc.h"
#include "bs_thread.h"
static void cmd_error_sense(struct scsi_cmd *cmd, uint8_t key, uint16_t asc)
{
scsi_set_result(cmd, SAM_STAT_CHECK_CONDITION);
sense_data_build(cmd, key, asc);
}
#define set_medium_error(cmd) cmd_error_sense(cmd, MEDIUM_ERROR, ASC_READ_ERROR)
static void bs_rdwr_request(struct scsi_cmd *cmd)
{
int ret = 0;
int fd = cmd->dev->fd;
uint32_t length = 0;
char *tmpbuf;
size_t blocksize;
uint64_t offset = cmd->offset;
uint32_t tl = cmd->tl;
int do_verify = 0;
int i;
char *ptr;
const char *write_buf = NULL;
/* overwritten on error */
scsi_set_result(cmd, SAM_STAT_GOOD);
switch (cmd->scb[0])
{
case ORWRITE_16:
length = scsi_get_out_length(cmd);
tmpbuf = malloc(length);
if (!tmpbuf) {
cmd_error_sense(cmd, HARDWARE_ERROR,
ASC_INTERNAL_TGT_FAILURE);
break;
}
ret = pread64(fd, tmpbuf, length, offset);
if (ret != length) {
set_medium_error(cmd);
free(tmpbuf);
break;
}
ptr = scsi_get_out_buffer(cmd);
for (i = 0; i < length; i++)
ptr[i] |= tmpbuf[i];
free(tmpbuf);
write_buf = scsi_get_out_buffer(cmd);
goto write;
case COMPARE_AND_WRITE:
/* Blocks are transferred twice, first the set that
* we compare to the existing data, and second the set
* to write if the compare was successful.
*/
length = scsi_get_out_length(cmd) / 2;
if (length != cmd->tl) {
cmd_error_sense(cmd, ILLEGAL_REQUEST,
ASC_INVALID_FIELD_IN_CDB);
break;
}
tmpbuf = malloc(length);
if (!tmpbuf) {
cmd_error_sense(cmd, HARDWARE_ERROR,
ASC_INTERNAL_TGT_FAILURE);
break;
}
ret = pread64(fd, tmpbuf, length, offset);
if (ret != length) {
set_medium_error(cmd);
free(tmpbuf);
break;
}
if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) {
uint64_t pos = 0;
char *spos = scsi_get_out_buffer(cmd);
char *dpos = tmpbuf;
/*
* Data differed, this is assumed to be 'rare'
* so use a much more expensive byte-by-byte
* comparasion to find out at which offset the
* data differs.
*/
for (pos = 0; pos < length && *spos++ == *dpos++;
pos++)
;
free(tmpbuf);
scsi_set_result(cmd, SAM_STAT_CHECK_CONDITION);
sense_data_build_with_info(cmd, MISCOMPARE,
ASC_MISCOMPARE_DURING_VERIFY_OPERATION, pos);
break;
}
if (cmd->scb[1] & 0x10)
posix_fadvise(fd, offset, length,
POSIX_FADV_NOREUSE);
free(tmpbuf);
write_buf = scsi_get_out_buffer(cmd) + length;
goto write;
case SYNCHRONIZE_CACHE:
case SYNCHRONIZE_CACHE_16:
/* TODO */
length = (cmd->scb[0] == SYNCHRONIZE_CACHE) ? 0 : 0;
if (cmd->scb[1] & 0x2) {
cmd_error_sense(cmd, ILLEGAL_REQUEST,
ASC_INVALID_FIELD_IN_CDB);
} else {
ret = fdatasync(fd);
if (ret)
set_medium_error(cmd);
}
break;
case WRITE_VERIFY:
case WRITE_VERIFY_12:
case WRITE_VERIFY_16:
do_verify = 1;
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
length = scsi_get_out_length(cmd);
write_buf = scsi_get_out_buffer(cmd);
write:
ret = pwrite64(fd, write_buf, length,
offset);
if (ret == length) {
struct mode_pg *pg;
/*
* it would be better not to access to pg
* directy.
*/
pg = find_mode_page(cmd->dev, 0x08, 0);
if (pg == NULL) {
cmd_error_sense(cmd, ILLEGAL_REQUEST,
ASC_INVALID_FIELD_IN_CDB);
break;
}
if (((cmd->scb[0] != WRITE_6) && (cmd->scb[1] & 0x8)) ||
!(pg->mode_data[0] & 0x04)) {
ret = fdatasync(fd);
if (ret)
set_medium_error(cmd);
}
} else
set_medium_error(cmd);
if ((cmd->scb[0] != WRITE_6) && (cmd->scb[1] & 0x10))
posix_fadvise(fd, offset, length,
POSIX_FADV_NOREUSE);
if (do_verify)
goto verify;
break;
case WRITE_SAME:
case WRITE_SAME_16:
/* WRITE_SAME used to punch hole in file */
if (cmd->scb[1] & 0x08) {
ret = unmap_file_region(fd, offset, tl);
if (ret != 0) {
eprintf("Failed to punch hole for WRITE_SAME"
" command\n");
cmd_error_sense(cmd, HARDWARE_ERROR,
ASC_INTERNAL_TGT_FAILURE);
break;
}
break;
}
while (tl > 0) {
blocksize = 1 << cmd->dev->blk_shift;
tmpbuf = scsi_get_out_buffer(cmd);
switch(cmd->scb[1] & 0x06) {
case 0x02: /* PBDATA==0 LBDATA==1 */
put_unaligned_be32(offset, tmpbuf);
break;
case 0x04: /* PBDATA==1 LBDATA==0 */
/* physical sector format */
put_unaligned_be64(offset, tmpbuf);
break;
}
ret = pwrite64(fd, tmpbuf, blocksize, offset);
if (ret != blocksize)
set_medium_error(cmd);
offset += blocksize;
tl -= blocksize;
}
break;
case READ_6:
case READ_10:
case READ_12:
case READ_16:
length = scsi_get_in_length(cmd);
ret = pread64(fd, scsi_get_in_buffer(cmd), length,
offset);
if (ret != length)
set_medium_error(cmd);
if ((cmd->scb[0] != READ_6) && (cmd->scb[1] & 0x10))
posix_fadvise(fd, offset, length,
POSIX_FADV_NOREUSE);
break;
case PRE_FETCH_10:
case PRE_FETCH_16:
ret = posix_fadvise(fd, offset, cmd->tl,
POSIX_FADV_WILLNEED);
if (ret != 0)
set_medium_error(cmd);
break;
case VERIFY_10:
case VERIFY_12:
case VERIFY_16:
verify:
length = scsi_get_out_length(cmd);
tmpbuf = malloc(length);
if (!tmpbuf) {
cmd_error_sense(cmd, HARDWARE_ERROR,
ASC_INTERNAL_TGT_FAILURE);
break;
}
ret = pread64(fd, tmpbuf, length, offset);
if (ret != length)
set_medium_error(cmd);
else if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) {
cmd_error_sense(cmd, MISCOMPARE,
ASC_MISCOMPARE_DURING_VERIFY_OPERATION);
}
if (cmd->scb[1] & 0x10)
posix_fadvise(fd, offset, length,
POSIX_FADV_NOREUSE);
free(tmpbuf);
break;
case UNMAP:
if (!cmd->dev->attrs.thinprovisioning) {
cmd_error_sense(cmd, ILLEGAL_REQUEST,
ASC_INVALID_FIELD_IN_CDB);
break;
}
length = scsi_get_out_length(cmd);
tmpbuf = scsi_get_out_buffer(cmd);
if (length < 8)
break;
length -= 8;
tmpbuf += 8;
while (length >= 16) {
offset = get_unaligned_be64(&tmpbuf[0]);
offset = offset << cmd->dev->blk_shift;
tl = get_unaligned_be32(&tmpbuf[8]);
tl = tl << cmd->dev->blk_shift;
if (offset + tl > cmd->dev->size) {
eprintf("UNMAP beyond EOF\n");
cmd_error_sense(cmd, ILLEGAL_REQUEST,
ASC_LBA_OUT_OF_RANGE);
break;
}
if (tl > 0) {
if (unmap_file_region(fd, offset, tl) != 0) {
eprintf("Failed to punch hole for"
" UNMAP at offset:%" PRIu64
" length:%d\n",
offset, tl);
cmd_error_sense(cmd, HARDWARE_ERROR,
ASC_INTERNAL_TGT_FAILURE);
break;
}
}
length -= 16;
tmpbuf += 16;
}
break;
default:
break;
}
dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length);
if (scsi_get_result(cmd) != SAM_STAT_GOOD) {
eprintf("io error %p %x %d %d %" PRIu64 ", %m\n",
cmd, cmd->scb[0], ret, length, offset);
}
}
static int bs_rdwr_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size)
{
uint32_t blksize = 0;
*fd = backed_file_open(path, O_RDWR|O_LARGEFILE|lu->bsoflags, size,
&blksize);
/* If we get access denied, try opening the file in readonly mode */
if (*fd == -1 && (errno == EACCES || errno == EROFS)) {
*fd = backed_file_open(path, O_RDONLY|O_LARGEFILE|lu->bsoflags,
size, &blksize);
lu->attrs.readonly = 1;
}
if (*fd < 0)
return *fd;
if (!lu->attrs.no_auto_lbppbe)
update_lbppbe(lu, blksize);
return 0;
}
static void bs_rdwr_close(struct scsi_lu *lu)
{
close(lu->fd);
}
static tgtadm_err bs_rdwr_init(struct scsi_lu *lu, char *bsopts)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
return bs_thread_open(info, bs_rdwr_request, nr_iothreads);
}
static void bs_rdwr_exit(struct scsi_lu *lu)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
bs_thread_close(info);
}
static struct backingstore_template rdwr_bst = {
.bs_name = "rdwr",
.bs_datasize = sizeof(struct bs_thread_info),
.bs_open = bs_rdwr_open,
.bs_close = bs_rdwr_close,
.bs_init = bs_rdwr_init,
.bs_exit = bs_rdwr_exit,
.bs_cmd_submit = bs_thread_cmd_submit,
.bs_oflags_supported = O_SYNC | O_DIRECT,
};
static struct backingstore_template mmc_bst = {
.bs_name = "mmc",
.bs_datasize = sizeof(struct bs_thread_info),
.bs_open = bs_rdwr_open,
.bs_close = bs_rdwr_close,
.bs_init = bs_rdwr_init,
.bs_exit = bs_rdwr_exit,
.bs_cmd_submit = bs_thread_cmd_submit,
.bs_oflags_supported = O_SYNC | O_DIRECT,
};
static struct backingstore_template smc_bst = {
.bs_name = "smc",
.bs_datasize = sizeof(struct bs_thread_info),
.bs_open = bs_rdwr_open,
.bs_close = bs_rdwr_close,
.bs_init = bs_rdwr_init,
.bs_exit = bs_rdwr_exit,
.bs_cmd_submit = bs_thread_cmd_submit,
.bs_oflags_supported = O_SYNC | O_DIRECT,
};
__attribute__((constructor)) static void bs_rdwr_constructor(void)
{
unsigned char sbc_opcodes[] = {
ALLOW_MEDIUM_REMOVAL,
COMPARE_AND_WRITE,
FORMAT_UNIT,
INQUIRY,
MAINT_PROTOCOL_IN,
MODE_SELECT,
MODE_SELECT_10,
MODE_SENSE,
MODE_SENSE_10,
ORWRITE_16,
PERSISTENT_RESERVE_IN,
PERSISTENT_RESERVE_OUT,
PRE_FETCH_10,
PRE_FETCH_16,
READ_10,
READ_12,
READ_16,
READ_6,
READ_CAPACITY,
RELEASE,
REPORT_LUNS,
REQUEST_SENSE,
RESERVE,
SEND_DIAGNOSTIC,
SERVICE_ACTION_IN,
START_STOP,
SYNCHRONIZE_CACHE,
SYNCHRONIZE_CACHE_16,
TEST_UNIT_READY,
UNMAP,
VERIFY_10,
VERIFY_12,
VERIFY_16,
WRITE_10,
WRITE_12,
WRITE_16,
WRITE_6,
WRITE_SAME,
WRITE_SAME_16,
WRITE_VERIFY,
WRITE_VERIFY_12,
WRITE_VERIFY_16
};
bs_create_opcode_map(&rdwr_bst, sbc_opcodes, ARRAY_SIZE(sbc_opcodes));
register_backingstore_template(&rdwr_bst);
unsigned char mmc_opcodes[] = {
ALLOW_MEDIUM_REMOVAL,
CLOSE_TRACK,
GET_CONFIGURATION,
GET_PERFORMACE,
INQUIRY,
MODE_SELECT,
MODE_SELECT_10,
MODE_SENSE,
MODE_SENSE_10,
PERSISTENT_RESERVE_IN,
PERSISTENT_RESERVE_OUT,
READ_10,
READ_12,
READ_BUFFER_CAP,
READ_CAPACITY,
READ_DISK_INFO,
READ_DVD_STRUCTURE,
READ_TOC,
READ_TRACK_INFO,
RELEASE,
REPORT_LUNS,
REQUEST_SENSE,
RESERVE,
SET_CD_SPEED,
SET_STREAMING,
START_STOP,
SYNCHRONIZE_CACHE,
TEST_UNIT_READY,
VERIFY_10,
WRITE_10,
WRITE_12,
WRITE_VERIFY,
};
bs_create_opcode_map(&mmc_bst, mmc_opcodes, ARRAY_SIZE(mmc_opcodes));
register_backingstore_template(&mmc_bst);
unsigned char smc_opcodes[] = {
INITIALIZE_ELEMENT_STATUS,
INITIALIZE_ELEMENT_STATUS_WITH_RANGE,
INQUIRY,
MAINT_PROTOCOL_IN,
MODE_SELECT,
MODE_SELECT_10,
MODE_SENSE,
MODE_SENSE_10,
MOVE_MEDIUM,
PERSISTENT_RESERVE_IN,
PERSISTENT_RESERVE_OUT,
REQUEST_SENSE,
TEST_UNIT_READY,
READ_ELEMENT_STATUS,
RELEASE,
REPORT_LUNS,
RESERVE,
};
bs_create_opcode_map(&smc_bst, smc_opcodes, ARRAY_SIZE(smc_opcodes));
register_backingstore_template(&smc_bst);
}
tgt-1.0.80/usr/bs_sg.c 0000664 0000000 0000000 00000027670 13747533545 0014504 0 ustar 00root root 0000000 0000000 /*
* SCSI Generic I/O backing store
*
* Copyright (C) 2008 Alexander Nezhinsky
*
* Added linux/block/bsg.c support using struct sg_io_v4.
* Copyright (C) 2010 Nicholas A. Bellinger
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "bsg.h" /* Copied from include/linux/bsg.h */
#include "list.h"
#include "util.h"
#include "tgtd.h"
#include "scsi.h"
#include "spc.h"
#include "tgtadm_error.h"
#define BS_SG_RESVD_SZ (512 * 1024)
static unsigned int sg_timeout = 30 * 1000; /* 30 seconds */
static int graceful_read(int fd, void *p_read, int to_read)
{
int err;
while (to_read > 0) {
err = read(fd, p_read, to_read);
if (err >= 0) {
to_read -= err;
p_read += err;
} else if (errno == EINTR)
continue;
else {
eprintf("sg device %d read failed, errno: %d\n",
fd, errno);
return errno;
}
}
return 0;
}
static int graceful_write(int fd, void *p_write, int to_write)
{
int err;
while (to_write > 0) {
err = write(fd, p_write, to_write);
if (err >= 0) {
to_write -= err;
p_write += err;
} else if (errno == EINTR)
continue;
else {
eprintf("sg device %d write failed, errno: %d\n",
fd, errno);
return errno;
}
}
return 0;
}
static int bs_sg_rw(int host_no, struct scsi_cmd *cmd)
{
int ret;
unsigned char key = ILLEGAL_REQUEST;
uint16_t asc = ASC_LUN_NOT_SUPPORTED;
ret = cmd->dev->bst->bs_cmd_submit(cmd);
if (ret) {
key = HARDWARE_ERROR;
asc = ASC_INTERNAL_TGT_FAILURE;
} else
return SAM_STAT_GOOD;
cmd->offset = 0;
scsi_set_in_resid_by_actual(cmd, 0);
scsi_set_out_resid_by_actual(cmd, 0);
sense_data_build(cmd, key, asc);
return SAM_STAT_CHECK_CONDITION;
}
static int set_cmd_failed(struct scsi_cmd *cmd)
{
int result = SAM_STAT_CHECK_CONDITION;
uint16_t asc = ASC_READ_ERROR;
uint8_t key = MEDIUM_ERROR;
scsi_set_result(cmd, result);
sense_data_build(cmd, key, asc);
return result;
}
static int bs_bsg_cmd_submit(struct scsi_cmd *cmd)
{
struct scsi_lu *dev = cmd->dev;
int fd = dev->fd;
struct sg_io_v4 io_hdr;
int err = 0;
memset(&io_hdr, 0, sizeof(io_hdr));
/*
* Following linux/include/linux/bsg.h
*/
/* [i] 'Q' to differentiate from v3 */
io_hdr.guard = 'Q';
io_hdr.protocol = BSG_PROTOCOL_SCSI;
io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
io_hdr.request_len = cmd->scb_len;
io_hdr.request = (unsigned long )cmd->scb;
io_hdr.dout_xfer_len = scsi_get_out_length(cmd);
io_hdr.dout_xferp = (unsigned long)scsi_get_out_buffer(cmd);
io_hdr.din_xfer_len = scsi_get_in_length(cmd);
io_hdr.din_xferp = (unsigned long)scsi_get_in_buffer(cmd);
io_hdr.max_response_len = sizeof(cmd->sense_buffer);
/* SCSI: (auto)sense data */
io_hdr.response = (unsigned long)cmd->sense_buffer;
/* Using the same 2000 millisecond timeout.. */
io_hdr.timeout = sg_timeout;
/* [i->o] unused internally */
io_hdr.usr_ptr = (unsigned long)cmd;
dprintf("[%d] Set io_hdr->usr_ptr from cmd: %p\n", getpid(), cmd);
/* Bsg does Q_AT_HEAD by default */
io_hdr.flags |= BSG_FLAG_Q_AT_TAIL;
dprintf("[%d] Calling graceful_write for CDB: 0x%02x\n", getpid(), cmd->scb[0]);
err = graceful_write(fd, &io_hdr, sizeof(io_hdr));
if (!err)
set_cmd_async(cmd);
else {
eprintf("failed to start cmd 0x%p\n", cmd);
return set_cmd_failed(cmd);
}
return 0;
}
static void bs_sg_cmd_setup(struct sg_io_hdr *hdr,
unsigned char *cmd, int cmd_len,
void *data, int data_len, int direction,
void *sense, int sense_len,
int timeout)
{
memset(hdr, 0, sizeof(*hdr));
hdr->interface_id = 'S';
hdr->cmdp = cmd;
hdr->cmd_len = cmd_len;
hdr->dxfer_direction = direction;
hdr->dxfer_len = data_len;
hdr->dxferp = data;
hdr->mx_sb_len = sense_len;
hdr->sbp = sense;
hdr->timeout = timeout;
hdr->pack_id = -1;
hdr->usr_ptr = NULL;
hdr->flags = 0;
}
static int bs_sg_cmd_submit(struct scsi_cmd *cmd)
{
struct scsi_lu *dev = cmd->dev;
int fd = dev->fd;
struct sg_io_hdr io_hdr;
int err = 0;
memset(&io_hdr, 0, sizeof(io_hdr));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = cmd->scb_len;
io_hdr.cmdp = cmd->scb;
if (scsi_get_data_dir(cmd) == DATA_WRITE) {
io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
io_hdr.dxfer_len = scsi_get_out_length(cmd);
io_hdr.dxferp = (void *)scsi_get_out_buffer(cmd);
} else {
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.dxfer_len = scsi_get_in_length(cmd);
io_hdr.dxferp = (void *)scsi_get_in_buffer(cmd);
}
io_hdr.mx_sb_len = sizeof(cmd->sense_buffer);
io_hdr.sbp = cmd->sense_buffer;
io_hdr.timeout = sg_timeout;
io_hdr.pack_id = -1;
io_hdr.usr_ptr = cmd;
io_hdr.flags |= SG_FLAG_DIRECT_IO;
err = graceful_write(fd, &io_hdr, sizeof(io_hdr));
if (!err)
set_cmd_async(cmd);
else {
eprintf("failed to start cmd 0x%p\n", cmd);
return set_cmd_failed(cmd);
}
return 0;
}
static void bs_bsg_cmd_complete(int fd, int events, void *data)
{
struct sg_io_v4 io_hdr;
struct scsi_cmd *cmd;
int err;
dprintf("[%d] bs_bsg_cmd_complete() called!\n", getpid());
memset(&io_hdr, 0, sizeof(io_hdr));
/* [i] 'Q' to differentiate from v3 */
io_hdr.guard = 'Q';
err = graceful_read(fd, &io_hdr, sizeof(io_hdr));
if (err)
return;
cmd = (struct scsi_cmd *)(unsigned long)io_hdr.usr_ptr;
dprintf("BSG Using cmd: %p for io_hdr.usr_ptr\n", cmd);
/*
* Check SCSI: command completion status
* */
if (!io_hdr.device_status) {
uint32_t actual_len;
if (io_hdr.dout_resid) {
actual_len = scsi_get_out_length(cmd) - io_hdr.dout_resid;
scsi_set_out_resid_by_actual(cmd, actual_len);
}
if (io_hdr.din_resid) {
actual_len = scsi_get_in_length(cmd) - io_hdr.din_resid;
scsi_set_in_resid_by_actual(cmd, actual_len);
}
} else {
/*
* NAB: Used by linux/block/bsg.c:bsg_ioctl(), is this
* right..?
*/
cmd->sense_len = SCSI_SENSE_BUFFERSIZE;
scsi_set_out_resid_by_actual(cmd, 0);
scsi_set_in_resid_by_actual(cmd, 0);
}
target_cmd_io_done(cmd, io_hdr.device_status);
}
static void bs_sg_cmd_complete(int fd, int events, void *data)
{
struct sg_io_hdr io_hdr;
struct scsi_cmd *cmd;
int err;
uint32_t actual_len;
memset(&io_hdr, 0, sizeof(io_hdr));
io_hdr.interface_id = 'S';
io_hdr.pack_id = -1;
err = graceful_read(fd, &io_hdr, sizeof(io_hdr));
if (err)
return;
cmd = (struct scsi_cmd *)io_hdr.usr_ptr;
if (!io_hdr.status) {
actual_len = io_hdr.dxfer_len - io_hdr.resid;
} else {
/* NO SENSE | ILI (Incorrect Length Indicator) */
if (io_hdr.sbp[2] == 0x20)
actual_len = io_hdr.dxfer_len - io_hdr.resid;
else
actual_len = 0;
cmd->sense_len = io_hdr.sb_len_wr;
}
if (!actual_len || io_hdr.resid) {
if (io_hdr.dxfer_direction == SG_DXFER_TO_DEV)
scsi_set_out_resid_by_actual(cmd, actual_len);
else
scsi_set_in_resid_by_actual(cmd, actual_len);
}
target_cmd_io_done(cmd, io_hdr.status);
}
static int get_bsg_major(char *path)
{
FILE *devfd;
int majorno, n;
char dev[64];
char tmp[16];
sscanf(path, "/dev/bsg/%s", tmp);
sprintf(dev, "/sys/class/bsg/%s/dev", tmp);
devfd = fopen(dev, "r");
if (!devfd) {
eprintf("%s open failed errno: %d\n", dev, errno);
return -1;
}
n = fscanf(devfd, "%d:", &majorno);
fclose(devfd);
if (n != 1) {
if (n < 0)
eprintf("reading major from %s failed errno: %d\n", dev, errno);
else
eprintf("reading major from %s failed: invalid input\n", dev);
return -1;
}
return majorno;
}
static int chk_sg_device(char *path)
{
struct stat st;
if (stat(path, &st) < 0) {
eprintf("stat() failed errno: %d\n", errno);
return -1;
}
if (!S_ISCHR(st.st_mode)) {
eprintf("Not a character device: %s\n", path);
return -1;
}
/* Check for SG_IO major first.. */
if (major(st.st_rdev) == SCSI_GENERIC_MAJOR)
return 0;
if (!strncmp("/dev/bsg", path, 8)) {
if (major(st.st_rdev) == get_bsg_major(path))
return 1;
}
return -1;
}
static int init_bsg_device(int fd)
{
int t, err;
err = ioctl(fd, SG_GET_COMMAND_Q, &t);
if (err < 0) {
eprintf("SG_GET_COMMAND_Q for bsd failed: %d\n", err);
return -1;
}
eprintf("bsg: Using max_queue: %d\n", t);
t = BS_SG_RESVD_SZ;
err = ioctl(fd, SG_SET_RESERVED_SIZE, &t);
if (err < 0) {
eprintf("SG_SET_RESERVED_SIZE errno: %d\n", errno);
return -1;
}
return 0;
}
static int init_sg_device(int fd)
{
int t, err;
struct sg_io_hdr hdr;
unsigned char cmd[6];
unsigned char resp[36];
err = ioctl(fd, SG_GET_VERSION_NUM, &t);
if ((err < 0) || (t < 30000)) {
eprintf("sg driver prior to 3.x\n");
return -1;
}
t = BS_SG_RESVD_SZ;
err = ioctl(fd, SG_SET_RESERVED_SIZE, &t);
if (err < 0) {
eprintf("SG_SET_RESERVED_SIZE errno: %d\n", errno);
return -1;
}
memset(&cmd, 0, sizeof(cmd));
memset(&resp, 0, sizeof(resp));
cmd[0] = INQUIRY;
cmd[4] = sizeof(resp);
bs_sg_cmd_setup(&hdr, cmd, sizeof(cmd), resp, sizeof(resp),
SG_DXFER_FROM_DEV, NULL, 0, 30000);
err = ioctl(fd, SG_IO, &hdr);
if (!err && (resp[0] & 0x1f) == TYPE_TAPE)
sg_timeout = 14000 * 1000;
return 0;
}
static tgtadm_err bs_sg_init(struct scsi_lu *lu, char *bsopts)
{
/*
* Setup struct scsi_lu->cmd_perform() passthrough pointer
* (if available) for the underlying device type.
*/
lu->cmd_perform = &target_cmd_perform_passthrough;
/*
* Setup struct scsi_lu->cmd_done() passthrough pointer using
* usr/target.c:__cmd_done_passthrough().
*/
lu->cmd_done = &__cmd_done_passthrough;
return TGTADM_SUCCESS;
}
static int bs_sg_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size)
{
void (*cmd_complete)(int, int, void *) = NULL;
int sg_fd, err, bsg = 0;
bsg = chk_sg_device(path);
if (bsg < 0) {
eprintf("Not recognized %s as an SG device\n", path);
return -EINVAL;
}
sg_fd = open(path, O_RDWR);
if (sg_fd < 0) {
eprintf("Could not open %s, %m\n", path);
return sg_fd;
}
if (bsg) {
cmd_complete = &bs_bsg_cmd_complete;
err = init_bsg_device(sg_fd);
} else {
cmd_complete = &bs_sg_cmd_complete;
err = init_sg_device(sg_fd);
}
if (err) {
eprintf("Failed to initialize sg device %s\n", path);
return err;
}
err = tgt_event_add(sg_fd, EPOLLIN, cmd_complete, NULL);
if (err) {
eprintf("Failed to add sg device event %s\n", path);
return err;
}
*fd = sg_fd;
*size = 0;
return 0;
}
static void bs_sg_close(struct scsi_lu *lu)
{
close(lu->fd);
}
static tgtadm_err bs_sg_lu_init(struct scsi_lu *lu)
{
if (spc_lu_init(lu))
return TGTADM_NOMEM;
return TGTADM_SUCCESS;
}
static struct backingstore_template sg_bst = {
.bs_name = "sg",
.bs_datasize = 0,
.bs_init = bs_sg_init,
.bs_open = bs_sg_open,
.bs_close = bs_sg_close,
.bs_cmd_submit = bs_sg_cmd_submit,
};
static struct backingstore_template bsg_bst = {
.bs_name = "bsg",
.bs_datasize = 0,
.bs_init = bs_sg_init,
.bs_open = bs_sg_open,
.bs_close = bs_sg_close,
.bs_cmd_submit = bs_bsg_cmd_submit,
};
static struct device_type_template sg_template = {
.type = TYPE_PT,
.lu_init = bs_sg_lu_init,
.lu_config = spc_lu_config,
.lu_online = spc_lu_online,
.lu_offline = spc_lu_offline,
.lu_exit = spc_lu_exit,
.cmd_passthrough = bs_sg_rw,
};
__attribute__((constructor)) static void bs_sg_constructor(void)
{
register_backingstore_template(&sg_bst);
register_backingstore_template(&bsg_bst);
device_type_register(&sg_template);
}
tgt-1.0.80/usr/bs_sheepdog.c 0000664 0000000 0000000 00000106320 13747533545 0015657 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2010 FUJITA Tomonori
* Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "list.h"
#include "tgtd.h"
#include "util.h"
#include "log.h"
#include "scsi.h"
#include "bs_thread.h"
#define SD_PROTO_VER 0x01
#define SD_DEFAULT_ADDR "localhost"
#define SD_DEFAULT_PORT 7000
#define SD_OP_CREATE_AND_WRITE_OBJ 0x01
#define SD_OP_READ_OBJ 0x02
#define SD_OP_WRITE_OBJ 0x03
/* 0x04 is used internally by Sheepdog */
#define SD_OP_DISCARD_OBJ 0x05
#define SD_OP_NEW_VDI 0x11
#define SD_OP_LOCK_VDI 0x12
#define SD_OP_RELEASE_VDI 0x13
#define SD_OP_GET_VDI_INFO 0x14
#define SD_OP_READ_VDIS 0x15
#define SD_OP_FLUSH_VDI 0x16
#define SD_OP_DEL_VDI 0x17
#define SD_FLAG_CMD_WRITE 0x01
#define SD_FLAG_CMD_COW 0x02
#define SD_FLAG_CMD_CACHE 0x04 /* Writeback mode for cache */
#define SD_FLAG_CMD_DIRECT 0x08 /* Don't use cache */
/* return something back while sending something to sheep */
#define SD_FLAG_CMD_PIGGYBACK 0x10
#define SD_FLAG_CMD_TGT 0x20
#define SD_RES_SUCCESS 0x00 /* Success */
#define SD_RES_UNKNOWN 0x01 /* Unknown error */
#define SD_RES_NO_OBJ 0x02 /* No object found */
#define SD_RES_EIO 0x03 /* I/O error */
#define SD_RES_VDI_EXIST 0x04 /* Vdi exists already */
#define SD_RES_INVALID_PARMS 0x05 /* Invalid parameters */
#define SD_RES_SYSTEM_ERROR 0x06 /* System error */
#define SD_RES_VDI_LOCKED 0x07 /* Vdi is locked */
#define SD_RES_NO_VDI 0x08 /* No vdi found */
#define SD_RES_NO_BASE_VDI 0x09 /* No base vdi found */
#define SD_RES_VDI_READ 0x0A /* Cannot read requested vdi */
#define SD_RES_VDI_WRITE 0x0B /* Cannot write requested vdi */
#define SD_RES_BASE_VDI_READ 0x0C /* Cannot read base vdi */
#define SD_RES_BASE_VDI_WRITE 0x0D /* Cannot write base vdi */
#define SD_RES_NO_TAG 0x0E /* Requested tag is not found */
#define SD_RES_STARTUP 0x0F /* Sheepdog is on starting up */
#define SD_RES_VDI_NOT_LOCKED 0x10 /* Vdi is not locked */
#define SD_RES_SHUTDOWN 0x11 /* Sheepdog is shutting down */
#define SD_RES_NO_MEM 0x12 /* Cannot allocate memory */
#define SD_RES_FULL_VDI 0x13 /* we already have the maximum vdis */
#define SD_RES_VER_MISMATCH 0x14 /* Protocol version mismatch */
#define SD_RES_NO_SPACE 0x15 /* Server has no room for new objects */
#define SD_RES_WAIT_FOR_FORMAT 0x16 /* Waiting for a format operation */
#define SD_RES_WAIT_FOR_JOIN 0x17 /* Waiting for other nodes joining */
#define SD_RES_JOIN_FAILED 0x18 /* Target node had failed to join sheepdog */
#define SD_RES_HALT 0x19 /* Sheepdog is stopped serving IO request */
#define SD_RES_READONLY 0x1A /* Object is read-only */
#define SD_RES_INCOMPLETE 0x1B /* Object (in kv) is incomplete uploading */
/* sheep is collecting cluster wide status, not ready for operation */
#define SD_RES_COLLECTING_CINFO 0x1C
/* inode object in client is invalidated, refreshing is required */
#define SD_RES_INODE_INVALIDATED 0x1D
/*
* Object ID rules
*
* 0 - 19 (20 bits): data object space
* 20 - 31 (12 bits): reserved data object space
* 32 - 55 (24 bits): vdi object space
* 56 - 59 ( 4 bits): reserved vdi object space
* 60 - 63 ( 4 bits): object type identifier space
*/
#define VDI_SPACE_SHIFT 32
#define VDI_BIT (UINT64_C(1) << 63)
#define VMSTATE_BIT (UINT64_C(1) << 62)
#define MAX_DATA_OBJS (UINT64_C(1) << 20)
#define MAX_CHILDREN 1024
#define SD_MAX_VDI_LEN 256
#define SD_MAX_VDI_TAG_LEN 256
#define SD_NR_VDIS (1U << 24)
#define SD_DATA_OBJ_SIZE (UINT64_C(1) << 22)
#define SD_MAX_VDI_SIZE (SD_DATA_OBJ_SIZE * MAX_DATA_OBJS)
#define SECTOR_SIZE 512
#define CURRENT_VDI_ID 0
struct sheepdog_req {
uint8_t proto_ver;
uint8_t opcode;
uint16_t flags;
uint32_t epoch;
uint32_t id;
uint32_t data_length;
uint32_t opcode_specific[8];
};
struct sheepdog_rsp {
uint8_t proto_ver;
uint8_t opcode;
uint16_t flags;
uint32_t epoch;
uint32_t id;
uint32_t data_length;
uint32_t result;
uint32_t opcode_specific[7];
};
struct sheepdog_obj_req {
uint8_t proto_ver;
uint8_t opcode;
uint16_t flags;
uint32_t epoch;
uint32_t id;
uint32_t data_length;
uint64_t oid;
uint64_t cow_oid;
uint8_t copies;
uint8_t copy_policy;
uint8_t ec_index;
uint8_t reserved;
uint32_t rsvd;
uint32_t offset;
uint32_t pad;
};
struct sheepdog_obj_rsp {
uint8_t proto_ver;
uint8_t opcode;
uint16_t flags;
uint32_t epoch;
uint32_t id;
uint32_t data_length;
uint32_t result;
uint8_t copies;
uint8_t reserved[3];
uint32_t pad[6];
};
#define LOCK_TYPE_NORMAL 0
#define LOCK_TYPE_SHARED 1 /* for iSCSI multipath */
struct sheepdog_vdi_req {
uint8_t proto_ver;
uint8_t opcode;
uint16_t flags;
uint32_t epoch;
uint32_t id;
uint32_t data_length;
uint64_t vdi_size;
uint32_t vdi_id;
uint8_t copies;
uint8_t copy_policy;
uint8_t ec_index;
uint8_t block_size_shift;
uint32_t snapid;
uint32_t type;
uint32_t pad[2];
};
struct sheepdog_vdi_rsp {
uint8_t proto_ver;
uint8_t opcode;
uint16_t flags;
uint32_t epoch;
uint32_t id;
uint32_t data_length;
uint32_t result;
uint32_t rsvd;
uint32_t vdi_id;
uint32_t attr_id;
uint8_t copies;
uint8_t block_size_shift;
uint8_t reserved[2];
uint32_t pad[3];
};
/*
* Historical notes: previous version of sheepdog (< v0.9.0) has a limit of
* maximum number of children which can be created from single VDI. So the inode
* object has an array for storing the IDs of the child VDIs. The constant
* OLD_MAX_CHILDREN represents it. Current sheepdog doesn't have the limitation,
* so we are recycling the area (4 * OLD_MAX_CHILDREN = 4KB) for storing new
* metadata.
*
* users of the released area:
* - uint32_t btree_counter
*/
#define OLD_MAX_CHILDREN 1024U
struct sheepdog_inode {
char name[SD_MAX_VDI_LEN];
char tag[SD_MAX_VDI_TAG_LEN];
uint64_t create_time;
uint64_t snap_ctime;
uint64_t vm_clock_nsec;
uint64_t vdi_size;
uint64_t vm_state_size;
uint8_t copy_policy;
uint8_t store_policy;
uint8_t nr_copies;
uint8_t block_size_shift;
uint32_t snap_id;
uint32_t vdi_id;
uint32_t parent_vdi_id;
uint32_t btree_counter;
uint32_t __unused[OLD_MAX_CHILDREN - 1];
uint32_t data_vdi_id[MAX_DATA_OBJS];
};
#define SD_INODE_SIZE (sizeof(struct sheepdog_inode))
struct sheepdog_fd_list {
int fd;
pthread_t id;
struct list_head list;
};
#define UNIX_PATH_MAX 108
struct sheepdog_access_info {
int is_unix;
/* tcp */
char hostname[HOST_NAME_MAX + 1];
int port;
/* unix domain socket */
char uds_path[UNIX_PATH_MAX];
/*
* maximum length of fd_list_head: nr_iothreads + 1
* (+ 1 is for main thread)
*
* TODO: more effective data structure for handling massive parallel
* access
*/
struct list_head fd_list_head;
pthread_rwlock_t fd_list_lock;
struct sheepdog_inode inode;
pthread_rwlock_t inode_lock;
pthread_mutex_t inode_version_mutex;
uint64_t inode_version;
struct list_head inflight_list_head;
pthread_mutex_t inflight_list_mutex;
pthread_cond_t inflight_list_cond;
};
static inline int is_data_obj_writeable(struct sheepdog_inode *inode,
unsigned int idx)
{
return inode->vdi_id == inode->data_vdi_id[idx];
}
static inline int is_data_obj(uint64_t oid)
{
return !(VDI_BIT & oid);
}
static inline uint64_t data_oid_to_idx(uint64_t oid)
{
return oid & (MAX_DATA_OBJS - 1);
}
static inline uint64_t vid_to_vdi_oid(uint32_t vid)
{
return VDI_BIT | ((uint64_t)vid << VDI_SPACE_SHIFT);
}
static inline uint64_t vid_to_vmstate_oid(uint32_t vid, uint32_t idx)
{
return VMSTATE_BIT | ((uint64_t)vid << VDI_SPACE_SHIFT) | idx;
}
static inline uint64_t vid_to_data_oid(uint32_t vid, uint32_t idx)
{
return ((uint64_t)vid << VDI_SPACE_SHIFT) | idx;
}
static const char *sd_strerror(int err)
{
int i;
static const struct {
int err;
const char *desc;
} errors[] = {
{SD_RES_SUCCESS,
"Success"},
{SD_RES_UNKNOWN,
"Unknown error"},
{SD_RES_NO_OBJ, "No object found"},
{SD_RES_EIO, "I/O error"},
{SD_RES_VDI_EXIST, "VDI exists already"},
{SD_RES_INVALID_PARMS, "Invalid parameters"},
{SD_RES_SYSTEM_ERROR, "System error"},
{SD_RES_VDI_LOCKED, "VDI is already locked"},
{SD_RES_NO_VDI, "No vdi found"},
{SD_RES_NO_BASE_VDI, "No base VDI found"},
{SD_RES_VDI_READ, "Failed read the requested VDI"},
{SD_RES_VDI_WRITE, "Failed to write the requested VDI"},
{SD_RES_BASE_VDI_READ, "Failed to read the base VDI"},
{SD_RES_BASE_VDI_WRITE, "Failed to write the base VDI"},
{SD_RES_NO_TAG, "Failed to find the requested tag"},
{SD_RES_STARTUP, "The system is still booting"},
{SD_RES_VDI_NOT_LOCKED, "VDI isn't locked"},
{SD_RES_SHUTDOWN, "The system is shutting down"},
{SD_RES_NO_MEM, "Out of memory on the server"},
{SD_RES_FULL_VDI, "We already have the maximum vdis"},
{SD_RES_VER_MISMATCH, "Protocol version mismatch"},
{SD_RES_NO_SPACE, "Server has no space for new objects"},
{SD_RES_WAIT_FOR_FORMAT, "Sheepdog is waiting for a format operation"},
{SD_RES_WAIT_FOR_JOIN, "Sheepdog is waiting for other nodes joining"},
{SD_RES_JOIN_FAILED, "Target node had failed to join sheepdog"},
{SD_RES_HALT, "Sheepdog is stopped serving IO request"},
{SD_RES_READONLY, "Object is read-only"},
{SD_RES_INODE_INVALIDATED, "Inode object is invalidated"},
};
for (i = 0; i < ARRAY_SIZE(errors); ++i) {
if (errors[i].err == err)
return errors[i].desc;
}
return "Invalid error code";
}
static int connect_to_sdog_tcp(const char *addr, int port)
{
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
int fd, ret;
struct addrinfo hints, *res, *res0;
char port_s[6];
if (!addr) {
addr = SD_DEFAULT_ADDR;
port = SD_DEFAULT_PORT;
}
memset(port_s, 0, 6);
snprintf(port_s, 5, "%d", port);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
ret = getaddrinfo(addr, port_s, &hints, &res0);
if (ret) {
eprintf("unable to get address info %s, %s\n",
addr, strerror(errno));
return -1;
}
for (res = res0; res; res = res->ai_next) {
ret = getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
sizeof(hbuf), sbuf, sizeof(sbuf),
NI_NUMERICHOST | NI_NUMERICSERV);
if (ret)
continue;
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (fd < 0)
continue;
reconnect:
ret = connect(fd, res->ai_addr, res->ai_addrlen);
if (ret < 0) {
if (errno == EINTR)
goto reconnect;
close(fd);
break;
}
dprintf("connected to %s:%d\n", addr, port);
goto success;
}
fd = -1;
eprintf("failed connect to %s:%d\n", addr, port);
success:
freeaddrinfo(res0);
return fd;
}
static int connect_to_sdog_unix(const char *path)
{
int fd, ret;
struct sockaddr_un un;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
eprintf("socket() failed: %m\n");
return -1;
}
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strncpy(un.sun_path, path, sizeof(un.sun_path) - 1);
ret = connect(fd, (const struct sockaddr *)&un, (socklen_t)sizeof(un));
if (ret < 0) {
eprintf("connect() failed: %m\n");
close(fd);
return -1;
}
return fd;
}
static int get_my_fd(struct sheepdog_access_info *ai)
{
pthread_t self_id = pthread_self();
struct sheepdog_fd_list *p;
int fd;
pthread_rwlock_rdlock(&ai->fd_list_lock);
list_for_each_entry(p, &ai->fd_list_head, list) {
if (p->id == self_id) {
pthread_rwlock_unlock(&ai->fd_list_lock);
return p->fd;
}
}
pthread_rwlock_unlock(&ai->fd_list_lock);
if (ai->is_unix)
fd = connect_to_sdog_unix(ai->uds_path);
else
fd = connect_to_sdog_tcp(ai->hostname, ai->port);
if (fd < 0)
return -1;
p = zalloc(sizeof(*p));
if (!p) {
close(fd);
return -1;
}
p->id = self_id;
p->fd = fd;
INIT_LIST_HEAD(&p->list);
pthread_rwlock_wrlock(&ai->fd_list_lock);
list_add_tail(&p->list, &ai->fd_list_head);
pthread_rwlock_unlock(&ai->fd_list_lock);
return p->fd;
}
static void close_my_fd(struct sheepdog_access_info *ai, int fd)
{
struct sheepdog_fd_list *p;
int closed = 0;
pthread_rwlock_wrlock(&ai->fd_list_lock);
list_for_each_entry(p, &ai->fd_list_head, list) {
if (p->fd == fd) {
close(fd);
list_del(&p->list);
free(p);
closed = 1;
break;
}
}
pthread_rwlock_unlock(&ai->fd_list_lock);
if (!closed)
eprintf("unknown fd to close: %d\n", fd);
}
static int do_read(int sockfd, void *buf, int len)
{
int ret;
reread:
ret = read(sockfd, buf, len);
if (!ret) {
eprintf("connection is closed (%d bytes left)\n", len);
return 1;
}
if (ret < 0) {
if (errno == EINTR || errno == EAGAIN)
goto reread;
eprintf("failed to read from socket: %d, %s\n",
ret, strerror(errno));
return 1;
}
len -= ret;
buf = (char *)buf + ret;
if (len)
goto reread;
return 0;
}
static void forward_iov(struct msghdr *msg, int len)
{
while (msg->msg_iov->iov_len <= len) {
len -= msg->msg_iov->iov_len;
msg->msg_iov++;
msg->msg_iovlen--;
}
msg->msg_iov->iov_base = (char *) msg->msg_iov->iov_base + len;
msg->msg_iov->iov_len -= len;
}
static int do_write(int sockfd, struct msghdr *msg, int len)
{
int ret;
rewrite:
ret = sendmsg(sockfd, msg, 0);
if (ret < 0) {
if (errno == EINTR || errno == EAGAIN)
goto rewrite;
eprintf("failed to write to socket: %d, %s\n",
ret, strerror(errno));
return 1;
}
len -= ret;
if (len) {
forward_iov(msg, ret);
goto rewrite;
}
return 0;
}
static int send_req(int sockfd, struct sheepdog_req *hdr, void *data,
unsigned int *wlen)
{
int ret;
struct iovec iov[2];
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 1;
iov[0].iov_base = hdr;
iov[0].iov_len = sizeof(*hdr);
if (*wlen) {
msg.msg_iovlen++;
iov[1].iov_base = data;
iov[1].iov_len = *wlen;
}
ret = do_write(sockfd, &msg, sizeof(*hdr) + *wlen);
if (ret) {
eprintf("failed to send a req, %s\n", strerror(errno));
ret = -1;
}
return ret;
}
static int do_req(struct sheepdog_access_info *ai, struct sheepdog_req *hdr,
void *data, unsigned int *wlen, unsigned int *rlen)
{
int ret, sockfd, count = 0;
retry:
if (count++) {
eprintf("retrying to reconnect (%d)\n", count);
if (0 <= sockfd)
close_my_fd(ai, sockfd);
sleep(1);
}
sockfd = get_my_fd(ai);
if (sockfd < 0)
goto retry;
ret = send_req(sockfd, hdr, data, wlen);
if (ret)
goto retry;
/* FIXME: retrying COW request should be handled in graceful way */
ret = do_read(sockfd, hdr, sizeof(*hdr));
if (ret)
goto retry;
if (hdr->data_length < *rlen)
*rlen = hdr->data_length;
if (*rlen) {
ret = do_read(sockfd, data, *rlen);
if (ret)
goto retry;
}
return 0;
}
static int find_vdi_name(struct sheepdog_access_info *ai, char *filename,
uint32_t snapid, char *tag, uint32_t *vid,
int for_snapshot);
static int read_object(struct sheepdog_access_info *ai, char *buf, uint64_t oid,
int copies, unsigned int datalen, uint32_t offset,
int *need_reload);
static int reload_inode(struct sheepdog_access_info *ai, int is_snapshot)
{
int ret = 0, need_reload = 0;
char tag[SD_MAX_VDI_TAG_LEN];
uint32_t vid;
static __thread uint64_t inode_version;
pthread_mutex_lock(&ai->inode_version_mutex);
if (inode_version != ai->inode_version) {
/* some other threads reloaded inode */
inode_version = ai->inode_version;
goto ret;
}
if (is_snapshot) {
memset(tag, 0, sizeof(tag));
ret = find_vdi_name(ai, ai->inode.name, CURRENT_VDI_ID, tag,
&vid, 0);
if (ret) {
ret = -1;
goto ret;
}
ret = read_object(ai, (char *)&ai->inode, vid_to_vdi_oid(vid),
ai->inode.nr_copies,
offsetof(struct sheepdog_inode, data_vdi_id),
0, &need_reload);
if (ret) {
ret = -1;
goto ret;
}
} else {
ret = read_object(ai, (char *)&ai->inode,
vid_to_vdi_oid(ai->inode.vdi_id),
ai->inode.nr_copies, SD_INODE_SIZE, 0,
&need_reload);
if (ret) {
ret = -1;
goto ret;
}
if (!!ai->inode.snap_ctime) {
/*
* This is a case like below:
* take snapshot -> write something -> failover
*
* Because invalidated inode is readonly and latest
* working VDI can have COWed objects, we need to
* resolve VID and reload its entire inode object.
*/
memset(tag, 0, sizeof(tag));
ret = find_vdi_name(ai, ai->inode.name, CURRENT_VDI_ID,
tag, &vid, 0);
if (ret) {
ret = -1;
goto ret;
}
ret = read_object(ai, (char *)&ai->inode,
vid_to_vdi_oid(vid),
ai->inode.nr_copies, SD_INODE_SIZE, 0,
&need_reload);
if (ret) {
ret = -1;
goto ret;
}
}
}
inode_version++;
ai->inode_version = inode_version;
ret:
pthread_mutex_unlock(&ai->inode_version_mutex);
return ret;
}
static int read_write_object(struct sheepdog_access_info *ai, char *buf,
uint64_t oid, int copies,
unsigned int datalen, uint32_t offset,
int write, int create, uint64_t old_oid,
uint16_t flags, int *need_reload)
{
struct sheepdog_obj_req hdr;
struct sheepdog_obj_rsp *rsp = (struct sheepdog_obj_rsp *)&hdr;
unsigned int wlen, rlen;
int ret;
retry:
memset(&hdr, 0, sizeof(hdr));
hdr.proto_ver = SD_PROTO_VER;
hdr.flags = flags;
if (write) {
wlen = datalen;
rlen = 0;
hdr.flags |= SD_FLAG_CMD_WRITE;
if (create) {
hdr.opcode = SD_OP_CREATE_AND_WRITE_OBJ;
hdr.cow_oid = old_oid;
} else {
hdr.opcode = SD_OP_WRITE_OBJ;
}
} else {
wlen = 0;
rlen = datalen;
hdr.opcode = SD_OP_READ_OBJ;
}
hdr.oid = oid;
hdr.data_length = datalen;
hdr.offset = offset;
hdr.copies = copies;
hdr.flags |= SD_FLAG_CMD_TGT;
ret = do_req(ai, (struct sheepdog_req *)&hdr, buf, &wlen, &rlen);
if (ret) {
eprintf("failed to send a request to the sheep\n");
return -1;
}
switch (rsp->result) {
case SD_RES_SUCCESS:
return 0;
case SD_RES_INODE_INVALIDATED:
dprintf("inode object is invalidated\n");
*need_reload = 2;
return 0;
case SD_RES_READONLY:
*need_reload = 1;
return 0;
case SD_RES_NO_OBJ:
if (!write && oid & (UINT64_C(1) << 63))
/*
* sheepdog doesn't provide a mechanism of metadata
* transaction, so tgt can see an inconsistent state
* like this (old working VDI became snapshot already
* but an inode object of new working VDI isn't
* created yet).
*/
goto retry;
return -1;
default:
eprintf("%s (oid: %" PRIx64 ", old_oid: %" PRIx64 ")\n",
sd_strerror(rsp->result), oid, old_oid);
return -1;
}
}
static int read_object(struct sheepdog_access_info *ai, char *buf,
uint64_t oid, int copies,
unsigned int datalen, uint32_t offset, int *need_reload)
{
return read_write_object(ai, buf, oid, copies, datalen, offset,
0, 0, 0, 0, need_reload);
}
static int write_object(struct sheepdog_access_info *ai, char *buf,
uint64_t oid, int copies,
unsigned int datalen, uint32_t offset, int create,
uint64_t old_oid, uint16_t flags, int *need_reload)
{
return read_write_object(ai, buf, oid, copies, datalen, offset, 1,
create, old_oid, flags, need_reload);
}
static int sd_sync(struct sheepdog_access_info *ai)
{
int ret;
struct sheepdog_obj_req hdr;
struct sheepdog_obj_rsp *rsp = (struct sheepdog_obj_rsp *)&hdr;
unsigned int wlen = 0, rlen = 0;
memset(&hdr, 0, sizeof(hdr));
hdr.proto_ver = SD_PROTO_VER;
hdr.opcode = SD_OP_FLUSH_VDI;
hdr.oid = vid_to_vdi_oid(ai->inode.vdi_id);
ret = do_req(ai, (struct sheepdog_req *)&hdr, NULL, &wlen, &rlen);
if (ret) {
eprintf("failed to send a request to the sheep\n");
return -1;
}
switch (rsp->result) {
case SD_RES_SUCCESS:
case SD_RES_INVALID_PARMS:
/*
* SD_RES_INVALID_PARMS means the sheep daemon doesn't use
* object caches
*/
return 0;
default:
eprintf("%s\n", sd_strerror(rsp->result));
return -1;
}
}
static int update_inode(struct sheepdog_access_info *ai, uint32_t min, uint32_t max)
{
int ret = 0, need_reload_inode = 0;
uint64_t oid = vid_to_vdi_oid(ai->inode.vdi_id);
uint32_t offset, data_len;
if (max < min)
goto end;
goto update;
reload:
reload_inode(ai, 0);
need_reload_inode = 0;
update:
offset = sizeof(ai->inode) - sizeof(ai->inode.data_vdi_id) +
min * sizeof(ai->inode.data_vdi_id[0]);
data_len = (max - min + 1) * sizeof(ai->inode.data_vdi_id[0]);
ret = write_object(ai, (char *)&ai->inode + offset, oid,
ai->inode.nr_copies, data_len, offset,
0, 0, 0, &need_reload_inode);
if (ret < 0)
eprintf("sync inode failed\n");
if (need_reload_inode) {
dprintf("reloading inode is required in the path"
" of update_inode()\n");
goto reload;
}
end:
return ret;
}
static int is_refresh_required(struct sheepdog_access_info *ai)
/*
* 0: refresh isn't required
* 1: refresh is required
*/
{
uint64_t inode_oid = vid_to_vdi_oid(ai->inode.vdi_id);
char dummy;
int need_reload_inode = 0;
/*
* Check inode of this tgtd is invaldiated or not.
* The inode object is the only one object which always exists.
*/
read_object(ai, &dummy, inode_oid, ai->inode.nr_copies, sizeof(dummy),
0, &need_reload_inode);
return need_reload_inode;
}
static int sd_io(struct sheepdog_access_info *ai, int write, char *buf, int len,
uint64_t offset)
{
uint32_t vid;
uint32_t object_size = (UINT32_C(1) << ai->inode.block_size_shift);
unsigned long idx = offset / object_size;
unsigned long max =
(offset + len + (object_size - 1)) / object_size;
unsigned obj_offset = offset % object_size;
size_t orig_size, size, rest = len;
int ret = 0, create;
uint64_t oid, old_oid;
uint16_t flags = 0;
int need_update_inode = 0, need_reload_inode;
int nr_copies = ai->inode.nr_copies;
int need_write_lock, check_idx;
int read_reload_snap = 0;
uint32_t min_dirty_data_idx = UINT32_MAX, max_dirty_data_idx = 0;
goto do_req;
reload_in_read_path:
pthread_rwlock_unlock(&ai->inode_lock); /* unlock current read lock */
pthread_rwlock_wrlock(&ai->inode_lock);
ret = reload_inode(ai, read_reload_snap);
if (ret) {
eprintf("failed to reload in read path\n");
goto out;
}
pthread_rwlock_unlock(&ai->inode_lock);
do_req:
need_write_lock = 0;
vid = ai->inode.vdi_id;
for (check_idx = idx; check_idx < max; check_idx++) {
if (ai->inode.data_vdi_id[check_idx] == vid)
continue;
need_write_lock = 1;
break;
}
if (need_write_lock)
pthread_rwlock_wrlock(&ai->inode_lock);
else
pthread_rwlock_rdlock(&ai->inode_lock);
for (; idx < max; idx++) {
orig_size = size;
size = object_size - obj_offset;
size = min_t(size_t, size, rest);
retry:
vid = ai->inode.vdi_id;
oid = vid_to_data_oid(ai->inode.data_vdi_id[idx], idx);
old_oid = 0;
if (write) {
/*
* tgt doesn't affect semantics of caching, so we can
* always turn on cache of sheep layer
*/
flags = SD_FLAG_CMD_CACHE;
create = 0;
if (ai->inode.data_vdi_id[idx] != vid) {
create = 1;
if (ai->inode.data_vdi_id[idx]) {
/* COW */
old_oid = oid;
flags |= SD_FLAG_CMD_COW;
}
oid = vid_to_data_oid(ai->inode.vdi_id, idx);
}
need_reload_inode = 0;
ret = write_object(ai, buf + (len - rest),
oid, nr_copies, size,
obj_offset, create,
old_oid, flags, &need_reload_inode);
if (!ret) {
if (need_reload_inode) {
/* If need_reload_inode is 1,
* snapshot was created.
* If it is 2, inode object is
* invalidated
*/
ret = reload_inode(ai,
need_reload_inode == 1);
if (!ret)
goto retry;
}
if (create) {
min_dirty_data_idx =
min_t(uint32_t, idx,
min_dirty_data_idx);
max_dirty_data_idx =
max_t(uint32_t, idx,
max_dirty_data_idx);
ai->inode.data_vdi_id[idx] = vid;
need_update_inode = 1;
create = 0;
}
}
} else {
if (!ai->inode.data_vdi_id[idx]) {
int check = is_refresh_required(ai);
if (!check) {
memset(buf, 0, size);
goto done;
} else {
dprintf("reload in read path for not"\
" written area\n");
size = orig_size;
read_reload_snap =
need_reload_inode == 1;
goto reload_in_read_path;
}
}
need_reload_inode = 0;
ret = read_object(ai, buf + (len - rest),
oid, nr_copies, size,
obj_offset, &need_reload_inode);
if (need_reload_inode) {
dprintf("reload in ordinal read path\n");
size = orig_size;
read_reload_snap = need_reload_inode == 1;
goto reload_in_read_path;
}
}
if (ret) {
eprintf("%lu %d\n", idx, ret);
goto out;
}
done:
rest -= size;
obj_offset = 0;
}
if (need_update_inode)
ret = update_inode(ai, min_dirty_data_idx, max_dirty_data_idx);
out:
pthread_rwlock_unlock(&ai->inode_lock);
return ret;
}
static int find_vdi_name(struct sheepdog_access_info *ai, char *filename,
uint32_t snapid, char *tag, uint32_t *vid,
int for_snapshot)
{
int ret;
struct sheepdog_vdi_req hdr;
struct sheepdog_vdi_rsp *rsp = (struct sheepdog_vdi_rsp *)&hdr;
unsigned int wlen, rlen = 0;
char buf[SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN];
memset(buf, 0, sizeof(buf));
strncpy(buf, filename, SD_MAX_VDI_LEN - 1);
strncpy(buf + SD_MAX_VDI_LEN, tag, SD_MAX_VDI_TAG_LEN - 1);
memset(&hdr, 0, sizeof(hdr));
if (for_snapshot)
hdr.opcode = SD_OP_GET_VDI_INFO;
else
hdr.opcode = SD_OP_LOCK_VDI;
hdr.type = LOCK_TYPE_SHARED;
wlen = SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN;
hdr.proto_ver = SD_PROTO_VER;
hdr.data_length = wlen;
hdr.snapid = snapid;
hdr.flags = SD_FLAG_CMD_WRITE;
ret = do_req(ai, (struct sheepdog_req *)&hdr, buf, &wlen, &rlen);
if (ret) {
ret = -1;
goto out;
}
if (rsp->result != SD_RES_SUCCESS) {
eprintf("cannot get vdi info, %s, %s %d %s\n",
sd_strerror(rsp->result), filename, snapid, tag);
ret = -1;
goto out;
}
*vid = rsp->vdi_id;
ret = 0;
out:
return ret;
}
static int sd_open(struct sheepdog_access_info *ai, char *filename, int flags)
{
int ret = 0, i, len, fd, need_reload = 0;
uint32_t vid = 0;
char *orig_filename;
char vdi_name[SD_MAX_VDI_LEN + 1];
char *saveptr = NULL, *result;
enum {
EXPECT_PROTO,
EXPECT_PATH,
EXPECT_HOST,
EXPECT_PORT,
EXPECT_VDI,
EXPECT_NOTHING,
} parse_state = EXPECT_PROTO;
memset(vdi_name, 0, sizeof(vdi_name));
orig_filename = strdup(filename);
if (!orig_filename) {
eprintf("saving original filename failed\n");
return -1;
}
/*
* expected form of filename:
*
* unix::
* tcp:::
*/
result = strtok_r(filename, ":", &saveptr);
do {
switch (parse_state) {
case EXPECT_PROTO:
if (!strcmp("unix", result)) {
ai->is_unix = 1;
parse_state = EXPECT_PATH;
} else if (!strcmp("tcp", result)) {
ai->is_unix = 0;
parse_state = EXPECT_HOST;
} else {
eprintf("unknown protocol of sheepdog vdi:"\
" %s\n", result);
ret = -1;
goto out;
}
break;
case EXPECT_PATH:
strncpy(ai->uds_path, result, UNIX_PATH_MAX - 1);
parse_state = EXPECT_VDI;
break;
case EXPECT_HOST:
strncpy(ai->hostname, result, HOST_NAME_MAX);
parse_state = EXPECT_PORT;
break;
case EXPECT_PORT:
len = strlen(result);
for (i = 0; i < len; i++) {
if (!isdigit(result[i])) {
eprintf("invalid tcp port number:"\
" %s\n", result);
ret = -1;
goto out;
}
}
ai->port = atoi(result);
parse_state = EXPECT_VDI;
break;
case EXPECT_VDI:
strncpy(vdi_name, result, SD_MAX_VDI_LEN);
parse_state = EXPECT_NOTHING;
break;
case EXPECT_NOTHING:
eprintf("invalid VDI path of sheepdog, unexpected"\
" token: %s (entire: %s)\n",
result, orig_filename);
ret = -1;
goto out;
default:
eprintf("BUG: invalid state of parser: %d\n",
parse_state);
exit(1);
}
} while ((result = strtok_r(NULL, ":", &saveptr)) != NULL);
if (parse_state != EXPECT_NOTHING) {
eprintf("invalid VDI path of sheepdog: %s (state: %d)\n",
orig_filename, parse_state);
ret = -1;
goto out;
}
dprintf("protocol: %s\n", ai->is_unix ? "unix" : "tcp");
if (ai->is_unix)
dprintf("path of unix domain socket: %s\n", ai->uds_path);
else
dprintf("hostname: %s, port: %d\n", ai->hostname, ai->port);
/*
* test connection for validating command line option
*
* if this step is skipped, the main thread of tgtd will try to
* reconnect to sheep process forever
*/
fd = ai->is_unix ?
connect_to_sdog_unix(ai->uds_path) :
connect_to_sdog_tcp(ai->hostname, ai->port);
if (fd < 0) {
eprintf("connecting to sheep process failed, "\
"please verify the --backing-store option: %s",
orig_filename);
ret = -1;
goto out;
}
close(fd); /* we don't need this connection */
dprintf("VDI name: %s\n", vdi_name);
ret = find_vdi_name(ai, vdi_name, 0, "", &vid, 0);
if (ret)
goto out;
ret = read_object(ai, (char *)&ai->inode, vid_to_vdi_oid(vid),
0, SD_INODE_SIZE, 0, &need_reload);
if (ret)
goto out;
ret = 0;
INIT_LIST_HEAD(&ai->inflight_list_head);
pthread_mutex_init(&ai->inflight_list_mutex, NULL);
pthread_cond_init(&ai->inflight_list_cond, NULL);
out:
strcpy(filename, orig_filename);
free(orig_filename);
return ret;
}
static void sd_close(struct sheepdog_access_info *ai)
{
struct sheepdog_vdi_req hdr;
struct sheepdog_vdi_rsp *rsp = (struct sheepdog_vdi_rsp *)&hdr;
unsigned int wlen = 0, rlen = 0;
int ret;
memset(&hdr, 0, sizeof(hdr));
hdr.opcode = SD_OP_RELEASE_VDI;
hdr.type = LOCK_TYPE_SHARED;
hdr.vdi_id = ai->inode.vdi_id;
ret = do_req(ai, (struct sheepdog_req *)&hdr, NULL, &wlen, &rlen);
if (!ret && rsp->result != SD_RES_SUCCESS &&
rsp->result != SD_RES_VDI_NOT_LOCKED)
eprintf("%s, %s", sd_strerror(rsp->result), ai->inode.name);
}
static void set_medium_error(int *result, uint8_t *key, uint16_t *asc)
{
*result = SAM_STAT_CHECK_CONDITION;
*key = MEDIUM_ERROR;
*asc = ASC_READ_ERROR;
}
struct inflight_thread {
unsigned long min_idx, max_idx;
struct list_head list;
};
static void inflight_block(struct sheepdog_access_info *ai,
struct inflight_thread *myself)
{
struct inflight_thread *inflight;
pthread_mutex_lock(&ai->inflight_list_mutex);
retry:
list_for_each_entry(inflight, &ai->inflight_list_head, list) {
if (!(myself->max_idx < inflight->min_idx ||
inflight->max_idx < myself->min_idx)) {
pthread_cond_wait(&ai->inflight_list_cond,
&ai->inflight_list_mutex);
goto retry;
}
}
list_add_tail(&myself->list, &ai->inflight_list_head);
pthread_mutex_unlock(&ai->inflight_list_mutex);
}
void inflight_release(struct sheepdog_access_info *ai,
struct inflight_thread *myself)
{
pthread_mutex_lock(&ai->inflight_list_mutex);
list_del(&myself->list);
pthread_mutex_unlock(&ai->inflight_list_mutex);
pthread_cond_signal(&ai->inflight_list_cond);
}
static void bs_sheepdog_request(struct scsi_cmd *cmd)
{
int ret = 0;
uint32_t length = 0;
int result = SAM_STAT_GOOD;
uint8_t key = 0;
uint16_t asc = 0;
struct bs_thread_info *info = BS_THREAD_I(cmd->dev);
struct sheepdog_access_info *ai =
(struct sheepdog_access_info *)(info + 1);
uint32_t object_size = (UINT32_C(1) << ai->inode.block_size_shift);
struct inflight_thread myself;
int inflight = 0;
memset(&myself, 0, sizeof(myself));
INIT_LIST_HEAD(&myself.list);
switch (cmd->scb[0]) {
case SYNCHRONIZE_CACHE:
case SYNCHRONIZE_CACHE_16:
ret = sd_sync(ai);
if (ret)
set_medium_error(&result, &key, &asc);
break;
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
length = scsi_get_out_length(cmd);
myself.min_idx = cmd->offset / object_size;
myself.max_idx = (cmd->offset + length + (object_size - 1))
/ object_size;
inflight_block(ai, &myself);
inflight = 1;
ret = sd_io(ai, 1, scsi_get_out_buffer(cmd),
length, cmd->offset);
if (ret)
set_medium_error(&result, &key, &asc);
break;
case READ_6:
case READ_10:
case READ_12:
case READ_16:
length = scsi_get_in_length(cmd);
myself.min_idx = cmd->offset / object_size;
myself.max_idx = (cmd->offset + length + (object_size - 1))
/ object_size;
inflight_block(ai, &myself);
inflight = 1;
ret = sd_io(ai, 0, scsi_get_in_buffer(cmd),
length, cmd->offset);
if (ret)
set_medium_error(&result, &key, &asc);
break;
default:
eprintf("cmd->scb[0]: %x\n", cmd->scb[0]);
break;
}
dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length);
scsi_set_result(cmd, result);
if (result != SAM_STAT_GOOD) {
eprintf("io error %p %x %d %d %" PRIu64 ", %m\n",
cmd, cmd->scb[0], ret, length, cmd->offset);
sense_data_build(cmd, key, asc);
}
if (inflight)
inflight_release(ai, &myself);
}
static int bs_sheepdog_open(struct scsi_lu *lu, char *path,
int *fd, uint64_t *size)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
struct sheepdog_access_info *ai =
(struct sheepdog_access_info *)(info + 1);
int ret;
ret = sd_open(ai, path, 0);
if (ret)
return ret;
*size = ai->inode.vdi_size;
return 0;
}
static void bs_sheepdog_close(struct scsi_lu *lu)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
struct sheepdog_access_info *ai =
(struct sheepdog_access_info *)(info + 1);
sd_close(ai);
}
static tgtadm_err bs_sheepdog_init(struct scsi_lu *lu, char *bsopts)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
struct sheepdog_access_info *ai =
(struct sheepdog_access_info *)(info + 1);
INIT_LIST_HEAD(&ai->fd_list_head);
pthread_rwlock_init(&ai->fd_list_lock, NULL);
pthread_rwlock_init(&ai->inode_lock, NULL);
pthread_mutex_init(&ai->inode_version_mutex, NULL);
return bs_thread_open(info, bs_sheepdog_request, nr_iothreads);
}
static void bs_sheepdog_exit(struct scsi_lu *lu)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
struct sheepdog_access_info *ai =
(struct sheepdog_access_info *)(info + 1);
struct sheepdog_fd_list *p, *next;
bs_thread_close(info);
list_for_each_entry_safe(p, next, &ai->fd_list_head, list) {
close(p->fd);
list_del(&p->list);
free(p);
}
pthread_rwlock_destroy(&ai->fd_list_lock);
pthread_rwlock_destroy(&ai->inode_lock);
dprintf("cleaned logical unit %p safely\n", lu);
}
static struct backingstore_template sheepdog_bst = {
.bs_name = "sheepdog",
.bs_datasize =
sizeof(struct bs_thread_info) + sizeof(struct sheepdog_access_info),
.bs_open = bs_sheepdog_open,
.bs_close = bs_sheepdog_close,
.bs_init = bs_sheepdog_init,
.bs_exit = bs_sheepdog_exit,
.bs_cmd_submit = bs_thread_cmd_submit,
};
__attribute__((constructor)) static void __constructor(void)
{
unsigned char opcodes[] = {
ALLOW_MEDIUM_REMOVAL,
FORMAT_UNIT,
INQUIRY,
MAINT_PROTOCOL_IN,
MODE_SELECT,
MODE_SELECT_10,
MODE_SENSE,
MODE_SENSE_10,
PERSISTENT_RESERVE_IN,
PERSISTENT_RESERVE_OUT,
READ_10,
READ_12,
READ_16,
READ_6,
READ_CAPACITY,
RELEASE,
REPORT_LUNS,
REQUEST_SENSE,
RESERVE,
SEND_DIAGNOSTIC,
SERVICE_ACTION_IN,
START_STOP,
SYNCHRONIZE_CACHE,
SYNCHRONIZE_CACHE_16,
TEST_UNIT_READY,
WRITE_10,
WRITE_12,
WRITE_16,
WRITE_6
};
bs_create_opcode_map(&sheepdog_bst, opcodes, ARRAY_SIZE(opcodes));
register_backingstore_template(&sheepdog_bst);
}
tgt-1.0.80/usr/bs_ssc.c 0000664 0000000 0000000 00000041334 13747533545 0014654 0 ustar 00root root 0000000 0000000 /*
* SCSI stream command processing backing store
*
* Copyright (C) 2008 Mark Harvey markh794@gmail.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "list.h"
#include "util.h"
#include "tgtd.h"
#include "scsi.h"
#include "bs_thread.h"
#include "media.h"
#include "bs_ssc.h"
#include "ssc.h"
#include "libssc.h"
#define SENSE_FILEMARK 0x80
#define SENSE_EOM 0x40
#define SENSE_ILI 0X20
static void ssc_sense_data_build(struct scsi_cmd *cmd, uint8_t key,
uint16_t asc, uint8_t *info, int info_len)
{
/* TODO: support descriptor format */
sense_data_build(cmd, key, asc);
if (info_len) {
memcpy(cmd->sense_buffer + 3, info, 4);
cmd->sense_buffer[0] |= 0x80;
}
}
static inline uint32_t ssc_get_block_length(struct scsi_lu *lu)
{
return get_unaligned_be24(lu->mode_block_descriptor + 5);
}
/* I'm sure there is a more efficent method then this */
static int32_t be24_to_2comp(uint8_t *c)
{
int count;
count = (c[0] << 16) | (c[1] << 8) | c[2];
if (c[0] & 0x80)
count += (0xff << 24);
return count;
}
static int skip_next_header(struct scsi_lu *lu)
{
struct ssc_info *ssc = dtype_priv(lu);
struct blk_header_info *h = &ssc->c_blk;
return ssc_read_blkhdr(lu->fd, h, h->next);
}
static int skip_prev_header(struct scsi_lu *lu)
{
ssize_t rd;
struct ssc_info *ssc = dtype_priv(lu);
struct blk_header_info *h = &ssc->c_blk;
rd = ssc_read_blkhdr(lu->fd, h, h->prev);
if (rd)
return 1;
if (h->blk_type == BLK_BOT)
return skip_next_header(lu);
return 0;
}
static int resp_rewind(struct scsi_lu *lu)
{
int fd;
ssize_t rd;
struct ssc_info *ssc = dtype_priv(lu);
struct blk_header_info *h = &ssc->c_blk;
fd = lu->fd;
dprintf("*** Backing store fd: %s %d %d ***\n", lu->path, lu->fd, fd);
rd = ssc_read_blkhdr(fd, h, 0);
if (rd) {
eprintf("fail to read the first block header\n");
return 1;
}
return skip_next_header(lu);
}
static uint64_t current_size(struct scsi_cmd *cmd)
{
struct ssc_info *ssc = dtype_priv(cmd->dev);
return ssc->c_blk.curr;
}
static int append_blk(struct scsi_cmd *cmd, uint8_t *data,
int size, int orig_sz, int type)
{
int fd;
struct ssc_info *ssc = dtype_priv(cmd->dev);
struct blk_header_info c, *curr = &c;
struct blk_header_info e, *eod = &e;
ssize_t ret;
fd = cmd->dev->fd;
*curr = ssc->c_blk;
dprintf("B4 update : prev/curr/next"
" <%" PRId64 "/%" PRId64 "/%" PRId64 "> type: %d,"
" num: %" PRIx64 ", ondisk sz: %d, about to write %d\n",
curr->prev, curr->curr, curr->next,
curr->blk_type, curr->blk_num,
curr->ondisk_sz, size);
curr->next = curr->curr + size + SSC_BLK_HDR_SIZE;
curr->blk_type = type;
curr->ondisk_sz = size;
curr->blk_sz = orig_sz;
eod->prev = curr->curr;
eod->curr = curr->next;
eod->next = curr->next;
eod->ondisk_sz = 0;
eod->blk_sz = 0;
eod->blk_type = BLK_EOD;
eod->blk_num = curr->blk_num + 1;
memcpy(&ssc->c_blk, eod, sizeof(*eod));
dprintf("After update : prev/curr/next <%" PRId64 "/%" PRId64
"/%" PRId64 "> type: %d, num: %" PRIx64 ", ondisk sz: %d\n",
curr->prev, curr->curr, curr->next, curr->blk_type,
curr->blk_num, curr->ondisk_sz);
dprintf("EOD blk header: prev/curr/next <%" PRId64 "/%" PRId64
"/%" PRId64 "> type: %d, num: %" PRIx64 ", ondisk sz: %d\n",
eod->prev, eod->curr, eod->next, eod->blk_type, eod->blk_num,
eod->ondisk_sz);
/* Rewrite previous header with updated positioning info */
ret = ssc_write_blkhdr(fd, curr, curr->curr);
if (ret) {
eprintf("Rewrite of blk header failed: %m\n");
sense_data_build(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR);
return SAM_STAT_CHECK_CONDITION;
}
/* Write new EOD blk header */
ret = ssc_write_blkhdr(fd, eod, eod->curr);
if (ret) {
eprintf("Write of EOD blk header failed: %m\n");
sense_data_build(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR);
return SAM_STAT_CHECK_CONDITION;
}
/* Write any data */
if (size) {
ret = pwrite64(fd, data, size,
curr->curr + SSC_BLK_HDR_SIZE);
if (ret != size) {
eprintf("Write of data failed: %m\n");
sense_data_build(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR);
return SAM_STAT_CHECK_CONDITION;
}
}
/* Write new EOD blk header */
return SAM_STAT_GOOD;
}
static int space_filemark_reverse(struct scsi_cmd *cmd, int32_t count)
{
struct ssc_info *ssc = dtype_priv(cmd->dev);
struct blk_header_info *h = &ssc->c_blk;
count *= -1;
again:
if (!h->prev) {
sense_data_build(cmd, NO_SENSE, ASC_BOM);
return SAM_STAT_CHECK_CONDITION;
}
if (h->blk_type == BLK_FILEMARK)
count--;
if (skip_prev_header(cmd->dev)) {
sense_data_build(cmd, MEDIUM_ERROR,
ASC_MEDIUM_FORMAT_CORRUPT);
return SAM_STAT_CHECK_CONDITION;
}
if (count > 0)
goto again;
return SAM_STAT_GOOD;
}
static int space_filemark_forward(struct scsi_cmd *cmd, int32_t count)
{
struct ssc_info *ssc = dtype_priv(cmd->dev);
struct blk_header_info *h = &ssc->c_blk;
again:
if (h->blk_type == BLK_EOD) {
sense_data_build(cmd, NO_SENSE, ASC_END_OF_DATA);
return SAM_STAT_CHECK_CONDITION;
}
if (h->blk_type == BLK_FILEMARK)
count--;
if (skip_next_header(cmd->dev)) {
sense_data_build(cmd, MEDIUM_ERROR,
ASC_MEDIUM_FORMAT_CORRUPT);
return SAM_STAT_CHECK_CONDITION;
}
if (count > 0)
goto again;
return SAM_STAT_GOOD;
}
static int space_filemark(struct scsi_cmd *cmd, int32_t count)
{
struct ssc_info *ssc = dtype_priv(cmd->dev);
struct blk_header_info *h = &ssc->c_blk;
int result;
dprintf("*** space %d filemarks, %" PRIu64 "\n", count, h->curr);
if (count > 0)
result = space_filemark_forward(cmd, count);
else if (count < 0)
result = space_filemark_reverse(cmd, count);
else
result = SAM_STAT_GOOD;
dprintf("%" PRIu64 "\n", h->curr);
return result;
}
static int space_blocks(struct scsi_cmd *cmd, int32_t count)
{
struct ssc_info *ssc = dtype_priv(cmd->dev);
struct blk_header_info *h = &ssc->c_blk;
dprintf("*** space %d blocks, %" PRIu64 "\n", count, h->curr);
while (count != 0) {
if (count > 0) {
if (skip_next_header(cmd->dev)) {
sense_data_build(cmd, MEDIUM_ERROR,
ASC_MEDIUM_FORMAT_CORRUPT);
return SAM_STAT_CHECK_CONDITION;
}
if (h->blk_type == BLK_EOD) {
sense_data_build(cmd, NO_SENSE,
ASC_END_OF_DATA);
return SAM_STAT_CHECK_CONDITION;
}
count--;
} else {
if (skip_prev_header(cmd->dev)) {
sense_data_build(cmd, MEDIUM_ERROR,
ASC_MEDIUM_FORMAT_CORRUPT);
return SAM_STAT_CHECK_CONDITION;
}
if (h->blk_type == BLK_BOT) {
/* Can't leave at BOT */
skip_next_header(cmd->dev);
sense_data_build(cmd, NO_SENSE, ASC_BOM);
return SAM_STAT_CHECK_CONDITION;
}
count++;
}
}
dprintf("%" PRIu64 "\n", h->curr);
return SAM_STAT_GOOD;
}
static int resp_var_read(struct scsi_cmd *cmd, uint8_t *buf, uint32_t length,
int *transferred)
{
struct ssc_info *ssc = dtype_priv(cmd->dev);
struct blk_header_info *h = &ssc->c_blk;
int ret = 0, result = SAM_STAT_GOOD;
length = min(length, get_unaligned_be24(&cmd->scb[2]));
*transferred = 0;
if (length != h->blk_sz) {
uint8_t info[4];
int val = length - h->blk_sz;
put_unaligned_be32(val, info);
if (h->blk_type == BLK_EOD)
sense_data_build(cmd, 0x40 | BLANK_CHECK,
NO_ADDITIONAL_SENSE);
else if (h->blk_type == BLK_FILEMARK)
ssc_sense_data_build(cmd, NO_SENSE | SENSE_FILEMARK,
ASC_MARK, info, sizeof(info));
else
ssc_sense_data_build(cmd, NO_SENSE | 0x20,
NO_ADDITIONAL_SENSE,
info, sizeof(info));
if (length > h->blk_sz)
scsi_set_in_resid_by_actual(cmd, length - h->blk_sz);
else
scsi_set_in_resid_by_actual(cmd, 0);
length = min(length, h->blk_sz);
result = SAM_STAT_CHECK_CONDITION;
if (!length) {
if (h->blk_type == BLK_FILEMARK)
goto skip_and_out;
goto out;
}
}
ret = pread64(cmd->dev->fd, buf, length, h->curr + SSC_BLK_HDR_SIZE);
if (ret != length) {
sense_data_build(cmd, MEDIUM_ERROR, ASC_READ_ERROR);
result = SAM_STAT_CHECK_CONDITION;
goto out;
}
*transferred = length;
skip_and_out:
ret = skip_next_header(cmd->dev);
if (ret) {
sense_data_build(cmd, MEDIUM_ERROR, ASC_MEDIUM_FORMAT_CORRUPT);
result = SAM_STAT_CHECK_CONDITION;
}
out:
return result;
}
static int resp_fixed_read(struct scsi_cmd *cmd, uint8_t *buf, uint32_t length,
int *transferred)
{
struct ssc_info *ssc = dtype_priv(cmd->dev);
struct blk_header_info *h = &ssc->c_blk;
int i, ret, result = SAM_STAT_GOOD;
int count;
ssize_t residue;
int fd;
uint32_t block_length = ssc_get_block_length(cmd->dev);
count = get_unaligned_be24(&cmd->scb[2]);
fd = cmd->dev->fd;
ret = 0;
for (i = 0; i < count; i++) {
if (h->blk_type == BLK_FILEMARK) {
uint8_t info[4];
eprintf("Oops - found filemark\n");
put_unaligned_be32(count - i, info);
ssc_sense_data_build(cmd, NO_SENSE | SENSE_FILEMARK,
ASC_MARK, info, sizeof(info));
skip_next_header(cmd->dev);
result = SAM_STAT_CHECK_CONDITION;
goto out;
}
if (block_length != h->blk_sz) {
eprintf("block size mismatch %d vs %d\n",
block_length, h->blk_sz);
sense_data_build(cmd, MEDIUM_ERROR,
ASC_MEDIUM_FORMAT_CORRUPT);
result = SAM_STAT_CHECK_CONDITION;
goto out;
}
residue = pread64(fd, buf, block_length,
h->curr + SSC_BLK_HDR_SIZE);
if (block_length != residue) {
eprintf("Could only read %d bytes, not %d\n",
(int)residue, block_length);
sense_data_build(cmd, MEDIUM_ERROR, ASC_READ_ERROR);
result = SAM_STAT_CHECK_CONDITION;
goto out;
}
ret += block_length;
buf += block_length;
if (skip_next_header(cmd->dev)) {
eprintf("Could not read next header\n");
sense_data_build(cmd, MEDIUM_ERROR,
ASC_MEDIUM_FORMAT_CORRUPT);
result = SAM_STAT_CHECK_CONDITION;
goto out;
}
}
*transferred = ret;
out:
return result;
}
static void tape_rdwr_request(struct scsi_cmd *cmd)
{
struct ssc_info *ssc = dtype_priv(cmd->dev);
struct blk_header_info *h = &ssc->c_blk;
int ret, code;
uint32_t length, i;
int result = SAM_STAT_GOOD;
uint8_t *buf;
int32_t count;
int8_t fixed;
int8_t sti;
uint32_t block_length = ssc_get_block_length(cmd->dev);
ret = 0;
length = 0;
i = 0;
code = 0;
ssc = dtype_priv(cmd->dev);
switch (cmd->scb[0]) {
case REZERO_UNIT:
dprintf("**** Rewind ****\n");
if (resp_rewind(cmd->dev)) {
sense_data_build(cmd,
MEDIUM_ERROR, ASC_SEQUENTIAL_POSITION_ERR);
result = SAM_STAT_CHECK_CONDITION;
}
break;
case WRITE_FILEMARKS:
ret = get_unaligned_be24(&cmd->scb[2]);
dprintf("*** Write %d filemark%s ***\n", ret,
((ret > 1) || (ret < 0)) ? "s" : "");
for (i = 0; i < ret; i++)
append_blk(cmd, scsi_get_out_buffer(cmd), 0,
0, BLK_FILEMARK);
fsync(cmd->dev->fd);
break;
case READ_6:
fixed = cmd->scb[1] & 1;
sti = cmd->scb[1] & 2;
if (fixed && sti) {
sense_data_build(cmd, ILLEGAL_REQUEST,
ASC_INVALID_FIELD_IN_CDB);
result = SAM_STAT_CHECK_CONDITION;
break;
}
length = scsi_get_in_length(cmd);
count = get_unaligned_be24(&cmd->scb[2]);
buf = scsi_get_in_buffer(cmd);
dprintf("*** READ_6: length %d, count %d, fixed block %s,"
" %" PRIu64 " %d\n", length, count,
(fixed) ? "Yes" : "No", h->curr, sti);
if (fixed)
result = resp_fixed_read(cmd, buf, length, &ret);
else
result = resp_var_read(cmd, buf, length, &ret);
eprintf("Executed READ_6, Read %d bytes, %" PRIu64 "\n",
ret, h->curr);
break;
case WRITE_6:
fixed = cmd->scb[1] & 1;
buf = scsi_get_out_buffer(cmd);
count = get_unaligned_be24(&cmd->scb[2]);
length = scsi_get_out_length(cmd);
if (!fixed) {
block_length = length;
count = 1;
}
for (i = 0, ret = 0; i < count; i++) {
if (append_blk(cmd, buf, block_length,
block_length, BLK_UNCOMPRESS_DATA)) {
sense_data_build(cmd, MEDIUM_ERROR,
ASC_WRITE_ERROR);
result = SAM_STAT_CHECK_CONDITION;
break;
}
buf += block_length;
ret += block_length;
}
dprintf("*** WRITE_6 count: %d, length: %d, ret: %d, fixed: %s,"
" ssc->blk_sz: %d\n",
count, length, ret, (fixed) ? "Yes" : "No",
block_length);
/* Check for end of media */
if (current_size(cmd) > ssc->mam.max_capacity) {
sense_data_build(cmd, NO_SENSE|SENSE_EOM,
NO_ADDITIONAL_SENSE);
result = SAM_STAT_CHECK_CONDITION;
break;
}
if (ret != length) {
sense_data_build(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR);
result = SAM_STAT_CHECK_CONDITION;
}
break;
case SPACE:
code = cmd->scb[1] & 0xf;
count = be24_to_2comp(&cmd->scb[2]);
if (code == 0) { /* Logical Blocks */
result = space_blocks(cmd, count);
break;
} else if (code == 1) { /* Filemarks */
result = space_filemark(cmd, count);
break;
} else if (code == 3) { /* End of data */
while (h->blk_type != BLK_EOD)
if (skip_next_header(cmd->dev)) {
sense_data_build(cmd, MEDIUM_ERROR,
ASC_MEDIUM_FORMAT_CORRUPT);
result = SAM_STAT_CHECK_CONDITION;
break;
}
} else { /* Unsupported */
sense_data_build(cmd, ILLEGAL_REQUEST,
ASC_INVALID_FIELD_IN_CDB);
result = SAM_STAT_CHECK_CONDITION;
}
break;
case READ_POSITION:
{
int service_action = cmd->scb[1] & 0x1f;
uint8_t *data = scsi_get_in_buffer(cmd);
int len = scsi_get_in_length(cmd);
dprintf("Size of in_buffer = %d\n", len);
dprintf("Sizeof(buf): %zd\n", sizeof(buf));
dprintf("service action: 0x%02x\n", service_action);
if (service_action == 0) { /* Short form - block ID */
memset(data, 0, 20);
data[0] = 20;
} else if (service_action == 1) { /* Short form - vendor uniq */
memset(data, 0, 20);
data[0] = 20;
} else if (service_action == 6) { /* Long form */
memset(data, 0, 32);
data[0] = 32;
} else {
sense_data_build(cmd, ILLEGAL_REQUEST,
ASC_INVALID_FIELD_IN_CDB);
result = SAM_STAT_CHECK_CONDITION;
}
break;
}
default:
eprintf("Unknown op code - should never see this\n");
sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_OP_CODE);
result = SAM_STAT_CHECK_CONDITION;
break;
}
dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length);
scsi_set_result(cmd, result);
if (result != SAM_STAT_GOOD)
eprintf("io error %p %x %d %d %" PRIu64 ", %m\n",
cmd, cmd->scb[0], ret, length, cmd->offset);
}
static tgtadm_err bs_ssc_init(struct scsi_lu *lu, char *bsopts)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
return bs_thread_open(info, tape_rdwr_request, 1);
}
static int bs_ssc_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size)
{
struct ssc_info *ssc;
char *cart = NULL;
ssize_t rd;
int ret;
struct blk_header_info *h;
ssc = dtype_priv(lu);
*fd = backed_file_open(path, O_RDWR | O_LARGEFILE, size, NULL);
if (*fd < 0) {
eprintf("Could not open %s %m\n", path);
return *fd;
}
eprintf("Backing store %s, %d\n", path, *fd);
if (*size < SSC_BLK_HDR_SIZE + sizeof(struct MAM)) {
eprintf("backing file too small - not correct media format\n");
return -1;
}
h = &ssc->c_blk;
/* Can't call 'resp_rewind() at this point as lu data not
* setup */
rd = ssc_read_blkhdr(*fd, h, 0);
if (rd) {
eprintf("Failed to read complete blk header: %d %m\n", (int)rd);
return -1;
}
ret = ssc_read_mam_info(*fd, &ssc->mam);
if (ret) {
eprintf("Failed to read MAM: %d %m\n", (int)rd);
return -1;
}
rd = ssc_read_blkhdr(*fd, h, h->next);
if (rd) {
eprintf("Failed to read complete blk header: %d %m\n", (int)rd);
return -1;
}
switch (ssc->mam.medium_type) {
case CART_CLEAN:
cart = "Cleaning cartridge";
break;
case CART_DATA:
cart = "data cartridge";
break;
case CART_WORM:
cart = "WORM cartridge";
break;
default:
cart = "Unknown cartridge type";
break;
}
dprintf("Media size: %d, media type: %s\n", h->blk_sz, cart);
return 0;
}
static void bs_ssc_exit(struct scsi_lu *lu)
{
struct bs_thread_info *info = BS_THREAD_I(lu);
bs_thread_close(info);
}
static void bs_ssc_close(struct scsi_lu *lu)
{
dprintf("##### Close #####\n");
close(lu->fd);
}
static struct backingstore_template ssc_bst = {
.bs_name = "ssc",
.bs_datasize = sizeof(struct bs_thread_info),
.bs_init = bs_ssc_init,
.bs_exit = bs_ssc_exit,
.bs_open = bs_ssc_open,
.bs_close = bs_ssc_close,
.bs_cmd_submit = bs_thread_cmd_submit,
};
__attribute__((constructor)) static void bs_ssc_constructor(void)
{
register_backingstore_template(&ssc_bst);
}
tgt-1.0.80/usr/bs_ssc.h 0000664 0000000 0000000 00000004522 13747533545 0014657 0 ustar 00root root 0000000 0000000 #ifndef __BS_SSC_H
#define __BS_SSC_H
/*
* structure of a 'poor mans double linked list' on disk.
*/
/**
* Block type definitations
*
* @BLK_NOOP: No Operation.. Dummy value
* @BLK_UNCOMPRESS_DATA: If true, data block is uncompressed
* @BLK_ENCRYPTED_DATA: If true, data block is encrypted
* @BLK_FILEMARK: Represents a filemark
* @BLK_SETMARK: Represents a setmark
* @BLK_BOT: Represents a Beginning of Tape marker
* @BLK_EOD: Represents an End of Data marker
*
* Defines for types of SSC data blocks
*/
#define BLK_NOOP 0x00000000
#define BLK_COMPRESSED_DATA 0x00000001
#define BLK_UNCOMPRESS_DATA 0x00000002
#define BLK_ENCRYPTED_DATA 0x00000004
#define BLK_BOT 0x00000010
#define BLK_EOD 0x00000020
#define BLK_FILEMARK 0x00000040
#define BLK_SETMARK 0x00000080
#define TGT_TAPE_VERSION 2
#define SSC_BLK_HDR_SIZE (sizeof(struct blk_header))
struct blk_header {
uint8_t h_csum[4];
uint32_t ondisk_sz;
uint32_t blk_sz;
uint32_t blk_type;
uint64_t blk_num;
uint64_t prev;
uint64_t curr;
uint64_t next;
};
/*
* MAM (media access memory) structure based from IBM Ultrium SCSI
* Reference WB1109-02
*/
struct MAM {
uint32_t tape_fmt_version;
uint32_t __pad1;
uint64_t remaining_capacity;
uint64_t max_capacity;
uint64_t TapeAlert;
uint64_t load_count;
uint64_t MAM_space_remaining;
uint8_t assigning_organization_1[8];
uint8_t formatted_density_code;
uint8_t __pad2[5];
uint8_t initialization_count[2];
uint8_t dev_make_serial_last_load[4][40];
uint64_t written_in_medium_life;
uint64_t read_in_medium_life;
uint64_t written_in_last_load;
uint64_t read_in_last_load;
uint8_t medium_manufacturer[8];
uint8_t medium_serial_number[32];
uint32_t medium_length;
uint32_t medium_width;
uint8_t assigning_organization_2[8];
uint8_t medium_density_code;
uint8_t __pad3[7];
uint8_t medium_manufacture_date[8];
uint64_t MAM_capacity;
uint8_t medium_type;
uint8_t __pad4;
uint16_t medium_type_information;
uint8_t __pad5[4];
uint8_t application_vendor[8];
uint8_t application_name[32];
uint8_t application_version[8];
uint8_t user_medium_text_label[160];
uint8_t date_time_last_written[12];
uint8_t __pad6[3];
uint8_t localization_identifier;
uint8_t barcode[32];
uint8_t owning_host_textual_name[80];
uint8_t media_pool[160];
uint8_t vendor_unique[256];
uint8_t dirty;
uint8_t __reserved[7];
};
#endif
tgt-1.0.80/usr/bs_thread.h 0000664 0000000 0000000 00000001356 13747533545 0015340 0 ustar 00root root 0000000 0000000 typedef void (request_func_t) (struct scsi_cmd *);
struct bs_thread_info {
pthread_t *worker_thread;
int nr_worker_threads;
/* wokers sleep on this and signaled by tgtd */
pthread_cond_t pending_cond;
/* locked by tgtd and workers */
pthread_mutex_t pending_lock;
/* protected by pending_lock */
struct list_head pending_list;
request_func_t *request_fn;
};
static inline struct bs_thread_info *BS_THREAD_I(struct scsi_lu *lu)
{
return (struct bs_thread_info *) ((char *)lu + sizeof(*lu));
}
extern tgtadm_err bs_thread_open(struct bs_thread_info *info, request_func_t *rfn,
int nr_threads);
extern void bs_thread_close(struct bs_thread_info *info);
extern int bs_thread_cmd_submit(struct scsi_cmd *cmd);
extern int nr_iothreads;
tgt-1.0.80/usr/bsg.h 0000664 0000000 0000000 00000006030 13747533545 0014152 0 ustar 00root root 0000000 0000000 #ifndef BSG_H
#define BSG_H
#include
#define BSG_PROTOCOL_SCSI 0
#define BSG_SUB_PROTOCOL_SCSI_CMD 0
#define BSG_SUB_PROTOCOL_SCSI_TMF 1
#define BSG_SUB_PROTOCOL_SCSI_TRANSPORT 2
/*
* For flags member below
* sg.h sg_io_hdr also has bits defined for it's flags member. However
* none of these bits are implemented/used by bsg. The bits below are
* allocated to not conflict with sg.h ones anyway.
*/
#define BSG_FLAG_Q_AT_TAIL 0x10 /* default, == 0 at this bit, is Q_AT_HEAD */
struct sg_io_v4 {
__s32 guard; /* [i] 'Q' to differentiate from v3 */
__u32 protocol; /* [i] 0 -> SCSI , .... */
__u32 subprotocol; /* [i] 0 -> SCSI command, 1 -> SCSI task
management function, .... */
__u32 request_len; /* [i] in bytes */
__u64 request; /* [i], [*i] {SCSI: cdb} */
__u64 request_tag; /* [i] {SCSI: task tag (only if flagged)} */
__u32 request_attr; /* [i] {SCSI: task attribute} */
__u32 request_priority; /* [i] {SCSI: task priority} */
__u32 request_extra; /* [i] {spare, for padding} */
__u32 max_response_len; /* [i] in bytes */
__u64 response; /* [i], [*o] {SCSI: (auto)sense data} */
/* "dout_": data out (to device); "din_": data in (from device) */
__u32 dout_iovec_count; /* [i] 0 -> "flat" dout transfer else
dout_xfer points to array of iovec */
__u32 dout_xfer_len; /* [i] bytes to be transferred to device */
__u32 din_iovec_count; /* [i] 0 -> "flat" din transfer */
__u32 din_xfer_len; /* [i] bytes to be transferred from device */
__u64 dout_xferp; /* [i], [*i] */
__u64 din_xferp; /* [i], [*o] */
__u32 timeout; /* [i] units: millisecond */
__u32 flags; /* [i] bit mask */
__u64 usr_ptr; /* [i->o] unused internally */
__u32 spare_in; /* [i] */
__u32 driver_status; /* [o] 0 -> ok */
__u32 transport_status; /* [o] 0 -> ok */
__u32 device_status; /* [o] {SCSI: command completion status} */
__u32 retry_delay; /* [o] {SCSI: status auxiliary information} */
__u32 info; /* [o] additional information */
__u32 duration; /* [o] time to complete, in milliseconds */
__u32 response_len; /* [o] bytes of response actually written */
__s32 din_resid; /* [o] din_xfer_len - actual_din_xfer_len */
__s32 dout_resid; /* [o] dout_xfer_len - actual_dout_xfer_len */
__u64 generated_tag; /* [o] {SCSI: transport generated task tag} */
__u32 spare_out; /* [o] */
__u32 padding;
};
#ifdef __KERNEL__
#if defined(CONFIG_BLK_DEV_BSG)
struct bsg_class_device {
struct device *class_dev;
struct device *parent;
int minor;
struct request_queue *queue;
struct kref ref;
void (*release)(struct device *);
};
extern int bsg_register_queue(struct request_queue *q,
struct device *parent, const char *name,
void (*release)(struct device *));
extern void bsg_unregister_queue(struct request_queue *);
#else
static inline int bsg_register_queue(struct request_queue *q,
struct device *parent, const char *name,
void (*release)(struct device *))
{
return 0;
}
static inline void bsg_unregister_queue(struct request_queue *q)
{
}
#endif
#endif /* __KERNEL__ */
#endif
tgt-1.0.80/usr/concat_buf.c 0000664 0000000 0000000 00000004115 13747533545 0015477 0 ustar 00root root 0000000 0000000 /*
* concat_buf functions
* appending formatted output to dynamically grown strings
*
* Copyright (C) 2011 Alexander Nezhinsky
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include "log.h"
#include "util.h"
void concat_buf_init(struct concat_buf *b)
{
b->streamf = open_memstream(&b->buf, &b->size);
b->err = b->streamf ? 0 : errno;
b->used = 0;
}
int concat_printf(struct concat_buf *b, const char *format, ...)
{
va_list args;
int nprinted;
if (!b->err) {
va_start(args, format);
nprinted = vfprintf(b->streamf, format, args);
if (nprinted >= 0)
b->used += nprinted;
else {
b->err = nprinted;
fclose(b->streamf);
b->streamf = NULL;
}
va_end(args);
}
return b->err;
}
const char *concat_delim(struct concat_buf *b, const char *delim)
{
return !b->used ? "" : delim;
}
int concat_buf_finish(struct concat_buf *b)
{
if (b->streamf) {
fclose(b->streamf);
b->streamf = NULL;
if (b->size)
b->size++; /* account for trailing NULL char */
}
return b->err;
}
int concat_write(struct concat_buf *b, int fd, int offset)
{
concat_buf_finish(b);
if (b->err) {
errno = b->err;
return -1;
}
if (b->size - offset > 0)
return write(fd, b->buf + offset, b->size - offset);
else {
errno = EINVAL;
return -1;
}
}
void concat_buf_release(struct concat_buf *b)
{
concat_buf_finish(b);
if (b->buf) {
free(b->buf);
memset(b, 0, sizeof(*b));
}
}
tgt-1.0.80/usr/crc32c.h 0000664 0000000 0000000 00000000576 13747533545 0014467 0 ustar 00root root 0000000 0000000 #ifndef _LINUX_CRC32C_H
#define _LINUX_CRC32C_H
#include
#include
extern uint32_t crc32c_le(uint32_t crc, unsigned char const *address, size_t length);
extern uint32_t crc32c_be(uint32_t crc, unsigned char const *address, size_t length);
#define crc32c(seed, data, length) crc32c_le(seed, (unsigned char const *)data, length)
#endif /* _LINUX_CRC32C_H */
tgt-1.0.80/usr/driver.c 0000664 0000000 0000000 00000003230 13747533545 0014664 0 ustar 00root root 0000000 0000000 /*
* driver routine
*
* Copyright (C) 2007 FUJITA Tomonori
* Copyright (C) 2007 Mike Christie
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include "list.h"
#include "util.h"
#include "tgtd.h"
#include "driver.h"
#define MAX_NR_DRIVERS 32
struct tgt_driver *tgt_drivers[MAX_NR_DRIVERS] = {
};
int get_driver_index(char *name)
{
int i;
for (i = 0; tgt_drivers[i]; i++) {
if (!strcmp(name, tgt_drivers[i]->name))
return i;
}
return -ENOENT;
}
int register_driver(struct tgt_driver *drv)
{
int i;
for (i = 0; i < ARRAY_SIZE(tgt_drivers); i++)
if (!tgt_drivers[i]) {
drv->drv_state = DRIVER_REGD;
tgt_drivers[i] = drv;
return 0;
}
return -1;
}
const char *driver_state_name(struct tgt_driver *drv)
{
switch (drv->drv_state) {
case DRIVER_REGD:
return "uninitialized";
case DRIVER_INIT:
return "ready";
case DRIVER_ERR:
return "error";
case DRIVER_EXIT:
return "stopped";
default:
return "unsupported";
}
}
tgt-1.0.80/usr/driver.h 0000664 0000000 0000000 00000002367 13747533545 0014703 0 ustar 00root root 0000000 0000000 #ifndef __DRIVER_H__
#define __DRIVER_H__
#include "tgtadm_error.h"
enum tgt_driver_state {
DRIVER_REGD = 0, /* just registered */
DRIVER_INIT, /* initialized ok */
DRIVER_ERR, /* failed to initialize */
DRIVER_EXIT /* exited */
};
struct tgt_driver {
const char *name;
enum tgt_driver_state drv_state;
int (*init)(int, char *);
void (*exit)(void);
int (*target_create)(struct target *);
void (*target_destroy)(int, int);
int (*portal_create)(char *);
int (*portal_destroy)(char *);
int (*lu_create)(struct scsi_lu *);
tgtadm_err (*update)(int, int, int ,uint64_t, uint64_t, uint32_t, char *);
tgtadm_err (*show)(int, int, uint64_t, uint32_t, uint64_t, struct concat_buf *);
tgtadm_err (*stat)(int, int, uint64_t, uint32_t, uint64_t, struct concat_buf *);
uint64_t (*scsi_get_lun)(uint8_t *);
int (*cmd_end_notify)(uint64_t nid, int result, struct scsi_cmd *);
int (*mgmt_end_notify)(struct mgmt_req *);
int (*transportid)(int, uint64_t, char *, int);
const char *default_bst;
struct list_head target_list;
};
extern struct tgt_driver *tgt_drivers[];
extern int get_driver_index(char *name);
extern int register_driver(struct tgt_driver *drv);
extern const char *driver_state_name(struct tgt_driver *drv);
#endif /* __DRIVER_H__ */
tgt-1.0.80/usr/iscsi/ 0000775 0000000 0000000 00000000000 13747533545 0014341 5 ustar 00root root 0000000 0000000 tgt-1.0.80/usr/iscsi/chap.c 0000664 0000000 0000000 00000040540 13747533545 0015423 0 ustar 00root root 0000000 0000000 /*
* chap.c - support for (mutual) CHAP authentication.
* (C) 2004 Xiranet Communications GmbH
*
* heavily based on code from iscsid.c:
* Copyright (C) 2002-2003 Ardis Technolgies ,
*
* and code taken from UNH iSCSI software:
* Copyright (C) 2001-2003 InterOperability Lab (IOL)
* University of New Hampshire (UNH)
* Durham, NH 03824
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include "iscsid.h"
#include "tgtd.h"
#include "md5.h"
#include "sha1.h"
#define HEX_FORMAT 0x01
#define BASE64_FORMAT 0x02
#define CHAP_DIGEST_ALG_MD5 5
#define CHAP_DIGEST_ALG_SHA1 7
#define CHAP_MD5_DIGEST_LEN 16
#define CHAP_SHA1_DIGEST_LEN 20
#define CHAP_INITIATOR_ERROR -1
#define CHAP_AUTH_ERROR -2
#define CHAP_TARGET_ERROR -3
#define CHAP_AUTH_STATE_START AUTH_STATE_START
#define CHAP_AUTH_STATE_CHALLENGE 1
#define CHAP_AUTH_STATE_RESPONSE 2
#define CHAP_INITIATOR_AUTH 0
#define CHAP_TARGET_AUTH 1
#define CHAP_CHALLENGE_MAX 50
static inline int decode_hex_digit(char c)
{
switch (c) {
case '0' ... '9':
return c - '0';
case 'a' ... 'f':
return c - 'a' + 10;
case 'A' ... 'F':
return c - 'A' + 10;
}
return 0;
}
static void decode_hex_string(char *hex_string, uint8_t *intnum, int intlen)
{
char *ptr;
int j;
j = strlen(hex_string);
ptr = hex_string + j;
j = --intlen;
do {
intnum[j] = decode_hex_digit(*--ptr);
intnum[j] |= decode_hex_digit(*--ptr) << 4;
j--;
} while (ptr > hex_string);
while (j >= 0)
intnum[j--] = 0;
}
/* Base64 decoding, taken from UNH-iSCSI "Base64codeToNumber()" */
static uint8_t decode_base64_digit(char base64)
{
switch (base64) {
case '=':
return 64;
case '/':
return 63;
case '+':
return 62;
default:
if ((base64 >= 'A') && (base64 <= 'Z'))
return base64 - 'A';
else if ((base64 >= 'a') && (base64 <= 'z'))
return 26 + (base64 - 'a');
else if ((base64 >= '0') && (base64 <= '9'))
return 52 + (base64 - '0');
else
return -1;
}
}
/* Base64 decoding, taken from UNH-iSCSI "Base64StringToInteger()" */
static void decode_base64_string(char *string, uint8_t *intnum, int int_len)
{
int len;
int count;
int intptr;
uint8_t num[4];
int octets;
if ((string == NULL) || (intnum == NULL))
return;
len = strlen(string);
if (len == 0)
return;
if ((len % 4) != 0)
return;
count = 0;
intptr = 0;
while (count < len - 4) {
num[0] = decode_base64_digit(string[count]);
num[1] = decode_base64_digit(string[count + 1]);
num[2] = decode_base64_digit(string[count + 2]);
num[3] = decode_base64_digit(string[count + 3]);
if ((num[0] == 65) || (num[1] == 65) || (num[2] == 65) || (num[3] == 65))
return;
count += 4;
octets =
(num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3];
intnum[intptr] = (octets & 0xFF0000) >> 16;
intnum[intptr + 1] = (octets & 0x00FF00) >> 8;
intnum[intptr + 2] = octets & 0x0000FF;
intptr += 3;
}
num[0] = decode_base64_digit(string[count]);
num[1] = decode_base64_digit(string[count + 1]);
num[2] = decode_base64_digit(string[count + 2]);
num[3] = decode_base64_digit(string[count + 3]);
if ((num[0] == 64) || (num[1] == 64))
return;
if (num[2] == 64) {
if (num[3] != 64)
return;
intnum[intptr] = (num[0] << 2) | (num[1] >> 4);
} else if (num[3] == 64) {
intnum[intptr] = (num[0] << 2) | (num[1] >> 4);
intnum[intptr + 1] = (num[1] << 4) | (num[2] >> 2);
} else {
octets =
(num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3];
intnum[intptr] = (octets & 0xFF0000) >> 16;
intnum[intptr + 1] = (octets & 0x00FF00) >> 8;
intnum[intptr + 2] = octets & 0x0000FF;
}
}
static inline void encode_hex_string(uint8_t *intnum, long length, char *string)
{
int i;
char *strptr;
strptr = string;
for (i = 0; i < length; i++, strptr += 2)
sprintf(strptr, "%.2hhx", intnum[i]);
}
/* Base64 encoding, taken from UNH iSCSI "IntegerToBase64String()" */
static void encode_base64_string(uint8_t *intnum, long length, char *string)
{
int count, octets, strptr, delta;
static const char base64code[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+',
'/', '=' };
if ((!intnum) || (!string) || (!length))
return;
count = 0;
octets = 0;
strptr = 0;
while ((delta = (length - count)) > 2) {
octets = (intnum[count] << 16) | (intnum[count + 1] << 8) | intnum[count + 2];
string[strptr] = base64code[(octets & 0xfc0000) >> 18];
string[strptr + 1] = base64code[(octets & 0x03f000) >> 12];
string[strptr + 2] = base64code[(octets & 0x000fc0) >> 6];
string[strptr + 3] = base64code[octets & 0x00003f];
count += 3;
strptr += 4;
}
if (delta == 1) {
string[strptr] = base64code[(intnum[count] & 0xfc) >> 2];
string[strptr + 1] = base64code[(intnum[count] & 0x03) << 4];
string[strptr + 2] = base64code[64];
string[strptr + 3] = base64code[64];
strptr += 4;
} else if (delta == 2) {
string[strptr] = base64code[(intnum[count] & 0xfc) >> 2];
string[strptr + 1] = base64code[((intnum[count] & 0x03) << 4) | ((intnum[count + 1] & 0xf0) >> 4)];
string[strptr + 2] = base64code[(intnum[count + 1] & 0x0f) << 2];
string[strptr + 3] = base64code[64];
strptr += 4;
}
string[strptr] = '\0';
}
static inline int chap_check_encoding_format(char *encoded)
{
int encoding_fmt;
if (!encoded)
return -1;
if ((strlen(encoded) < 3) || (encoded[0] != '0'))
return -1;
if (encoded[1] == 'x' || encoded[1] == 'X')
encoding_fmt = HEX_FORMAT;
else if (encoded[1] == 'b' || encoded[1] == 'B')
encoding_fmt = BASE64_FORMAT;
else
return -1;
return encoding_fmt;
}
static int chap_alloc_decode_buffer(char *encoded, uint8_t **decode_buf, int encoding_fmt)
{
int i;
int decode_len = 0;
i = strlen(encoded);
i -= 2;
if (encoding_fmt == HEX_FORMAT)
decode_len = (i - 1) / 2 + 1;
else if (encoding_fmt == BASE64_FORMAT) {
if (i % 4)
return CHAP_INITIATOR_ERROR;
decode_len = i / 4 * 3;
if (encoded[i + 1] == '=')
decode_len--;
if (encoded[i] == '=')
decode_len--;
}
if (!decode_len)
return CHAP_INITIATOR_ERROR;
*decode_buf = malloc(decode_len);
if (!*decode_buf)
return CHAP_TARGET_ERROR;
return decode_len;
}
static int chap_decode_string(char *encoded, uint8_t *decode_buf, int buf_len, int encoding_fmt)
{
if (encoding_fmt == HEX_FORMAT) {
if ((strlen(encoded) - 2) > (2 * buf_len)) {
eprintf("buf[%d] !sufficient to decode string[%d]\n",
buf_len, (int) strlen(encoded));
return CHAP_TARGET_ERROR;
}
decode_hex_string(encoded + 2, decode_buf, buf_len);
} else if (encoding_fmt == BASE64_FORMAT) {
if ((strlen(encoded) - 2) > ((buf_len - 1) / 3 + 1) * 4) {
eprintf("buf[%d] !sufficient to decode string[%d]\n",
buf_len, (int) strlen(encoded));
return CHAP_TARGET_ERROR;
}
decode_base64_string(encoded + 2, decode_buf, buf_len);
} else
return CHAP_INITIATOR_ERROR;
return 0;
}
static inline void chap_encode_string(uint8_t *intnum, int buf_len, char *encode_buf, int encoding_fmt)
{
encode_buf[0] = '0';
if (encoding_fmt == HEX_FORMAT) {
encode_buf[1] = 'x';
encode_hex_string(intnum, buf_len, encode_buf + 2);
} else if (encoding_fmt == BASE64_FORMAT) {
encode_buf[1] = 'b';
encode_base64_string(intnum, buf_len, encode_buf + 2);
}
}
static inline void chap_calc_digest_md5(char chap_id, char *secret, int secret_len, uint8_t *challenge, int challenge_len, uint8_t *digest)
{
struct MD5Context ctx;
MD5Init(&ctx);
MD5Update(&ctx, (unsigned char*)&chap_id, 1);
MD5Update(&ctx, (unsigned char*)secret, secret_len);
MD5Update(&ctx, challenge, challenge_len);
MD5Final(digest, &ctx);
}
static inline void chap_calc_digest_sha1(char chap_id, char *secret, int secret_len, uint8_t *challenge, int challenge_len, uint8_t *digest)
{
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, (unsigned char*)&chap_id, 1);
sha1_update(&ctx, (unsigned char*)secret, secret_len);
sha1_update(&ctx, challenge, challenge_len);
sha1_final(&ctx, digest);
}
static int chap_initiator_auth_create_challenge(struct iscsi_connection *conn)
{
char *value, *p;
char text[CHAP_CHALLENGE_MAX * 2 + 8];
static int chap_id;
int i;
value = text_key_find(conn, "CHAP_A");
if (!value)
return CHAP_INITIATOR_ERROR;
while ((p = strsep(&value, ","))) {
if (!strcmp(p, "5")) {
conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_MD5;
conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
break;
} else if (!strcmp(p, "7")) {
conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_SHA1;
conn->auth_state = CHAP_AUTH_STATE_CHALLENGE;
break;
}
}
if (!p)
return CHAP_INITIATOR_ERROR;
text_key_add(conn, "CHAP_A", p);
conn->auth.chap.id = ++chap_id;
sprintf(text, "%u", (unsigned char)conn->auth.chap.id);
text_key_add(conn, "CHAP_I", text);
/*
* FIXME: does a random challenge length provide any benefits security-
* wise, or should we rather always use the max. allowed length of
* 1024 for the (unencoded) challenge?
*/
conn->auth.chap.challenge_size = (rand() % (CHAP_CHALLENGE_MAX / 2)) + CHAP_CHALLENGE_MAX / 2;
conn->auth.chap.challenge = malloc(conn->auth.chap.challenge_size);
if (!conn->auth.chap.challenge)
return CHAP_TARGET_ERROR;
p = text;
strcpy(p, "0x");
p += 2;
for (i = 0; i < conn->auth.chap.challenge_size; i++) {
conn->auth.chap.challenge[i] = rand();
sprintf(p, "%.2hhx", conn->auth.chap.challenge[i]);
p += 2;
}
text_key_add(conn, "CHAP_C", text);
return 0;
}
static int chap_initiator_auth_check_response(struct iscsi_connection *conn)
{
char *value;
uint8_t *his_digest = NULL, *our_digest = NULL;
int digest_len = 0, retval = 0, encoding_format, err;
char pass[ISCSI_NAME_LEN];
memset(pass, 0, sizeof(pass));
err = account_available(conn->tid, AUTH_DIR_INCOMING);
if (!err) {
eprintf("No CHAP credentials configured\n");
retval = CHAP_TARGET_ERROR;
goto out;
}
if (!(value = text_key_find(conn, "CHAP_N"))) {
retval = CHAP_INITIATOR_ERROR;
goto out;
}
memset(pass, 0, sizeof(pass));
err = account_lookup(conn->tid, AUTH_DIR_INCOMING, value, 0, pass,
ISCSI_NAME_LEN);
if (err) {
eprintf("No valid user/pass combination for initiator %s "
"found\n", conn->initiator);
retval = CHAP_AUTH_ERROR;
goto out;
}
if (!(value = text_key_find(conn, "CHAP_R"))) {
retval = CHAP_INITIATOR_ERROR;
goto out;
}
if ((encoding_format = chap_check_encoding_format(value)) < 0) {
retval = CHAP_INITIATOR_ERROR;
goto out;
}
switch (conn->auth.chap.digest_alg) {
case CHAP_DIGEST_ALG_MD5:
digest_len = CHAP_MD5_DIGEST_LEN;
break;
case CHAP_DIGEST_ALG_SHA1:
digest_len = CHAP_SHA1_DIGEST_LEN;
break;
default:
retval = CHAP_TARGET_ERROR;
goto out;
}
if (!(his_digest = malloc(digest_len))) {
retval = CHAP_TARGET_ERROR;
goto out;
}
if (!(our_digest = malloc(digest_len))) {
retval = CHAP_TARGET_ERROR;
goto out;
}
if (chap_decode_string(value, his_digest, digest_len, encoding_format) < 0) {
retval = CHAP_INITIATOR_ERROR;
goto out;
}
switch (conn->auth.chap.digest_alg) {
case CHAP_DIGEST_ALG_MD5:
chap_calc_digest_md5(conn->auth.chap.id, pass, strlen(pass),
conn->auth.chap.challenge,
conn->auth.chap.challenge_size,
our_digest);
break;
case CHAP_DIGEST_ALG_SHA1:
chap_calc_digest_sha1(conn->auth.chap.id, pass, strlen(pass),
conn->auth.chap.challenge,
conn->auth.chap.challenge_size,
our_digest);
break;
default:
retval = CHAP_TARGET_ERROR;
goto out;
}
if (memcmp(our_digest, his_digest, digest_len)) {
log_warning("CHAP initiator auth.: "
"authentication of %s failed (wrong secret!?)",
conn->initiator);
retval = CHAP_AUTH_ERROR;
goto out;
}
conn->auth_state = CHAP_AUTH_STATE_RESPONSE;
out:
if (his_digest)
free(his_digest);
if (our_digest)
free(our_digest);
return retval;
}
static int chap_target_auth_create_response(struct iscsi_connection *conn)
{
char chap_id, *value, *response = NULL;
uint8_t *challenge = NULL, *digest = NULL;
int encoding_format, response_len;
int challenge_len = 0, digest_len = 0, retval = 0, err;
char pass[ISCSI_NAME_LEN], name[ISCSI_NAME_LEN];
if (!(value = text_key_find(conn, "CHAP_I"))) {
/* initiator doesn't want target auth!? */
conn->state = STATE_SECURITY_DONE;
retval = 0;
goto out;
}
chap_id = strtol(value, &value, 10);
memset(pass, 0, sizeof(pass));
memset(name, 0, sizeof(name));
err = account_lookup(conn->tid, AUTH_DIR_OUTGOING, name, sizeof(name),
pass, sizeof(pass));
if (err) {
log_warning("CHAP target auth.: "
"no outgoing credentials configured%s",
conn->tid ? "." : " for discovery.");
retval = CHAP_AUTH_ERROR;
goto out;
}
if (!(value = text_key_find(conn, "CHAP_C"))) {
log_warning("CHAP target auth.: "
"got no challenge from initiator %s",
conn->initiator);
retval = CHAP_INITIATOR_ERROR;
goto out;
}
if ((encoding_format = chap_check_encoding_format(value)) < 0) {
retval = CHAP_INITIATOR_ERROR;
goto out;
}
retval = chap_alloc_decode_buffer(value, &challenge, encoding_format);
if (retval <= 0)
goto out;
else if (retval > 1024) {
log_warning("CHAP target auth.: "
"initiator %s sent challenge of invalid length %d",
conn->initiator, challenge_len);
retval = CHAP_INITIATOR_ERROR;
goto out;
}
challenge_len = retval;
retval = 0;
switch (conn->auth.chap.digest_alg) {
case CHAP_DIGEST_ALG_MD5:
digest_len = CHAP_MD5_DIGEST_LEN;
break;
case CHAP_DIGEST_ALG_SHA1:
digest_len = CHAP_SHA1_DIGEST_LEN;
break;
default:
retval = CHAP_TARGET_ERROR;
goto out;
}
if (encoding_format == HEX_FORMAT)
response_len = 2 * digest_len;
else
response_len = ((digest_len - 1) / 3 + 1) * 4;
//"0x" / "0b" and "\0":
response_len += 3;
if (!(digest = malloc(digest_len))) {
retval = CHAP_TARGET_ERROR;
goto out;
}
if (!(response = malloc(response_len))) {
retval = CHAP_TARGET_ERROR;
goto out;
}
if (chap_decode_string(value, challenge, challenge_len, encoding_format) < 0) {
retval = CHAP_INITIATOR_ERROR;
goto out;
}
/* RFC 3720, 8.2.1: CHAP challenges MUST NOT be reused */
if (challenge_len == conn->auth.chap.challenge_size) {
if (!memcmp(challenge, conn->auth.chap.challenge,
challenge_len)) {
//FIXME: RFC 3720 demands to close TCP conn.
log_warning("CHAP target auth.: "
"initiator %s reflected our challenge",
conn->initiator);
retval = CHAP_INITIATOR_ERROR;
goto out;
}
}
switch (conn->auth.chap.digest_alg) {
case CHAP_DIGEST_ALG_MD5:
chap_calc_digest_md5(chap_id, pass, strlen(pass),
challenge, challenge_len, digest);
break;
case CHAP_DIGEST_ALG_SHA1:
chap_calc_digest_sha1(chap_id, pass, strlen(pass),
challenge, challenge_len, digest);
break;
default:
retval = CHAP_TARGET_ERROR;
goto out;
}
memset(response, 0x0, response_len);
chap_encode_string(digest, digest_len, response, encoding_format);
text_key_add(conn, "CHAP_N", name);
text_key_add(conn, "CHAP_R", response);
conn->state = STATE_SECURITY_DONE;
out:
if (challenge)
free(challenge);
if (digest)
free(digest);
if (response)
free(response);
return retval;
}
int cmnd_exec_auth_chap(struct iscsi_connection *conn)
{
int res;
switch(conn->auth_state) {
case CHAP_AUTH_STATE_START:
res = chap_initiator_auth_create_challenge(conn);
break;
case CHAP_AUTH_STATE_CHALLENGE:
res = chap_initiator_auth_check_response(conn);
if (res < 0)
break;
/* fall through */
case CHAP_AUTH_STATE_RESPONSE:
res = chap_target_auth_create_response(conn);
break;
default:
eprintf("BUG. unknown conn->auth_state %d\n", conn->auth_state);
res = CHAP_TARGET_ERROR;
}
return res;
}
tgt-1.0.80/usr/iscsi/conn.c 0000664 0000000 0000000 00000015574 13747533545 0015456 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2002-2003 Ardis Technolgies
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "iscsid.h"
#include "tgtd.h"
#include "util.h"
#include "tgtadm_error.h"
void conn_add_to_session(struct iscsi_connection *conn, struct iscsi_session *session)
{
if (!list_empty(&conn->clist)) {
eprintf("%" PRIx64 " %u\n",
sid64(session->isid, session->tsih), conn->cid);
exit(0);
}
/* release in conn_free */
session_get(session);
conn->session = session;
list_add(&conn->clist, &session->conn_list);
}
int conn_init(struct iscsi_connection *conn)
{
conn->req_buffer = malloc(INCOMING_BUFSIZE);
if (!conn->req_buffer)
return -ENOMEM;
conn->rsp_buffer = malloc(INCOMING_BUFSIZE);
if (!conn->rsp_buffer) {
free(conn->req_buffer);
return -ENOMEM;
}
conn->rsp_buffer_size = INCOMING_BUFSIZE;
conn->refcount = 1;
conn->state = STATE_FREE;
param_set_defaults(conn->session_param, session_keys);
INIT_LIST_HEAD(&conn->clist);
INIT_LIST_HEAD(&conn->tx_clist);
INIT_LIST_HEAD(&conn->task_list);
return 0;
}
void conn_exit(struct iscsi_connection *conn)
{
struct iscsi_session *session = conn->session;
list_del(&conn->clist);
free(conn->req_buffer);
free(conn->rsp_buffer);
free(conn->initiator);
if (conn->initiator_alias)
free(conn->initiator_alias);
if (session)
session_put(session);
}
void conn_close(struct iscsi_connection *conn)
{
struct iscsi_task *task, *tmp;
int ret;
if (conn->closed) {
eprintf("already closed %p %u\n", conn, conn->refcount);
return;
}
conn->closed = 1;
ret = conn->tp->ep_close(conn);
if (ret)
eprintf("failed to close a connection, %p %u %s\n",
conn, conn->refcount, strerror(errno));
else
dprintf("connection closed, %p %u\n", conn, conn->refcount);
/* may not have been in FFP yet */
if (!conn->session)
goto done;
dprintf("session %p %d\n", conn->session, conn->session->refcount);
/*
* We just closed the ep so we are not going to send/recv anything.
* Just free these up since they are not going to complete.
*/
list_for_each_entry_safe(task, tmp, &conn->session->pending_cmd_list,
c_list) {
if (task->conn != conn)
continue;
eprintf("Forcing release of pending task %p %" PRIx64 "\n",
task, task->tag);
list_del(&task->c_list);
iscsi_free_task(task);
}
if (conn->tx_task) {
dprintf("Add current tx task to the tx list for removal "
"%p %" PRIx64 "\n",
conn->tx_task, conn->tx_task->tag);
list_add(&conn->tx_task->c_list, &conn->tx_clist);
conn->tx_task = NULL;
}
list_for_each_entry_safe(task, tmp, &conn->tx_clist, c_list) {
uint8_t op;
op = task->req.opcode & ISCSI_OPCODE_MASK;
eprintf("Forcing release of tx task %p %" PRIx64 " %x\n",
task, task->tag, op);
switch (op) {
case ISCSI_OP_SCSI_CMD:
/*
* We can't call iscsi_free_cmd_task for a
* command waiting for SCSI_DATA_OUT. There
* would be a better way to see
* task->scmd.c_target though.
*/
if (task->scmd.c_target)
iscsi_free_cmd_task(task);
else
iscsi_free_task(task);
break;
case ISCSI_OP_NOOP_IN:
/* NOOP_IN req is allocated within iscsi_tcp
* by a direct call to the transport
* allocation routine, unaccounted in the
* connection refcount and not added to
* task_list, hence it should be freed when
* it's done by a similar direct call.
*
* We're overprotective here by checking tp's
* free_task pointer, avoiding interference
* with iser (I'm unsure if it's relevant
* though).
*/
if (task->conn->tp->free_task)
task->conn->tp->free_task(task);
break;
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_LOGOUT:
case ISCSI_OP_SCSI_TMFUNC:
iscsi_free_task(task);
break;
default:
eprintf("%x\n", op);
break;
}
}
if (conn->rx_task) {
eprintf("Forcing release of rx task %p %" PRIx64 "\n",
conn->rx_task, conn->rx_task->tag);
iscsi_free_task(conn->rx_task);
}
conn->rx_task = NULL;
/* cleaning up commands waiting for SCSI_DATA_OUT */
list_for_each_entry_safe(task, tmp, &conn->task_list, c_siblings) {
/*
* This task is in SCSI. We need to wait for I/O
* completion.
*/
if (task_in_scsi(task))
continue;
iscsi_free_task(task);
}
done:
conn_put(conn);
}
void conn_put(struct iscsi_connection *conn)
{
conn->refcount--;
if (!conn->refcount)
conn->tp->ep_release(conn);
}
int conn_get(struct iscsi_connection *conn)
{
/* TODO: check state */
conn->refcount++;
return 0;
}
struct iscsi_connection *conn_find(struct iscsi_session *session, uint32_t cid)
{
struct iscsi_connection *conn;
list_for_each_entry(conn, &session->conn_list, clist) {
if (conn->cid == cid)
return conn;
}
return NULL;
}
int conn_take_fd(struct iscsi_connection *conn)
{
dprintf("%u %u %u %" PRIx64 "\n", conn->cid, conn->stat_sn,
conn->exp_stat_sn, sid64(conn->isid, conn->tsih));
conn->session->conn_cnt++;
return 0;
}
/* called by tgtadm */
tgtadm_err conn_close_admin(uint32_t tid, uint64_t sid, uint32_t cid)
{
struct iscsi_target* target = NULL;
struct iscsi_session *session;
struct iscsi_connection *conn;
int sess_found = 0;
target = target_find_by_id(tid);
if (!target)
return TGTADM_NO_TARGET;
list_for_each_entry(session, &target->sessions_list, slist) {
if (session->tsih == sid) {
sess_found = 1;
list_for_each_entry(conn, &session->conn_list, clist) {
if (conn->cid == cid) {
eprintf("close %" PRIx64 " %u\n", sid, cid);
conn->tp->ep_force_close(conn);
return TGTADM_SUCCESS;
}
}
}
}
return sess_found ? TGTADM_NO_CONNECTION : TGTADM_NO_SESSION;
}
void iscsi_update_conn_stats_rx(struct iscsi_connection *conn, int size, int opcode)
{
conn->stats.rxdata_octets += (uint64_t)size;
if (unlikely(opcode < 0))
return;
if (opcode == ISCSI_OP_SCSI_CMD)
conn->stats.scsicmd_pdus++;
else if (opcode == ISCSI_OP_SCSI_DATA_OUT)
conn->stats.dataout_pdus++;
}
void iscsi_update_conn_stats_tx(struct iscsi_connection *conn, int size, int opcode)
{
conn->stats.txdata_octets += (uint64_t)size;
if (unlikely(opcode < 0))
return;
if (opcode == ISCSI_OP_SCSI_DATA_IN)
conn->stats.datain_pdus++;
else if (opcode == ISCSI_OP_SCSI_CMD_RSP)
conn->stats.scsirsp_pdus++;
}
tgt-1.0.80/usr/iscsi/iscsi_if.h 0000664 0000000 0000000 00000017706 13747533545 0016315 0 ustar 00root root 0000000 0000000 /*
* iSCSI User/Kernel Shares (Defines, Constants, Protocol definitions, etc)
*
* Copyright (C) 2005 Dmitry Yusupov
* Copyright (C) 2005 Alex Aizman
* maintained by open-iscsi@googlegroups.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* See the file COPYING included with this distribution for more details.
*/
#ifndef ISCSI_IF_H
#define ISCSI_IF_H
#define UEVENT_BASE 10
#define KEVENT_BASE 100
#define ISCSI_ERR_BASE 1000
enum iscsi_uevent_e {
ISCSI_UEVENT_UNKNOWN = 0,
/* down events */
ISCSI_UEVENT_CREATE_SESSION = UEVENT_BASE + 1,
ISCSI_UEVENT_DESTROY_SESSION = UEVENT_BASE + 2,
ISCSI_UEVENT_CREATE_CONN = UEVENT_BASE + 3,
ISCSI_UEVENT_DESTROY_CONN = UEVENT_BASE + 4,
ISCSI_UEVENT_BIND_CONN = UEVENT_BASE + 5,
ISCSI_UEVENT_SET_PARAM = UEVENT_BASE + 6,
ISCSI_UEVENT_START_CONN = UEVENT_BASE + 7,
ISCSI_UEVENT_STOP_CONN = UEVENT_BASE + 8,
ISCSI_UEVENT_SEND_PDU = UEVENT_BASE + 9,
ISCSI_UEVENT_GET_STATS = UEVENT_BASE + 10,
ISCSI_UEVENT_GET_PARAM = UEVENT_BASE + 11,
ISCSI_UEVENT_TRANSPORT_EP_CONNECT = UEVENT_BASE + 12,
ISCSI_UEVENT_TRANSPORT_EP_POLL = UEVENT_BASE + 13,
ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT = UEVENT_BASE + 14,
ISCSI_UEVENT_TGT_DSCVR = UEVENT_BASE + 15,
/* up events */
ISCSI_KEVENT_RECV_PDU = KEVENT_BASE + 1,
ISCSI_KEVENT_CONN_ERROR = KEVENT_BASE + 2,
ISCSI_KEVENT_IF_ERROR = KEVENT_BASE + 3,
ISCSI_KEVENT_DESTROY_SESSION = KEVENT_BASE + 4,
};
enum iscsi_tgt_dscvr {
ISCSI_TGT_DSCVR_SEND_TARGETS = 1,
ISCSI_TGT_DSCVR_ISNS = 2,
ISCSI_TGT_DSCVR_SLP = 3,
};
struct iscsi_uevent {
uint32_t type; /* k/u events type */
uint32_t iferror; /* carries interface or resource errors */
uint64_t transport_handle;
union {
/* messages u -> k */
struct msg_create_session {
uint32_t initial_cmdsn;
} c_session;
struct msg_destroy_session {
uint32_t sid;
} d_session;
struct msg_create_conn {
uint32_t sid;
uint32_t cid;
} c_conn;
struct msg_bind_conn {
uint32_t sid;
uint32_t cid;
uint64_t transport_eph;
uint32_t is_leading;
} b_conn;
struct msg_destroy_conn {
uint32_t sid;
uint32_t cid;
} d_conn;
struct msg_send_pdu {
uint32_t sid;
uint32_t cid;
uint32_t hdr_size;
uint32_t data_size;
} send_pdu;
struct msg_set_param {
uint32_t sid;
uint32_t cid;
uint32_t param; /* enum iscsi_param */
uint32_t len;
} set_param;
struct msg_start_conn {
uint32_t sid;
uint32_t cid;
} start_conn;
struct msg_stop_conn {
uint32_t sid;
uint32_t cid;
uint64_t conn_handle;
uint32_t flag;
} stop_conn;
struct msg_get_stats {
uint32_t sid;
uint32_t cid;
} get_stats;
struct msg_transport_connect {
uint32_t non_blocking;
} ep_connect;
struct msg_transport_poll {
uint64_t ep_handle;
uint32_t timeout_ms;
} ep_poll;
struct msg_transport_disconnect {
uint64_t ep_handle;
} ep_disconnect;
struct msg_tgt_dscvr {
enum iscsi_tgt_dscvr type;
uint32_t host_no;
/*
* enable = 1 to establish a new connection
* with the server. enable = 0 to disconnect
* from the server. Used primarily to switch
* from one iSNS server to another.
*/
uint32_t enable;
} tgt_dscvr;
} u;
union {
/* messages k -> u */
int retcode;
struct msg_create_session_ret {
uint32_t sid;
uint32_t host_no;
} c_session_ret;
struct msg_create_conn_ret {
uint32_t sid;
uint32_t cid;
} c_conn_ret;
struct msg_recv_req {
uint32_t sid;
uint32_t cid;
uint64_t recv_handle;
} recv_req;
struct msg_conn_error {
uint32_t sid;
uint32_t cid;
uint32_t error; /* enum iscsi_err */
} connerror;
struct msg_session_destroyed {
uint32_t host_no;
uint32_t sid;
} d_session;
struct msg_transport_connect_ret {
uint64_t handle;
} ep_connect_ret;
} r;
} __attribute__ ((aligned (sizeof(uint64_t))));
/*
* Common error codes
*/
enum iscsi_err {
ISCSI_OK = 0,
ISCSI_ERR_DATASN = ISCSI_ERR_BASE + 1,
ISCSI_ERR_DATA_OFFSET = ISCSI_ERR_BASE + 2,
ISCSI_ERR_MAX_CMDSN = ISCSI_ERR_BASE + 3,
ISCSI_ERR_EXP_CMDSN = ISCSI_ERR_BASE + 4,
ISCSI_ERR_BAD_OPCODE = ISCSI_ERR_BASE + 5,
ISCSI_ERR_DATALEN = ISCSI_ERR_BASE + 6,
ISCSI_ERR_AHSLEN = ISCSI_ERR_BASE + 7,
ISCSI_ERR_PROTO = ISCSI_ERR_BASE + 8,
ISCSI_ERR_LUN = ISCSI_ERR_BASE + 9,
ISCSI_ERR_BAD_ITT = ISCSI_ERR_BASE + 10,
ISCSI_ERR_CONN_FAILED = ISCSI_ERR_BASE + 11,
ISCSI_ERR_R2TSN = ISCSI_ERR_BASE + 12,
ISCSI_ERR_SESSION_FAILED = ISCSI_ERR_BASE + 13,
ISCSI_ERR_HDR_DGST = ISCSI_ERR_BASE + 14,
ISCSI_ERR_DATA_DGST = ISCSI_ERR_BASE + 15,
ISCSI_ERR_PARAM_NOT_FOUND = ISCSI_ERR_BASE + 16,
ISCSI_ERR_NO_SCSI_CMD = ISCSI_ERR_BASE + 17,
};
/*
* iSCSI Parameters (RFC3720). Keep the session_keys and
* default_tgt_session_param arrays consistent with this list.
*/
enum iscsi_param {
ISCSI_PARAM_MAX_RECV_DLENGTH,
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,
};
#define iscsi_ptr(_handle) ((void*)(unsigned long)_handle)
#define iscsi_handle(_ptr) ((uint64_t)(unsigned long)_ptr)
#define hostdata_session(_hostdata) (iscsi_ptr(*(unsigned long *)_hostdata))
/**
* iscsi_hostdata - get LLD hostdata from scsi_host
* @_hostdata: pointer to scsi host's hostdata
**/
#define iscsi_hostdata(_hostdata) ((void*)_hostdata + sizeof(unsigned long))
/*
* These flags presents iSCSI Data-Path capabilities.
*/
#define CAP_RECOVERY_L0 0x1
#define CAP_RECOVERY_L1 0x2
#define CAP_RECOVERY_L2 0x4
#define CAP_MULTI_R2T 0x8
#define CAP_HDRDGST 0x10
#define CAP_DATADGST 0x20
#define CAP_MULTI_CONN 0x40
#define CAP_TEXT_NEGO 0x80
#define CAP_MARKERS 0x100
/*
* These flags describes reason of stop_conn() call
*/
#define STOP_CONN_TERM 0x1
#define STOP_CONN_SUSPEND 0x2
#define STOP_CONN_RECOVER 0x3
#define ISCSI_STATS_CUSTOM_MAX 32
#define ISCSI_STATS_CUSTOM_DESC_MAX 64
struct iscsi_stats_custom {
char desc[ISCSI_STATS_CUSTOM_DESC_MAX];
uint64_t value;
};
/*
* struct iscsi_stats - iSCSI Statistics (iSCSI MIB)
*
* Note: this structure contains counters collected on per-connection basis.
*/
struct iscsi_stats {
/* octets */
uint64_t txdata_octets;
uint64_t rxdata_octets;
/* xmit pdus */
uint32_t noptx_pdus;
uint32_t scsicmd_pdus;
uint32_t tmfcmd_pdus;
uint32_t login_pdus;
uint32_t text_pdus;
uint32_t dataout_pdus;
uint32_t logout_pdus;
uint32_t snack_pdus;
/* recv pdus */
uint32_t noprx_pdus;
uint32_t scsirsp_pdus;
uint32_t tmfrsp_pdus;
uint32_t textrsp_pdus;
uint32_t datain_pdus;
uint32_t logoutrsp_pdus;
uint32_t r2t_pdus;
uint32_t async_pdus;
uint32_t rjt_pdus;
/* errors */
uint32_t digest_err;
uint32_t timeout_err;
/*
* iSCSI Custom Statistics support, i.e. Transport could
* extend existing MIB statistics with its own specific statistics
* up to ISCSI_STATS_CUSTOM_MAX
*/
uint32_t custom_length;
struct iscsi_stats_custom custom[0]
__attribute__ ((aligned (sizeof(uint64_t))));
};
#endif
tgt-1.0.80/usr/iscsi/iscsi_proto.h 0000664 0000000 0000000 00000034635 13747533545 0017062 0 ustar 00root root 0000000 0000000 /*
* RFC 3720 (iSCSI) protocol data types
*
* Copyright (C) 2005 Dmitry Yusupov
* Copyright (C) 2005 Alex Aizman
* maintained by open-iscsi@googlegroups.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* See the file COPYING included with this distribution for more details.
*/
#ifndef ISCSI_PROTO_H
#define ISCSI_PROTO_H
#define ISCSI_DRAFT20_VERSION 0x00
/* default iSCSI listen port for incoming connections */
#define ISCSI_LISTEN_PORT 3260
/* Padding word length */
#define PAD_WORD_LEN 4
/*
* useful common(control and data pathes) macro
*/
#define ntoh24(p) (((p)[0] << 16) | ((p)[1] << 8) | ((p)[2]))
#define hton24(p, v) { \
p[0] = (((v) >> 16) & 0xFF); \
p[1] = (((v) >> 8) & 0xFF); \
p[2] = ((v) & 0xFF); \
}
#define zero_data(p) {p[0]=0;p[1]=0;p[2]=0;}
/*
* iSCSI Template Message Header
*/
struct iscsi_hdr {
uint8_t opcode;
uint8_t flags; /* Final bit */
uint8_t rsvd2[2];
uint8_t hlength; /* AHSs total length */
uint8_t dlength[3]; /* Data length */
uint8_t lun[8];
uint32_t itt; /* Initiator Task Tag */
uint32_t ttt; /* Target Task Tag */
uint32_t statsn;
uint32_t exp_statsn;
uint32_t max_statsn;
uint8_t other[12];
};
/************************* RFC 3720 Begin *****************************/
#define ISCSI_RESERVED_TAG 0xffffffff
/* Opcode encoding bits */
#define ISCSI_OP_RETRY 0x80
#define ISCSI_OP_IMMEDIATE 0x40
#define ISCSI_OPCODE_MASK 0x3F
/* Initiator Opcode values */
#define ISCSI_OP_NOOP_OUT 0x00
#define ISCSI_OP_SCSI_CMD 0x01
#define ISCSI_OP_SCSI_TMFUNC 0x02
#define ISCSI_OP_LOGIN 0x03
#define ISCSI_OP_TEXT 0x04
#define ISCSI_OP_SCSI_DATA_OUT 0x05
#define ISCSI_OP_LOGOUT 0x06
#define ISCSI_OP_SNACK 0x10
#define ISCSI_OP_VENDOR1_CMD 0x1c
#define ISCSI_OP_VENDOR2_CMD 0x1d
#define ISCSI_OP_VENDOR3_CMD 0x1e
#define ISCSI_OP_VENDOR4_CMD 0x1f
/* Target Opcode values */
#define ISCSI_OP_NOOP_IN 0x20
#define ISCSI_OP_SCSI_CMD_RSP 0x21
#define ISCSI_OP_SCSI_TMFUNC_RSP 0x22
#define ISCSI_OP_LOGIN_RSP 0x23
#define ISCSI_OP_TEXT_RSP 0x24
#define ISCSI_OP_SCSI_DATA_IN 0x25
#define ISCSI_OP_LOGOUT_RSP 0x26
#define ISCSI_OP_R2T 0x31
#define ISCSI_OP_ASYNC_EVENT 0x32
#define ISCSI_OP_REJECT 0x3f
struct iscsi_ahs_hdr {
uint16_t ahslength;
uint8_t ahstype;
uint8_t ahspec[5];
};
#define ISCSI_AHSTYPE_CDB 1
#define ISCSI_AHSTYPE_RLENGTH 2
/* iSCSI PDU Header */
struct iscsi_cmd {
uint8_t opcode;
uint8_t flags;
uint16_t rsvd2;
uint8_t hlength;
uint8_t dlength[3];
uint8_t lun[8];
uint32_t itt; /* Initiator Task Tag */
uint32_t data_length;
uint32_t cmdsn;
uint32_t exp_statsn;
uint8_t cdb[16]; /* SCSI Command Block */
/* Additional Data (Command Dependent) */
};
/* Command PDU flags */
#define ISCSI_FLAG_CMD_FINAL 0x80
#define ISCSI_FLAG_CMD_READ 0x40
#define ISCSI_FLAG_CMD_WRITE 0x20
#define ISCSI_FLAG_CMD_ATTR_MASK 0x07 /* 3 bits */
/* SCSI Command Attribute values */
#define ISCSI_ATTR_UNTAGGED 0
#define ISCSI_ATTR_SIMPLE 1
#define ISCSI_ATTR_ORDERED 2
#define ISCSI_ATTR_HEAD_OF_QUEUE 3
#define ISCSI_ATTR_ACA 4
struct iscsi_rlength_ahdr {
uint16_t ahslength;
uint8_t ahstype;
uint8_t reserved;
uint32_t read_length;
};
/* Extended CDB AHS */
struct iscsi_ecdb_ahdr {
uint16_t ahslength; /* CDB length - 15, including reserved byte */
uint8_t ahstype;
uint8_t reserved;
uint8_t ecdb[260 - 16]; /* 4-byte aligned extended CDB spillover */
};
/* SCSI Response Header */
struct iscsi_cmd_rsp {
uint8_t opcode;
uint8_t flags;
uint8_t response;
uint8_t cmd_status;
uint8_t hlength;
uint8_t dlength[3];
uint8_t rsvd[8];
uint32_t itt; /* Initiator Task Tag */
uint32_t rsvd1;
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint32_t exp_datasn;
uint32_t bi_residual_count;
uint32_t residual_count;
/* Response or Sense Data (optional) */
};
/* Command Response PDU flags */
#define ISCSI_FLAG_CMD_BIDI_OVERFLOW 0x10
#define ISCSI_FLAG_CMD_BIDI_UNDERFLOW 0x08
#define ISCSI_FLAG_CMD_OVERFLOW 0x04
#define ISCSI_FLAG_CMD_UNDERFLOW 0x02
/* iSCSI Status values. Valid if Rsp Selector bit is not set */
#define ISCSI_STATUS_CMD_COMPLETED 0
#define ISCSI_STATUS_TARGET_FAILURE 1
#define ISCSI_STATUS_SUBSYS_FAILURE 2
/* Asynchronous Event Header */
struct iscsi_async {
uint8_t opcode;
uint8_t flags;
uint8_t rsvd2[2];
uint8_t rsvd3;
uint8_t dlength[3];
uint8_t lun[8];
uint8_t rsvd4[8];
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint8_t async_event;
uint8_t async_vcode;
uint16_t param1;
uint16_t param2;
uint16_t param3;
uint8_t rsvd5[4];
};
/* iSCSI Event Codes */
#define ISCSI_ASYNC_MSG_SCSI_EVENT 0
#define ISCSI_ASYNC_MSG_REQUEST_LOGOUT 1
#define ISCSI_ASYNC_MSG_DROPPING_CONNECTION 2
#define ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS 3
#define ISCSI_ASYNC_MSG_PARAM_NEGOTIATION 4
#define ISCSI_ASYNC_MSG_VENDOR_SPECIFIC 255
/* NOP-Out Message */
struct iscsi_nopout {
uint8_t opcode;
uint8_t flags;
uint16_t rsvd2;
uint8_t rsvd3;
uint8_t dlength[3];
uint8_t lun[8];
uint32_t itt; /* Initiator Task Tag */
uint32_t ttt; /* Target Transfer Tag */
uint32_t cmdsn;
uint32_t exp_statsn;
uint8_t rsvd4[16];
};
/* NOP-In Message */
struct iscsi_nopin {
uint8_t opcode;
uint8_t flags;
uint16_t rsvd2;
uint8_t rsvd3;
uint8_t dlength[3];
uint8_t lun[8];
uint32_t itt; /* Initiator Task Tag */
uint32_t ttt; /* Target Transfer Tag */
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint8_t rsvd4[12];
};
/* SCSI Task Management Message Header */
struct iscsi_tm {
uint8_t opcode;
uint8_t flags;
uint8_t rsvd1[2];
uint8_t hlength;
uint8_t dlength[3];
uint8_t lun[8];
uint32_t itt; /* Initiator Task Tag */
uint32_t rtt; /* Reference Task Tag */
uint32_t cmdsn;
uint32_t exp_statsn;
uint32_t refcmdsn;
uint32_t exp_datasn;
uint8_t rsvd2[8];
};
#define ISCSI_FLAG_TM_FUNC_MASK 0x7F
/* Function values */
#define ISCSI_TM_FUNC_ABORT_TASK 1
#define ISCSI_TM_FUNC_ABORT_TASK_SET 2
#define ISCSI_TM_FUNC_CLEAR_ACA 3
#define ISCSI_TM_FUNC_CLEAR_TASK_SET 4
#define ISCSI_TM_FUNC_LOGICAL_UNIT_RESET 5
#define ISCSI_TM_FUNC_TARGET_WARM_RESET 6
#define ISCSI_TM_FUNC_TARGET_COLD_RESET 7
#define ISCSI_TM_FUNC_TASK_REASSIGN 8
/* SCSI Task Management Response Header */
struct iscsi_tm_rsp {
uint8_t opcode;
uint8_t flags;
uint8_t response; /* see Response values below */
uint8_t qualifier;
uint8_t hlength;
uint8_t dlength[3];
uint8_t rsvd2[8];
uint32_t itt; /* Initiator Task Tag */
uint32_t rtt; /* Reference Task Tag */
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint8_t rsvd3[12];
};
/* Response values */
#define ISCSI_TMF_RSP_COMPLETE 0x00
#define ISCSI_TMF_RSP_NO_TASK 0x01
#define ISCSI_TMF_RSP_NO_LUN 0x02
#define ISCSI_TMF_RSP_TASK_ALLEGIANT 0x03
#define ISCSI_TMF_RSP_NO_FAILOVER 0x04
#define ISCSI_TMF_RSP_NOT_SUPPORTED 0x05
#define ISCSI_TMF_RSP_AUTH_FAILED 0x06
#define ISCSI_TMF_RSP_REJECTED 0xff
/* Ready To Transfer Header */
struct iscsi_r2t_rsp {
uint8_t opcode;
uint8_t flags;
uint8_t rsvd2[2];
uint8_t hlength;
uint8_t dlength[3];
uint8_t lun[8];
uint32_t itt; /* Initiator Task Tag */
uint32_t ttt; /* Target Transfer Tag */
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint32_t r2tsn;
uint32_t data_offset;
uint32_t data_length;
};
/* SCSI Data Hdr */
struct iscsi_data {
uint8_t opcode;
uint8_t flags;
uint8_t rsvd2[2];
uint8_t rsvd3;
uint8_t dlength[3];
uint8_t lun[8];
uint32_t itt;
uint32_t ttt;
uint32_t rsvd4;
uint32_t exp_statsn;
uint32_t rsvd5;
uint32_t datasn;
uint32_t offset;
uint32_t rsvd6;
/* Payload */
};
/* SCSI Data Response Hdr */
struct iscsi_data_rsp {
uint8_t opcode;
uint8_t flags;
uint8_t rsvd2;
uint8_t cmd_status;
uint8_t hlength;
uint8_t dlength[3];
uint8_t lun[8];
uint32_t itt;
uint32_t ttt;
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint32_t datasn;
uint32_t offset;
uint32_t residual_count;
};
/* Data Response PDU flags */
#define ISCSI_FLAG_DATA_ACK 0x40
#define ISCSI_FLAG_DATA_OVERFLOW 0x04
#define ISCSI_FLAG_DATA_UNDERFLOW 0x02
#define ISCSI_FLAG_DATA_STATUS 0x01
/* Text Header */
struct iscsi_text {
uint8_t opcode;
uint8_t flags;
uint8_t rsvd2[2];
uint8_t hlength;
uint8_t dlength[3];
uint8_t rsvd4[8];
uint32_t itt;
uint32_t ttt;
uint32_t cmdsn;
uint32_t exp_statsn;
uint8_t rsvd5[16];
/* Text - key=value pairs */
};
#define ISCSI_FLAG_TEXT_CONTINUE 0x40
/* Text Response Header */
struct iscsi_text_rsp {
uint8_t opcode;
uint8_t flags;
uint8_t rsvd2[2];
uint8_t hlength;
uint8_t dlength[3];
uint8_t rsvd4[8];
uint32_t itt;
uint32_t ttt;
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint8_t rsvd5[12];
/* Text Response - key:value pairs */
};
/* Login Header */
struct iscsi_login {
uint8_t opcode;
uint8_t flags;
uint8_t max_version; /* Max. version supported */
uint8_t min_version; /* Min. version supported */
uint8_t hlength;
uint8_t dlength[3];
uint8_t isid[6]; /* Initiator Session ID */
uint16_t tsih; /* Target Session Handle */
uint32_t itt; /* Initiator Task Tag */
uint16_t cid;
uint16_t rsvd3;
uint32_t cmdsn;
uint32_t exp_statsn;
uint8_t rsvd5[16];
};
/* Login PDU flags */
#define ISCSI_FLAG_LOGIN_TRANSIT 0x80
#define ISCSI_FLAG_LOGIN_CONTINUE 0x40
#define ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK 0x0C /* 2 bits */
#define ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK 0x03 /* 2 bits */
#define ISCSI_LOGIN_CURRENT_STAGE(flags) \
((flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2)
#define ISCSI_LOGIN_NEXT_STAGE(flags) \
(flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK)
/* Login Response Header */
struct iscsi_login_rsp {
uint8_t opcode;
uint8_t flags;
uint8_t max_version; /* Max. version supported */
uint8_t active_version; /* Active version */
uint8_t hlength;
uint8_t dlength[3];
uint8_t isid[6]; /* Initiator Session ID */
uint16_t tsih; /* Target Session Handle */
uint32_t itt; /* Initiator Task Tag */
uint32_t rsvd3;
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint8_t status_class; /* see Login RSP ststus classes below */
uint8_t status_detail; /* see Login RSP Status details below */
uint8_t rsvd4[10];
};
/* Login stage (phase) codes for CSG, NSG */
#define ISCSI_INITIAL_LOGIN_STAGE -1
#define ISCSI_SECURITY_NEGOTIATION_STAGE 0
#define ISCSI_OP_PARMS_NEGOTIATION_STAGE 1
#define ISCSI_FULL_FEATURE_PHASE 3
/* Login Status response classes */
#define ISCSI_STATUS_CLS_SUCCESS 0x00
#define ISCSI_STATUS_CLS_REDIRECT 0x01
#define ISCSI_STATUS_CLS_INITIATOR_ERR 0x02
#define ISCSI_STATUS_CLS_TARGET_ERR 0x03
/* Login Status response detail codes */
/* Class-0 (Success) */
#define ISCSI_LOGIN_STATUS_ACCEPT 0x00
/* Class-1 (Redirection) */
#define ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP 0x01
#define ISCSI_LOGIN_STATUS_TGT_MOVED_PERM 0x02
/* Class-2 (Initiator Error) */
#define ISCSI_LOGIN_STATUS_INIT_ERR 0x00
#define ISCSI_LOGIN_STATUS_AUTH_FAILED 0x01
#define ISCSI_LOGIN_STATUS_TGT_FORBIDDEN 0x02
#define ISCSI_LOGIN_STATUS_TGT_NOT_FOUND 0x03
#define ISCSI_LOGIN_STATUS_TGT_REMOVED 0x04
#define ISCSI_LOGIN_STATUS_NO_VERSION 0x05
#define ISCSI_LOGIN_STATUS_ISID_ERROR 0x06
#define ISCSI_LOGIN_STATUS_MISSING_FIELDS 0x07
#define ISCSI_LOGIN_STATUS_CONN_ADD_FAILED 0x08
#define ISCSI_LOGIN_STATUS_NO_SESSION_TYPE 0x09
#define ISCSI_LOGIN_STATUS_NO_SESSION 0x0a
#define ISCSI_LOGIN_STATUS_INVALID_REQUEST 0x0b
/* Class-3 (Target Error) */
#define ISCSI_LOGIN_STATUS_TARGET_ERROR 0x00
#define ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE 0x01
#define ISCSI_LOGIN_STATUS_NO_RESOURCES 0x02
/* Logout Header */
struct iscsi_logout {
uint8_t opcode;
uint8_t flags;
uint8_t rsvd1[2];
uint8_t hlength;
uint8_t dlength[3];
uint8_t rsvd2[8];
uint32_t itt; /* Initiator Task Tag */
uint16_t cid;
uint8_t rsvd3[2];
uint32_t cmdsn;
uint32_t exp_statsn;
uint8_t rsvd4[16];
};
/* Logout PDU flags */
#define ISCSI_FLAG_LOGOUT_REASON_MASK 0x7F
/* logout reason_code values */
#define ISCSI_LOGOUT_REASON_CLOSE_SESSION 0
#define ISCSI_LOGOUT_REASON_CLOSE_CONNECTION 1
#define ISCSI_LOGOUT_REASON_RECOVERY 2
#define ISCSI_LOGOUT_REASON_AEN_REQUEST 3
/* Logout Response Header */
struct iscsi_logout_rsp {
uint8_t opcode;
uint8_t flags;
uint8_t response; /* see Logout response values below */
uint8_t rsvd2;
uint8_t hlength;
uint8_t dlength[3];
uint8_t rsvd3[8];
uint32_t itt; /* Initiator Task Tag */
uint32_t rsvd4;
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint32_t rsvd5;
uint16_t t2wait;
uint16_t t2retain;
uint32_t rsvd6;
};
/* logout response status values */
#define ISCSI_LOGOUT_SUCCESS 0
#define ISCSI_LOGOUT_CID_NOT_FOUND 1
#define ISCSI_LOGOUT_RECOVERY_UNSUPPORTED 2
#define ISCSI_LOGOUT_CLEANUP_FAILED 3
/* SNACK Header */
struct iscsi_snack {
uint8_t opcode;
uint8_t flags;
uint8_t rsvd2[14];
uint32_t itt;
uint32_t begrun;
uint32_t runlength;
uint32_t exp_statsn;
uint32_t rsvd3;
uint32_t exp_datasn;
uint8_t rsvd6[8];
};
/* SNACK PDU flags */
#define ISCSI_FLAG_SNACK_TYPE_MASK 0x0F /* 4 bits */
/* Reject Message Header */
struct iscsi_reject {
uint8_t opcode;
uint8_t flags;
uint8_t reason;
uint8_t rsvd2;
uint8_t hlength;
uint8_t dlength[3];
uint8_t rsvd3[8];
uint32_t ffffffff;
uint8_t rsvd4[4];
uint32_t statsn;
uint32_t exp_cmdsn;
uint32_t max_cmdsn;
uint32_t datasn;
uint8_t rsvd5[8];
/* Text - Rejected hdr */
};
/* Reason for Reject */
#define ISCSI_REASON_CMD_BEFORE_LOGIN 1
#define ISCSI_REASON_DATA_DIGEST_ERROR 0x02
#define ISCSI_REASON_DATA_SNACK_REJECT 0x03
#define ISCSI_REASON_PROTOCOL_ERROR 0x04
#define ISCSI_REASON_CMD_NOT_SUPPORTED 0x05
#define ISCSI_REASON_IMM_CMD_REJECT 0x06
#define ISCSI_REASON_TASK_IN_PROGRESS 0x07
#define ISCSI_REASON_INVALID_SNACK 0x08
#define ISCSI_REASON_INVALID_PDU_FIELD 0x09
#define ISCSI_REASON_OUT_OF_RESOURCES 0x0a
#define ISCSI_REASON_NEGOTIATION_RESET 0x0b
#define ISCSI_REASON_WAINTING_FOR_LOGOUT 0x0c
/* Max. number of Key=Value pairs in a text message */
#define MAX_KEY_VALUE_PAIRS 8192
/* maximum length for text keys/values */
#define KEY_MAXLEN 64
#define VALUE_MAXLEN 255
#define TARGET_NAME_MAXLEN VALUE_MAXLEN
#define DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH 8192
/************************* RFC 3720 End *****************************/
#endif /* ISCSI_PROTO_H */
tgt-1.0.80/usr/iscsi/iscsi_tcp.c 0000664 0000000 0000000 00000036423 13747533545 0016475 0 ustar 00root root 0000000 0000000 /*
* Software iSCSI target over TCP/IP Data-Path
*
* Copyright (C) 2006-2007 FUJITA Tomonori
* Copyright (C) 2006-2007 Mike Christie
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "iscsid.h"
#include "tgtd.h"
#include "util.h"
#include "work.h"
static void iscsi_tcp_event_handler(int fd, int events, void *data);
static void iscsi_tcp_release(struct iscsi_connection *conn);
static struct iscsi_task *iscsi_tcp_alloc_task(struct iscsi_connection *conn,
size_t ext_len);
static void iscsi_tcp_free_task(struct iscsi_task *task);
static long nop_ttt;
static int listen_fds[8];
static struct iscsi_transport iscsi_tcp;
struct iscsi_tcp_connection {
int fd;
struct list_head tcp_conn_siblings;
int nop_inflight_count;
int nop_interval;
int nop_tick;
int nop_count;
long ttt;
struct iscsi_connection iscsi_conn;
};
static inline struct iscsi_tcp_connection *TCP_CONN(struct iscsi_connection *conn)
{
return container_of(conn, struct iscsi_tcp_connection, iscsi_conn);
}
static struct tgt_work nop_work;
/* all iscsi connections */
static struct list_head iscsi_tcp_conn_list;
static int iscsi_send_ping_nop_in(struct iscsi_tcp_connection *tcp_conn)
{
struct iscsi_connection *conn = &tcp_conn->iscsi_conn;
struct iscsi_task *task = NULL;
task = iscsi_tcp_alloc_task(&tcp_conn->iscsi_conn, 0);
task->conn = conn;
task->tag = ISCSI_RESERVED_TAG;
task->req.opcode = ISCSI_OP_NOOP_IN;
task->req.itt = cpu_to_be32(ISCSI_RESERVED_TAG);
task->req.ttt = cpu_to_be32(tcp_conn->ttt);
list_add_tail(&task->c_list, &task->conn->tx_clist);
task->conn->tp->ep_event_modify(task->conn, EPOLLIN | EPOLLOUT);
return 0;
}
static void iscsi_tcp_nop_work_handler(void *data)
{
struct iscsi_tcp_connection *tcp_conn;
list_for_each_entry(tcp_conn, &iscsi_tcp_conn_list, tcp_conn_siblings) {
if (tcp_conn->nop_interval == 0)
continue;
tcp_conn->nop_tick--;
if (tcp_conn->nop_tick > 0)
continue;
tcp_conn->nop_tick = tcp_conn->nop_interval;
tcp_conn->nop_inflight_count++;
if (tcp_conn->nop_inflight_count > tcp_conn->nop_count) {
eprintf("tcp connection timed out after %d failed " \
"NOP-OUT\n", tcp_conn->nop_count);
conn_close(&tcp_conn->iscsi_conn);
/* cant/shouldnt delete tcp_conn from within the loop */
break;
}
nop_ttt++;
if (nop_ttt == ISCSI_RESERVED_TAG)
nop_ttt = 1;
tcp_conn->ttt = nop_ttt;
iscsi_send_ping_nop_in(tcp_conn);
}
add_work(&nop_work, 1);
}
static void iscsi_tcp_nop_reply(long ttt)
{
struct iscsi_tcp_connection *tcp_conn;
list_for_each_entry(tcp_conn, &iscsi_tcp_conn_list, tcp_conn_siblings) {
if (tcp_conn->ttt != ttt)
continue;
tcp_conn->nop_inflight_count = 0;
}
}
int iscsi_update_target_nop_count(int tid, int count)
{
struct iscsi_target *target;
list_for_each_entry(target, &iscsi_targets_list, tlist) {
if (target->tid != tid)
continue;
target->nop_count = count;
return 0;
}
return -1;
}
int iscsi_update_target_nop_interval(int tid, int interval)
{
struct iscsi_target *target;
list_for_each_entry(target, &iscsi_targets_list, tlist) {
if (target->tid != tid)
continue;
target->nop_interval = interval;
return 0;
}
return -1;
}
void iscsi_set_nop_interval(int interval)
{
default_nop_interval = interval;
}
void iscsi_set_nop_count(int count)
{
default_nop_count = count;
}
static int set_keepalive(int fd)
{
int ret, opt;
opt = 1;
ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
if (ret)
return ret;
opt = 1800;
ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &opt, sizeof(opt));
if (ret)
return ret;
opt = 6;
ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &opt, sizeof(opt));
if (ret)
return ret;
opt = 300;
ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &opt, sizeof(opt));
if (ret)
return ret;
return 0;
}
static int set_nodelay(int fd)
{
int ret, opt;
opt = 1;
ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
return ret;
}
static void accept_connection(int afd, int events, void *data)
{
struct sockaddr_storage from;
socklen_t namesize;
struct iscsi_connection *conn;
struct iscsi_tcp_connection *tcp_conn;
int fd, ret;
dprintf("%d\n", afd);
namesize = sizeof(from);
fd = accept(afd, (struct sockaddr *) &from, &namesize);
if (fd < 0) {
eprintf("can't accept, %m\n");
return;
}
if (!is_system_available())
goto out;
if (list_empty(&iscsi_targets_list))
goto out;
ret = set_keepalive(fd);
if (ret)
goto out;
ret = set_nodelay(fd);
if (ret)
goto out;
tcp_conn = zalloc(sizeof(*tcp_conn));
if (!tcp_conn)
goto out;
conn = &tcp_conn->iscsi_conn;
ret = conn_init(conn);
if (ret) {
free(tcp_conn);
goto out;
}
tcp_conn->fd = fd;
conn->tp = &iscsi_tcp;
conn_read_pdu(conn);
set_non_blocking(fd);
ret = tgt_event_add(fd, EPOLLIN, iscsi_tcp_event_handler, conn);
if (ret) {
conn_exit(conn);
free(tcp_conn);
goto out;
}
list_add(&tcp_conn->tcp_conn_siblings, &iscsi_tcp_conn_list);
return;
out:
close(fd);
return;
}
static void iscsi_tcp_event_handler(int fd, int events, void *data)
{
struct iscsi_connection *conn = (struct iscsi_connection *) data;
if (events & EPOLLIN)
iscsi_rx_handler(conn);
if (conn->state == STATE_CLOSE)
dprintf("connection closed\n");
if (conn->state != STATE_CLOSE && events & EPOLLOUT)
iscsi_tx_handler(conn);
if (conn->state == STATE_CLOSE) {
dprintf("connection closed %p\n", conn);
conn_close(conn);
}
}
int iscsi_tcp_init_portal(char *addr, int port, int tpgt)
{
struct addrinfo hints, *res, *res0;
char servname[64];
int ret, fd, opt, nr_sock = 0;
struct iscsi_portal *portal = NULL;
char addrstr[64];
void *addrptr = NULL;
if (port < 0 || port > 65535) {
errno = EINVAL;
eprintf("port out of range, %m\n");
return -errno;
}
memset(servname, 0, sizeof(servname));
snprintf(servname, sizeof(servname), "%d", port);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
ret = getaddrinfo(addr, servname, &hints, &res0);
if (ret) {
eprintf("unable to get address info, %m\n");
return -errno;
}
for (res = res0; res; res = res->ai_next) {
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (fd < 0) {
if (res->ai_family == AF_INET6)
dprintf("IPv6 support is disabled.\n");
else
eprintf("unable to create fdet %d %d %d, %m\n",
res->ai_family, res->ai_socktype,
res->ai_protocol);
continue;
}
opt = 1;
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt,
sizeof(opt));
if (ret)
dprintf("unable to set SO_REUSEADDR, %m\n");
opt = 1;
if (res->ai_family == AF_INET6) {
ret = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt,
sizeof(opt));
if (ret) {
close(fd);
continue;
}
}
ret = bind(fd, res->ai_addr, res->ai_addrlen);
if (ret) {
close(fd);
eprintf("unable to bind server socket, %m\n");
continue;
}
ret = listen(fd, SOMAXCONN);
if (ret) {
eprintf("unable to listen to server socket, %m\n");
close(fd);
continue;
}
ret = getsockname(fd, res->ai_addr, &res->ai_addrlen);
if (ret) {
close(fd);
eprintf("unable to get socket address, %m\n");
continue;
}
set_non_blocking(fd);
ret = tgt_event_add(fd, EPOLLIN, accept_connection, NULL);
if (ret)
close(fd);
else {
listen_fds[nr_sock] = fd;
nr_sock++;
}
portal = zalloc(sizeof(struct iscsi_portal));
switch (res->ai_family) {
case AF_INET:
addrptr = &((struct sockaddr_in *)
res->ai_addr)->sin_addr;
port = ntohs(((struct sockaddr_in *)
res->ai_addr)->sin_port);
break;
case AF_INET6:
addrptr = &((struct sockaddr_in6 *)
res->ai_addr)->sin6_addr;
port = ntohs(((struct sockaddr_in6 *)
res->ai_addr)->sin6_port);
break;
}
portal->addr = strdup(inet_ntop(res->ai_family, addrptr,
addrstr, sizeof(addrstr)));
portal->port = port;
portal->tpgt = tpgt;
portal->fd = fd;
portal->af = res->ai_family;
list_add(&portal->iscsi_portal_siblings, &iscsi_portals_list);
}
freeaddrinfo(res0);
return !nr_sock;
}
int iscsi_add_portal(char *addr, int port, int tpgt)
{
const char *addr_str = "";
if (iscsi_tcp_init_portal(addr, port, tpgt)) {
if (addr) {
addr_str = addr;
}
eprintf("failed to create/bind to portal %s:%d\n",
addr_str, port);
return -1;
}
return 0;
};
int iscsi_delete_portal(char *addr, int port)
{
struct iscsi_portal *portal;
list_for_each_entry(portal, &iscsi_portals_list,
iscsi_portal_siblings) {
if (!strcmp(addr, portal->addr) && port == portal->port) {
if (portal->fd != -1)
tgt_event_del(portal->fd);
close(portal->fd);
list_del(&portal->iscsi_portal_siblings);
free(portal->addr);
free(portal);
return 0;
}
}
eprintf("delete_portal failed. No such portal found %s:%d\n",
addr, port);
return -1;
}
static int iscsi_tcp_init(void)
{
/* If we were passed any portals on the command line */
if (portal_arguments)
iscsi_param_parse_portals(portal_arguments, 1, 0);
/* if the user did not set a portal we default to wildcard
for ipv4 and ipv6
*/
if (list_empty(&iscsi_portals_list)) {
iscsi_add_portal(NULL, ISCSI_LISTEN_PORT, 1);
}
INIT_LIST_HEAD(&iscsi_tcp_conn_list);
nop_work.func = iscsi_tcp_nop_work_handler;
nop_work.data = &nop_work;
add_work(&nop_work, 1);
return 0;
}
static void iscsi_tcp_exit(void)
{
struct iscsi_portal *portal, *ptmp;
list_for_each_entry_safe(portal, ptmp, &iscsi_portals_list,
iscsi_portal_siblings) {
iscsi_delete_portal(portal->addr, portal->port);
}
}
static int iscsi_tcp_conn_login_complete(struct iscsi_connection *conn)
{
struct iscsi_tcp_connection *tcp_conn;
struct iscsi_target *target;
list_for_each_entry(tcp_conn, &iscsi_tcp_conn_list, tcp_conn_siblings)
if (&tcp_conn->iscsi_conn == conn)
break;
if (tcp_conn == NULL)
return 0;
list_for_each_entry(target, &iscsi_targets_list, tlist) {
if (target->tid != conn->tid)
continue;
tcp_conn->nop_count = target->nop_count;
tcp_conn->nop_interval = target->nop_interval;
tcp_conn->nop_tick = target->nop_interval;
break;
}
return 0;
}
static size_t iscsi_tcp_read(struct iscsi_connection *conn, void *buf,
size_t nbytes)
{
struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn);
return read(tcp_conn->fd, buf, nbytes);
}
static size_t iscsi_tcp_write_begin(struct iscsi_connection *conn, void *buf,
size_t nbytes)
{
struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn);
int opt = 1;
setsockopt(tcp_conn->fd, SOL_TCP, TCP_CORK, &opt, sizeof(opt));
return write(tcp_conn->fd, buf, nbytes);
}
static void iscsi_tcp_write_end(struct iscsi_connection *conn)
{
struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn);
int opt = 0;
setsockopt(tcp_conn->fd, SOL_TCP, TCP_CORK, &opt, sizeof(opt));
}
static size_t iscsi_tcp_close(struct iscsi_connection *conn)
{
struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn);
tgt_event_del(tcp_conn->fd);
conn->state = STATE_CLOSE;
tcp_conn->nop_interval = 0;
return 0;
}
static void iscsi_tcp_release(struct iscsi_connection *conn)
{
struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn);
conn_exit(conn);
close(tcp_conn->fd);
list_del(&tcp_conn->tcp_conn_siblings);
free(tcp_conn);
}
static int iscsi_tcp_show(struct iscsi_connection *conn, char *buf, int rest)
{
struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn);
int err, total = 0;
socklen_t slen;
char dst[INET6_ADDRSTRLEN];
struct sockaddr_storage from;
slen = sizeof(from);
err = getpeername(tcp_conn->fd, (struct sockaddr *) &from, &slen);
if (err < 0) {
eprintf("%m\n");
return 0;
}
err = getnameinfo((struct sockaddr *)&from, sizeof(from), dst,
sizeof(dst), NULL, 0, NI_NUMERICHOST);
if (err < 0) {
eprintf("%m\n");
return 0;
}
total = snprintf(buf, rest, "IP Address: %s", dst);
return total > 0 ? total : 0;
}
static void iscsi_event_modify(struct iscsi_connection *conn, int events)
{
struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn);
int ret;
ret = tgt_event_modify(tcp_conn->fd, events);
if (ret)
eprintf("tgt_event_modify failed\n");
}
static struct iscsi_task *iscsi_tcp_alloc_task(struct iscsi_connection *conn,
size_t ext_len)
{
struct iscsi_task *task;
task = malloc(sizeof(*task) + ext_len);
if (task)
memset(task, 0, sizeof(*task) + ext_len);
return task;
}
static void iscsi_tcp_free_task(struct iscsi_task *task)
{
free(task);
}
static void *iscsi_tcp_alloc_data_buf(struct iscsi_connection *conn, size_t sz)
{
void *addr = NULL;
if (posix_memalign(&addr, sysconf(_SC_PAGESIZE), sz) != 0)
return addr;
return addr;
}
static void iscsi_tcp_free_data_buf(struct iscsi_connection *conn, void *buf)
{
if (buf)
free(buf);
}
static int iscsi_tcp_getsockname(struct iscsi_connection *conn,
struct sockaddr *sa, socklen_t *len)
{
struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn);
return getsockname(tcp_conn->fd, sa, len);
}
static int iscsi_tcp_getpeername(struct iscsi_connection *conn,
struct sockaddr *sa, socklen_t *len)
{
struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn);
return getpeername(tcp_conn->fd, sa, len);
}
static void iscsi_tcp_conn_force_close(struct iscsi_connection *conn)
{
conn->state = STATE_CLOSE;
conn->tp->ep_event_modify(conn, EPOLLIN|EPOLLOUT|EPOLLERR);
}
void iscsi_print_nop_settings(struct concat_buf *b, int tid)
{
struct iscsi_target *target;
list_for_each_entry(target, &iscsi_targets_list, tlist) {
if (target->tid != tid)
continue;
if (target->nop_interval == 0)
continue;
concat_printf(b,
_TAB2 "Nop interval: %d\n"
_TAB2 "Nop count: %d\n",
target->nop_interval,
target->nop_count);
break;
}
}
static struct iscsi_transport iscsi_tcp = {
.name = "iscsi",
.rdma = 0,
.data_padding = PAD_WORD_LEN,
.ep_init = iscsi_tcp_init,
.ep_exit = iscsi_tcp_exit,
.ep_login_complete = iscsi_tcp_conn_login_complete,
.alloc_task = iscsi_tcp_alloc_task,
.free_task = iscsi_tcp_free_task,
.ep_read = iscsi_tcp_read,
.ep_write_begin = iscsi_tcp_write_begin,
.ep_write_end = iscsi_tcp_write_end,
.ep_close = iscsi_tcp_close,
.ep_force_close = iscsi_tcp_conn_force_close,
.ep_release = iscsi_tcp_release,
.ep_show = iscsi_tcp_show,
.ep_event_modify = iscsi_event_modify,
.alloc_data_buf = iscsi_tcp_alloc_data_buf,
.free_data_buf = iscsi_tcp_free_data_buf,
.ep_getsockname = iscsi_tcp_getsockname,
.ep_getpeername = iscsi_tcp_getpeername,
.ep_nop_reply = iscsi_tcp_nop_reply,
};
__attribute__((constructor)) static void iscsi_transport_init(void)
{
iscsi_transport_register(&iscsi_tcp);
}
tgt-1.0.80/usr/iscsi/iscsid.c 0000664 0000000 0000000 00000174430 13747533545 0015774 0 ustar 00root root 0000000 0000000 /*
* Software iSCSI target protocol routines
*
* Copyright (C) 2005-2007 FUJITA Tomonori
* Copyright (C) 2005-2007 Mike Christie
* Copyright (C) 2007 Pete Wyckoff
*
* This code is based on Ardis's iSCSI implementation.
* http://www.ardistech.com/iscsi/
* Copyright (C) 2002-2003 Ardis Technolgies
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "iscsid.h"
#include "tgtd.h"
#include "util.h"
#include "driver.h"
#include "scsi.h"
#include "tgtadm.h"
#include "crc32c.h"
int default_nop_interval;
int default_nop_count;
LIST_HEAD(iscsi_portals_list);
char *portal_arguments;
enum {
IOSTATE_FREE,
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,
};
void conn_read_pdu(struct iscsi_connection *conn)
{
conn->rx_iostate = IOSTATE_RX_BHS;
conn->rx_buffer = (void *)&conn->req.bhs;
conn->rx_size = BHS_SIZE;
}
static void conn_write_pdu(struct iscsi_connection *conn)
{
conn->tx_iostate = IOSTATE_TX_BHS;
memset(&conn->rsp, 0, sizeof(conn->rsp));
conn->tx_buffer = (void *)&conn->rsp.bhs;
conn->tx_size = BHS_SIZE;
}
static struct iscsi_key login_keys[] = {
{"InitiatorName",},
{"InitiatorAlias",},
{"SessionType",},
{"TargetName",},
{NULL, 0, 0, 0, NULL},
};
char *text_key_find(struct iscsi_connection *conn, char *searchKey)
{
char *data, *key, *value;
int keylen, datasize;
keylen = strlen(searchKey);
data = conn->req.data;
datasize = conn->req.datasize;
while (1) {
for (key = data; datasize > 0 && *data != '='; data++, datasize--)
;
if (!datasize)
return NULL;
data++;
datasize--;
for (value = data; datasize > 0 && *data != 0; data++, datasize--)
;
if (!datasize)
return NULL;
data++;
datasize--;
if (keylen == value - key - 1
&& !strncmp(key, searchKey, keylen))
return value;
}
}
static char *next_key(char **data, int *datasize, char **value)
{
char *key, *p, *q;
int size = *datasize;
key = p = *data;
for (; size > 0 && *p != '='; p++, size--)
;
if (!size)
return NULL;
*p++ = 0;
size--;
for (q = p; size > 0 && *p != 0; p++, size--)
;
if (!size)
return NULL;
p++;
size--;
*data = p;
*value = q;
*datasize = size;
return key;
}
void text_key_add(struct iscsi_connection *conn, char *key, char *value)
{
int keylen = strlen(key);
int valuelen = strlen(value);
int len = keylen + valuelen + 2;
char *buffer;
int max_len;
if (conn->state == STATE_FULL)
max_len = conn->session_param[ISCSI_PARAM_MAX_XMIT_DLENGTH].val;
else
max_len = conn->rsp_buffer_size;
if (!conn->rsp.datasize)
conn->rsp.data = conn->rsp_buffer;
buffer = conn->rsp_buffer;
if (conn->rsp.datasize + len > max_len &&
(conn->req.bhs.opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_TEXT)
goto drop;
if (conn->rsp.datasize + len > conn->rsp_buffer_size) {
buffer = realloc(buffer, conn->rsp.datasize + len);
if (buffer) {
conn->rsp_buffer = buffer;
conn->rsp.data = conn->rsp_buffer;
conn->rsp_buffer_size = conn->rsp.datasize + len;
} else
goto drop;
}
buffer += conn->rsp.datasize;
conn->rsp.datasize += len;
strcpy(buffer, key);
buffer += keylen;
*buffer++ = '=';
strcpy(buffer, value);
return;
drop:
log_warning("Dropping key (%s=%s)", key, value);
return;
}
static void text_key_add_reject(struct iscsi_connection *conn, char *key)
{
text_key_add(conn, key, "Reject");
}
static void text_scan_security(struct iscsi_connection *conn)
{
struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *)&conn->rsp.bhs;
char *key, *value, *data, *nextValue;
int datasize;
data = conn->req.data;
datasize = conn->req.datasize;
while ((key = next_key(&data, &datasize, &value))) {
if (!(param_index_by_name(key, login_keys) < 0))
;
else if (!strcmp(key, "AuthMethod")) {
do {
nextValue = strchr(value, ',');
if (nextValue)
*nextValue++ = 0;
if (!strcmp(value, "None")) {
if (account_available(conn->tid, AUTH_DIR_INCOMING))
continue;
conn->auth_method = AUTH_NONE;
text_key_add(conn, key, "None");
break;
} else if (!strcmp(value, "CHAP")) {
if (!account_available(conn->tid, AUTH_DIR_INCOMING))
continue;
conn->auth_method = AUTH_CHAP;
text_key_add(conn, key, "CHAP");
break;
}
} while ((value = nextValue));
if (conn->auth_method == AUTH_UNKNOWN)
text_key_add_reject(conn, key);
} else
text_key_add(conn, key, "NotUnderstood");
}
if (conn->auth_method == AUTH_UNKNOWN) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_AUTH_FAILED;
conn->state = STATE_EXIT;
}
}
static void login_security_done(struct iscsi_connection *conn)
{
struct iscsi_login *req = (struct iscsi_login *)&conn->req.bhs;
struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *) &conn->rsp.bhs;
struct iscsi_session *session;
if (!conn->tid)
return;
session = session_find_name(conn->tid, conn->initiator, req->isid);
if (session) {
if (!req->tsih) {
struct iscsi_connection *ent, *next;
/* do session reinstatement */
session_get(session);
list_for_each_entry_safe(ent, next, &session->conn_list,
clist) {
conn_close(ent);
}
session_put(session);
session = NULL;
} else if (req->tsih != session->tsih) {
/* fail the login */
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND;
conn->state = STATE_EXIT;
return;
} else if (conn_find(session, conn->cid)) {
/* do connection reinstatement */
}
/* add a new connection to the session */
if (session)
conn_add_to_session(conn, session);
} else {
if (req->tsih) {
/* fail the login */
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_NO_SESSION;
conn->state = STATE_EXIT;
return;
}
/*
* We do nothing here and instantiate a new session
* later at login_finish().
*/
}
}
static void text_scan_login(struct iscsi_connection *conn)
{
char *key, *value, *data;
int datasize, idx, is_rdma = 0;
struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *)&conn->rsp.bhs;
data = conn->req.data;
datasize = conn->req.datasize;
while ((key = next_key(&data, &datasize, &value))) {
if (!(param_index_by_name(key, login_keys) < 0))
;
else if (!strcmp(key, "AuthMethod"))
;
else if (!((idx = param_index_by_name(key, session_keys)) < 0)) {
int err;
unsigned int val;
char buf[32];
if (idx == ISCSI_PARAM_MAX_RECV_DLENGTH)
idx = ISCSI_PARAM_MAX_XMIT_DLENGTH;
if (idx == ISCSI_PARAM_RDMA_EXTENSIONS)
is_rdma = 1;
if (param_str_to_val(session_keys, idx, value, &val) < 0) {
if (conn->session_param[idx].state
== KEY_STATE_START) {
text_key_add_reject(conn, key);
continue;
} else {
rsp->status_class =
ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail =
ISCSI_LOGIN_STATUS_INIT_ERR;
conn->state = STATE_EXIT;
goto out;
}
}
err = param_check_val(session_keys, idx, &val);
if (err) {
text_key_add_reject(conn, key);
continue;
}
if (idx >= ISCSI_PARAM_FIRST_LOCAL)
conn->session_param[idx].val = val;
else
param_set_val(session_keys,
conn->session_param,
idx, &val);
switch (conn->session_param[idx].state) {
case KEY_STATE_START:
if (idx >= ISCSI_PARAM_FIRST_LOCAL)
break;
memset(buf, 0, sizeof(buf));
param_val_to_str(session_keys, idx, val, buf);
text_key_add(conn, key, buf);
break;
case KEY_STATE_REQUEST:
if (val != conn->session_param[idx].val) {
rsp->status_class =
ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail =
ISCSI_LOGIN_STATUS_INIT_ERR;
conn->state = STATE_EXIT;
log_warning("%s %u %u\n", key,
val, conn->session_param[idx].val);
goto out;
}
break;
case KEY_STATE_DONE:
break;
}
conn->session_param[idx].state = KEY_STATE_DONE;
} else
text_key_add(conn, key, "NotUnderstood");
}
if (is_rdma) {
/* do not try to do digests, not supported in iser */
conn->session_param[ISCSI_PARAM_HDRDGST_EN].val = DIGEST_NONE;
conn->session_param[ISCSI_PARAM_DATADGST_EN].val = DIGEST_NONE;
} else {
/* do not offer RDMA, initiator must explicitly request */
conn->session_param[ISCSI_PARAM_RDMA_EXTENSIONS].val = 0;
}
out:
return;
}
static int text_check_param(struct iscsi_connection *conn)
{
struct param *p = conn->session_param;
char buf[32];
int i, cnt;
for (i = 0, cnt = 0; session_keys[i].name; i++) {
if (p[i].state == KEY_STATE_START && p[i].val != session_keys[i].def) {
if (conn->state == STATE_LOGIN) {
if (i >= ISCSI_PARAM_FIRST_LOCAL) {
if (p[i].val > session_keys[i].def)
p[i].val = session_keys[i].def;
p[i].state = KEY_STATE_DONE;
continue;
}
if (p[ISCSI_PARAM_RDMA_EXTENSIONS].val == 1) {
if (i == ISCSI_PARAM_MAX_RECV_DLENGTH)
continue;
} else {
if (i >= ISCSI_PARAM_RDMA_EXTENSIONS)
continue;
}
memset(buf, 0, sizeof(buf));
param_val_to_str(session_keys, i, p[i].val,
buf);
text_key_add(conn, session_keys[i].name, buf);
p[i].state = KEY_STATE_REQUEST;
}
cnt++;
}
}
return cnt;
}
static void login_start(struct iscsi_connection *conn)
{
struct iscsi_login *req = (struct iscsi_login *)&conn->req.bhs;
struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *)&conn->rsp.bhs;
char *name, *alias, *session_type, *target_name;
struct iscsi_target *target;
char buf[NI_MAXHOST + NI_MAXSERV + 4];
int reason, redir;
conn->cid = be16_to_cpu(req->cid);
memcpy(conn->isid, req->isid, sizeof(req->isid));
conn->tsih = req->tsih;
if (!sid64(conn->isid, conn->tsih)) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS;
conn->state = STATE_EXIT;
return;
}
name = text_key_find(conn, "InitiatorName");
if (!name) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS;
conn->state = STATE_EXIT;
return;
}
conn->initiator = strdup(name);
alias = text_key_find(conn, "InitiatorAlias");
if (alias)
conn->initiator_alias = strdup(alias);
session_type = text_key_find(conn, "SessionType");
target_name = text_key_find(conn, "TargetName");
conn->auth_method = -1;
conn->session_type = SESSION_NORMAL;
if (session_type) {
if (!strcmp(session_type, "Discovery"))
conn->session_type = SESSION_DISCOVERY;
else if (strcmp(session_type, "Normal")) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_NO_SESSION_TYPE;
conn->state = STATE_EXIT;
return;
}
}
if (conn->session_type == SESSION_DISCOVERY)
conn->tid = GLOBAL_TID;
if (conn->session_type == SESSION_NORMAL) {
if (!target_name) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS;
conn->state = STATE_EXIT;
return;
}
target = target_find_by_name(target_name);
if (!target) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND;
conn->state = STATE_EXIT;
return;
}
if (target->rdma) {
eprintf("Target %s is RDMA, but conn cid:%d from %s is TCP\n",
target_name, conn->cid, conn->initiator);
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND;
conn->state = STATE_EXIT;
return;
}
conn->tid = target->tid;
redir = target_redirected(target, conn, buf, &reason);
if (redir < 0) {
rsp->status_class = ISCSI_STATUS_CLS_TARGET_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR;
conn->state = STATE_EXIT;
return;
}
else if (redir) {
text_key_add(conn, "TargetAddress", buf);
rsp->status_class = ISCSI_STATUS_CLS_REDIRECT;
rsp->status_detail = reason;
conn->state = STATE_EXIT;
return;
}
if (tgt_get_target_state(target->tid) != SCSI_TARGET_READY) {
rsp->status_class = ISCSI_STATUS_CLS_TARGET_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR;
conn->state = STATE_EXIT;
return;
}
if (ip_acl(conn->tid, conn)) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND;
conn->state = STATE_EXIT;
return;
}
if (iqn_acl(conn->tid, conn)) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND;
conn->state = STATE_EXIT;
return;
}
if (isns_scn_access(conn->tid, name)) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND;
conn->state = STATE_EXIT;
return;
}
/* if (conn->target->max_sessions && */
/* (++conn->target->session_cnt > conn->target->max_sessions)) { */
/* conn->target->session_cnt--; */
/* rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; */
/* rsp->status_detail = ISCSI_STATUS_TOO_MANY_CONN; */
/* conn->state = STATE_EXIT; */
/* return; */
/* } */
memcpy(conn->session_param, target->session_param,
sizeof(conn->session_param));
}
conn->exp_cmd_sn = be32_to_cpu(req->cmdsn);
conn->max_cmd_sn = conn->exp_cmd_sn + 1;
dprintf("exp_cmd_sn: %d,%d\n", conn->exp_cmd_sn, req->cmdsn);
text_key_add(conn, "TargetPortalGroupTag", "1");
}
static void login_finish(struct iscsi_connection *conn)
{
struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *) &conn->rsp.bhs;
int ret;
uint8_t class, detail;
switch (conn->session_type) {
case SESSION_NORMAL:
/*
* Allocate transport resources for this connection.
*/
ret = conn->tp->ep_login_complete(conn);
if (ret) {
class = ISCSI_STATUS_CLS_TARGET_ERR;
detail = ISCSI_LOGIN_STATUS_NO_RESOURCES;
goto fail;
}
if (!conn->session) {
ret = session_create(conn);
if (ret) {
class = ISCSI_STATUS_CLS_TARGET_ERR;
detail = ISCSI_LOGIN_STATUS_TARGET_ERROR;
goto fail;
}
} else {
if (conn->tp->rdma ^ conn->session->rdma) {
eprintf("new conn rdma %d, but session %d\n",
conn->tp->rdma, conn->session->rdma);
class = ISCSI_STATUS_CLS_INITIATOR_ERR;
detail =ISCSI_LOGIN_STATUS_INVALID_REQUEST;
goto fail;
}
}
memcpy(conn->isid, conn->session->isid, sizeof(conn->isid));
conn->tsih = conn->session->tsih;
break;
case SESSION_DISCOVERY:
/* set a dummy tsih value */
conn->tsih = 1;
break;
}
return;
fail:
rsp->flags = 0;
rsp->status_class = class;
rsp->status_detail = detail;
conn->state = STATE_EXIT;
return;
}
static int cmnd_exec_auth(struct iscsi_connection *conn)
{
int res;
switch (conn->auth_method) {
case AUTH_CHAP:
res = cmnd_exec_auth_chap(conn);
break;
case AUTH_NONE:
res = 0;
break;
default:
eprintf("Unknown auth. method %d\n", conn->auth_method);
res = -3;
}
return res;
}
static void cmnd_reject(struct iscsi_connection *conn, uint8_t reason)
{
struct iscsi_reject *rsp = (struct iscsi_reject *)&conn->rsp.bhs;
memset(rsp, 0, BHS_SIZE);
rsp->opcode = ISCSI_OP_REJECT;
rsp->reason = reason;
rsp->ffffffff = ISCSI_RESERVED_TAG;
rsp->flags = ISCSI_FLAG_CMD_FINAL;
rsp->statsn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmdsn = cpu_to_be32(conn->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->max_cmd_sn);
conn->rsp.data = conn->rsp_buffer;
conn->rsp.datasize = BHS_SIZE;
memcpy(conn->rsp.data, &conn->req.bhs, BHS_SIZE);
}
static void cmnd_exec_login(struct iscsi_connection *conn)
{
struct iscsi_login *req = (struct iscsi_login *)&conn->req.bhs;
struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *)&conn->rsp.bhs;
int stay = 0, nsg_disagree = 0;
memset(rsp, 0, BHS_SIZE);
if ((req->opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_LOGIN ||
!(req->opcode & ISCSI_OP_IMMEDIATE)) {
cmnd_reject(conn, ISCSI_REASON_PROTOCOL_ERROR);
return;
}
rsp->opcode = ISCSI_OP_LOGIN_RSP;
rsp->max_version = ISCSI_DRAFT20_VERSION;
rsp->active_version = ISCSI_DRAFT20_VERSION;
rsp->itt = req->itt;
if (/* req->max_version < ISCSI_VERSION || */
req->min_version > ISCSI_DRAFT20_VERSION) {
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_NO_VERSION;
conn->state = STATE_EXIT;
return;
}
switch (ISCSI_LOGIN_CURRENT_STAGE(req->flags)) {
case ISCSI_SECURITY_NEGOTIATION_STAGE:
dprintf("Login request (security negotiation): %d\n",
conn->state);
rsp->flags = ISCSI_SECURITY_NEGOTIATION_STAGE << 2;
switch (conn->state) {
case STATE_FREE:
conn->state = STATE_SECURITY;
login_start(conn);
if (rsp->status_class)
return;
/* fall through */
case STATE_SECURITY:
text_scan_security(conn);
if (rsp->status_class)
return;
if (conn->auth_method != AUTH_NONE) {
conn->state = STATE_SECURITY_AUTH;
conn->auth_state = AUTH_STATE_START;
}
break;
case STATE_SECURITY_AUTH:
switch (cmnd_exec_auth(conn)) {
case 0:
break;
default:
case -1:
goto init_err;
case -2:
goto auth_err;
}
break;
default:
goto init_err;
}
break;
case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
dprintf("Login request (operational negotiation): %d\n",
conn->state);
rsp->flags = ISCSI_OP_PARMS_NEGOTIATION_STAGE << 2;
switch (conn->state) {
case STATE_FREE:
conn->state = STATE_LOGIN;
login_start(conn);
if (account_available(conn->tid, AUTH_DIR_INCOMING))
goto auth_err;
if (rsp->status_class)
return;
text_scan_login(conn);
if (rsp->status_class)
return;
stay = text_check_param(conn);
break;
case STATE_LOGIN:
text_scan_login(conn);
if (rsp->status_class)
return;
stay = text_check_param(conn);
break;
default:
goto init_err;
}
break;
default:
goto init_err;
}
if (rsp->status_class)
return;
if (conn->state != STATE_SECURITY_AUTH &&
req->flags & ISCSI_FLAG_LOGIN_TRANSIT) {
int nsg = ISCSI_LOGIN_NEXT_STAGE(req->flags);
switch (nsg) {
case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
switch (conn->state) {
case STATE_SECURITY:
case STATE_SECURITY_DONE:
conn->state = STATE_SECURITY_LOGIN;
login_security_done(conn);
break;
default:
goto init_err;
}
break;
case ISCSI_FULL_FEATURE_PHASE:
switch (conn->state) {
case STATE_SECURITY:
case STATE_SECURITY_DONE:
if ((nsg_disagree = text_check_param(conn))) {
conn->state = STATE_LOGIN;
nsg = ISCSI_OP_PARMS_NEGOTIATION_STAGE;
break;
}
conn->state = STATE_SECURITY_FULL;
login_security_done(conn);
break;
case STATE_LOGIN:
if (stay)
nsg = ISCSI_OP_PARMS_NEGOTIATION_STAGE;
else
conn->state = STATE_LOGIN_FULL;
break;
default:
goto init_err;
}
if (!stay && !nsg_disagree) {
login_finish(conn);
if (rsp->status_class)
return;
}
break;
default:
goto init_err;
}
rsp->flags |= nsg | (stay ? 0 : ISCSI_FLAG_LOGIN_TRANSIT);
}
memcpy(rsp->isid, conn->isid, sizeof(rsp->isid));
rsp->tsih = conn->tsih;
rsp->statsn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmdsn = cpu_to_be32(conn->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->max_cmd_sn);
return;
init_err:
rsp->flags = 0;
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_INIT_ERR;
conn->state = STATE_EXIT;
return;
auth_err:
rsp->flags = 0;
rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR;
rsp->status_detail = ISCSI_LOGIN_STATUS_AUTH_FAILED;
conn->state = STATE_EXIT;
return;
}
static void text_scan_text(struct iscsi_connection *conn)
{
char *key, *value, *data;
int datasize;
data = conn->req.data;
datasize = conn->req.datasize;
while ((key = next_key(&data, &datasize, &value))) {
if (!strcmp(key, "SendTargets")) {
struct sockaddr_storage ss;
socklen_t slen, blen;
char *p, buf[NI_MAXHOST + 128];
int ret, port;
if (value[0] == 0)
continue;
p = buf;
blen = sizeof(buf);
slen = sizeof(ss);
ret = conn->tp->ep_getsockname(conn,
(struct sockaddr *)&ss,
&slen);
if (ret) {
eprintf("getsockname failed\n");
continue;
}
if (ss.ss_family == AF_INET6) {
*p++ = '[';
blen--;
}
ret = getnameinfo((struct sockaddr *)&ss, slen, p, blen,
NULL, 0, NI_NUMERICHOST);
if (ret) {
eprintf("getnameinfo failed, %m\n");
continue;
}
/* strip zone id */
if (ss.ss_family == AF_INET6)
(void) strsep(&p, "%");
p = buf + strlen(buf);
if (ss.ss_family == AF_INET6)
*p++ = ']';
if (ss.ss_family == AF_INET6)
port = ntohs(((struct sockaddr_in6 *)
&ss)->sin6_port);
else
port = ntohs(((struct sockaddr_in *)
&ss)->sin_port);
sprintf(p, ":%d,1", port);
target_list_build(conn, buf,
strcmp(value, "All") ? value : NULL);
} else
text_key_add(conn, key, "NotUnderstood");
}
}
static void cmnd_exec_text(struct iscsi_connection *conn)
{
struct iscsi_text *req = (struct iscsi_text *)&conn->req.bhs;
struct iscsi_text_rsp *rsp = (struct iscsi_text_rsp *)&conn->rsp.bhs;
int max_len = conn->session_param[ISCSI_PARAM_MAX_XMIT_DLENGTH].val;
memset(rsp, 0, BHS_SIZE);
rsp->opcode = ISCSI_OP_TEXT_RSP;
rsp->itt = req->itt;
conn->exp_cmd_sn = be32_to_cpu(req->cmdsn);
if (!(req->opcode & ISCSI_OP_IMMEDIATE))
conn->exp_cmd_sn++;
if (be32_to_cpu(req->ttt) == ISCSI_RESERVED_TAG) {
conn->text_datasize = 0;
text_scan_text(conn);
conn->text_rsp_buffer = conn->rsp_buffer;
conn->text_datasize = conn->rsp.datasize;
if (conn->text_datasize > max_len) {
conn->ttt++;
if (conn->ttt == ISCSI_RESERVED_TAG)
conn->ttt++;
} else
conn->ttt = ISCSI_RESERVED_TAG;
} else if (!conn->text_datasize || conn->ttt != be32_to_cpu(req->ttt)) {
cmnd_reject(conn, ISCSI_REASON_INVALID_PDU_FIELD);
return;
}
if (conn->text_datasize <= max_len) {
rsp->flags = ISCSI_FLAG_CMD_FINAL;
conn->ttt = ISCSI_RESERVED_TAG;
}
conn->rsp.datasize = min(max_len, conn->text_datasize);
conn->rsp.data = conn->text_rsp_buffer;
conn->text_rsp_buffer += conn->rsp.datasize;
conn->text_datasize -= conn->rsp.datasize;
rsp->ttt = cpu_to_be32(conn->ttt);
rsp->statsn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmdsn = cpu_to_be32(conn->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->max_cmd_sn);
}
static void cmnd_exec_logout(struct iscsi_connection *conn)
{
struct iscsi_logout *req = (struct iscsi_logout *)&conn->req.bhs;
struct iscsi_logout_rsp *rsp = (struct iscsi_logout_rsp *)&conn->rsp.bhs;
memset(rsp, 0, BHS_SIZE);
rsp->opcode = ISCSI_OP_LOGOUT_RSP;
rsp->flags = ISCSI_FLAG_CMD_FINAL;
rsp->itt = req->itt;
conn->exp_cmd_sn = be32_to_cpu(req->cmdsn);
if (!(req->opcode & ISCSI_OP_IMMEDIATE))
conn->exp_cmd_sn++;
rsp->statsn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmdsn = cpu_to_be32(conn->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->max_cmd_sn);
}
static int cmnd_execute(struct iscsi_connection *conn)
{
int res = 0;
switch (conn->req.bhs.opcode & ISCSI_OPCODE_MASK) {
case ISCSI_OP_LOGIN:
cmnd_exec_login(conn);
conn->rsp.bhs.hlength = conn->rsp.ahssize / 4;
hton24(conn->rsp.bhs.dlength, conn->rsp.datasize);
break;
case ISCSI_OP_TEXT:
cmnd_exec_text(conn);
conn->rsp.bhs.hlength = conn->rsp.ahssize / 4;
hton24(conn->rsp.bhs.dlength, conn->rsp.datasize);
break;
case ISCSI_OP_LOGOUT:
cmnd_exec_logout(conn);
conn->rsp.bhs.hlength = conn->rsp.ahssize / 4;
hton24(conn->rsp.bhs.dlength, conn->rsp.datasize);
break;
default:
cmnd_reject(conn, ISCSI_REASON_CMD_NOT_SUPPORTED);
res = 1;
break;
}
return res;
}
static void cmnd_finish(struct iscsi_connection *conn)
{
switch (conn->state) {
case STATE_EXIT:
conn->state = STATE_CLOSE;
break;
case STATE_SECURITY_LOGIN:
conn->state = STATE_LOGIN;
break;
case STATE_SECURITY_FULL:
/* fall through */
case STATE_LOGIN_FULL:
if (conn->session_type == SESSION_NORMAL)
conn->state = STATE_KERNEL;
else
conn->state = STATE_FULL;
break;
}
}
void iscsi_set_data_rsp_residual(struct iscsi_data_rsp *data_in, struct scsi_cmd *scmd)
{
int32_t resid = scsi_get_in_resid(scmd);
if (likely(!resid))
data_in->residual_count = 0;
else if (resid > 0) {
data_in->flags |= ISCSI_FLAG_CMD_UNDERFLOW;
data_in->residual_count = cpu_to_be32((uint32_t)resid);
} else {
data_in->flags |= ISCSI_FLAG_CMD_OVERFLOW;
data_in->residual_count = cpu_to_be32((uint32_t)-resid);
}
}
static inline void iscsi_rsp_set_resid(struct iscsi_cmd_rsp *rsp,
int32_t resid)
{
if (likely(!resid))
rsp->residual_count = 0;
else if (resid > 0) {
rsp->flags |= ISCSI_FLAG_CMD_UNDERFLOW;
rsp->residual_count = cpu_to_be32((uint32_t)resid);
} else {
rsp->flags |= ISCSI_FLAG_CMD_OVERFLOW;
rsp->residual_count = cpu_to_be32((uint32_t)-resid);
}
}
static inline void iscsi_rsp_set_bidir_resid(struct iscsi_cmd_rsp *rsp,
int32_t resid)
{
if (likely(!resid))
rsp->bi_residual_count = 0;
else if (resid > 0) {
rsp->flags |= ISCSI_FLAG_CMD_BIDI_UNDERFLOW;
rsp->bi_residual_count = cpu_to_be32((uint32_t)resid);
} else {
rsp->flags |= ISCSI_FLAG_CMD_BIDI_OVERFLOW;
rsp->bi_residual_count = cpu_to_be32((uint32_t)-resid);
}
}
void iscsi_rsp_set_residual(struct iscsi_cmd_rsp *rsp, struct scsi_cmd *scmd)
{
rsp->bi_residual_count = 0;
if (scsi_get_data_dir(scmd) == DATA_READ)
iscsi_rsp_set_resid(rsp, scsi_get_in_resid(scmd));
else if (scsi_get_data_dir(scmd) == DATA_WRITE)
iscsi_rsp_set_resid(rsp, scsi_get_out_resid(scmd));
else if (scsi_get_data_dir(scmd) == DATA_BIDIRECTIONAL) {
iscsi_rsp_set_bidir_resid(rsp, scsi_get_in_resid(scmd));
iscsi_rsp_set_resid(rsp, scsi_get_out_resid(scmd));
} else
rsp->residual_count = 0;
}
struct iscsi_sense_data {
uint16_t length;
uint8_t data[0];
} __attribute__((__packed__));
static int iscsi_cmd_rsp_build(struct iscsi_task *task)
{
struct iscsi_connection *conn = task->conn;
struct iscsi_cmd_rsp *rsp = (struct iscsi_cmd_rsp *) &conn->rsp.bhs;
struct iscsi_sense_data *sense;
unsigned char sense_len;
memset(rsp, 0, sizeof(*rsp));
rsp->opcode = ISCSI_OP_SCSI_CMD_RSP;
rsp->itt = task->tag;
rsp->flags = ISCSI_FLAG_CMD_FINAL;
rsp->response = ISCSI_STATUS_CMD_COMPLETED;
rsp->cmd_status = scsi_get_result(&task->scmd);
rsp->statsn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn +
conn->session->max_queue_cmd);
iscsi_rsp_set_residual(rsp, &task->scmd);
sense_len = task->scmd.sense_len;
if (sense_len) {
sense = (struct iscsi_sense_data *)task->scmd.sense_buffer;
memmove(sense->data, sense, sense_len);
sense->length = cpu_to_be16(sense_len);
conn->rsp.datasize = sense_len + sizeof(*sense);
hton24(rsp->dlength, sense_len + sizeof(*sense));
conn->rsp.data = sense;
}
return 0;
}
static int iscsi_data_rsp_build(struct iscsi_task *task)
{
struct iscsi_connection *conn = task->conn;
struct iscsi_data_rsp *rsp = (struct iscsi_data_rsp *) &conn->rsp.bhs;
int datalen, maxdatalen;
int result = scsi_get_result(&task->scmd);
memset(rsp, 0, sizeof(*rsp));
rsp->opcode = ISCSI_OP_SCSI_DATA_IN;
rsp->itt = task->tag;
rsp->ttt = cpu_to_be32(ISCSI_RESERVED_TAG);
rsp->offset = cpu_to_be32(task->offset);
rsp->datasn = cpu_to_be32(task->exp_r2tsn++);
datalen = scsi_get_in_transfer_len(&task->scmd) - task->offset;
maxdatalen = conn->tp->rdma ?
conn->session_param[ISCSI_PARAM_MAX_BURST].val :
conn->session_param[ISCSI_PARAM_MAX_XMIT_DLENGTH].val;
dprintf("%d %d %d %" PRIu32 "%x\n", datalen,
scsi_get_in_transfer_len(&task->scmd), task->offset, maxdatalen,
rsp->itt);
if (datalen <= maxdatalen) {
rsp->flags = ISCSI_FLAG_CMD_FINAL;
/* collapse status into final packet if successful */
if (result == SAM_STAT_GOOD &&
scsi_get_data_dir(&task->scmd) != DATA_BIDIRECTIONAL &&
!conn->tp->rdma) {
rsp->flags |= ISCSI_FLAG_DATA_STATUS;
rsp->cmd_status = result;
rsp->statsn = cpu_to_be32(conn->stat_sn++);
iscsi_set_data_rsp_residual(rsp, &task->scmd);
}
} else
datalen = maxdatalen;
rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn +
conn->session->max_queue_cmd);
conn->rsp.datasize = datalen;
hton24(rsp->dlength, datalen);
conn->rsp.data = scsi_get_in_buffer(&task->scmd);
conn->rsp.data += task->offset;
task->offset += datalen;
return 0;
}
static int iscsi_r2t_build(struct iscsi_task *task)
{
struct iscsi_connection *conn = task->conn;
struct iscsi_r2t_rsp *rsp = (struct iscsi_r2t_rsp *) &conn->rsp.bhs;
uint32_t length;
memset(rsp, 0, sizeof(*rsp));
rsp->opcode = ISCSI_OP_R2T;
rsp->flags = ISCSI_FLAG_CMD_FINAL;
memcpy(rsp->lun, task->req.lun, sizeof(rsp->lun));
rsp->itt = task->req.itt;
rsp->r2tsn = cpu_to_be32(task->exp_r2tsn++);
rsp->data_offset = cpu_to_be32(task->offset);
/* return next statsn for this conn w/o advancing it */
rsp->statsn = cpu_to_be32(conn->stat_sn);
rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn +
conn->session->max_queue_cmd);
rsp->ttt = (unsigned long) task;
length = min_t(uint32_t, task->r2t_count,
conn->session_param[ISCSI_PARAM_MAX_BURST].val);
rsp->data_length = cpu_to_be32(length);
return 0;
}
static struct iscsi_task *iscsi_alloc_task(struct iscsi_connection *conn,
int ext_len, int data_len)
{
struct iscsi_hdr *req = (struct iscsi_hdr *) &conn->req.bhs;
struct iscsi_task *task;
void *buf;
task = conn->tp->alloc_task(conn, ext_len);
if (!task)
return NULL;
if (data_len) {
buf = conn->tp->alloc_data_buf(conn, data_len);
if (!buf) {
conn->tp->free_task(task);
return NULL;
}
task->data = buf;
}
memcpy(&task->req, req, sizeof(*req));
task->conn = conn;
INIT_LIST_HEAD(&task->c_hlist);
INIT_LIST_HEAD(&task->c_list);
list_add(&task->c_siblings, &conn->task_list);
conn_get(conn);
return task;
}
void iscsi_free_task(struct iscsi_task *task)
{
struct iscsi_connection *conn = task->conn;
list_del(&task->c_siblings);
if (task_opcode(task) == ISCSI_OP_SCSI_CMD)
list_del(&task->c_hlist);
conn->tp->free_data_buf(conn, scsi_get_in_buffer(&task->scmd));
conn->tp->free_data_buf(conn, scsi_get_out_buffer(&task->scmd));
/*
* If freeing task before in/out buffers are set, make sure to free
* task->data or it leaks.
*/
if ((task->data != scsi_get_in_buffer(&task->scmd)) &&
(task->data != scsi_get_out_buffer(&task->scmd)))
conn->tp->free_data_buf(conn, task->data);
conn->tp->free_task(task);
conn_put(conn);
}
static inline struct iscsi_task *ITASK(struct scsi_cmd *scmd)
{
return container_of(scmd, struct iscsi_task, scmd);
}
void iscsi_free_cmd_task(struct iscsi_task *task)
{
target_cmd_done(&task->scmd);
iscsi_free_task(task);
}
static int iscsi_scsi_cmd_done(uint64_t nid, int result, struct scsi_cmd *scmd)
{
struct iscsi_task *task = ITASK(scmd);
/*
* Since the connection is closed we just free the task.
* We could delay the closing of the conn in some cases and send
* the response with a little extra code or we can check if this
* task got reassinged to another connection.
*/
if (task->conn->state == STATE_CLOSE) {
iscsi_free_cmd_task(task);
return 0;
}
list_add_tail(&task->c_list, &task->conn->tx_clist);
task->conn->tp->ep_event_modify(task->conn, EPOLLIN | EPOLLOUT);
return 0;
}
static int cmd_attr(struct iscsi_task *task)
{
int attr;
struct iscsi_cmd *req = (struct iscsi_cmd *) &task->req;
switch (req->flags & ISCSI_FLAG_CMD_ATTR_MASK) {
case ISCSI_ATTR_UNTAGGED:
case ISCSI_ATTR_SIMPLE:
attr = MSG_SIMPLE_TAG;
break;
case ISCSI_ATTR_HEAD_OF_QUEUE:
attr = MSG_HEAD_TAG;
break;
case ISCSI_ATTR_ORDERED:
default:
attr = MSG_ORDERED_TAG;
}
return attr;
}
static int iscsi_target_cmd_queue(struct iscsi_task *task)
{
struct scsi_cmd *scmd = &task->scmd;
struct iscsi_connection *conn = task->conn;
struct iscsi_cmd *req = (struct iscsi_cmd *) &task->req;
uint32_t data_len;
uint8_t *ahs;
int ahslen;
int err;
enum data_direction dir = scsi_get_data_dir(scmd);
scmd->cmd_itn_id = conn->session->tsih;
scmd->scb = req->cdb;
scmd->scb_len = sizeof(req->cdb);
ahs = task->ahs;
ahslen = req->hlength * 4;
if (ahslen >= 4) {
struct iscsi_ecdb_ahdr *ahs_extcdb = (void *) ahs;
if (ahs_extcdb->ahstype == ISCSI_AHSTYPE_CDB) {
int extcdb_len = ntohs(ahs_extcdb->ahslength) - 1;
unsigned char *p = (void *)task->extdata;
if (4 + extcdb_len > ahslen) {
eprintf("AHS len %d too short for extcdb %d\n",
ahslen, extcdb_len);
return -EINVAL;
}
if (extcdb_len + sizeof(req->cdb) > 260) {
eprintf("invalid extcdb len %d\n", extcdb_len);
return -EINVAL;
}
memcpy(p, req->cdb, sizeof(req->cdb));
memmove(p + sizeof(req->cdb), ahs_extcdb->ecdb,
extcdb_len);
scmd->scb = p;
scmd->scb_len = sizeof(req->cdb) + extcdb_len;
ahs += 4 + extcdb_len;
ahslen -= 4 + extcdb_len;
}
}
data_len = ntohl(req->data_length);
/* figure out incoming (write) and outgoing (read) sizes */
if (dir == DATA_WRITE || dir == DATA_BIDIRECTIONAL) {
scsi_set_out_length(scmd, data_len);
scsi_set_out_buffer(scmd, task->data);
} else if (dir == DATA_READ) {
scsi_set_in_length(scmd, data_len);
scsi_set_in_buffer(scmd, task->data);
}
if (dir == DATA_BIDIRECTIONAL && ahslen >= 8) {
struct iscsi_rlength_ahdr *ahs_bidi = (void *) ahs;
if (ahs_bidi->ahstype == ISCSI_AHSTYPE_RLENGTH) {
uint32_t in_length = ntohl(ahs_bidi->read_length);
dprintf("bidi read len %u\n", in_length);
if (in_length) {
uint32_t len;
void *buf;
len = roundup(in_length,
conn->tp->data_padding);
buf = conn->tp->alloc_data_buf(conn, len);
if (!buf)
return -ENOMEM;
scsi_set_in_buffer(scmd, buf);
scsi_set_in_length(scmd, in_length);
}
}
}
memcpy(scmd->lun, task->req.lun, sizeof(scmd->lun));
scmd->attribute = cmd_attr(task);
scmd->tag = req->itt;
set_task_in_scsi(task);
err = target_cmd_queue(conn->session->target->tid, scmd);
if (err)
clear_task_in_scsi(task);
return err;
}
int iscsi_scsi_cmd_execute(struct iscsi_task *task)
{
struct iscsi_connection *conn = task->conn;
struct iscsi_cmd *req = (struct iscsi_cmd *) &task->req;
int ret = 0;
if ((req->flags & ISCSI_FLAG_CMD_WRITE) && task->r2t_count) {
if (!task->unsol_count)
list_add_tail(&task->c_list, &task->conn->tx_clist);
goto no_queuing;
}
task->offset = 0; /* for use as transmit pointer for data-ins */
ret = iscsi_target_cmd_queue(task);
no_queuing:
conn->tp->ep_event_modify(conn, EPOLLIN | EPOLLOUT);
return ret;
}
static int iscsi_tm_done(struct mgmt_req *mreq)
{
struct iscsi_task *task;
task = (struct iscsi_task *) (unsigned long) mreq->mid;
switch (mreq->result) {
case 0:
task->result = ISCSI_TMF_RSP_COMPLETE;
break;
case -EINVAL:
task->result = ISCSI_TMF_RSP_NOT_SUPPORTED;
break;
case -EEXIST:
/*
* the command completed or we could not find it so
* we retrun no task here
*/
task->result = ISCSI_TMF_RSP_NO_TASK;
break;
default:
task->result = ISCSI_TMF_RSP_REJECTED;
break;
}
if (task->conn->state == STATE_CLOSE) {
iscsi_free_task(task);
return 0;
}
list_add_tail(&task->c_list, &task->conn->tx_clist);
task->conn->tp->ep_event_modify(task->conn, EPOLLIN | EPOLLOUT);
return 0;
}
static int iscsi_tm_execute(struct iscsi_task *task)
{
struct iscsi_connection *conn = task->conn;
struct iscsi_tm *req = (struct iscsi_tm *) &task->req;
int fn = 0, err = 0;
switch (req->flags & ISCSI_FLAG_TM_FUNC_MASK) {
case ISCSI_TM_FUNC_ABORT_TASK:
fn = ABORT_TASK;
break;
case ISCSI_TM_FUNC_ABORT_TASK_SET:
fn = ABORT_TASK_SET;
break;
case ISCSI_TM_FUNC_CLEAR_ACA:
err = ISCSI_TMF_RSP_NOT_SUPPORTED;
break;
case ISCSI_TM_FUNC_CLEAR_TASK_SET:
err = ISCSI_TMF_RSP_NOT_SUPPORTED;
break;
case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET:
fn = LOGICAL_UNIT_RESET;
break;
case ISCSI_TM_FUNC_TARGET_WARM_RESET:
case ISCSI_TM_FUNC_TARGET_COLD_RESET:
case ISCSI_TM_FUNC_TASK_REASSIGN:
err = ISCSI_TMF_RSP_NOT_SUPPORTED;
break;
default:
err = ISCSI_TMF_RSP_REJECTED;
eprintf("unknown task management function %d\n",
req->flags & ISCSI_FLAG_TM_FUNC_MASK);
}
if (err)
task->result = err;
else {
int ret;
ret = target_mgmt_request(conn->session->target->tid,
conn->session->tsih,
(unsigned long)task, fn, req->lun,
req->rtt, 0);
set_task_in_scsi(task);
switch (ret) {
case MGMT_REQ_QUEUED:
break;
case MGMT_REQ_FAILED:
case MGMT_REQ_DONE:
clear_task_in_scsi(task);
break;
}
}
return err;
}
static int iscsi_task_execute(struct iscsi_task *task)
{
struct iscsi_hdr *hdr = (struct iscsi_hdr *) &task->req;
uint8_t op = hdr->opcode & ISCSI_OPCODE_MASK;
int err;
switch (op) {
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_LOGOUT:
list_add_tail(&task->c_list, &task->conn->tx_clist);
task->conn->tp->ep_event_modify(task->conn, EPOLLIN | EPOLLOUT);
break;
case ISCSI_OP_SCSI_CMD:
/* convenient directionality for our internal use */
if (hdr->flags & ISCSI_FLAG_CMD_READ) {
if (hdr->flags & ISCSI_FLAG_CMD_WRITE)
scsi_set_data_dir(&task->scmd, DATA_BIDIRECTIONAL);
else
scsi_set_data_dir(&task->scmd, DATA_READ);
} else if (hdr->flags & ISCSI_FLAG_CMD_WRITE) {
scsi_set_data_dir(&task->scmd, DATA_WRITE);
} else
scsi_set_data_dir(&task->scmd, DATA_NONE);
err = iscsi_scsi_cmd_execute(task);
break;
case ISCSI_OP_SCSI_TMFUNC:
err = iscsi_tm_execute(task);
if (err) {
list_add_tail(&task->c_list, &task->conn->tx_clist);
task->conn->tp->ep_event_modify(task->conn,
EPOLLIN | EPOLLOUT);
}
break;
case ISCSI_OP_TEXT:
case ISCSI_OP_SNACK:
break;
default:
break;
}
return 0;
}
static int iscsi_data_out_rx_done(struct iscsi_task *task)
{
struct iscsi_hdr *hdr = &task->conn->req.bhs;
int err = 0;
if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
if (hdr->flags & ISCSI_FLAG_CMD_FINAL) {
task->unsol_count = 0;
if (!task_pending(task))
err = iscsi_scsi_cmd_execute(task);
}
} else {
if (!(hdr->flags & ISCSI_FLAG_CMD_FINAL))
return err;
err = iscsi_scsi_cmd_execute(task);
}
return err;
}
static int iscsi_data_out_rx_start(struct iscsi_connection *conn)
{
struct iscsi_task *task;
struct iscsi_data *req = (struct iscsi_data *) &conn->req.bhs;
list_for_each_entry(task, &conn->session->cmd_list, c_hlist) {
if (task->tag == req->itt)
goto found;
}
return -EINVAL;
found:
dprintf("found a task %" PRIx64 " %u %u %u %u %u\n", task->tag,
ntohl(((struct iscsi_cmd *) (&task->req))->data_length),
task->offset,
task->r2t_count,
ntoh24(req->dlength), be32_to_cpu(req->offset));
conn->req.data = task->data + be32_to_cpu(req->offset);
task->offset += ntoh24(req->dlength);
task->r2t_count -= ntoh24(req->dlength);
conn->rx_task = task;
return 0;
}
static int iscsi_task_queue(struct iscsi_task *task)
{
struct iscsi_session *session = task->conn->session;
struct iscsi_hdr *req = (struct iscsi_hdr *) &task->req;
uint32_t cmd_sn;
struct iscsi_task *ent;
dprintf("%x %x %x\n", be32_to_cpu(req->statsn), session->exp_cmd_sn,
req->opcode);
if (req->opcode & ISCSI_OP_IMMEDIATE)
return iscsi_task_execute(task);
cmd_sn = be32_to_cpu(req->statsn);
if (cmd_sn == session->exp_cmd_sn) {
retry:
session->exp_cmd_sn = ++cmd_sn;
/* Should we close the connection... */
iscsi_task_execute(task);
if (list_empty(&session->pending_cmd_list))
return 0;
task = list_first_entry(&session->pending_cmd_list,
struct iscsi_task, c_list);
if (be32_to_cpu(task->req.statsn) != cmd_sn)
return 0;
list_del(&task->c_list);
clear_task_pending(task);
goto retry;
} else {
if (before(cmd_sn, session->exp_cmd_sn)) {
eprintf("unexpected cmd_sn (%u,%u)\n",
cmd_sn, session->exp_cmd_sn);
return -EINVAL;
}
/* TODO: check max cmd_sn */
list_for_each_entry(ent, &session->pending_cmd_list, c_list) {
if (before(cmd_sn, be32_to_cpu(ent->req.statsn)))
break;
}
list_add_tail(&task->c_list, &ent->c_list);
set_task_pending(task);
}
return 0;
}
static int iscsi_scsi_cmd_rx_start(struct iscsi_connection *conn)
{
struct iscsi_cmd *req = (struct iscsi_cmd *) &conn->req.bhs;
struct iscsi_task *task;
int ahs_len, imm_len, data_len, ext_len;
ahs_len = req->hlength * 4;
imm_len = roundup(ntoh24(req->dlength), conn->tp->data_padding);
data_len = roundup(ntohl(req->data_length), conn->tp->data_padding);
dprintf("%u %x %d %d %d %x %x\n", conn->session->tsih,
req->cdb[0], ahs_len, imm_len, data_len,
req->flags & ISCSI_FLAG_CMD_ATTR_MASK, req->itt);
ext_len = ahs_len ? sizeof(req->cdb) + ahs_len : 0;
task = iscsi_alloc_task(conn, ext_len, max(imm_len, data_len));
if (task)
conn->rx_task = task;
else
return -ENOMEM;
task->tag = req->itt;
if (ahs_len) {
task->ahs = (uint8_t *) task->extdata + sizeof(req->cdb);
conn->req.ahs = task->ahs;
conn->req.data = task->data;
} else if (data_len)
conn->req.data = task->data;
if (req->flags & ISCSI_FLAG_CMD_WRITE) {
task->offset = ntoh24(req->dlength);
task->r2t_count = ntohl(req->data_length) - task->offset;
task->unsol_count = !(req->flags & ISCSI_FLAG_CMD_FINAL);
dprintf("%d %d %d %d\n", conn->rx_size, task->r2t_count,
task->unsol_count, task->offset);
}
list_add(&task->c_hlist, &conn->session->cmd_list);
return 0;
}
static int iscsi_noop_out_rx_start(struct iscsi_connection *conn)
{
struct iscsi_hdr *req = (struct iscsi_hdr *) &conn->req.bhs;
struct iscsi_task *task;
int len, err = 0;
dprintf("%x %x %u\n", req->ttt, req->itt, ntoh24(req->dlength));
if (req->ttt != cpu_to_be32(ISCSI_RESERVED_TAG)) {
if ((req->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_NOOP_OUT) {
goto good;
}
}
if (req->itt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
if (!(req->opcode & ISCSI_OP_IMMEDIATE)) {
eprintf("initiator bug\n");
err = -ISCSI_REASON_PROTOCOL_ERROR;
goto out;
}
}
good:
conn->exp_stat_sn = be32_to_cpu(req->exp_statsn);
len = ntoh24(req->dlength);
task = iscsi_alloc_task(conn, 0, len);
if (task)
conn->rx_task = task;
else {
err = -ENOMEM;
goto out;
}
if (len) {
task->len = len;
conn->req.data = task->data;
}
out:
return err;
}
static int iscsi_task_rx_done(struct iscsi_connection *conn)
{
struct iscsi_hdr *hdr = &conn->req.bhs;
struct iscsi_task *task = conn->rx_task;
uint8_t op;
int err = 0;
op = hdr->opcode & ISCSI_OPCODE_MASK;
switch (op) {
case ISCSI_OP_SCSI_CMD:
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_SCSI_TMFUNC:
case ISCSI_OP_LOGOUT:
err = iscsi_task_queue(task);
break;
case ISCSI_OP_SCSI_DATA_OUT:
err = iscsi_data_out_rx_done(task);
break;
case ISCSI_OP_TEXT:
case ISCSI_OP_SNACK:
default:
eprintf("Cannot handle yet %x\n", op);
break;
}
conn->rx_task = NULL;
return err;
}
static int iscsi_task_rx_start(struct iscsi_connection *conn)
{
struct iscsi_hdr *hdr = &conn->req.bhs;
struct iscsi_task *task;
uint8_t op;
int err = 0;
op = hdr->opcode & ISCSI_OPCODE_MASK;
switch (op) {
case ISCSI_OP_SCSI_CMD:
err = iscsi_scsi_cmd_rx_start(conn);
if (!err)
conn->exp_stat_sn = be32_to_cpu(hdr->exp_statsn);
break;
case ISCSI_OP_SCSI_DATA_OUT:
err = iscsi_data_out_rx_start(conn);
if (!err)
conn->exp_stat_sn = be32_to_cpu(hdr->exp_statsn);
break;
case ISCSI_OP_NOOP_OUT:
err = iscsi_noop_out_rx_start(conn);
break;
case ISCSI_OP_SCSI_TMFUNC:
case ISCSI_OP_LOGOUT:
task = iscsi_alloc_task(conn, 0, 0);
if (task)
conn->rx_task = task;
else
err = -ENOMEM;
break;
case ISCSI_OP_TEXT:
case ISCSI_OP_SNACK:
eprintf("Cannot handle yet %x\n", op);
err = -EINVAL;
break;
default:
eprintf("Unknown op %x\n", op);
err = -EINVAL;
break;
}
return err;
}
static int iscsi_scsi_cmd_tx_start(struct iscsi_task *task)
{
enum data_direction data_dir = scsi_get_data_dir(&task->scmd);
int err = 0;
switch (data_dir) {
case DATA_NONE:
err = iscsi_cmd_rsp_build(task);
break;
case DATA_READ:
if (task->offset < scsi_get_in_transfer_len(&task->scmd))
err = iscsi_data_rsp_build(task);
else
err = iscsi_cmd_rsp_build(task);
break;
case DATA_WRITE:
if (task->r2t_count)
err = iscsi_r2t_build(task);
else
err = iscsi_cmd_rsp_build(task);
break;
case DATA_BIDIRECTIONAL:
if (task->r2t_count)
err = iscsi_r2t_build(task);
else if (task->offset < scsi_get_in_transfer_len(&task->scmd))
err = iscsi_data_rsp_build(task);
else
err = iscsi_cmd_rsp_build(task);
break;
default:
eprintf("Unexpected data_dir %d task %p\n", data_dir, task);
exit(-1);
}
return err;
}
static int iscsi_logout_tx_start(struct iscsi_task *task)
{
struct iscsi_connection *conn = task->conn;
struct iscsi_logout_rsp *rsp =
(struct iscsi_logout_rsp *) &conn->rsp.bhs;
rsp->opcode = ISCSI_OP_LOGOUT_RSP;
rsp->flags = ISCSI_FLAG_CMD_FINAL;
rsp->itt = task->req.itt;
rsp->statsn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn +
conn->session->max_queue_cmd);
return 0;
}
static int iscsi_noop_in_tx_start(struct iscsi_task *task)
{
struct iscsi_connection *conn = task->conn;
struct iscsi_data_rsp *rsp = (struct iscsi_data_rsp *) &conn->rsp.bhs;
memset(rsp, 0, sizeof(*rsp));
rsp->opcode = ISCSI_OP_NOOP_IN;
rsp->flags = ISCSI_FLAG_CMD_FINAL;
rsp->itt = task->req.itt;
rsp->ttt = task->req.ttt;
rsp->statsn = cpu_to_be32(conn->stat_sn);
rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn +
conn->session->max_queue_cmd);
/* TODO: honor max_burst */
conn->rsp.datasize = task->len;
hton24(rsp->dlength, task->len);
conn->rsp.data = task->data;
return 0;
}
static int iscsi_noop_out_tx_start(struct iscsi_task *task, int *is_rsp)
{
struct iscsi_connection *conn = task->conn;
struct iscsi_data_rsp *rsp = (struct iscsi_data_rsp *) &conn->rsp.bhs;
if (task->req.itt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
*is_rsp = 0;
if (conn->tp->ep_nop_reply)
conn->tp->ep_nop_reply(be32_to_cpu(task->req.ttt));
iscsi_free_task(task);
} else {
*is_rsp = 1;
memset(rsp, 0, sizeof(*rsp));
rsp->opcode = ISCSI_OP_NOOP_IN;
rsp->flags = ISCSI_FLAG_CMD_FINAL;
rsp->itt = task->req.itt;
rsp->ttt = cpu_to_be32(ISCSI_RESERVED_TAG);
rsp->statsn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn +
conn->session->max_queue_cmd);
/* TODO: honor max_burst */
conn->rsp.datasize = task->len;
hton24(rsp->dlength, task->len);
conn->rsp.data = task->data;
}
return 0;
}
static int iscsi_tm_tx_start(struct iscsi_task *task)
{
struct iscsi_connection *conn = task->conn;
struct iscsi_tm_rsp *rsp = (struct iscsi_tm_rsp *) &conn->rsp.bhs;
memset(rsp, 0, sizeof(*rsp));
rsp->opcode = ISCSI_OP_SCSI_TMFUNC_RSP;
rsp->flags = ISCSI_FLAG_CMD_FINAL;
rsp->itt = task->req.itt;
rsp->response = task->result;
rsp->statsn = cpu_to_be32(conn->stat_sn++);
rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn);
rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn +
conn->session->max_queue_cmd);
return 0;
}
static int iscsi_scsi_cmd_tx_done(struct iscsi_connection *conn)
{
struct iscsi_hdr *hdr = &conn->rsp.bhs;
struct iscsi_task *task = conn->tx_task;
switch (hdr->opcode & ISCSI_OPCODE_MASK) {
case ISCSI_OP_R2T:
break;
case ISCSI_OP_SCSI_DATA_IN:
if (task->offset < scsi_get_in_transfer_len(&task->scmd) ||
scsi_get_result(&task->scmd) != SAM_STAT_GOOD ||
scsi_get_data_dir(&task->scmd) == DATA_BIDIRECTIONAL) {
dprintf("more data or sense or bidir %x\n", hdr->itt);
list_add(&task->c_list, &task->conn->tx_clist);
return 0;
}
case ISCSI_OP_SCSI_CMD_RSP:
iscsi_free_cmd_task(task);
break;
default:
eprintf("target bug %x\n", hdr->opcode & ISCSI_OPCODE_MASK);
}
return 0;
}
static int iscsi_task_tx_done(struct iscsi_connection *conn)
{
struct iscsi_task *task = conn->tx_task;
uint8_t op;
op = task->req.opcode & ISCSI_OPCODE_MASK;
switch (op) {
case ISCSI_OP_SCSI_CMD:
iscsi_scsi_cmd_tx_done(conn);
break;
case ISCSI_OP_NOOP_IN:
/* NOOP_IN req is allocated within iscsi_tcp
* by a direct call to the transport
* allocation routine, unaccounted in the
* connection refcount and not added to
* task_list, hence it should be freed when
* it's done by a similar direct call.
*
* We're overprotective here by checking tp's
* free_task pointer, avoiding interference
* with iser (I'm unsure if it's relevant
* though).
*/
if (task->conn->tp->free_task)
task->conn->tp->free_task(task);
break;
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_LOGOUT:
case ISCSI_OP_SCSI_TMFUNC:
iscsi_free_task(task);
if (op == ISCSI_OP_LOGOUT)
conn->state = STATE_CLOSE;
}
conn->tx_task = NULL;
return 0;
}
static int iscsi_task_tx_start(struct iscsi_connection *conn)
{
struct iscsi_task *task;
int is_rsp, err = 0;
if (list_empty(&conn->tx_clist))
goto nodata;
conn_write_pdu(conn);
task = list_first_entry(&conn->tx_clist, struct iscsi_task, c_list);
dprintf("found a task %" PRIx64 " %u %u %u\n", task->tag,
ntohl(((struct iscsi_cmd *) (&task->req))->data_length),
task->offset,
task->r2t_count);
list_del(&task->c_list);
switch (task->req.opcode & ISCSI_OPCODE_MASK) {
case ISCSI_OP_SCSI_CMD:
err = iscsi_scsi_cmd_tx_start(task);
break;
case ISCSI_OP_NOOP_IN:
err = iscsi_noop_in_tx_start(task);
break;
case ISCSI_OP_NOOP_OUT:
err = iscsi_noop_out_tx_start(task, &is_rsp);
if (!is_rsp)
goto nodata;
break;
case ISCSI_OP_LOGOUT:
err = iscsi_logout_tx_start(task);
break;
case ISCSI_OP_SCSI_TMFUNC:
err = iscsi_tm_tx_start(task);
break;
}
conn->tx_task = task;
return err;
nodata:
dprintf("no more data\n");
conn->tp->ep_event_modify(conn, EPOLLIN);
return -EAGAIN;
}
static int do_recv(struct iscsi_connection *conn, int next_state)
{
int ret, opcode;
ret = conn->tp->ep_read(conn, conn->rx_buffer, conn->rx_size);
if (!ret) {
conn->state = STATE_CLOSE;
return 0;
} else if (ret < 0) {
if (errno == EINTR || errno == EAGAIN)
return 0;
else
return -EIO;
}
conn->rx_size -= ret;
conn->rx_buffer += ret;
opcode = (conn->rx_iostate == IOSTATE_RX_BHS) ?
(conn->req.bhs.opcode & ISCSI_OPCODE_MASK) : -1;
iscsi_update_conn_stats_rx(conn, ret, opcode);
if (!conn->rx_size)
conn->rx_iostate = next_state;
return ret;
}
void iscsi_rx_handler(struct iscsi_connection *conn)
{
int ret = 0, hdigest, ddigest;
uint32_t crc;
if (conn->state == STATE_SCSI) {
struct param *p = conn->session_param;
hdigest = p[ISCSI_PARAM_HDRDGST_EN].val & DIGEST_CRC32C;
ddigest = p[ISCSI_PARAM_DATADGST_EN].val & DIGEST_CRC32C;
} else
hdigest = ddigest = 0;
again:
switch (conn->rx_iostate) {
case IOSTATE_RX_BHS:
ret = do_recv(conn, IOSTATE_RX_INIT_AHS);
if (ret <= 0 || conn->rx_iostate != IOSTATE_RX_INIT_AHS)
break;
case IOSTATE_RX_INIT_AHS:
if (conn->state == STATE_SCSI) {
ret = iscsi_task_rx_start(conn);
if (ret) {
conn->state = STATE_CLOSE;
break;
}
} else {
conn->rx_buffer = conn->req_buffer;
conn->req.ahs = conn->rx_buffer;
conn->req.data = conn->rx_buffer
+ conn->req.bhs.hlength * 4;
}
conn->req.ahssize = conn->req.bhs.hlength * 4;
conn->req.datasize = ntoh24(conn->req.bhs.dlength);
conn->rx_size = conn->req.ahssize;
if (conn->state != STATE_SCSI &&
conn->req.ahssize > INCOMING_BUFSIZE) {
conn->state = STATE_CLOSE;
return;
}
if (conn->rx_size) {
conn->rx_buffer = conn->req.ahs;
conn->rx_iostate = IOSTATE_RX_AHS;
} else
conn->rx_iostate = hdigest ?
IOSTATE_RX_INIT_HDIGEST : IOSTATE_RX_INIT_DATA;
/*
* if the datasize is zero, we must go to
* IOSTATE_RX_END via IOSTATE_RX_INIT_DATA now. Note
* iscsi_rx_handler will not called since tgtd doesn't
* have data to read.
*/
if (conn->rx_iostate == IOSTATE_RX_INIT_DATA)
goto again;
else if (conn->rx_iostate != IOSTATE_RX_AHS)
break;
case IOSTATE_RX_AHS:
ret = do_recv(conn, hdigest ?
IOSTATE_RX_INIT_HDIGEST : IOSTATE_RX_INIT_DATA);
if (ret <= 0)
break;
if (conn->rx_iostate == IOSTATE_RX_INIT_DATA)
goto again;
if (conn->rx_iostate != IOSTATE_RX_INIT_HDIGEST)
break;
case IOSTATE_RX_INIT_HDIGEST:
conn->rx_buffer = conn->rx_digest;
conn->rx_size = sizeof(conn->rx_digest);
conn->rx_iostate = IOSTATE_RX_HDIGEST;
case IOSTATE_RX_HDIGEST:
ret = do_recv(conn, IOSTATE_RX_CHECK_HDIGEST);
if (ret <= 0 || conn->rx_iostate != IOSTATE_RX_CHECK_HDIGEST)
break;
case IOSTATE_RX_CHECK_HDIGEST:
crc = ~0;
crc = crc32c(crc, &conn->req.bhs, BHS_SIZE);
if (conn->req.ahssize)
crc = crc32c(crc, conn->req.ahs, conn->req.ahssize);
crc = ~crc;
if (*((uint32_t *)conn->rx_digest) != crc) {
eprintf("rx hdr digest error 0x%x calc 0x%x\n",
*((uint32_t *)conn->rx_digest), crc);
conn->state = STATE_CLOSE;
}
conn->rx_iostate = IOSTATE_RX_INIT_DATA;
case IOSTATE_RX_INIT_DATA:
conn->rx_size = roundup(conn->req.datasize,
conn->tp->data_padding);
if (conn->rx_size) {
conn->rx_iostate = IOSTATE_RX_DATA;
conn->rx_buffer = conn->req.data;
if (conn->state != STATE_SCSI) {
if (conn->req.ahssize + conn->rx_size >
INCOMING_BUFSIZE) {
conn->state = STATE_CLOSE;
return;
}
}
} else {
conn->rx_iostate = IOSTATE_RX_END;
break;
}
case IOSTATE_RX_DATA:
ret = do_recv(conn, ddigest ?
IOSTATE_RX_INIT_DDIGEST : IOSTATE_RX_END);
if (ret <= 0 || conn->rx_iostate != IOSTATE_RX_INIT_DDIGEST)
break;
case IOSTATE_RX_INIT_DDIGEST:
conn->rx_buffer = conn->rx_digest;
conn->rx_size = sizeof(conn->rx_digest);
conn->rx_iostate = IOSTATE_RX_DDIGEST;
case IOSTATE_RX_DDIGEST:
ret = do_recv(conn, IOSTATE_RX_CHECK_DDIGEST);
if (ret <= 0 || conn->rx_iostate != IOSTATE_RX_CHECK_DDIGEST)
break;
case IOSTATE_RX_CHECK_DDIGEST:
crc = ~0;
crc = crc32c(crc, conn->req.data,
roundup(conn->req.datasize,
conn->tp->data_padding));
crc = ~crc;
conn->rx_iostate = IOSTATE_RX_END;
if (*((uint32_t *)conn->rx_digest) != crc) {
eprintf("rx hdr digest error 0x%x calc 0x%x\n",
*((uint32_t *)conn->rx_digest), crc);
conn->state = STATE_CLOSE;
}
break;
default:
eprintf("error %d %d\n", conn->state, conn->rx_iostate);
exit(1);
}
if (ret < 0 ||
conn->rx_iostate != IOSTATE_RX_END ||
conn->state == STATE_CLOSE)
return;
if (conn->rx_size) {
eprintf("error %d %d %d\n", conn->state, conn->rx_iostate,
conn->rx_size);
exit(1);
}
if (conn->state == STATE_SCSI) {
ret = iscsi_task_rx_done(conn);
if (ret)
conn->state = STATE_CLOSE;
else
conn_read_pdu(conn);
} else {
conn_write_pdu(conn);
conn->tp->ep_event_modify(conn, EPOLLOUT);
ret = cmnd_execute(conn);
if (ret)
conn->state = STATE_CLOSE;
}
}
static int do_send(struct iscsi_connection *conn, int next_state)
{
int ret, opcode;
again:
ret = conn->tp->ep_write_begin(conn, conn->tx_buffer, conn->tx_size);
if (ret < 0) {
if (errno != EINTR && errno != EAGAIN)
conn->state = STATE_CLOSE;
else if (errno == EINTR || errno == EAGAIN)
goto again;
return -EIO;
}
conn->tx_size -= ret;
conn->tx_buffer += ret;
opcode = (conn->tx_iostate == IOSTATE_TX_BHS) ?
(conn->req.bhs.opcode & ISCSI_OPCODE_MASK) : -1;
iscsi_update_conn_stats_tx(conn, ret, opcode);
if (conn->tx_size)
goto again;
conn->tx_iostate = next_state;
return 0;
}
int iscsi_tx_handler(struct iscsi_connection *conn)
{
int ret = 0, hdigest, ddigest;
uint32_t crc;
if (conn->state == STATE_SCSI) {
struct param *p = conn->session_param;
hdigest = p[ISCSI_PARAM_HDRDGST_EN].val & DIGEST_CRC32C;
ddigest = p[ISCSI_PARAM_DATADGST_EN].val & DIGEST_CRC32C;
} else
hdigest = ddigest = 0;
if (conn->state == STATE_SCSI && !conn->tx_task) {
ret = iscsi_task_tx_start(conn);
if (ret)
goto out;
}
/*
* For rdma, grab the data-in or r2t packet and covert to
* an RDMA operation.
*/
if (conn->tp->rdma && conn->state == STATE_SCSI) {
switch (conn->rsp.bhs.opcode) {
case ISCSI_OP_R2T:
ret = conn->tp->ep_rdma_read(conn);
if (ret < 0) /* wait for free slot */
goto out;
goto finish;
case ISCSI_OP_SCSI_DATA_IN:
ret = conn->tp->ep_rdma_write(conn);
if (ret < 0)
goto out;
goto finish;
default:
break;
}
}
again:
switch (conn->tx_iostate) {
case IOSTATE_TX_BHS:
ret = do_send(conn, IOSTATE_TX_INIT_AHS);
if (ret < 0)
break;
case IOSTATE_TX_INIT_AHS:
if (conn->rsp.ahssize) {
conn->tx_iostate = IOSTATE_TX_AHS;
conn->tx_buffer = conn->rsp.ahs;
conn->tx_size = conn->rsp.ahssize;
conn->tx_iostate = IOSTATE_TX_AHS;
} else
conn->tx_iostate = hdigest ?
IOSTATE_TX_INIT_HDIGEST : IOSTATE_TX_INIT_DATA;
if (conn->tx_iostate != IOSTATE_TX_AHS)
break;
case IOSTATE_TX_AHS:
conn->tx_iostate = hdigest ?
IOSTATE_TX_INIT_HDIGEST : IOSTATE_TX_INIT_DATA;
if (conn->tx_iostate != IOSTATE_TX_INIT_HDIGEST)
break;
case IOSTATE_TX_INIT_HDIGEST:
crc = ~0;
crc = crc32c(crc, &conn->rsp.bhs, BHS_SIZE);
*(uint32_t *)conn->tx_digest = ~crc;
conn->tx_iostate = IOSTATE_TX_HDIGEST;
conn->tx_buffer = conn->tx_digest;
conn->tx_size = sizeof(conn->tx_digest);
case IOSTATE_TX_HDIGEST:
ret = do_send(conn, IOSTATE_TX_INIT_DATA);
if (ret < 0)
break;
case IOSTATE_TX_INIT_DATA:
if (conn->rsp.datasize) {
int pad;
conn->tx_iostate = IOSTATE_TX_DATA;
conn->tx_buffer = conn->rsp.data;
conn->tx_size = conn->rsp.datasize;
pad = conn->tx_size & (conn->tp->data_padding - 1);
if (pad) {
pad = PAD_WORD_LEN - pad;
memset(conn->tx_buffer + conn->tx_size, 0, pad);
conn->tx_size += pad;
}
} else
conn->tx_iostate = IOSTATE_TX_END;
if (conn->tx_iostate != IOSTATE_TX_DATA)
break;
case IOSTATE_TX_DATA:
ret = do_send(conn, ddigest ?
IOSTATE_TX_INIT_DDIGEST : IOSTATE_TX_END);
if (ret < 0)
goto out;
if (conn->tx_iostate != IOSTATE_TX_INIT_DDIGEST)
break;
case IOSTATE_TX_INIT_DDIGEST:
crc = ~0;
crc = crc32c(crc, conn->rsp.data,
roundup(conn->rsp.datasize,
conn->tp->data_padding));
*(uint32_t *)conn->tx_digest = ~crc;
conn->tx_iostate = IOSTATE_TX_DDIGEST;
conn->tx_buffer = conn->tx_digest;
conn->tx_size = sizeof(conn->tx_digest);
case IOSTATE_TX_DDIGEST:
ret = do_send(conn, IOSTATE_TX_END);
break;
default:
eprintf("error %d %d\n", conn->state, conn->tx_iostate);
exit(1);
}
if (ret < 0 || conn->state == STATE_CLOSE)
goto out;
if (conn->tx_iostate != IOSTATE_TX_END) {
if (conn->tp->rdma)
goto again; /* avoid event loop, just push */
goto out;
}
if (conn->tx_size) {
eprintf("error %d %d %d\n", conn->state, conn->tx_iostate,
conn->tx_size);
exit(1);
}
conn->tp->ep_write_end(conn);
finish:
cmnd_finish(conn);
switch (conn->state) {
case STATE_KERNEL:
ret = conn_take_fd(conn);
if (ret)
conn->state = STATE_CLOSE;
else {
conn->state = STATE_SCSI;
conn_read_pdu(conn);
conn->tp->ep_event_modify(conn, EPOLLIN);
}
break;
case STATE_EXIT:
case STATE_CLOSE:
break;
case STATE_SCSI:
iscsi_task_tx_done(conn);
break;
default:
conn_read_pdu(conn);
conn->tp->ep_event_modify(conn, EPOLLIN);
break;
}
out:
return ret;
}
int iscsi_transportid(int tid, uint64_t itn_id, char *buf, int size)
{
struct iscsi_session *session;
char *p;
uint16_t len;
session = session_lookup_by_tsih(itn_id);
if (!session)
return 0;
len = 4;
len += strlen(session->initiator) + 1;
len += 5; /* separator */
len += 7; /* isid + '\0' */
len = ALIGN(len, 4);
if (len > size)
return len;
memset(buf, 0, size);
buf[0] = 0x05;
buf[0] |= 0x40;
put_unaligned_be16(len - 4, buf + 2);
sprintf(buf + 4, "%s", session->initiator);
p = buf + (4 + strlen(session->initiator) + 1);
p += sprintf(p, ",i,0x");
memcpy(p, session->isid, sizeof(session->isid));
return len;
}
int iscsi_param_parse_portals(char *p, int do_add,
int do_delete)
{
while (*p) {
if (!strncmp(p, "portal", 6)) {
char *addr, *q;
int len = 0, port = ISCSI_LISTEN_PORT;
addr = p + 7;
if (addr[0] == '[') {
addr++;
q = strchr(addr, ']');
if (!q) {
eprintf("malformed string when parsing "
"portal (%s). mismatched ipv6 "
"'[' ']'\n", p);
return -1;
}
q++;
len = q - addr -1;
if (*q != ':')
q = NULL;
} else
q = strchr(addr, ':');
if (q)
port = atoi(q + 1);
else
q = strchr(addr, ',');
if (!len) {
if (q)
len = q - addr;
else
len = strlen(addr);
}
if (len) {
char *tmp;
tmp = zalloc(len + 1);
memcpy(tmp, addr, len);
if (do_add && iscsi_add_portal(tmp,
port, 1)) {
free(tmp);
return -1;
}
if (do_delete && iscsi_delete_portal(tmp,
port)) {
free(tmp);
return -1;
}
}
} else if (!strncmp(p, "nop_interval", 12)) {
iscsi_set_nop_interval(atoi(p+13));
} else if (!strncmp(p, "nop_count", 9)) {
iscsi_set_nop_count(atoi(p+10));
}
p += strcspn(p, ",");
if (*p == ',')
++p;
}
return 0;
}
static int iscsi_param_parser(char *p)
{
portal_arguments = p;
return 0;
}
static int iscsi_portal_create(char *p)
{
return iscsi_param_parse_portals(p, 1, 0);
}
static int iscsi_portal_destroy(char *p)
{
return iscsi_param_parse_portals(p, 0, 1);
}
static struct tgt_driver iscsi = {
.name = "iscsi",
.init = iscsi_init,
.exit = iscsi_exit,
.target_create = iscsi_target_create,
.target_destroy = iscsi_target_destroy,
.portal_create = iscsi_portal_create,
.portal_destroy = iscsi_portal_destroy,
.update = iscsi_target_update,
.show = iscsi_target_show,
.stat = iscsi_stat,
.cmd_end_notify = iscsi_scsi_cmd_done,
.mgmt_end_notify = iscsi_tm_done,
.transportid = iscsi_transportid,
.default_bst = "rdwr",
};
__attribute__((constructor)) static void iscsi_driver_constructor(void)
{
register_driver(&iscsi);
setup_param("iscsi", iscsi_param_parser);
}
tgt-1.0.80/usr/iscsi/iscsid.h 0000664 0000000 0000000 00000023543 13747533545 0015777 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2002-2003 Ardis Technolgies
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef ISCSID_H
#define ISCSID_H
#include
#include
#include
#include "transport.h"
#include "list.h"
#include "param.h"
#include "log.h"
#include "tgtd.h"
#include "util.h"
#include "iscsi_proto.h"
#include "iscsi_if.h"
#define cpu_to_be16(x) __cpu_to_be16(x)
#define cpu_to_be32(x) __cpu_to_be32(x)
#define cpu_to_be64(x) __cpu_to_be64(x)
#define be16_to_cpu(x) __be16_to_cpu(x)
#define be32_to_cpu(x) __be32_to_cpu(x)
#define be64_to_cpu(x) __be64_to_cpu(x)
#define MAX_QUEUE_CMD_MIN 1
#define MAX_QUEUE_CMD_DEF 128
#define MAX_QUEUE_CMD_MAX 512
#define ISCSI_NAME_LEN 256
#define DIGEST_ALL (DIGEST_NONE | DIGEST_CRC32C)
#define DIGEST_NONE (1 << 0)
#define DIGEST_CRC32C (1 << 1)
#define sid64(isid, tsih) \
({ \
(uint64_t) isid[0] << 0 | (uint64_t) isid[1] << 8 | \
(uint64_t) isid[2] << 16 | (uint64_t) isid[3] << 24 | \
(uint64_t) isid[4] << 32 | (uint64_t) isid[5] << 40 | \
(uint64_t) tsih << 48; \
})
#define sid_to_tsih(sid) ((sid) >> 48)
#define task_opcode(task) ((task)->req.opcode & ISCSI_OPCODE_MASK)
struct iscsi_pdu {
struct iscsi_hdr bhs;
void *ahs;
unsigned int ahssize;
void *data;
unsigned int datasize;
};
struct iscsi_session {
int refcount;
/* linked to target->sessions_list */
struct list_head slist;
/* linked to sessions_list */
struct list_head hlist;
char *initiator;
char *initiator_alias;
struct iscsi_target *target;
uint8_t isid[6];
uint16_t tsih;
/* links all connections (conn->clist) */
struct list_head conn_list;
int conn_cnt;
/* links all tasks (task->c_hlist) */
struct list_head cmd_list;
/* links pending tasks (task->c_list) */
struct list_head pending_cmd_list;
uint32_t exp_cmd_sn;
uint32_t max_queue_cmd;
struct param session_param[ISCSI_PARAM_MAX];
char *info;
/* if this session uses rdma connections */
int rdma;
};
struct iscsi_task {
struct iscsi_hdr req;
struct iscsi_hdr rsp;
uint64_t tag;
struct iscsi_connection *conn;
/* linked to session->cmd_list */
struct list_head c_hlist;
/* linked to conn->tx_clist or session->cmd_pending_list */
struct list_head c_list;
/* linked to conn->tx_clist or conn->task_list */
struct list_head c_siblings;
unsigned long flags;
int result;
int len;
int offset;
int r2t_count;
int unsol_count;
int exp_r2tsn;
void *ahs;
void *data;
struct scsi_cmd scmd;
unsigned long extdata[0];
};
struct iscsi_connection {
int state;
/* should be a new state */
int closed;
int rx_iostate;
int tx_iostate;
int refcount;
struct list_head clist;
struct iscsi_session *session;
int tid;
struct param session_param[ISCSI_PARAM_MAX];
char *initiator;
char *initiator_alias;
uint8_t isid[6];
uint16_t tsih;
uint16_t cid;
int session_type;
int auth_method;
uint32_t stat_sn;
uint32_t exp_stat_sn;
uint32_t cmd_sn;
uint32_t exp_cmd_sn;
uint32_t max_cmd_sn;
struct iscsi_pdu req;
void *req_buffer;
struct iscsi_pdu rsp;
void *rsp_buffer;
int rsp_buffer_size;
unsigned char *rx_buffer;
unsigned char *tx_buffer;
int rx_size;
int tx_size;
uint32_t ttt;
int text_datasize;
void *text_rsp_buffer;
struct iscsi_task *rx_task;
struct iscsi_task *tx_task;
struct list_head tx_clist;
struct list_head task_list;
unsigned char rx_digest[4];
unsigned char tx_digest[4];
int auth_state;
union {
struct {
int digest_alg;
int id;
int challenge_size;
unsigned char *challenge;
} chap;
} auth;
struct iscsi_transport *tp;
struct iscsi_stats stats;
};
#define STATE_FREE 0
#define STATE_SECURITY 1
#define STATE_SECURITY_AUTH 2
#define STATE_SECURITY_DONE 3
#define STATE_SECURITY_LOGIN 4
#define STATE_SECURITY_FULL 5
#define STATE_LOGIN 6
#define STATE_LOGIN_FULL 7
#define STATE_FULL 8
#define STATE_KERNEL 9
#define STATE_CLOSE 10
#define STATE_EXIT 11
#define STATE_SCSI 12
#define STATE_INIT 13
#define STATE_START 14
#define STATE_READY 15
#define AUTH_STATE_START 0
#define AUTH_STATE_CHALLENGE 1
/* don't touch these */
#define AUTH_DIR_INCOMING 0
#define AUTH_DIR_OUTGOING 1
#define SESSION_NORMAL 0
#define SESSION_DISCOVERY 1
#define AUTH_UNKNOWN -1
#define AUTH_NONE 0
#define AUTH_CHAP 1
#define DIGEST_UNKNOWN -1
#define BHS_SIZE sizeof(struct iscsi_hdr)
#define INCOMING_BUFSIZE 8192
extern int default_nop_interval;
extern int default_nop_count;
struct iscsi_target {
struct list_head tlist;
struct list_head sessions_list;
struct param session_param[ISCSI_PARAM_MAX];
int tid;
char *alias;
int max_nr_sessions;
int nr_sessions;
struct redirect_info {
char addr[NI_MAXHOST + 1];
char port[NI_MAXSERV + 1];
uint8_t reason;
char *callback;
} redirect_info;
struct list_head isns_list;
int rdma;
int nop_interval;
int nop_count;
};
enum task_flags {
TASK_pending,
TASK_in_scsi,
};
struct iscsi_portal {
struct list_head iscsi_portal_siblings;
char *addr;
int port;
int tpgt;
int fd;
int af;
};
extern struct list_head iscsi_portals_list;
extern char *portal_arguments;
#define set_task_pending(t) ((t)->flags |= (1 << TASK_pending))
#define clear_task_pending(t) ((t)->flags &= ~(1 << TASK_pending))
#define task_pending(t) ((t)->flags & (1 << TASK_pending))
#define set_task_in_scsi(t) ((t)->flags |= (1 << TASK_in_scsi))
#define clear_task_in_scsi(t) ((t)->flags &= ~(1 << TASK_in_scsi))
#define task_in_scsi(t) ((t)->flags & (1 << TASK_in_scsi))
extern int lld_index;
extern struct list_head iscsi_targets_list;
/* chap.c */
extern int cmnd_exec_auth_chap(struct iscsi_connection *conn);
/* conn.c */
extern int conn_init(struct iscsi_connection *conn);
extern void conn_exit(struct iscsi_connection *conn);
extern void conn_close(struct iscsi_connection *conn);
extern void conn_put(struct iscsi_connection *conn);
extern int conn_get(struct iscsi_connection *conn);
extern struct iscsi_connection * conn_find(struct iscsi_session *session, uint32_t cid);
extern int conn_take_fd(struct iscsi_connection *conn);
extern void conn_add_to_session(struct iscsi_connection *conn, struct iscsi_session *session);
extern tgtadm_err conn_close_admin(uint32_t tid, uint64_t sid, uint32_t cid);
/* iscsid.c */
extern char *text_key_find(struct iscsi_connection *conn, char *searchKey);
extern void text_key_add(struct iscsi_connection *conn, char *key, char *value);
extern void conn_read_pdu(struct iscsi_connection *conn);
extern int iscsi_tx_handler(struct iscsi_connection *conn);
extern void iscsi_rx_handler(struct iscsi_connection *conn);
extern int iscsi_scsi_cmd_execute(struct iscsi_task *task);
extern int iscsi_transportid(int tid, uint64_t itn_id, char *buf, int size);
extern int iscsi_add_portal(char *addr, int port, int tpgt);
extern void iscsi_print_nop_settings(struct concat_buf *b, int tid);
extern int iscsi_update_target_nop_count(int tid, int count);
extern int iscsi_update_target_nop_interval(int tid, int interval);
extern void iscsi_set_nop_interval(int interval);
extern void iscsi_set_nop_count(int count);
extern int iscsi_delete_portal(char *addr, int port);
extern int iscsi_param_parse_portals(char *p, int do_add, int do_delete);
extern void iscsi_update_conn_stats_rx(struct iscsi_connection *conn, int size, int opcode);
extern void iscsi_update_conn_stats_tx(struct iscsi_connection *conn, int size, int opcode);
extern void iscsi_rsp_set_residual(struct iscsi_cmd_rsp *rsp, struct scsi_cmd *scmd);
/* iscsid.c iscsi_task */
extern void iscsi_free_task(struct iscsi_task *task);
extern void iscsi_free_cmd_task(struct iscsi_task *task);
/* session.c */
extern struct iscsi_session *session_find_name(int tid, const char *iname, uint8_t *isid);
extern struct iscsi_session *session_lookup_by_tsih(uint16_t tsih);
extern int session_create(struct iscsi_connection *conn);
extern void session_get(struct iscsi_session *session);
extern void session_put(struct iscsi_session *session);
/* target.c */
extern struct iscsi_target * target_find_by_name(const char *name);
extern struct iscsi_target * target_find_by_id(int tid);
extern void target_list_build(struct iscsi_connection *, char *, char *);
extern int ip_acl(int tid, struct iscsi_connection *conn);
extern int iqn_acl(int tid, struct iscsi_connection *conn);
extern int iscsi_target_create(struct target *);
extern void iscsi_target_destroy(int tid, int force);
extern tgtadm_err iscsi_target_show(int mode, int tid, uint64_t sid, uint32_t cid,
uint64_t lun, struct concat_buf *b);
extern tgtadm_err iscsi_stat(int mode, int tid, uint64_t sid, uint32_t cid,
uint64_t lun, struct concat_buf *b);
extern tgtadm_err iscsi_target_update(int mode, int op, int tid, uint64_t sid, uint64_t lun,
uint32_t cid, char *name);
extern int target_redirected(struct iscsi_target *target,
struct iscsi_connection *conn, char *buf, int *reason);
/* param.c */
extern int param_index_by_name(char *name, struct iscsi_key *keys);
/* transport.c */
extern int iscsi_init(int, char *);
extern void iscsi_exit(void);
/* isns.c */
extern int isns_init(void);
extern void isns_exit(void);
extern tgtadm_err isns_show(struct concat_buf *b);
extern tgtadm_err isns_update(char *params);
extern int isns_scn_access(int tid, char *name);
extern int isns_target_register(char *name);
extern int isns_target_deregister(char *name);
#endif /* ISCSID_H */
tgt-1.0.80/usr/iscsi/iser.c 0000664 0000000 0000000 00000301463 13747533545 0015456 0 ustar 00root root 0000000 0000000 /*
* iSCSI extensions for RDMA (iSER) data path
*
* Copyright (C) 2007 Dennis Dalessandro (dennis@osc.edu)
* Copyright (C) 2007 Ananth Devulapalli (ananth@osc.edu)
* Copyright (C) 2007 Pete Wyckoff (pw@osc.edu)
* Copyright (C) 2010 Voltaire, Inc. All rights reserved.
* Copyright (C) 2010 Alexander Nezhinsky (alexandern@voltaire.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "util.h"
#include "iscsid.h"
#include "target.h"
#include "driver.h"
#include "scsi.h"
#include "work.h"
#include "iser.h"
#if defined(HAVE_VALGRIND) && !defined(NDEBUG)
#include
#else
#define VALGRIND_MAKE_MEM_DEFINED(addr, len)
#endif
static struct iscsi_transport iscsi_iser;
/* global, across all devices */
static struct rdma_event_channel *rdma_evt_channel;
LIST_HEAD(iser_portals_list);
/* accepted at RDMA layer, but not yet established */
static LIST_HEAD(temp_conn);
/* all devices */
static LIST_HEAD(iser_dev_list);
/* all iser connections */
static LIST_HEAD(iser_conn_list);
#define uint64_from_ptr(p) (uint64_t)(uintptr_t)(p)
#define ptr_from_int64(p) (void *)(unsigned long)(p)
#define ISER_LISTEN_PORT 3260
short int iser_listen_port = ISER_LISTEN_PORT;
char *iser_portal_addr;
/*
* Crazy hard-coded linux iser settings need 128 * 8 slots + slop, plus
* room for our rdmas and send requests.
*/
#define MAX_WQE 1800
#define DEFAULT_RDMA_TRANSFER_SIZE (512 * 1024)
#define MAX_POLL_WC 32
#define DEFAULT_POOL_SIZE_MB 1024
#define MAX_CQ_ENTRIES (MAX_QUEUE_CMD_MAX * DEFAULT_POOL_SIZE_MB)
#define MASK_BY_BIT(b) ((1UL << b) - 1)
#define ALIGN_TO_BIT(x, b) ((((unsigned long)x) + MASK_BY_BIT(b)) & \
~MASK_BY_BIT(b))
#define ALIGN_TO_32(x) ALIGN_TO_BIT(x, 5)
struct iscsi_sense_data {
uint16_t length;
uint8_t data[0];
} __attribute__((__packed__));
static size_t buf_pool_sz_mb = DEFAULT_POOL_SIZE_MB;
static int cq_vector = -1;
static int membuf_num;
static size_t membuf_size = DEFAULT_RDMA_TRANSFER_SIZE;
static int iser_conn_get(struct iser_conn *conn);
static int iser_conn_getn(struct iser_conn *conn, int n);
static void iser_conn_put(struct iser_conn *conn);
static void iser_free_login_resources(struct iser_conn *conn);
static void iser_free_ff_resources(struct iser_conn *conn);
static void iser_login_rx(struct iser_task *task);
static int iser_device_init(struct iser_device *dev);
static void iser_handle_cq_event(int fd __attribute__ ((unused)),
int events __attribute__ ((unused)),
void *data);
static void iser_handle_async_event(int fd __attribute__ ((unused)),
int events __attribute__ ((unused)),
void *data);
static void iser_sched_poll_cq(struct event_data *tev);
static void iser_sched_consume_cq(struct event_data *tev);
static void iser_sched_tx(struct event_data *evt);
static void iser_sched_post_recv(struct event_data *evt);
static void iser_sched_buf_alloc(struct event_data *evt);
static void iser_sched_iosubmit(struct event_data *evt);
static void iser_sched_rdma_rd(struct event_data *evt);
static inline void schedule_task_iosubmit(struct iser_task *task,
struct iser_conn *conn)
{
list_add_tail(&task->exec_list, &conn->iosubmit_list);
tgt_add_sched_event(&conn->sched_iosubmit);
dprintf("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
task, task->tag, task->cmd_sn);
}
static inline void schedule_rdma_read(struct iser_task *task,
struct iser_conn *conn)
{
list_add_tail(&task->rdma_list, &conn->rdma_rd_list);
tgt_add_sched_event(&conn->sched_rdma_rd);
dprintf("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
task, task->tag, task->cmd_sn);
}
static inline void schedule_resp_tx(struct iser_task *task,
struct iser_conn *conn)
{
list_add_tail(&task->tx_list, &conn->resp_tx_list);
tgt_add_sched_event(&conn->sched_tx);
dprintf("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
task, task->tag, task->cmd_sn);
}
static inline void schedule_post_recv(struct iser_task *task,
struct iser_conn *conn)
{
list_add_tail(&task->recv_list, &conn->post_recv_list);
tgt_add_sched_event(&conn->sched_post_recv);
dprintf("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
task, task->tag, task->cmd_sn);
}
static char *iser_ib_op_to_str(enum iser_ib_op_code iser_ib_op)
{
char *op_str;
switch (iser_ib_op) {
case ISER_IB_RECV:
op_str = "recv";
break;
case ISER_IB_SEND:
op_str = "send";
break;
case ISER_IB_RDMA_WRITE:
op_str = "rdma_wr";
break;
case ISER_IB_RDMA_READ:
op_str = "rdma_rd";
break;
default:
op_str = "Unknown";
break;
}
return op_str;
}
static void iser_pdu_init(struct iser_pdu *pdu, void *buf)
{
pdu->iser_hdr = (struct iser_hdr *) buf;
buf += sizeof(struct iser_hdr);
pdu->bhs = (struct iscsi_hdr *) buf;
buf += sizeof(struct iscsi_hdr);
pdu->ahs = buf;
pdu->ahssize = 0;
pdu->membuf.addr = buf;
pdu->membuf.size = 0;
}
static void iser_rxd_init(struct iser_work_req *rxd,
struct iser_task *task,
void *buf, unsigned size,
struct ibv_mr *srmr)
{
rxd->task = task;
rxd->iser_ib_op = ISER_IB_RECV;
rxd->sge.addr = uint64_from_ptr(buf);
rxd->sge.length = size;
rxd->sge.lkey = srmr->lkey;
rxd->recv_wr.wr_id = uint64_from_ptr(rxd);
rxd->recv_wr.sg_list = &rxd->sge;
rxd->recv_wr.num_sge = 1;
rxd->recv_wr.next = NULL;
}
static void iser_txd_init(struct iser_work_req *txd,
struct iser_task *task,
void *buf, unsigned size,
struct ibv_mr *srmr)
{
txd->task = task;
txd->iser_ib_op = ISER_IB_SEND;
txd->sge.addr = uint64_from_ptr(buf);
txd->sge.length = size;
txd->sge.lkey = srmr->lkey;
txd->send_wr.wr_id = uint64_from_ptr(txd);
txd->send_wr.next = NULL;
txd->send_wr.sg_list = &txd->sge;
txd->send_wr.num_sge = 1;
txd->send_wr.opcode = IBV_WR_SEND;
txd->send_wr.send_flags = IBV_SEND_SIGNALED;
INIT_LIST_HEAD(&txd->wr_list);
}
static void iser_rdmad_init(struct iser_work_req *rdmad,
struct iser_task *task,
struct ibv_mr *srmr)
{
rdmad->task = task;
rdmad->sge.lkey = srmr->lkey;
rdmad->send_wr.wr_id = uint64_from_ptr(rdmad);
rdmad->send_wr.sg_list = &rdmad->sge;
rdmad->send_wr.num_sge = 1;
rdmad->send_wr.next = NULL;
rdmad->send_wr.send_flags = IBV_SEND_SIGNALED;
/* to be set before posting:
rdmad->iser_ib_op, rdmad->send_wr.opcode
rdmad->sge.addr, rdmad->sge.length
rdmad->send_wr.wr.rdma.(remote_addr,rkey) */
INIT_LIST_HEAD(&rdmad->wr_list);
}
static void iser_task_init(struct iser_task *task,
struct iser_conn *conn,
void *pdu_buf,
unsigned long buf_size,
struct ibv_mr *srmr)
{
task->conn = conn;
task->unsolicited = 0;
iser_pdu_init(&task->pdu, pdu_buf);
iser_rxd_init(&task->rxd, task, pdu_buf, buf_size, srmr);
iser_txd_init(&task->txd, task, pdu_buf, buf_size, srmr);
iser_rdmad_init(&task->rdmad, task, conn->dev->membuf_mr);
INIT_LIST_HEAD(&task->in_buf_list);
INIT_LIST_HEAD(&task->out_buf_list);
INIT_LIST_HEAD(&task->exec_list);
INIT_LIST_HEAD(&task->rdma_list);
INIT_LIST_HEAD(&task->tx_list);
INIT_LIST_HEAD(&task->recv_list);
INIT_LIST_HEAD(&task->session_list);
INIT_LIST_HEAD(&task->dout_task_list);
}
static void iser_unsolicited_task_init(struct iser_task *task,
struct iser_conn *conn,
void *pdu_data_buf,
unsigned long buf_size,
struct ibv_mr *srmr)
{
task->conn = conn;
task->unsolicited = 1;
iser_txd_init(&task->txd, task, pdu_data_buf, buf_size, srmr);
iser_pdu_init(&task->pdu, pdu_data_buf);
}
static void iser_task_add_out_pdu_buf(struct iser_task *task,
struct iser_membuf *data_buf,
unsigned int offset)
{
data_buf->offset = offset;
task->out_buf_num++;
if (!list_empty(&task->out_buf_list)) {
struct iser_membuf *cur_buf;
list_for_each_entry(cur_buf, &task->out_buf_list, task_list) {
if (offset < cur_buf->offset) {
dprintf("task:%p offset:%d size:%d data_buf:%p add before:%p\n",
task, offset, data_buf->size, data_buf->addr, cur_buf->addr);
list_add_tail(&data_buf->task_list, &cur_buf->task_list);
return;
}
}
}
dprintf("task:%p offset:%d size:%d data_buf:%p add last\n",
task, offset, data_buf->size, data_buf->addr);
list_add_tail(&data_buf->task_list, &task->out_buf_list);
}
static void iser_task_add_out_rdma_buf(struct iser_task *task,
struct iser_membuf *data_buf,
unsigned int offset)
{
data_buf->offset = offset;
task->out_buf_num++;
dprintf("task:%p offset:%d size:%d data_buf:%p add last\n",
task, offset, data_buf->size, data_buf->addr);
list_add_tail(&data_buf->task_list, &task->out_buf_list);
}
static void iser_task_del_out_buf(struct iser_task *task,
struct iser_membuf *data_buf)
{
dprintf("task:%p offset:%d size:%d data_buf:%p\n",
task, data_buf->offset, data_buf->size, data_buf->addr);
list_del(&data_buf->task_list);
task->out_buf_num--;
}
static void iser_task_add_in_rdma_buf(struct iser_task *task,
struct iser_membuf *data_buf,
unsigned int offset)
{
dprintf("task:%p offset:0x%d size:%d data_buf:%p add last\n",
task, offset, data_buf->size, data_buf->addr);
data_buf->offset = offset;
task->in_buf_num++;
list_add_tail(&data_buf->task_list, &task->in_buf_list);
}
static void iser_task_del_in_buf(struct iser_task *task,
struct iser_membuf *data_buf)
{
dprintf("task:%p offset:%d size:%d data_buf:%p\n",
task, data_buf->offset, data_buf->size, data_buf->addr);
list_del(&data_buf->task_list);
task->in_buf_num--;
}
static void iser_prep_resp_send_req(struct iser_task *task,
struct iser_work_req *next_wr,
int signaled)
{
struct iser_pdu *pdu = &task->pdu;
struct iser_hdr *iser_hdr = pdu->iser_hdr;
struct iscsi_hdr *bhs = pdu->bhs;
struct iser_work_req *txd = &task->txd;
bhs->hlength = pdu->ahssize / 4;
hton24(bhs->dlength, pdu->membuf.size);
txd->sge.length = ISER_HDRS_SZ;
txd->sge.length += pdu->ahssize;
txd->sge.length += pdu->membuf.size;
memset(iser_hdr, 0, sizeof(*iser_hdr));
iser_hdr->flags = ISCSI_CTRL;
txd->send_wr.next = (next_wr ? &next_wr->send_wr : NULL);
txd->send_wr.send_flags = (signaled ? IBV_SEND_SIGNALED : 0);
dprintf("task:%p wr_id:0x%"PRIx64 " tag:0x%04"PRIx64 " dtbuf:%p "
"dtsz:%u ahs_sz:%u stat:0x%x statsn:0x%x expcmdsn:0x%x\n",
task, txd->send_wr.wr_id, task->tag,
pdu->membuf.addr, pdu->membuf.size, pdu->ahssize,
bhs->rsvd2[1], ntohl(bhs->statsn), ntohl(bhs->exp_statsn));
}
static void iser_prep_rdma_wr_send_req(struct iser_task *task,
struct iser_work_req *next_wr,
int signaled)
{
struct iser_work_req *rdmad = &task->rdmad;
struct iser_membuf *rdma_buf;
uint64_t offset = 0; /* ToDo: multiple RDMA-Write buffers */
rdmad->iser_ib_op = ISER_IB_RDMA_WRITE;
/* RDMA-Write buffer is the only one on the list */
/* ToDo: multiple RDMA-Write buffers, use rdma_buf->offset */
rdma_buf = list_first_entry(&task->in_buf_list, struct iser_membuf, task_list);
rdmad->sge.addr = uint64_from_ptr(rdma_buf->addr);
if (likely(task->rdma_wr_remains <= rdma_buf->size)) {
rdmad->sge.length = task->rdma_wr_remains;
task->rdma_wr_remains = 0;
} else {
rdmad->sge.length = rdma_buf->size;
task->rdma_wr_remains -= rdmad->sge.length;
}
rdmad->send_wr.next = (next_wr ? &next_wr->send_wr : NULL);
rdmad->send_wr.opcode = IBV_WR_RDMA_WRITE;
rdmad->send_wr.send_flags = (signaled ? IBV_SEND_SIGNALED : 0);
rdmad->send_wr.wr.rdma.remote_addr = task->rem_read_va + offset;
/* offset += rdmad->sge.length // ToDo: multiple RDMA-Write buffers */
rdmad->send_wr.wr.rdma.rkey = task->rem_read_stag;
dprintf(" task:%p tag:0x%04"PRIx64 " wr_id:0x%"PRIx64 " daddr:0x%"PRIx64 " dsz:%u "
"bufsz:%u rdma:%d lkey:%x raddr:%"PRIx64 " rkey:%x rems:%u\n",
task, task->tag, rdmad->send_wr.wr_id, rdmad->sge.addr,
rdmad->sge.length, rdma_buf->size, rdma_buf->rdma,
rdmad->sge.lkey, rdmad->send_wr.wr.rdma.remote_addr,
rdmad->send_wr.wr.rdma.rkey, task->rdma_wr_remains);
}
static void iser_prep_rdma_rd_send_req(struct iser_task *task,
struct iser_work_req *next_wr,
int signaled)
{
struct iser_work_req *rdmad = &task->rdmad;
struct iser_membuf *rdma_buf;
uint64_t offset;
int cur_req_sz;
rdmad->iser_ib_op = ISER_IB_RDMA_READ;
/* RDMA-Read buffer is always put at the list's tail */
/* ToDo: multiple RDMA-Write buffers */
rdma_buf = container_of(task->out_buf_list.prev,
struct iser_membuf, task_list);
offset = rdma_buf->offset;
rdmad->sge.addr = uint64_from_ptr(rdma_buf->addr) + offset;
if (task->rdma_rd_sz <= rdma_buf->size)
cur_req_sz = task->rdma_rd_sz;
else
cur_req_sz = rdma_buf->size;
/* task->rdma_rd_offset += cur_req_sz; // ToDo: multiple RDMA-Write buffers */
rdmad->sge.length = cur_req_sz;
rdmad->send_wr.next = (next_wr ? &next_wr->send_wr : NULL);
rdmad->send_wr.opcode = IBV_WR_RDMA_READ;
rdmad->send_wr.send_flags = (signaled ? IBV_SEND_SIGNALED : 0);
rdmad->send_wr.wr.rdma.remote_addr =
(uint64_t) task->rem_write_va;
/* + (offset - task->unsol_sz); // ToDo: multiple RDMA-Write buffers */
rdmad->send_wr.wr.rdma.rkey =
(uint32_t) task->rem_write_stag;
dprintf("task:%p wr_id:0x%"PRIx64 " tag:0x%04"PRIx64 " daddr:0x%"PRIx64 " dsz:%u "
"bufsz:%u rdma:%d lkey:%x raddr:%"PRIx64 " rkey:%x rems:%u\n",
task, rdmad->send_wr.wr_id, task->tag, rdmad->sge.addr,
rdmad->sge.length, rdma_buf->size, rdma_buf->rdma,
rdmad->sge.lkey, rdmad->send_wr.wr.rdma.remote_addr,
rdmad->send_wr.wr.rdma.rkey, task->rdma_rd_sz);
}
static int iser_post_send(struct iser_conn *conn,
struct iser_work_req *iser_send,
int num_send_reqs)
{
struct ibv_send_wr *bad_wr;
int err, nr_posted;
err = ibv_post_send(conn->qp_hndl, &iser_send->send_wr, &bad_wr);
if (likely(!err)) {
nr_posted = num_send_reqs;
dprintf("conn:%p posted:%d 1st wr:%p wr_id:0x%lx sge_sz:%u\n",
&conn->h, nr_posted, iser_send,
(unsigned long)iser_send->send_wr.wr_id,
iser_send->sge.length);
} else {
struct ibv_send_wr *wr;
nr_posted = 0;
for (wr = &iser_send->send_wr; wr != bad_wr; wr = wr->next)
nr_posted++;
eprintf("%m, conn:%p posted:%d/%d 1st wr:%p wr_id:0x%lx sge_sz:%u\n",
&conn->h, nr_posted, num_send_reqs, iser_send,
(unsigned long)iser_send->send_wr.wr_id,
iser_send->sge.length);
}
iser_conn_getn(conn, nr_posted);
return err;
}
static int iser_post_recv(struct iser_conn *conn,
struct iser_task *task,
int num_recv_bufs)
{
struct ibv_recv_wr *bad_wr;
int err, nr_posted;
err = ibv_post_recv(conn->qp_hndl, &task->rxd.recv_wr, &bad_wr);
if (likely(!err)) {
nr_posted = num_recv_bufs;
dprintf("conn:%p posted:%d 1st task:%p "
"wr_id:0x%lx sge_sz:%u\n",
&conn->h, nr_posted, task,
(unsigned long)task->rxd.recv_wr.wr_id,
task->rxd.sge.length);
} else {
struct ibv_recv_wr *wr;
nr_posted = 0;
for (wr = &task->rxd.recv_wr; wr != bad_wr; wr = wr->next)
nr_posted++;
eprintf("%m, conn:%p posted:%d/%d 1st task:%p "
"wr_id:0x%lx sge_sz:%u\n",
&conn->h, nr_posted, num_recv_bufs, task,
(unsigned long)task->rxd.recv_wr.wr_id,
task->rxd.sge.length);
}
iser_conn_getn(conn, nr_posted);
return err;
}
static inline void iser_set_rsp_stat_sn(struct iscsi_session *session,
struct iscsi_hdr *rsp)
{
if (session) {
rsp->exp_statsn = cpu_to_be32(session->exp_cmd_sn);
rsp->max_statsn = cpu_to_be32(session->exp_cmd_sn +
session->max_queue_cmd);
}
}
static uint8_t* iser_alloc_pool(size_t pool_size, int *shmid)
{
int shmemid;
uint8_t *buf;
/* allocate memory */
shmemid = shmget(IPC_PRIVATE, pool_size,
SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W);
if (shmemid < 0) {
eprintf("shmget rdma pool sz:%zu failed\n", pool_size);
goto failed_huge_page;
}
/* get pointer to allocated memory */
buf = shmat(shmemid, NULL, 0);
if (buf == (void*)-1) {
eprintf("Shared memory attach failure (errno=%d %m)", errno);
shmctl(shmemid, IPC_RMID, NULL);
goto failed_huge_page;
}
/* mark 'to be destroyed' when process detaches from shmem segment
this will clear the HugePage resources even if process if killed not nicely.
From checking shmctl man page it is unlikely that it will fail here. */
if (shmctl(shmemid, IPC_RMID, NULL)) {
eprintf("Shared memory contrl mark 'to be destroyed' failed (errno=%d %m)", errno);
}
dprintf("Allocated huge page sz:%zu\n", pool_size);
*shmid = shmemid;
return buf;
failed_huge_page:
*shmid = -1;
return valloc(pool_size);
}
static void iser_free_pool(uint8_t *pool_buf, int shmid) {
if (shmid >= 0) {
if (shmdt(pool_buf) != 0) {
eprintf("shmem detach failure (errno=%d %m)", errno);
}
} else {
free(pool_buf);
}
}
static int iser_init_rdma_buf_pool(struct iser_device *dev)
{
uint8_t *pool_buf, *list_buf;
size_t pool_size, list_size;
struct iser_membuf *rdma_buf;
int shmid;
int i;
/* The membuf size is rounded up at initialization time to the hardware
page size so that allocations for direct IO devices are aligned. */
membuf_size = roundup(membuf_size, pagesize);
pool_size = buf_pool_sz_mb * 1024 * 1024;
membuf_num = pool_size / membuf_size;
pool_size = membuf_num * membuf_size; /* reflect possible round-down */
pool_buf = iser_alloc_pool(pool_size, &shmid);
if (!pool_buf) {
eprintf("malloc rdma pool sz:%zu failed\n", pool_size);
return -ENOMEM;
}
list_size = membuf_num * sizeof(*rdma_buf);
list_buf = malloc(list_size);
if (!list_buf) {
eprintf("malloc list_buf sz:%zu failed\n", list_size);
iser_free_pool(pool_buf, shmid);
return -ENOMEM;
}
/* One pool of registered memory per PD */
dev->membuf_mr = ibv_reg_mr(dev->pd, pool_buf, pool_size,
IBV_ACCESS_LOCAL_WRITE);
if (!dev->membuf_mr) {
eprintf("ibv_reg_mr failed, %m\n");
iser_free_pool(pool_buf, shmid);
free(list_buf);
return -1;
}
dprintf("pool buf:%p list:%p mr:%p lkey:0x%x\n",
pool_buf, list_buf, dev->membuf_mr, dev->membuf_mr->lkey);
dev->rdma_hugetbl_shmid = shmid;
dev->membuf_regbuf = pool_buf;
dev->membuf_listbuf = list_buf;
INIT_LIST_HEAD(&dev->membuf_free);
INIT_LIST_HEAD(&dev->membuf_alloc);
for (i = 0; i < membuf_num; i++) {
rdma_buf = (void *) list_buf;
list_buf += sizeof(*rdma_buf);
list_add_tail(&rdma_buf->pool_list, &dev->membuf_free);
INIT_LIST_HEAD(&rdma_buf->task_list);
rdma_buf->addr = pool_buf;
rdma_buf->size = membuf_size;
rdma_buf->rdma = 1;
pool_buf += membuf_size;
}
return 0;
}
static void iser_destroy_rdma_buf_pool(struct iser_device *dev)
{
int err;
assert(list_empty(&dev->membuf_alloc));
err = ibv_dereg_mr(dev->membuf_mr);
if (err)
eprintf("ibv_dereg_mr failed: (errno=%d %m)\n", errno);
iser_free_pool(dev->membuf_regbuf, dev->rdma_hugetbl_shmid);
free(dev->membuf_listbuf);
dev->membuf_mr = NULL;
dev->membuf_regbuf = NULL;
dev->membuf_listbuf = NULL;
}
static struct iser_membuf *iser_dev_alloc_rdma_buf(struct iser_device *dev)
{
struct iser_membuf *rdma_buf;
if (unlikely(list_empty(&dev->membuf_free)))
return NULL;
rdma_buf = list_first_entry(&dev->membuf_free, struct iser_membuf,
pool_list);
list_del(&rdma_buf->pool_list);
list_add_tail(&rdma_buf->pool_list, &dev->membuf_alloc);
dprintf("alloc:%p\n", rdma_buf);
return rdma_buf;
}
static void iser_dev_free_rdma_buf(struct iser_device *dev, struct iser_membuf *rdma_buf)
{
if (unlikely(!rdma_buf || !rdma_buf->rdma))
return;
dprintf("free %p\n", rdma_buf);
/* add to the free list head to reuse recently used buffers first */
list_del(&rdma_buf->pool_list);
list_add(&rdma_buf->pool_list, &dev->membuf_free);
}
static int iser_task_alloc_rdma_bufs(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
struct iser_membuf *rdma_wr_buf = NULL, *rdma_rd_buf = NULL;
if (task->is_write && task->rdma_rd_sz > 0) {
/* ToDo: multiple RDMA-Read buffers */
if (unlikely(task->rdma_rd_sz > membuf_size)) {
eprintf("conn:%p task:%p tag:0x%04"PRIx64 ", "
"rdma-rd size:%u too big\n",
&conn->h, task, task->tag, task->rdma_rd_sz);
return -E2BIG;
}
rdma_rd_buf = iser_dev_alloc_rdma_buf(conn->dev);
if (unlikely(rdma_rd_buf == NULL))
goto no_mem_err;
/* if this is a bidir task, allocation of the rdma_wr buffer
may still fail, thus don't add the buffer to the task yet */
}
if (task->is_read && task->rdma_wr_sz > 0) {
/* ToDo: multiple RDMA-Read buffers */
if (unlikely(task->rdma_wr_sz > membuf_size)) {
eprintf("conn:%p task:%p tag:0x%04"PRIx64 ", "
"rdma-wr size:%u too big\n",
&conn->h, task, task->tag, task->rdma_wr_sz);
return -E2BIG;
}
rdma_wr_buf = iser_dev_alloc_rdma_buf(conn->dev);
if (unlikely(rdma_wr_buf == NULL))
goto no_mem_err;
iser_task_add_in_rdma_buf(task, rdma_wr_buf, 0);
dprintf("conn:%p task:%p tag:0x%04"PRIx64 ", "
"rdma-wr buf:%p sz:%u\n",
&conn->h, task, task->tag,
rdma_wr_buf->addr, rdma_wr_buf->size);
}
/* With a write or bidir task there may be rdma portion and data-outs,
independently. If data-outs remain, wait until they all arrive */
if (task->is_write) {
if (rdma_rd_buf) {
/* ToDo: multiple RDMA-Read buffers */
iser_task_add_out_rdma_buf(task, rdma_rd_buf,
task->unsol_sz);
dprintf("conn:%p task:%p tag:0x%04"PRIx64 ", "
"rdma-rd buf:%p sz:%u\n",
&conn->h, task, task->tag,
rdma_rd_buf->addr, rdma_rd_buf->size);
schedule_rdma_read(task, conn);
return 0;
}
if (task->unsol_remains > 0)
return 0;
}
schedule_task_iosubmit(task, conn);
return 0;
no_mem_err:
if (rdma_rd_buf)
iser_dev_free_rdma_buf(conn->dev, rdma_rd_buf);
conn->dev->waiting_for_mem = 1;
eprintf("conn:%p task:%p tag:0x%04"PRIx64 ", free list empty\n",
&conn->h, task, task->tag);
return -ENOMEM;
}
static void iser_task_free_rdma_buf(struct iser_task *task, struct iser_membuf *rdma_buf)
{
struct iser_conn *conn = task->conn;
struct iser_device *dev = conn->dev;
iser_dev_free_rdma_buf(dev, rdma_buf);
if (unlikely(dev->waiting_for_mem)) {
dev->waiting_for_mem = 0;
tgt_add_sched_event(&conn->sched_buf_alloc);
}
}
static void iser_task_free_out_bufs(struct iser_task *task)
{
struct iser_membuf *membuf, *mbnext;
list_for_each_entry_safe(membuf, mbnext, &task->out_buf_list, task_list) {
iser_task_del_out_buf(task, membuf);
if (membuf->rdma)
iser_task_free_rdma_buf(task, membuf);
}
assert(task->out_buf_num == 0);
}
static void iser_task_free_in_bufs(struct iser_task *task)
{
struct iser_membuf *membuf, *mbnext;
list_for_each_entry_safe(membuf, mbnext, &task->in_buf_list, task_list) {
iser_task_del_in_buf(task, membuf);
iser_task_free_rdma_buf(task, membuf);
}
assert(task->in_buf_num == 0);
}
static void iser_complete_task(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
if (unlikely(task->opcode != ISCSI_OP_SCSI_CMD)) {
dprintf("task:%p, non-cmd\n", task);
return;
}
list_del(&task->session_list);
if (task->is_read)
iser_task_free_in_bufs(task);
if (task->is_write) {
iser_task_free_out_bufs(task);
/* iser_task_free_dout_tasks(task); // ToDo: multiple out buffers */
}
/* we are completing scsi cmd task, returning from target */
if (likely(task_in_scsi(task))) {
target_cmd_done(&task->scmd);
clear_task_in_scsi(task);
iser_conn_put(conn);
}
if (task->extdata) {
free(task->extdata);
task->extdata = NULL;
}
}
static char *iser_conn_login_phase_name(enum iser_login_phase phase)
{
switch (phase) {
case LOGIN_PHASE_INIT:
return "INIT";
case LOGIN_PHASE_START:
return "START";
case LOGIN_PHASE_LAST_SEND:
return "LAST_SEND";
case LOGIN_PHASE_FF:
return "FF";
default:
return "Illegal";
}
}
static void iser_conn_login_phase_set(struct iser_conn *conn,
enum iser_login_phase phase)
{
dprintf("conn:%p from:%s to:%s\n", &conn->h,
iser_conn_login_phase_name(conn->login_phase),
iser_conn_login_phase_name(phase));
conn->login_phase = phase;
}
/*
* Called at accept time, builds resources just for login phase.
*/
#define NUM_LOGIN_TASKS 2 /* one posted for req rx, one for reply tx */
static int iser_alloc_login_resources(struct iser_conn *conn)
{
unsigned long buf_size = ALIGN_TO_32(conn->rsize);
unsigned long pool_size = NUM_LOGIN_TASKS * buf_size;
struct iser_task *login_task[NUM_LOGIN_TASKS];
uint8_t *pdu_data_buf, *task_buf;
unsigned int i;
int err = 0;
dprintf("conn:%p login tasks num:%u, buf_sz:%lu (rx_sz:%u tx_sz:%u)\n",
&conn->h, NUM_LOGIN_TASKS, buf_size, conn->rsize, conn->ssize);
conn->login_data_pool = malloc(pool_size);
if (!conn->login_data_pool) {
eprintf("conn:%p malloc login_data_pool sz:%lu failed\n",
&conn->h, pool_size);
err = -1;
goto out;
}
conn->login_data_mr = ibv_reg_mr(conn->dev->pd, conn->login_data_pool,
pool_size, IBV_ACCESS_LOCAL_WRITE);
if (!conn->login_data_mr) {
eprintf("conn:%p ibv_reg_mr login pool failed, %m\n", &conn->h);
free(conn->login_data_pool);
conn->login_data_pool = NULL;
err = -1;
goto out;
}
pool_size = NUM_LOGIN_TASKS * sizeof(struct iser_task);
conn->login_task_pool = malloc(pool_size);
if (!conn->login_task_pool) {
eprintf("conn:%p malloc login_task_pool sz:%lu failed\n",
&conn->h, pool_size);
ibv_dereg_mr(conn->login_data_mr);
conn->login_data_mr = NULL;
free(conn->login_data_pool);
conn->login_data_pool = NULL;
goto out;
}
memset(conn->login_task_pool, 0, pool_size);
conn->login_res_alloc = 1;
pdu_data_buf = conn->login_data_pool;
task_buf = conn->login_task_pool;
for (i = 0; i < NUM_LOGIN_TASKS; i++) {
login_task[i] = (struct iser_task *) task_buf;
iser_task_init(login_task[i], conn,
pdu_data_buf, buf_size,
conn->login_data_mr);
task_buf += sizeof(struct iser_task);
pdu_data_buf += buf_size;
}
dprintf("post_recv login rx task:%p\n", login_task[0]);
err = iser_post_recv(conn, login_task[0], 1);
if (err) {
eprintf("conn:%p post_recv login rx-task failed\n", &conn->h);
iser_free_login_resources(conn);
goto out;
}
dprintf("saved login tx-task:%p\n", login_task[1]);
conn->login_tx_task = login_task[1];
out:
return err;
}
/*
* When ready for full-feature mode, free login-phase resources.
*/
static void iser_free_login_resources(struct iser_conn *conn)
{
int err;
if (!conn->login_res_alloc)
return;
dprintf("conn:%p, login phase:%s\n", &conn->h,
iser_conn_login_phase_name(conn->login_phase));
/* release mr and free the lists */
if (conn->login_data_mr) {
err = ibv_dereg_mr(conn->login_data_mr);
if (err)
eprintf("conn:%p ibv_dereg_mr failed, %m\n", &conn->h);
}
if (conn->login_data_pool)
free(conn->login_data_pool);
if (conn->login_task_pool)
free(conn->login_task_pool);
conn->login_tx_task = NULL;
conn->login_res_alloc = 0;
}
/*
* Ready for full feature, allocate resources.
*/
static int iser_alloc_ff_resources(struct iser_conn *conn)
{
/* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */
/* buf_size = ALIGN_TO_32(MAX(conn->rsize, conn->ssize)); */
unsigned long buf_size = ALIGN_TO_32(conn->rsize); /* includes headers */
unsigned long num_tasks = conn->max_outst_pdu + 2; /* all rx, text tx, nop-in*/
unsigned long alloc_sz;
uint8_t *pdu_data_buf, *task_buf;
struct iser_task *task;
unsigned int i;
int err = 0;
dprintf("conn:%p max_outst:%u buf_sz:%lu (ssize:%u rsize:%u)\n",
&conn->h, conn->max_outst_pdu, buf_size,
conn->ssize, conn->rsize);
alloc_sz = num_tasks * buf_size;
conn->pdu_data_pool = malloc(alloc_sz);
if (!conn->pdu_data_pool) {
eprintf("conn:%p malloc pdu_data_buf sz:%lu failed\n",
&conn->h, alloc_sz);
err = -1;
goto out;
}
conn->pdu_data_mr = ibv_reg_mr(conn->dev->pd, conn->pdu_data_pool,
alloc_sz, IBV_ACCESS_LOCAL_WRITE);
if (!conn->pdu_data_mr) {
eprintf("conn:%p ibv_reg_mr pdu_data_pool failed, %m\n",
&conn->h);
free(conn->pdu_data_pool);
conn->pdu_data_pool = NULL;
err = -1;
goto out;
}
alloc_sz = num_tasks * sizeof(struct iser_task);
conn->task_pool = malloc(alloc_sz);
if (!conn->task_pool) {
eprintf("conn:%p malloc task_pool sz:%lu failed\n",
&conn->h, alloc_sz);
ibv_dereg_mr(conn->pdu_data_mr);
conn->pdu_data_mr = NULL;
free(conn->pdu_data_pool);
conn->pdu_data_pool = NULL;
err = -1;
goto out;
}
memset(conn->task_pool, 0, alloc_sz);
conn->ff_res_alloc = 1;
pdu_data_buf = conn->pdu_data_pool;
task_buf = conn->task_pool;
for (i = 0; i < conn->max_outst_pdu; i++) {
task = (void *) task_buf;
iser_task_init(task, conn,
pdu_data_buf, buf_size,
conn->pdu_data_mr);
task_buf += sizeof(*task);
/* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */
/* pdu_data_buf += conn->rsize + conn->ssize; */
pdu_data_buf += buf_size;
err = iser_post_recv(conn, task, 1);
if (err) {
eprintf("conn:%p post_recv (%d/%d) failed\n",
&conn->h, i, conn->max_outst_pdu);
iser_free_ff_resources(conn);
err = -1;
goto out;
}
}
/* initialize unsolicited tx task: nop-in */
task = (void *) task_buf;
iser_unsolicited_task_init(task, conn, pdu_data_buf,
buf_size, conn->pdu_data_mr);
conn->nop_in_task = task;
task_buf += sizeof(*task);
pdu_data_buf += buf_size;
/* initialize tx task: text/login */
task = (void *) task_buf;
iser_unsolicited_task_init(task, conn, pdu_data_buf,
buf_size, conn->pdu_data_mr);
conn->text_tx_task = task;
out:
return err;
}
/*
* On connection shutdown.
*/
static void iser_free_ff_resources(struct iser_conn *conn)
{
int err;
if (!conn->ff_res_alloc)
return;
dprintf("conn:%p pdu_mr:%p pdu_pool:%p task_pool:%p\n",
&conn->h, conn->pdu_data_mr,
conn->pdu_data_pool, conn->task_pool);
/* release mr and free the lists */
if (conn->pdu_data_mr) {
err = ibv_dereg_mr(conn->pdu_data_mr);
if (err)
eprintf("conn:%p ibv_dereg_mr failed, %m\n", &conn->h);
}
if (conn->pdu_data_pool)
free(conn->pdu_data_pool);
if (conn->task_pool)
free(conn->task_pool);
conn->nop_in_task = NULL;
conn->text_tx_task = NULL;
conn->ff_res_alloc = 0;
}
/*
* Allocate resources for this new connection. Called after login, when
* final negotiated transfer parameters are known.
*/
int iser_login_complete(struct iscsi_connection *iscsi_conn)
{
struct iser_conn *conn = ISER_CONN(iscsi_conn);
unsigned int trdsl;
/* unsigned int irdsl; */
unsigned int outst_pdu, hdrsz;
uint32_t max_q_cmd;
int err = -1;
dprintf("entry\n");
/* one more send, then done; login resources are left until then */
iser_conn_login_phase_set(conn, LOGIN_PHASE_LAST_SEND);
/* irdsl = iscsi_conn->session_param[ISCSI_PARAM_INITIATOR_RDSL].val; */
trdsl = iscsi_conn->session_param[ISCSI_PARAM_TARGET_RDSL].val;
/* ToDo: outstanding pdus num */
/* outst_pdu =
iscsi_conn->session_param[ISCSI_PARAM_MAX_OUTST_PDU].val; */
/* hack, ib/ulp/iser does not have this param, but reading the code
* shows their formula for max tx dtos outstanding
* = cmds_max * (1 + dataouts) + rx_misc + tx_misc
*/
#define ISER_INFLIGHT_DATAOUTS 0
#define ISER_MAX_RX_MISC_PDUS 4
#define ISER_MAX_TX_MISC_PDUS 6
max_q_cmd = iscsi_conn->session_param[ISCSI_PARAM_MAX_QUEUE_CMD].val;
/* if (outst_pdu == 0) // ToDo: outstanding pdus num */
outst_pdu =
3 * max_q_cmd * (1 + ISER_INFLIGHT_DATAOUTS) +
ISER_MAX_RX_MISC_PDUS + ISER_MAX_TX_MISC_PDUS;
/* RDSLs do not include headers. */
hdrsz = sizeof(struct iser_hdr) +
sizeof(struct iscsi_hdr) +
sizeof(struct iscsi_ecdb_ahdr) +
sizeof(struct iscsi_rlength_ahdr);
if (trdsl < 1024)
trdsl = 1024;
conn->rsize = hdrsz + trdsl;
/* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */
/* conn->ssize = hdrsz + irdsl; */
conn->ssize = conn->rsize;
conn->max_outst_pdu = outst_pdu;
err = iser_alloc_ff_resources(conn);
if (err)
goto out;
/* How much data to grab in an RDMA operation, read or write */
/* ToDo: fix iscsi login code, broken handling of MAX_XMIT_DL */
iscsi_conn->session_param[ISCSI_PARAM_MAX_XMIT_DLENGTH].val = membuf_size;
out:
return err;
}
static int iser_ib_clear_iosubmit_list(struct iser_conn *conn)
{
struct iser_task *task;
dprintf("start\n");
while (!list_empty(&conn->iosubmit_list)) {
task = list_first_entry(&conn->iosubmit_list,
struct iser_task, exec_list);
list_del(&task->exec_list);
iser_complete_task(task); /* must free, task keeps rdma buffer */
}
return 0;
}
static int iser_ib_clear_rdma_rd_list(struct iser_conn *conn)
{
struct iser_task *task;
dprintf("start\n");
while (!list_empty(&conn->rdma_rd_list)) {
task = list_first_entry(&conn->rdma_rd_list,
struct iser_task, rdma_list);
list_del(&task->rdma_list);
iser_complete_task(task); /* must free, task keeps rdma buffer */
}
return 0;
}
static int iser_ib_clear_tx_list(struct iser_conn *conn)
{
struct iser_task *task;
dprintf("start\n");
while (!list_empty(&conn->resp_tx_list)) {
task = list_first_entry(&conn->resp_tx_list,
struct iser_task, tx_list);
list_del(&task->tx_list);
iser_complete_task(task); /* must free, task keeps rdma buffer */
}
return 0;
}
static int iser_ib_clear_sent_list(struct iser_conn *conn)
{
struct iser_task *task;
dprintf("start\n");
while (!list_empty(&conn->sent_list)) {
task = list_first_entry(&conn->sent_list,
struct iser_task, tx_list);
list_del(&task->tx_list); /* don't free, future completion guaranteed */
}
return 0;
}
static int iser_ib_clear_post_recv_list(struct iser_conn *conn)
{
struct iser_task *task;
dprintf("start\n");
while (!list_empty(&conn->post_recv_list)) {
task = list_first_entry(&conn->post_recv_list,
struct iser_task, recv_list);
list_del(&task->recv_list); /* don't free, future completion guaranteed */
}
return 0;
}
int iser_conn_init(struct iser_conn *conn)
{
conn->h.refcount = 0;
conn->h.state = STATE_INIT;
param_set_defaults(conn->h.session_param, session_keys);
INIT_LIST_HEAD(&conn->h.clist);
INIT_LIST_HEAD(&conn->buf_alloc_list);
INIT_LIST_HEAD(&conn->rdma_rd_list);
/* INIT_LIST_HEAD(&conn->rdma_wr_list); */
INIT_LIST_HEAD(&conn->iosubmit_list);
INIT_LIST_HEAD(&conn->resp_tx_list);
INIT_LIST_HEAD(&conn->sent_list);
INIT_LIST_HEAD(&conn->post_recv_list);
return 0;
}
/*
* Start closing connection. Transfer IB QP to error state.
* This will be followed by WC error and buffers flush events.
* We also should expect DISCONNECTED and TIMEWAIT_EXIT events.
* Only after the draining is over we are sure to have reclaimed
* all buffers (and tasks). After the RDMA CM events are collected,
* the connection QP may be destroyed, and its number may be recycled.
*/
void iser_conn_close(struct iser_conn *conn)
{
int err;
if (conn->h.state == STATE_CLOSE)
return;
dprintf("rdma_disconnect conn:%p\n", &conn->h);
err = rdma_disconnect(conn->cm_id);
if (err)
eprintf("conn:%p rdma_disconnect failed, %m\n", &conn->h);
iser_ib_clear_tx_list(conn);
list_del(&conn->conn_list);
tgt_remove_sched_event(&conn->sched_buf_alloc);
tgt_remove_sched_event(&conn->sched_rdma_rd);
tgt_remove_sched_event(&conn->sched_iosubmit);
tgt_remove_sched_event(&conn->sched_tx);
tgt_remove_sched_event(&conn->sched_post_recv);
conn->h.state = STATE_CLOSE;
eprintf("conn:%p cm_id:0x%p state: CLOSE, refcnt:%d\n",
&conn->h, conn->cm_id, conn->h.refcount);
}
static void iser_conn_force_close(struct iscsi_connection *iscsi_conn)
{
struct iser_conn *conn = ISER_CONN(iscsi_conn);
eprintf("conn:%p\n", &conn->h);
conn->h.closed = 1;
iser_conn_close(conn);
iser_conn_put(conn);
}
/*
* Called when the connection is freed, from iscsi, but won't do anything until
* all posted WRs have gone away. So also called again from RX progress when
* it notices this happens.
*/
void iser_conn_free(struct iser_conn *conn)
{
int err;
dprintf("conn:%p refcnt:%d qp:%p cm_id:%p\n",
&conn->h, conn->h.refcount, conn->qp_hndl, conn->cm_id);
assert(conn->h.refcount == 0);
iser_ib_clear_iosubmit_list(conn);
iser_ib_clear_rdma_rd_list(conn);
iser_ib_clear_tx_list(conn);
iser_ib_clear_sent_list(conn);
iser_ib_clear_post_recv_list(conn);
/* try to free unconditionally, resources freed only if necessary */
iser_free_login_resources(conn);
iser_free_ff_resources(conn);
if (conn->qp_hndl) {
err = ibv_destroy_qp(conn->qp_hndl);
if (err)
eprintf("conn:%p ibv_destroy_qp failed, %m\n", &conn->h);
}
if (conn->cm_id) {
err = rdma_destroy_id(conn->cm_id);
if (err)
eprintf("conn:%p rdma_destroy_id failed, %m\n", &conn->h);
}
/* delete from session; was put there by conn_add_to_session() */
list_del(&conn->h.clist);
if (conn->h.initiator)
free(conn->h.initiator);
if (conn->h.session)
session_put(conn->h.session);
if (conn->peer_name)
free(conn->peer_name);
if (conn->self_name)
free(conn->self_name);
conn->h.state = STATE_INIT;
free(conn);
dprintf("conn:%p freed\n", &conn->h);
}
static void iser_sched_conn_free(struct event_data *evt)
{
struct iser_conn *conn = (struct iser_conn *) evt->data;
iser_conn_free(conn);
}
int iser_conn_get(struct iser_conn *conn)
{
/* TODO: check state */
conn->h.refcount++;
dprintf("refcnt:%d\n", conn->h.refcount);
return 0;
}
int iser_conn_getn(struct iser_conn *conn, int n)
{
int new_count = conn->h.refcount + n;
dprintf("refcnt:%d + %d = %d\n", conn->h.refcount, n, new_count);
conn->h.refcount = new_count;
return 0;
}
void iser_conn_put(struct iser_conn *conn)
{
conn->h.refcount--;
dprintf("refcnt:%d\n", conn->h.refcount);
if (unlikely(conn->h.refcount == 0)) {
assert(conn->h.state == STATE_CLOSE);
tgt_add_sched_event(&conn->sched_conn_free);
}
}
static int iser_get_host_name(struct sockaddr_storage *addr, char **name)
{
int err;
char host[NI_MAXHOST];
if (name == NULL)
return -EINVAL;
err = getnameinfo((struct sockaddr *) addr, sizeof(*addr),
host, sizeof(host), NULL, 0, NI_NUMERICHOST);
if (!err) {
*name = strdup(host);
if (*name == NULL)
err = -ENOMEM;
} else
eprintf("getnameinfo failed, %m\n");
return err;
}
static int iser_show(struct iscsi_connection *iscsi_conn, char *buf,
int rest)
{
struct iser_conn *conn = ISER_CONN(iscsi_conn);
int len;
len = snprintf(buf, rest, "RDMA IP Address: %s", conn->peer_name);
return len;
}
static int iser_getsockname(struct iscsi_connection *iscsi_conn,
struct sockaddr *sa, socklen_t *len)
{
struct iser_conn *conn = ISER_CONN(iscsi_conn);
if (*len > sizeof(conn->self_addr))
*len = sizeof(conn->self_addr);
memcpy(sa, &conn->self_addr, *len);
return 0;
}
static int iser_getpeername(struct iscsi_connection *iscsi_conn,
struct sockaddr *sa, socklen_t *len)
{
struct iser_conn *conn = ISER_CONN(iscsi_conn);
if (*len > sizeof(conn->peer_addr))
*len = sizeof(conn->peer_addr);
memcpy(sa, &conn->peer_addr, *len);
return 0;
}
static void iser_cm_connect_request(struct rdma_cm_event *ev)
{
struct rdma_cm_id *cm_id = ev->id;
struct ibv_qp_init_attr qp_init_attr;
struct iser_conn *conn;
struct iser_device *dev;
int err, dev_found;
struct rdma_conn_param conn_param = {
.responder_resources = 1,
.initiator_depth = 1,
.retry_count = 5,
};
/* find device */
dev_found = 0;
list_for_each_entry(dev, &iser_dev_list, list) {
if (dev->ibv_ctxt == cm_id->verbs) {
dev_found = 1;
break;
}
}
if (!dev_found) {
dev = malloc(sizeof(*dev));
if (dev == NULL) {
eprintf("cm_id:%p malloc dev failed\n", cm_id);
goto reject;
}
dev->ibv_ctxt = cm_id->verbs;
err = iser_device_init(dev);
if (err) {
free(dev);
goto reject;
}
}
/* build a new connection structure */
conn = zalloc(sizeof(*conn));
if (!conn) {
eprintf("cm_id:%p malloc conn failed\n", cm_id);
goto reject;
}
err = iser_conn_init(conn);
if (err) {
free(conn);
goto reject;
}
dprintf("alloc conn:%p cm_id:%p\n", &conn->h, cm_id);
list_add(&conn->conn_list, &iser_conn_list);
/* relate iser and rdma connections */
conn->cm_id = cm_id;
cm_id->context = conn;
conn->dev = dev;
iser_conn_login_phase_set(conn, LOGIN_PHASE_START);
tgt_init_sched_event(&conn->sched_buf_alloc, iser_sched_buf_alloc,
conn);
tgt_init_sched_event(&conn->sched_iosubmit, iser_sched_iosubmit,
conn);
tgt_init_sched_event(&conn->sched_rdma_rd, iser_sched_rdma_rd,
conn);
tgt_init_sched_event(&conn->sched_tx, iser_sched_tx,
conn);
tgt_init_sched_event(&conn->sched_post_recv, iser_sched_post_recv,
conn);
tgt_init_sched_event(&conn->sched_conn_free, iser_sched_conn_free,
conn);
/* initiator is dst, target is src */
memcpy(&conn->peer_addr, &cm_id->route.addr.dst_addr,
sizeof(conn->peer_addr));
err = iser_get_host_name(&conn->peer_addr, &conn->peer_name);
if (err)
conn->peer_name = strdup("Unresolved");
memcpy(&conn->self_addr, &cm_id->route.addr.src_addr,
sizeof(conn->self_addr));
err = iser_get_host_name(&conn->self_addr, &conn->self_name);
if (err)
conn->self_name = strdup("Unresolved");
/* create qp */
memset(&qp_init_attr, 0, sizeof(qp_init_attr));
qp_init_attr.qp_context = conn;
/* both send and recv to the same CQ */
qp_init_attr.send_cq = dev->cq;
qp_init_attr.recv_cq = dev->cq;
qp_init_attr.cap.max_send_wr = MAX_WQE;
qp_init_attr.cap.max_recv_wr = MAX_WQE;
qp_init_attr.cap.max_send_sge = 1; /* scatter/gather entries */
qp_init_attr.cap.max_recv_sge = 1;
qp_init_attr.qp_type = IBV_QPT_RC;
/* only generate completion queue entries if requested */
qp_init_attr.sq_sig_all = 0;
err = rdma_create_qp(cm_id, dev->pd, &qp_init_attr);
if (err) {
eprintf("conn:%p cm_id:%p rdma_create_qp failed, %m\n",
&conn->h, cm_id);
goto free_conn;
}
conn->qp_hndl = cm_id->qp;
VALGRIND_MAKE_MEM_DEFINED(conn->qp_hndl, sizeof(*conn->qp_hndl));
dprintf("conn:%p cm_id:%p, created qp:%p\n", &conn->h, cm_id,
conn->qp_hndl);
/*
* Post buffers for the login phase, only.
*/
conn->rsize =
sizeof(struct iser_hdr) +
sizeof(struct iscsi_hdr) +
sizeof(struct iscsi_ecdb_ahdr) +
sizeof(struct iscsi_rlength_ahdr) + 8192;
conn->ssize = conn->rsize;
err = iser_alloc_login_resources(conn);
if (err)
goto free_conn;
conn_param.initiator_depth = dev->device_attr.max_qp_init_rd_atom;
if (conn_param.initiator_depth > ev->param.conn.initiator_depth)
conn_param.initiator_depth = ev->param.conn.initiator_depth;
/* Increment reference count to be able to wait for TIMEWAIT_EXIT
when finalizing the disconnect process */
iser_conn_get(conn);
/* now we can actually accept the connection */
err = rdma_accept(conn->cm_id, &conn_param);
if (err) {
eprintf("conn:%p cm_id:%p rdma_accept failed, %m\n",
&conn->h, cm_id);
goto free_conn;
}
conn->h.tp = &iscsi_iser;
conn->h.state = STATE_START;
dprintf("conn:%p cm_id:%p, %s -> %s, accepted\n",
&conn->h, cm_id, conn->peer_name, conn->self_name);
return;
free_conn:
iser_conn_free(conn);
reject:
err = rdma_reject(cm_id, NULL, 0);
if (err)
eprintf("cm_id:%p rdma_reject failed, %m\n", cm_id);
}
/*
* Finish putting the connection together, now that the other side
* has ACKed our acceptance. Moves it from the temp_conn to the
* iser_conn_list.
*
* Release the temporary conn_info and glue it into iser_conn_list.
*/
static void iser_cm_conn_established(struct rdma_cm_event *ev)
{
struct rdma_cm_id *cm_id = ev->id;
struct iser_conn *conn = cm_id->context;
if (conn->h.state == STATE_START) {
conn->h.state = STATE_READY;
eprintf("conn:%p cm_id:%p, %s -> %s, established\n",
&conn->h, cm_id, conn->peer_name, conn->self_name);
} else if (conn->h.state == STATE_READY) {
eprintf("conn:%p cm_id:%p, %s -> %s, "
"execute delayed login_rx now\n",
&conn->h, cm_id, conn->peer_name, conn->self_name);
iser_login_rx(conn->login_rx_task);
}
}
/*
* Handle RDMA_CM_EVENT_DISCONNECTED or an equivalent event.
* Start closing the target's side connection.
*/
static void iser_cm_disconnected(struct rdma_cm_event *ev)
{
struct rdma_cm_id *cm_id = ev->id;
struct iser_conn *conn = cm_id->context;
enum rdma_cm_event_type ev_type = ev->event;
eprintf("conn:%p cm_id:%p event:%d, %s\n", &conn->h, cm_id,
ev_type, rdma_event_str(ev_type));
iser_conn_close(conn);
}
/*
* Handle RDMA_CM_EVENT_TIMEWAIT_EXIT which is expected to be the last
* event during the lifecycle of a connection, when it had been shut down
* and the network has cleared from the remaining in-flight messages.
*/
static void iser_cm_timewait_exit(struct rdma_cm_event *ev)
{
struct rdma_cm_id *cm_id = ev->id;
struct iser_conn *conn = cm_id->context;
eprintf("conn:%p refcnt:%d cm_id:%p\n",
&conn->h, conn->h.refcount, cm_id);
if (conn->h.state == STATE_CLOSE) {
/*
* Tasks sitting in the conn->tx_list are stuck there after we
* close the conn and since each holds a reference on the conn
* we need to clean them up explicitly now. Otherwise they will
* prevent the conn from being cleaned up (since the refcount
* won't reach zero). If the conn doesn't get cleaned up, then
* along with leaking the conn itself (and all its resources),
* we'll also leak any rdma_buf's associated with the tasks.
* Since the rdma_buf pool is associated with the iser_device
* and so gets reused when a new iser_conn connection is
* established, leaking too many of those bufs will eventually
* clear the pool.
*/
iser_ib_clear_tx_list(conn);
eprintf("conn:%p refcnt:%d cm_id:%p (after cleanup)\n",
&conn->h, conn->h.refcount, cm_id);
}
/* Refcount was incremented just before accepting the connection,
typically this is the last decrement and the connection will be
released instantly */
iser_conn_put(conn);
}
/*
* Handle RDMA CM events.
*/
static void iser_handle_rdmacm(int fd __attribute__ ((unused)),
int events __attribute__ ((unused)),
void *data __attribute__ ((unused)))
{
struct rdma_cm_event *ev;
enum rdma_cm_event_type ev_type;
int err;
err = rdma_get_cm_event(rdma_evt_channel, &ev);
if (err) {
eprintf("rdma_get_cm_event failed, %m\n");
return;
}
VALGRIND_MAKE_MEM_DEFINED(ev, sizeof(*ev));
ev_type = ev->event;
switch (ev_type) {
case RDMA_CM_EVENT_CONNECT_REQUEST:
iser_cm_connect_request(ev);
break;
case RDMA_CM_EVENT_ESTABLISHED:
iser_cm_conn_established(ev);
break;
case RDMA_CM_EVENT_CONNECT_ERROR:
case RDMA_CM_EVENT_REJECTED:
case RDMA_CM_EVENT_ADDR_CHANGE:
case RDMA_CM_EVENT_DISCONNECTED:
iser_cm_disconnected(ev);
break;
case RDMA_CM_EVENT_TIMEWAIT_EXIT:
iser_cm_timewait_exit(ev);
break;
case RDMA_CM_EVENT_MULTICAST_JOIN:
case RDMA_CM_EVENT_MULTICAST_ERROR:
eprintf("UD-related event:%d, %s - ignored\n", ev_type,
rdma_event_str(ev_type));
break;
case RDMA_CM_EVENT_DEVICE_REMOVAL:
eprintf("Unsupported event:%d, %s - ignored\n", ev_type,
rdma_event_str(ev_type));
break;
case RDMA_CM_EVENT_ADDR_RESOLVED:
case RDMA_CM_EVENT_ADDR_ERROR:
case RDMA_CM_EVENT_ROUTE_RESOLVED:
case RDMA_CM_EVENT_ROUTE_ERROR:
case RDMA_CM_EVENT_CONNECT_RESPONSE:
case RDMA_CM_EVENT_UNREACHABLE:
eprintf("Active side event:%d, %s - ignored\n", ev_type,
rdma_event_str(ev_type));
break;
default:
eprintf("Illegal event:%d - ignored\n", ev_type);
break;
}
err = rdma_ack_cm_event(ev);
if (err)
eprintf("ack cm event failed, %s\n", rdma_event_str(ev_type));
}
static int iser_logout_exec(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
struct iscsi_logout_rsp *rsp_bhs =
(struct iscsi_logout_rsp *) task->pdu.bhs;
memset(rsp_bhs, 0, BHS_SIZE);
rsp_bhs->opcode = ISCSI_OP_LOGOUT_RSP;
rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL;
rsp_bhs->response = ISCSI_LOGOUT_SUCCESS;
rsp_bhs->itt = task->tag;
rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn++);
rsp_bhs->exp_cmdsn = cpu_to_be32(conn->h.exp_cmd_sn);
rsp_bhs->max_cmdsn = cpu_to_be32(conn->h.max_cmd_sn);
task->pdu.ahssize = 0;
task->pdu.membuf.size = 0;
dprintf("rsp task:%p op:%0x itt:%0x statsn:%0x\n",
task, (unsigned int)rsp_bhs->opcode,
(unsigned int)rsp_bhs->itt,
(unsigned int)rsp_bhs->statsn);
/* add the logout resp to tx list, and force all scheduled tx now */
list_add_tail(&task->tx_list, &conn->resp_tx_list);
dprintf("add to tx list, logout resp task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
task, task->tag, task->cmd_sn);
tgt_remove_sched_event(&conn->sched_tx);
iser_sched_tx(&conn->sched_tx);
return 0;
}
static int iser_nop_out_exec(struct iser_task *task)
{
struct iscsi_nopin *rsp_bhs = (struct iscsi_nopin *) task->pdu.bhs;
struct iser_conn *conn = task->conn;
rsp_bhs->opcode = ISCSI_OP_NOOP_IN;
rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL;
rsp_bhs->rsvd2 = 0;
rsp_bhs->rsvd3 = 0;
memset(rsp_bhs->lun, 0, sizeof(rsp_bhs->lun));
rsp_bhs->itt = task->tag;
rsp_bhs->ttt = ISCSI_RESERVED_TAG;
rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn);
if (task->tag != ISCSI_RESERVED_TAG)
conn->h.stat_sn++;
iser_set_rsp_stat_sn(conn->h.session, task->pdu.bhs);
memset(rsp_bhs->rsvd4, 0, sizeof(rsp_bhs->rsvd4));
task->pdu.ahssize = 0;
task->pdu.membuf.size = task->out_len; /* ping back nop-out data */
schedule_resp_tx(task, conn);
return 0;
}
static int iser_send_ping_nop_in(struct iser_task *task)
{
struct iscsi_nopin *rsp_bhs = (struct iscsi_nopin *) task->pdu.bhs;
struct iser_conn *conn = task->conn;
task->opcode = ISCSI_OP_NOOP_IN;
task->tag = ISCSI_RESERVED_TAG;
rsp_bhs->opcode = ISCSI_OP_NOOP_IN;
rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL;
rsp_bhs->rsvd2 = 0;
rsp_bhs->rsvd3 = 0;
memset(rsp_bhs->lun, 0, sizeof(rsp_bhs->lun));
rsp_bhs->itt = ISCSI_RESERVED_TAG;
rsp_bhs->ttt = ISCSI_RESERVED_TAG;
rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn);
iser_set_rsp_stat_sn(conn->h.session, task->pdu.bhs);
memset(rsp_bhs->rsvd4, 0, sizeof(rsp_bhs->rsvd4));
task->pdu.ahssize = 0;
task->pdu.membuf.size = 0;
dprintf("task:%p conn:%p\n", task, &conn->h);
schedule_resp_tx(task, conn);
return 0;
}
/*
static int iser_send_reject(struct iser_task *task, uint8 reason)
{
struct iser_conn *conn = task->conn;
struct iscsi_session *session = conn->h.session;
struct iscsi_hdr *req = (struct iscsi_hdr *) task->req.bhs;
struct iscsi_reject *rsp = (struct iscsi_reject *) task->rsp.bhs;
memset(rsp, 0, BHS_SIZE);
rsp->opcode = ISCSI_OP_REJECT;
rsp->flags = ISCSI_FLAG_CMD_FINAL;
rsp->reason = reason;
hton24(rsp->dlength, sizeof(struct iser_hdr));
rsp->ffffffff = 0xffffffff;
rsp->itt = req->itt;
rsp->statsn = cpu_to_be32(conn->h.stat_sn++);
iser_set_rsp_stat_sn(session, task->rsp.bhs);
task->rsp.ahssize = 0;
task->rsp.membuf.addr = task->req.bhs;
task->rsp.membuf.size = sizeof(struct iser_hdr);
schedule_resp_tx(task, conn);
return 0;
}
*/
static int iser_tm_exec(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
struct iscsi_session *session = conn->h.session;
struct iscsi_tm *req_bhs = (struct iscsi_tm *) task->pdu.bhs;
int fn = 0;
int err = 0;
eprintf("conn:%p TM itt:0x%x cmdsn:0x%x "
"ref.itt:0x%x ref.cmdsn:0x%x "
"exp.cmdsn:0x%x "
"lun:0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
&conn->h,
be32_to_cpu(req_bhs->itt), be32_to_cpu(req_bhs->cmdsn),
be32_to_cpu(req_bhs->rtt), be32_to_cpu(req_bhs->refcmdsn),
be32_to_cpu(req_bhs->exp_statsn),
req_bhs->lun[0], req_bhs->lun[1], req_bhs->lun[2], req_bhs->lun[3],
req_bhs->lun[4], req_bhs->lun[5], req_bhs->lun[6], req_bhs->lun[7]);
switch (req_bhs->flags & ISCSI_FLAG_TM_FUNC_MASK) {
case ISCSI_TM_FUNC_ABORT_TASK:
fn = ABORT_TASK;
break;
case ISCSI_TM_FUNC_ABORT_TASK_SET:
fn = ABORT_TASK_SET;
break;
case ISCSI_TM_FUNC_CLEAR_ACA:
fn = CLEAR_TASK_SET;
break;
case ISCSI_TM_FUNC_CLEAR_TASK_SET:
fn = CLEAR_ACA;
break;
case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET:
fn = LOGICAL_UNIT_RESET;
break;
case ISCSI_TM_FUNC_TARGET_WARM_RESET:
case ISCSI_TM_FUNC_TARGET_COLD_RESET:
case ISCSI_TM_FUNC_TASK_REASSIGN:
err = ISCSI_TMF_RSP_NOT_SUPPORTED;
eprintf("unsupported TMF %d\n",
req_bhs->flags & ISCSI_FLAG_TM_FUNC_MASK);
break;
default:
err = ISCSI_TMF_RSP_REJECTED;
eprintf("unknown TMF %d\n",
req_bhs->flags & ISCSI_FLAG_TM_FUNC_MASK);
}
if (err)
task->result = err;
else {
int ret;
ret = target_mgmt_request(session->target->tid, session->tsih,
(unsigned long) task, fn, req_bhs->lun,
req_bhs->rtt, 0);
set_task_in_scsi(task);
iser_conn_get(conn);
switch (ret) {
case MGMT_REQ_QUEUED:
break;
case MGMT_REQ_FAILED:
case MGMT_REQ_DONE:
clear_task_in_scsi(task);
iser_conn_put(conn);
break;
}
}
return err;
}
static int iser_tm_done(struct mgmt_req *mreq)
{
struct iser_task *task = (struct iser_task *) (unsigned long) mreq->mid;
struct iser_conn *conn = task->conn;
struct iscsi_session *session = conn->h.session;
struct iscsi_tm_rsp *rsp_bhs =
(struct iscsi_tm_rsp *) task->pdu.bhs;
memset(rsp_bhs, 0, sizeof(*rsp_bhs));
rsp_bhs->opcode = ISCSI_OP_SCSI_TMFUNC_RSP;
rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL;
rsp_bhs->itt = task->tag;
switch (mreq->result) {
case 0:
rsp_bhs->response = ISCSI_TMF_RSP_COMPLETE;
break;
case -EINVAL:
rsp_bhs->response = ISCSI_TMF_RSP_NOT_SUPPORTED;
break;
case -EEXIST:
/*
* the command completed or we could not find it so
* we retrun no task here
*/
rsp_bhs->response = ISCSI_TMF_RSP_NO_TASK;
break;
default:
rsp_bhs->response = ISCSI_TMF_RSP_REJECTED;
break;
}
rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn++);
iser_set_rsp_stat_sn(session, task->pdu.bhs);
/* Must be zero according to 10.6.3 */
task->pdu.ahssize = 0;
task->pdu.membuf.size = 0;
schedule_resp_tx(task, conn);
return 0;
}
static int scsi_cmd_attr(unsigned int flags)
{
int attr;
switch (flags & ISCSI_FLAG_CMD_ATTR_MASK) {
case ISCSI_ATTR_UNTAGGED:
case ISCSI_ATTR_SIMPLE:
attr = MSG_SIMPLE_TAG;
break;
case ISCSI_ATTR_HEAD_OF_QUEUE:
attr = MSG_HEAD_TAG;
break;
case ISCSI_ATTR_ORDERED:
default:
attr = MSG_ORDERED_TAG;
}
return attr;
}
static void iser_task_free_dout_tasks(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
struct iser_task *dout_task, *tnext;
list_for_each_entry_safe(dout_task, tnext, &task->dout_task_list, dout_task_list) {
list_del(&dout_task->dout_task_list);
iser_task_del_out_buf(task, &dout_task->pdu.membuf);
schedule_post_recv(dout_task, conn);
}
}
static void iser_scsi_cmd_iosubmit(struct iser_task *task, int not_last)
{
struct iscsi_cmd *req_bhs = (struct iscsi_cmd *) task->pdu.bhs;
struct scsi_cmd *scmd = &task->scmd;
struct iser_conn *conn = task->conn;
struct iscsi_session *session = conn->h.session;
struct iser_membuf *data_buf;
scmd->state = 0;
if (not_last)
set_cmd_not_last(scmd);
scsi_set_in_length(scmd, 0);
scsi_set_out_length(scmd, 0);
if (task->is_write) {
/* It's either the last buffer, which is RDMA,
or the only buffer */
data_buf = list_entry(task->out_buf_list.prev,
struct iser_membuf, task_list);
/* ToDo: multiple RDMA-Write buffers */
if (task->out_buf_num > 1) {
unsigned char *ptr = data_buf->addr;
struct iser_membuf *cur_buf;
list_for_each_entry(cur_buf, &task->out_buf_list, task_list) {
if (cur_buf->rdma)
break;
memcpy(ptr, cur_buf->addr, cur_buf->size);
ptr += cur_buf->size;
}
iser_task_free_dout_tasks(task);
}
scsi_set_out_buffer(scmd, data_buf->addr);
scsi_set_out_length(scmd, task->out_len);
}
if (task->is_read) {
/* ToDo: multiple RDMA-Read buffers */
data_buf = list_entry(task->in_buf_list.next,
struct iser_membuf, task_list);
scsi_set_in_buffer(scmd, data_buf->addr);
scsi_set_in_length(scmd, task->in_len);
}
scmd->cmd_itn_id = session->tsih;
scmd->scb = req_bhs->cdb;
scmd->scb_len = sizeof(req_bhs->cdb);
memcpy(scmd->lun, req_bhs->lun, sizeof(scmd->lun));
scmd->attribute = scsi_cmd_attr(req_bhs->flags);
scmd->tag = task->tag;
scmd->result = 0;
scmd->mreq = NULL;
scmd->sense_len = 0;
dprintf("task:%p tag:0x%04"PRIx64 "\n", task, task->tag);
set_task_in_scsi(task);
iser_conn_get(conn);
target_cmd_queue(session->target->tid, scmd);
}
static int iser_scsi_cmd_done(uint64_t nid, int result,
struct scsi_cmd *scmd)
{
struct iser_task *task = container_of(scmd, struct iser_task, scmd);
struct iscsi_cmd_rsp *rsp_bhs =
(struct iscsi_cmd_rsp *) task->pdu.bhs;
struct iser_conn *conn = task->conn;
struct iscsi_session *session = conn->h.session;
unsigned char sense_len = scmd->sense_len;
assert(nid == scmd->cmd_itn_id);
if (unlikely(conn->h.state != STATE_FULL)) {
/* Connection is closed, but its resources are not released
to allow receiving completion of such late tasks.
When all tasks are released, and connection refcnt
drops to zero, then all the resources can be freed. */
iser_complete_task(task);
return 0;
}
rsp_bhs->opcode = ISCSI_OP_SCSI_CMD_RSP;
rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL;
rsp_bhs->response = ISCSI_STATUS_CMD_COMPLETED;
rsp_bhs->cmd_status = scsi_get_result(scmd);
*((uint64_t *) rsp_bhs->rsvd) = (uint64_t) 0;
rsp_bhs->itt = task->tag;
rsp_bhs->rsvd1 = 0;
rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn++);
iser_set_rsp_stat_sn(session, task->pdu.bhs);
rsp_bhs->exp_datasn = 0;
iscsi_rsp_set_residual(rsp_bhs, scmd);
if (task->is_read) {
task->rdma_wr_remains = scsi_get_in_transfer_len(scmd);
task->rdma_wr_sz = scsi_get_in_transfer_len(scmd);
}
task->pdu.ahssize = 0;
task->pdu.membuf.size = 0;
if (unlikely(sense_len > 0)) {
struct iscsi_sense_data *sense =
(struct iscsi_sense_data *) task->pdu.membuf.addr;
sense->length = cpu_to_be16(sense_len);
/* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */
assert(sense_len + 2 <= conn->rsize);
memcpy(sense->data, scmd->sense_buffer, sense_len);
task->pdu.membuf.size = sense_len + sizeof(*sense);
}
dprintf("task:%p tag:0x%04"PRIx64 " status:%x statsn:%d flags:0x%x "
"in_len:%d out_len:%d rsd:%d birsd:%d\n",
task, task->tag, rsp_bhs->cmd_status,
conn->h.stat_sn-1, rsp_bhs->flags,
scsi_get_in_length(scmd), scsi_get_out_length(scmd),
ntohl(rsp_bhs->residual_count),
ntohl(rsp_bhs->bi_residual_count));
schedule_resp_tx(task, conn);
return 0;
}
static void iser_sched_buf_alloc(struct event_data *evt)
{
struct iser_conn *conn = (struct iser_conn *) evt->data;
struct iser_task *task;
int err;
while (!list_empty(&conn->buf_alloc_list)) {
task = list_first_entry(&conn->buf_alloc_list,
struct iser_task,
exec_list);
err = iser_task_alloc_rdma_bufs(task);
if (likely(!err))
list_del(&task->exec_list);
else {
if (err != -ENOMEM)
iser_conn_close(conn);
break;
}
}
}
static inline int task_can_batch(struct iser_task *task)
{
struct iscsi_cmd *req_bhs = (struct iscsi_cmd *) task->pdu.bhs;
switch (req_bhs->cdb[0]) {
case SYNCHRONIZE_CACHE:
case SYNCHRONIZE_CACHE_16:
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
case READ_6:
case READ_10:
case READ_12:
case READ_16:
return 1;
default:
return 0;
}
}
static void iser_sched_iosubmit(struct event_data *evt)
{
struct iser_conn *conn = (struct iser_conn *) evt->data;
struct iser_task *task, *next_task;
int last_in_batch;
list_for_each_entry_safe(task, next_task, &conn->iosubmit_list, exec_list) {
if (&next_task->exec_list == &conn->iosubmit_list)
last_in_batch = 1; /* end of list */
else
last_in_batch = (task_can_batch(task) &&
task_can_batch(next_task)) ? 0 : 1;
list_del(&task->exec_list);
iser_scsi_cmd_iosubmit(task, !last_in_batch);
}
}
static void iser_sched_rdma_rd(struct event_data *evt)
{
struct iser_conn *conn = (struct iser_conn *) evt->data;
struct iser_work_req *first_wr = NULL;
struct iser_task *prev_task = NULL;
struct iser_task *task = NULL;
int num_reqs = 0;
if (unlikely(conn->h.state != STATE_FULL)) {
dprintf("conn:%p closing, ignoring rdma_rd\n", conn);
/* ToDo: free all tasks and buffers */
return;
}
while (!list_empty(&conn->rdma_rd_list)) {
task = list_first_entry(&conn->rdma_rd_list,
struct iser_task, rdma_list);
list_del(&task->rdma_list);
iser_prep_rdma_rd_send_req(task, NULL, 1);
if (first_wr == NULL)
first_wr = &task->rdmad;
else
prev_task->rdmad.send_wr.next = &task->rdmad.send_wr;
prev_task = task;
num_reqs++;
}
if (prev_task) {
prev_task->rdmad.send_wr.next = NULL;
/* submit the chain of rdma-rd requests, start from the first */
iser_post_send(conn, first_wr, num_reqs);
/* ToDo: error handling */
}
}
static void iser_sched_tx(struct event_data *evt)
{
struct iser_conn *conn = (struct iser_conn *) evt->data;
struct iser_work_req *first_wr = NULL;
struct iser_task *prev_task = NULL;
struct iser_task *task;
struct iser_work_req *cur_send_wr;
int num_reqs = 0;
if (unlikely(conn->h.state == STATE_CLOSE)) {
dprintf("conn:%p closing, ignoring tx\n", conn);
return;
}
while (!list_empty(&conn->resp_tx_list)) {
task = list_first_entry(&conn->resp_tx_list,
struct iser_task,
tx_list);
list_del(&task->tx_list);
list_add_tail(&task->tx_list, &conn->sent_list);
if (task->is_read && task->rdma_wr_remains) {
iser_prep_rdma_wr_send_req(task, &task->txd, 1);
cur_send_wr = &task->rdmad;
num_reqs++;
} else
cur_send_wr = &task->txd;
if (prev_task == NULL)
first_wr = cur_send_wr;
else
prev_task->txd.send_wr.next = &cur_send_wr->send_wr;
iser_prep_resp_send_req(task, NULL, 1);
prev_task = task;
num_reqs++;
}
if (prev_task) {
prev_task->txd.send_wr.next = NULL;
/* submit the chain of rdma-wr & tx reqs, start from the first */
iser_post_send(conn, first_wr, num_reqs);
/* ToDo: error handling */
}
}
static void iser_sched_post_recv(struct event_data *evt)
{
struct iser_conn *conn = (struct iser_conn *) evt->data;
struct iser_task *first_task = NULL;
struct iser_task *prev_task = NULL;
struct iser_task *task;
int num_recv_bufs = 0;
if (unlikely(conn->h.state != STATE_FULL)) {
dprintf("conn:%p closing, ignoring post recv\n", conn);
return;
}
while (!list_empty(&conn->post_recv_list)) {
task = list_first_entry(&conn->post_recv_list,
struct iser_task,
recv_list);
list_del(&task->recv_list);
if (prev_task == NULL)
first_task = task;
else
prev_task->rxd.recv_wr.next = &task->rxd.recv_wr;
prev_task = task;
num_recv_bufs++;
}
if (prev_task) {
prev_task->rxd.recv_wr.next = NULL;
/* post the chain of recv buffers, start from the first */
iser_post_recv(conn, first_task, num_recv_bufs);
/* ToDo: error handling */
}
}
static int iser_task_handle_ahs(struct iser_task *task)
{
struct iscsi_connection *conn = &task->conn->h;
struct iscsi_cmd *req_bhs = (struct iscsi_cmd *) task->pdu.bhs;
int ahslen = task->pdu.ahssize;
void *ahs = task->pdu.ahs;
struct scsi_cmd *scmd = &task->scmd;
enum data_direction dir = scsi_get_data_dir(scmd);
if (ahslen >= 4) {
struct iscsi_ecdb_ahdr *ahs_extcdb = ahs;
if (ahs_extcdb->ahstype == ISCSI_AHSTYPE_CDB) {
int extcdb_len = ntohs(ahs_extcdb->ahslength) - 1;
int total_cdb_len = sizeof(req_bhs->cdb) + extcdb_len;
unsigned char *p;
if (4 + extcdb_len > ahslen) {
eprintf("AHS len:%d too short for extcdb %d\n",
ahslen, extcdb_len);
return -EINVAL;
}
if (total_cdb_len > 260) {
eprintf("invalid extcdb len:%d\n", extcdb_len);
return -EINVAL;
}
p = malloc(total_cdb_len);
if (!p) {
eprintf("failed to allocate extdata len:%d\n", total_cdb_len);
return -ENOMEM;
}
memcpy(p, req_bhs->cdb, sizeof(req_bhs->cdb));
p += sizeof(req_bhs->cdb);
memcpy(p, ahs_extcdb->ecdb, extcdb_len);
task->extdata = p;
scmd->scb = ahs;
scmd->scb_len = total_cdb_len;
ahs += 4 + extcdb_len;
ahslen -= 4 + extcdb_len;
}
}
if (dir == DATA_BIDIRECTIONAL && ahslen >= 8) {
struct iscsi_rlength_ahdr *ahs_bidi = ahs;
if (ahs_bidi->ahstype == ISCSI_AHSTYPE_RLENGTH) {
uint32_t in_length = ntohl(ahs_bidi->read_length);
if (in_length)
task->in_len = roundup(in_length,
conn->tp->data_padding);
dprintf("bidi read len:%u, padded:%u\n",
in_length, task->in_len);
}
}
return 0;
}
static int iser_scsi_cmd_rx(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
struct iscsi_session *session = conn->h.session;
struct iscsi_cmd *req_bhs = (struct iscsi_cmd *) task->pdu.bhs;
unsigned int flags = req_bhs->flags;
uint32_t imm_data_sz = ntoh24(req_bhs->dlength);
uint32_t xfer_sz = ntohl(req_bhs->data_length);
int err = 0;
task->is_read = flags & ISCSI_FLAG_CMD_READ;
task->is_write = flags & ISCSI_FLAG_CMD_WRITE;
if (task->is_write) {
task->out_len = xfer_sz;
if (!task->is_read) {
scsi_set_data_dir(&task->scmd, DATA_WRITE);
/* reset irrelevant fields */
task->in_len = 0;
task->rdma_wr_sz = 0;
task->rdma_wr_remains = 0;
} else
scsi_set_data_dir(&task->scmd, DATA_BIDIRECTIONAL);
} else {
if (likely(task->is_read)) {
task->in_len = xfer_sz;
scsi_set_data_dir(&task->scmd, DATA_READ);
/* reset irrelevant fields */
task->out_len = 0;
task->unsol_sz = 0;
task->unsol_remains = 0;
task->rdma_rd_sz = 0;
task->rdma_rd_remains = 0;
} else {
scsi_set_data_dir(&task->scmd, DATA_NONE);
task->out_len = 0;
task->in_len = 0;
}
}
if (task->pdu.ahssize) {
err = iser_task_handle_ahs(task); /* may set task->in_len */
if (err)
goto out;
}
if (task->is_write) {
/* add immediate data to the task */
if (!conn->h.session_param[ISCSI_PARAM_INITIAL_R2T_EN].val) {
task->unsol_sz = conn->h.session_param[ISCSI_PARAM_FIRST_BURST].val;
if (task->out_len < task->unsol_sz)
task->unsol_sz = task->out_len;
} else
task->unsol_sz = 0;
if (conn->h.session_param[ISCSI_PARAM_IMM_DATA_EN].val) {
if (imm_data_sz > 0) {
if (task->unsol_sz == 0)
task->unsol_sz = imm_data_sz;
iser_task_add_out_pdu_buf(task, &task->pdu.membuf, 0);
}
} else if (imm_data_sz > 0) {
eprintf("ImmediateData disabled but received\n");
err = -EINVAL;
goto out;
}
/* immediate data is the first chunk of the unsolicited data */
task->unsol_remains = task->unsol_sz - imm_data_sz;
/* rdma-reads cover the entire solicited data */
task->rdma_rd_sz = task->out_len - task->unsol_sz;
task->rdma_rd_remains = task->rdma_rd_sz;
/* ToDo: multiple RDMA-Write buffers */
/* task->rdma_rd_offset = task->unsol_sz; */
}
if (task->is_read) {
task->rdma_wr_sz = task->in_len;
task->rdma_wr_remains = task->in_len;
}
list_add_tail(&task->session_list, &session->cmd_list);
out:
dprintf("task:%p tag:0x%04"PRIx64 " scsi_op:0x%x %s%s in_len:%d out_len:%d "
"imm_sz:%d unsol_sz:%d cmdsn:0x%x expcmdsn:0x%x\n",
task, task->tag, req_bhs->cdb[0],
task->is_read ? "rd" : "", task->is_write ? "wr" : "",
task->in_len, task->out_len, imm_data_sz, task->unsol_sz,
task->cmd_sn, session->exp_cmd_sn);
return err;
}
static int iser_data_out_rx(struct iser_task *dout_task)
{
struct iser_conn *conn = dout_task->conn;
struct iscsi_session *session = conn->h.session;
struct iscsi_data *req_bhs =
(struct iscsi_data *) dout_task->pdu.bhs;
struct iser_task *task;
int err = 0;
list_for_each_entry(task, &session->cmd_list, session_list) {
if (task->tag == req_bhs->itt)
goto found;
}
return -EINVAL;
found:
iser_task_add_out_pdu_buf(task, &dout_task->pdu.membuf,
be32_to_cpu(req_bhs->offset));
list_add_tail(&dout_task->dout_task_list, &task->dout_task_list);
/* ToDo: BUG!!! add an indication that it's data-out task so that
it can be released when the buffer is released */
task->unsol_remains -= ntoh24(req_bhs->dlength);
dprintf("task:%p tag:0x%04"PRIx64 ", dout taskptr:%p out_len:%d "
"uns_rem:%d rdma_rd_rem:%d expcmdsn:0x%x\n",
task, task->tag, dout_task, task->out_len,
task->unsol_remains, task->rdma_rd_remains,
session->exp_cmd_sn);
/* ToDo: look at the task counters !!! */
if (req_bhs->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
if (req_bhs->flags & ISCSI_FLAG_CMD_FINAL) {
if (!task_pending(task)) {
/* ToDo: this condition is weird ... */
if (task->rdma_rd_remains == 0 && task->unsol_remains == 0)
schedule_task_iosubmit(task, conn);
}
}
} else {
if (!(req_bhs->flags & ISCSI_FLAG_CMD_FINAL))
return err;
if (task->rdma_rd_remains == 0 && task->unsol_remains == 0)
schedule_task_iosubmit(task, conn);
}
return err;
}
/* usually rx_task is passed directly from the rx handler,
but due to the connection establishment event-data race
the first login task may be saved and injected afterwards.
*/
static void iser_login_rx(struct iser_task *rx_task)
{
struct iser_conn *conn = rx_task->conn;
struct iser_task *tx_task = conn->login_tx_task;
struct iscsi_login_rsp *rsp_bhs =
(struct iscsi_login_rsp *)tx_task->pdu.bhs;
if (conn->h.state == STATE_START) {
eprintf("conn:%p, not established yet, delaying login_rx\n",
&conn->h);
conn->h.state = STATE_READY;
conn->login_rx_task = rx_task;
return;
}
iser_login_exec(&conn->h, &rx_task->pdu, &tx_task->pdu);
if (rsp_bhs->status_class) {
eprintf("conn:%p, login failed, class:%0x detail:%0x\n",
&conn->h, rsp_bhs->status_class, rsp_bhs->status_detail);
}
if (conn->login_phase == LOGIN_PHASE_LAST_SEND)
dprintf("transitioning to full-feature, no repost\n");
else
iser_post_recv(conn, rx_task, 1);
schedule_resp_tx(tx_task, conn);
}
static int iser_nop_out_rx(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
struct iscsi_nopout *req_bhs =
(struct iscsi_nopout *) task->pdu.bhs;
int err = 0;
if (req_bhs->ttt != cpu_to_be32(ISCSI_RESERVED_TAG)) {
/*
* When sending NOP-In, we don't request a NOP-Out .
* by sending a valid Target Tranfer Tag.
* See iser_send_ping_nop_in() and 10.18.2 in the draft 20.
*/
eprintf("conn:%p task:%p initiator bug, ttt not reserved\n",
&conn->h, task);
err = -ISCSI_REASON_PROTOCOL_ERROR;
goto reject;
}
if (req_bhs->itt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
if (req_bhs->opcode & ISCSI_OP_IMMEDIATE) {
dprintf("No response to Nop-Out\n");
iser_post_recv(conn, task, 1);
return -EAGAIN; /* ToDo: fix the ret codes */
} else {
eprintf("conn:%p task:%p initiator bug, "
"itt reserved with no imm. flag\n", &conn->h, task);
err = -ISCSI_REASON_PROTOCOL_ERROR;
goto reject;
}
}
task->out_len = ntoh24(req_bhs->dlength);
dprintf("conn:%p nop-out task:%p cmdsn:0x%x data_sz:%d\n",
&conn->h, task, task->cmd_sn, task->out_len);
return 0;
reject: /* ToDo: prepare and send reject pdu */
/* iser_send_reject(task, ISCSI_REASON_INVALID_PDU_FIELD); */
return err;
}
static int iser_task_delivery(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
int err;
switch (task->opcode) {
case ISCSI_OP_SCSI_CMD:
err = iser_task_alloc_rdma_bufs(task);
if (unlikely(err == -ENOMEM))
list_add_tail(&task->exec_list,
&task->conn->buf_alloc_list);
break;
case ISCSI_OP_NOOP_OUT:
err = iser_nop_out_exec(task);
break;
case ISCSI_OP_LOGOUT:
err = iser_logout_exec(task);
break;
case ISCSI_OP_SCSI_TMFUNC:
err = iser_tm_exec(task);
break;
case ISCSI_OP_TEXT:
err = iser_text_exec(&conn->h, &task->pdu, &conn->text_tx_task->pdu);
schedule_resp_tx(conn->text_tx_task, conn);
break;
default:
eprintf("Internal error: Unexpected op:0x%x\n", task->opcode);
err = -EINVAL;
break;
}
return err;
}
static inline int cmdsn_cmp(uint32_t sn1, uint32_t sn2)
{
if (sn1 == sn2)
return 0;
return ((int32_t)sn1 - (int32_t)sn2) > 0 ? 1 : -1;
}
/* queues the task according to cmd-sn, no exec here */
static int iser_queue_task(struct iscsi_session *session,
struct iser_task *task)
{
uint32_t cmd_sn = task->cmd_sn;
struct list_head *cmp_entry;
int err;
if (unlikely(task->is_immediate)) {
dprintf("exec imm task task:%p tag:0x%0"PRIx64 " cmd_sn:0x%x\n",
task, task->tag, cmd_sn);
err = iser_task_delivery(task);
if (likely(!err || err == -ENOMEM))
return 0;
else
return err;
}
/* if the current command is the expected one, exec it
and all others possibly acumulated on the queue */
while (session->exp_cmd_sn == cmd_sn) {
session->exp_cmd_sn++;
dprintf("exec task:%p cmd_sn:0x%x\n", task, cmd_sn);
err = iser_task_delivery(task);
if (unlikely(err && err != -ENOMEM)) {
/* when no free buffers remains, the task will wait
on queue, so it is not a real error, but we should
not attempt to start other tasks until more
memory becomes available */
return err;
/* ToDo: what if there are more tasks in case of error */
}
if (list_empty(&session->pending_cmd_list))
return 0;
task = list_first_entry(&session->pending_cmd_list,
struct iser_task, exec_list);
list_del(&task->exec_list);
clear_task_pending(task);
cmd_sn = be32_to_cpu(task->pdu.bhs->statsn);
}
/* cmd_sn > (exp_cmd_sn+max_queue_cmd), i.e. beyond allowed window */
if (cmdsn_cmp(cmd_sn,
session->exp_cmd_sn+session->max_queue_cmd) == 1) {
eprintf("unexpected cmd_sn:0x%x, max:0x%x\n",
cmd_sn, session->exp_cmd_sn+session->max_queue_cmd);
return -EINVAL;
}
/* insert the current task, ordered by cmd_sn */
list_for_each_prev(cmp_entry, &session->pending_cmd_list) {
struct iser_task *cmp_task;
uint32_t cmp_cmd_sn;
int cmp_res;
cmp_task = list_entry(cmp_entry, struct iser_task, exec_list);
cmp_cmd_sn = cmp_task->cmd_sn;
cmp_res = cmdsn_cmp(cmd_sn, cmp_cmd_sn);
if (cmp_res == 1) { /* cmd_sn > cmp_cmd_sn */
dprintf("inserted cmdsn:0x%x after cmdsn:0x%x\n",
cmd_sn, cmp_cmd_sn);
break;
} else if (cmp_res == -1) { /* cmd_sn < cmp_cmd_sn */
dprintf("inserting cmdsn:0x%x skip cmdsn:0x%x\n",
cmd_sn, cmp_cmd_sn);
continue;
} else { /* cmd_sn == cmp_cmd_sn */
eprintf("duplicate cmd_sn:0x%x, exp:%u\n",
cmd_sn, session->exp_cmd_sn);
return -EINVAL;
}
}
list_add(&task->exec_list, cmp_entry);
set_task_pending(task);
return 0;
}
static int iser_parse_req_headers(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
struct iser_hdr *iser_hdr = task->pdu.iser_hdr;
struct iscsi_hdr *iscsi_hdr = task->pdu.bhs;
unsigned pdu_dlength = ntoh24(iscsi_hdr->dlength);
unsigned pdu_len = pdu_dlength + sizeof(struct iscsi_hdr);
int err = -1;
switch (iser_hdr->flags & 0xF0) {
case ISCSI_CTRL:
if (iser_hdr->flags & ISER_RSV) {
task->rem_read_stag =
be32_to_cpu(iser_hdr->read_stag);
task->rem_read_va = be64_to_cpu(iser_hdr->read_va);
dprintf("task:%p rstag:0x%x va:0x%"PRIx64 "\n", task,
task->rem_read_stag, task->rem_read_va);
}
if (iser_hdr->flags & ISER_WSV) {
task->rem_write_stag =
be32_to_cpu(iser_hdr->write_stag);
task->rem_write_va =
be64_to_cpu(iser_hdr->write_va);
dprintf("task:%p wstag:0x%x va:0x%"PRIx64 "\n", task,
task->rem_write_stag, task->rem_write_va);
}
err = 0;
break;
case ISER_HELLO:
dprintf("iSER Hello message??\n");
break;
default:
eprintf("malformed iser iser_hdr, flags 0x%02x\n",
iser_hdr->flags);
break;
}
task->opcode = iscsi_hdr->opcode & ISCSI_OPCODE_MASK;
task->is_immediate = iscsi_hdr->opcode & ISCSI_OP_IMMEDIATE ? 1 : 0;
task->is_read = 0; /* valid for cmds only */
task->is_write = 0; /* valid for cmds only */
task->pdu.ahssize = iscsi_hdr->hlength * 4;
task->pdu.membuf.addr += task->pdu.ahssize;
pdu_len += task->pdu.ahssize;
task->pdu.membuf.size = pdu_dlength;
task->pdu.membuf.rdma = 0;
task->tag = iscsi_hdr->itt;
task->cmd_sn = be32_to_cpu(iscsi_hdr->statsn);
conn->h.exp_stat_sn = be32_to_cpu(iscsi_hdr->exp_statsn);
iscsi_update_conn_stats_rx(&conn->h, pdu_len, task->opcode);
return err;
}
static int iser_rx_handler_non_ff(struct iser_task *task)
{
struct iser_conn *conn = task->conn;
int err = 0;
switch (conn->h.state) {
case STATE_START:
case STATE_READY:
case STATE_SECURITY:
case STATE_SECURITY_AUTH:
case STATE_SECURITY_DONE:
case STATE_SECURITY_LOGIN:
case STATE_SECURITY_FULL:
case STATE_LOGIN:
case STATE_LOGIN_FULL:
if (task->opcode == ISCSI_OP_LOGIN) {
dprintf("login rx, conn:%p\n", conn);
iser_login_rx(task);
} else {
eprintf("non-login pdu during login phase, "
"conn:%p opcode:0x%0x\n",
conn, task->opcode);
err = -EINVAL;
}
break;
case STATE_EXIT:
case STATE_CLOSE:
dprintf("ignored rx, while conn:%p closing\n", conn);
break;
default:
dprintf("ignored rx, conn:%p unexpected state:%d\n",
conn, conn->h.state);
break;
}
return err;
}
static void iser_rx_handler(struct iser_work_req *rxd)
{
struct iser_task *task = rxd->task;
struct iser_conn *conn = task->conn;
int queue_task = 1;
int err = 0;
iser_conn_put(conn);
err = iser_parse_req_headers(task);
if (unlikely(err))
goto out;
if (unlikely(conn->h.state != STATE_FULL)) {
err = iser_rx_handler_non_ff(task);
goto out;
}
INIT_LIST_HEAD(&task->in_buf_list);
task->in_buf_num = 0;
INIT_LIST_HEAD(&task->out_buf_list);
task->out_buf_num = 0;
if (likely(task->opcode == ISCSI_OP_SCSI_CMD))
err = iser_scsi_cmd_rx(task);
else {
switch (task->opcode) {
case ISCSI_OP_SCSI_DATA_OUT:
err = iser_data_out_rx(task);
queue_task = 0;
break;
case ISCSI_OP_NOOP_OUT:
err = iser_nop_out_rx(task);
break;
case ISCSI_OP_LOGOUT:
dprintf("logout rx\n");
break;
case ISCSI_OP_SCSI_TMFUNC:
dprintf("tmfunc rx\n");
break;
case ISCSI_OP_TEXT:
dprintf("text rx\n");
err = iser_task_delivery(task);
queue_task = 0;
break;
case ISCSI_OP_SNACK:
eprintf("Cannot handle SNACK yet\n");
err = -EINVAL;
break;
default:
eprintf("Unknown op 0x%x\n", task->opcode);
err = -EINVAL;
break;
}
}
if (likely(!err && queue_task))
err = iser_queue_task(conn->h.session, task);
out:
if (unlikely(err)) {
eprintf("conn:%p task:%p err:%d, closing\n",
&conn->h, task, err);
iser_conn_close(conn);
}
}
static void iser_login_tx_complete(struct iser_conn *conn)
{
switch (conn->h.state) {
case STATE_SECURITY_LOGIN:
conn->h.state = STATE_LOGIN;
break;
case STATE_SECURITY_FULL: /* fall through */
case STATE_LOGIN_FULL:
dprintf("conn:%p, last login send completed\n", conn);
conn->h.state = STATE_FULL;
iser_conn_login_phase_set(conn, LOGIN_PHASE_FF);
iser_free_login_resources(conn);
break;
}
}
static void iser_tx_complete_handler(struct iser_work_req *txd)
{
struct iser_task *task = txd->task;
struct iser_conn *conn = task->conn;
int opcode = task->pdu.bhs->opcode & ISCSI_OPCODE_MASK;
iscsi_update_conn_stats_tx(&conn->h, txd->sge.length, opcode);
dprintf("conn:%p task:%p tag:0x%04"PRIx64 " opcode:0x%x\n",
&conn->h, task, task->tag, opcode);
iser_conn_put(conn);
list_del(&task->tx_list); /* remove from conn->sent_list */
if (unlikely(task->unsolicited)) {
conn->nop_in_task = task;
return;
}
iser_complete_task(task);
if (unlikely(conn->h.state != STATE_FULL))
iser_login_tx_complete(conn);
else
schedule_post_recv(task, conn);
}
static void iser_rdma_wr_complete_handler(struct iser_work_req *rdmad)
{
struct iser_task *task = rdmad->task;
struct iser_conn *conn = task->conn;
iscsi_update_conn_stats_tx(&conn->h, rdmad->sge.length, ISCSI_OP_SCSI_DATA_IN);
dprintf("conn:%p task:%p tag:0x%04"PRIx64 "\n",
&conn->h, task, task->tag);
iser_conn_put(conn);
/* no need to remove from conn->sent_list, it is done in
iser_tx_complete_handler(), as rdma-wr is followed by tx */
}
static void iser_rdma_rd_complete_handler(struct iser_work_req *rdmad)
{
struct iser_task *task = rdmad->task;
struct iser_conn *conn = task->conn;
iscsi_update_conn_stats_rx(&conn->h, rdmad->sge.length, ISCSI_OP_SCSI_DATA_OUT);
task->rdma_rd_remains -= rdmad->sge.length;
dprintf("conn:%p task:%p tag:0x%04"PRIx64 ", rems rdma:%d unsol:%d\n",
&conn->h, task, task->tag, task->rdma_rd_remains,
task->unsol_remains);
iser_conn_put(conn);
/* no need to remove from a list, was removed before rdma-rd */
if (unlikely(conn->h.state != STATE_FULL))
return;
if (task->rdma_rd_remains == 0 && task->unsol_remains == 0)
schedule_task_iosubmit(task, conn);
}
/*
* Deal with just one work completion.
*/
static void handle_wc(struct ibv_wc *wc)
{
struct iser_work_req *req = ptr_from_int64(wc->wr_id);
struct iser_task *task;
struct iser_conn *conn;
dprintf("%s complete, wr_id:%p len:%d\n",
iser_ib_op_to_str(req->iser_ib_op), req, wc->byte_len);
switch (req->iser_ib_op) {
case ISER_IB_RECV:
iser_rx_handler(req);
break;
case ISER_IB_SEND:
iser_tx_complete_handler(req);
break;
case ISER_IB_RDMA_WRITE:
iser_rdma_wr_complete_handler(req);
break;
case ISER_IB_RDMA_READ:
iser_rdma_rd_complete_handler(req);
break;
default:
task = req->task;
conn = (task ? task->conn : NULL);
eprintf("unexpected req op:%d, wc op%d, wc::%p "
"wr_id:%p task:%p conn:%p\n",
req->iser_ib_op, wc->opcode, wc, req, task, conn);
if (conn)
iser_conn_close(conn);
break;
}
}
static void handle_wc_error(struct ibv_wc *wc)
{
struct iser_work_req *req = ptr_from_int64(wc->wr_id);
struct iser_task *task = req->task;
struct iser_conn *conn = task ? task->conn : NULL;
if (wc->status != IBV_WC_WR_FLUSH_ERR)
eprintf("conn:%p task:%p tag:0x%04"PRIx64 " wr_id:0x%p op:%s "
"err:%s vendor_err:0x%0x\n",
&conn->h, task, task->tag, req,
iser_ib_op_to_str(req->iser_ib_op),
ibv_wc_status_str(wc->status), wc->vendor_err);
else
dprintf("conn:%p task:%p tag:0x%04"PRIx64 " wr_id:0x%p op:%s "
"err:%s vendor_err:0x%0x\n",
&conn->h, task, task->tag, req,
iser_ib_op_to_str(req->iser_ib_op),
ibv_wc_status_str(wc->status), wc->vendor_err);
switch (req->iser_ib_op) {
case ISER_IB_SEND:
/* in both read and write tasks SEND is last,
the task should be completed now */
case ISER_IB_RDMA_READ:
/* RDMA-RD is sent separately, and Response
is to be SENT after its completion, so if RDMA-RD fails,
task to be completed now */
iser_complete_task(task);
break;
case ISER_IB_RECV:
/* this should be the Flush, no task has been created yet */
case ISER_IB_RDMA_WRITE:
/* RDMA-WR and SEND response of a READ task
are sent together, so when receiving RDMA-WR error,
wait until SEND error arrives to complete the task */
break;
default:
eprintf("unexpected opcode %d, "
"wc:%p wr_id:%p task:%p conn:%p\n",
wc->opcode, wc, req, task, conn);
break;
}
if (conn) {
iser_conn_put(conn);
iser_conn_close(conn);
}
}
/*
* Could read as many entries as possible without blocking, but
* that just fills up a list of tasks. Instead pop out of here
* so that tx progress, like issuing rdma reads and writes, can
* happen periodically.
*/
static int iser_poll_cq(struct iser_device *dev, int max_wc)
{
int err = 0, numwc = 0;
struct ibv_wc wc;
for (;;) {
err = ibv_poll_cq(dev->cq, 1, &wc);
if (err == 0) /* no completions retrieved */
break;
if (unlikely(err < 0)) {
eprintf("ibv_poll_cq failed\n");
break;
}
VALGRIND_MAKE_MEM_DEFINED(&wc, sizeof(wc));
if (likely(wc.status == IBV_WC_SUCCESS))
handle_wc(&wc);
else
handle_wc_error(&wc);
if (++numwc == max_wc) {
err = 1;
break;
}
}
return err;
}
static int num_delayed_arm;
#define MAX_NUM_DELAYED_ARM 16
static void iser_rearm_completions(struct iser_device *dev)
{
int err;
err = ibv_req_notify_cq(dev->cq, 0);
if (unlikely(err))
eprintf("ibv_req_notify_cq failed\n");
dev->poll_sched.sched_handler = iser_sched_consume_cq;
tgt_add_sched_event(&dev->poll_sched);
num_delayed_arm = 0;
}
static void iser_poll_cq_armable(struct iser_device *dev)
{
int err;
err = iser_poll_cq(dev, MAX_POLL_WC);
if (unlikely(err < 0)) {
iser_rearm_completions(dev);
return;
}
if (err == 0 && (++num_delayed_arm == MAX_NUM_DELAYED_ARM))
/* no more completions on cq, give up and arm the interrupts */
iser_rearm_completions(dev);
else {
dev->poll_sched.sched_handler = iser_sched_poll_cq;
tgt_add_sched_event(&dev->poll_sched);
}
}
/* iser_sched_consume_cq() is scheduled to consume completion events that
could arrive after the cq had been seen empty, but just before
the interrupts were re-armed.
Intended to consume those remaining completions only, the function
does not re-arm interrupts, but polls the cq until it's empty.
As we always limit the number of completions polled at a time, we may
need to schedule this functions few times.
It may happen that during this process new completions occur, and
we get an interrupt about that. Some of the "new" completions may be
processed by the self-scheduling iser_sched_consume_cq(), which is
a good thing, because we don't need to wait for the interrupt event.
When the interrupt notification arrives, its handler will remove the
scheduled event, and call iser_poll_cq_armable(), so that the polling
cycle resumes normally.
*/
static void iser_sched_consume_cq(struct event_data *tev)
{
struct iser_device *dev = tev->data;
int err;
err = iser_poll_cq(dev, MAX_POLL_WC);
if (err > 0) {
dev->poll_sched.sched_handler = iser_sched_consume_cq;
tgt_add_sched_event(&dev->poll_sched);
}
}
/* Scheduled to poll cq after a completion event has been
received and acknowledged, if no more completions are found
the interrupts are re-armed */
static void iser_sched_poll_cq(struct event_data *tev)
{
struct iser_device *dev = tev->data;
iser_poll_cq_armable(dev);
}
/*
* Called from main event loop when a CQ notification is available.
*/
static void iser_handle_cq_event(int fd __attribute__ ((unused)),
int events __attribute__ ((unused)),
void *data)
{
struct iser_device *dev = data;
void *cq_context;
int err;
err = ibv_get_cq_event(dev->cq_channel, &dev->cq, &cq_context);
if (unlikely(err != 0)) {
/* Just print the log message, if that was a serious problem,
it will express itself elsewhere */
eprintf("failed to retrieve CQ event, cq:%p\n", dev->cq);
return;
}
ibv_ack_cq_events(dev->cq, 1);
/* if a poll was previosuly scheduled, remove it,
as it will be scheduled when necessary */
if (dev->poll_sched.scheduled)
tgt_remove_sched_event(&dev->poll_sched);
iser_poll_cq_armable(dev);
}
/*
* Called from main event loop when async event is available.
*/
static void iser_handle_async_event(int fd __attribute__ ((unused)),
int events __attribute__ ((unused)),
void *data)
{
struct iser_device *dev = data;
char *dev_name = dev->ibv_ctxt->device->name;
struct ibv_async_event async_event;
struct iser_conn *conn;
if (ibv_get_async_event(dev->ibv_ctxt, &async_event)) {
eprintf("ibv_get_async_event failed\n");
return;
}
switch (async_event.event_type) {
case IBV_EVENT_COMM_EST:
conn = async_event.element.qp->qp_context;
eprintf("conn:0x%p cm_id:0x%p dev:%s, QP evt: %s\n",
&conn->h, conn->cm_id, dev_name,
ibv_event_type_str(IBV_EVENT_COMM_EST));
/* force "connection established" event */
rdma_notify(conn->cm_id, IBV_EVENT_COMM_EST);
break;
/* rest of QP-related events */
case IBV_EVENT_QP_FATAL:
case IBV_EVENT_QP_REQ_ERR:
case IBV_EVENT_QP_ACCESS_ERR:
case IBV_EVENT_SQ_DRAINED:
case IBV_EVENT_PATH_MIG:
case IBV_EVENT_PATH_MIG_ERR:
case IBV_EVENT_QP_LAST_WQE_REACHED:
conn = async_event.element.qp->qp_context;
eprintf("conn:0x%p cm_id:0x%p dev:%s, QP evt: %s\n",
&conn->h, conn->cm_id, dev_name,
ibv_event_type_str(async_event.event_type));
break;
/* CQ-related events */
case IBV_EVENT_CQ_ERR:
eprintf("dev:%s CQ evt: %s\n", dev_name,
ibv_event_type_str(async_event.event_type));
break;
/* SRQ events */
case IBV_EVENT_SRQ_ERR:
case IBV_EVENT_SRQ_LIMIT_REACHED:
eprintf("dev:%s SRQ evt: %s\n", dev_name,
ibv_event_type_str(async_event.event_type));
break;
/* Port events */
case IBV_EVENT_PORT_ACTIVE:
case IBV_EVENT_PORT_ERR:
case IBV_EVENT_LID_CHANGE:
case IBV_EVENT_PKEY_CHANGE:
case IBV_EVENT_SM_CHANGE:
case IBV_EVENT_CLIENT_REREGISTER:
eprintf("dev:%s port:%d evt: %s\n",
dev_name, async_event.element.port_num,
ibv_event_type_str(async_event.event_type));
break;
/* HCA events */
case IBV_EVENT_DEVICE_FATAL:
eprintf("dev:%s HCA evt: %s\n", dev_name,
ibv_event_type_str(async_event.event_type));
break;
default:
eprintf("dev:%s evt: %s\n", dev_name,
ibv_event_type_str(async_event.event_type));
break;
}
ibv_ack_async_event(&async_event);
}
/*
* First time a new connection is received on an RDMA device, record
* it and build a PD and static memory.
*/
static int iser_device_init(struct iser_device *dev)
{
int cqe_num;
int err = -1;
dprintf("dev %p\n", dev);
dev->pd = ibv_alloc_pd(dev->ibv_ctxt);
if (dev->pd == NULL) {
eprintf("ibv_alloc_pd failed\n");
goto out;
}
err = iser_init_rdma_buf_pool(dev);
if (err) {
eprintf("iser_init_rdma_buf_pool failed\n");
goto out;
}
err = ibv_query_device(dev->ibv_ctxt, &dev->device_attr);
if (err < 0) {
eprintf("ibv_query_device failed, %m\n");
goto out;
}
cqe_num = min(dev->device_attr.max_cqe, MAX_CQ_ENTRIES);
dprintf("max %d CQEs\n", cqe_num);
err = -1;
dev->cq_channel = ibv_create_comp_channel(dev->ibv_ctxt);
if (dev->cq_channel == NULL) {
eprintf("ibv_create_comp_channel failed\n");
goto out;
}
/* verify cq_vector */
if (cq_vector < 0)
cq_vector = control_port % dev->ibv_ctxt->num_comp_vectors;
else if (cq_vector >= dev->ibv_ctxt->num_comp_vectors) {
eprintf("Bad CQ vector. max: %d\n",
dev->ibv_ctxt->num_comp_vectors);
goto out;
}
dprintf("CQ vector: %d\n", cq_vector);
dev->cq = ibv_create_cq(dev->ibv_ctxt, cqe_num, NULL,
dev->cq_channel, cq_vector);
if (dev->cq == NULL) {
eprintf("ibv_create_cq failed\n");
goto out;
}
dprintf("dev->cq:%p\n", dev->cq);
tgt_init_sched_event(&dev->poll_sched, iser_sched_poll_cq, dev);
err = ibv_req_notify_cq(dev->cq, 0);
if (err) {
eprintf("ibv_req_notify failed, %m\n");
goto out;
}
err = tgt_event_add(dev->cq_channel->fd, EPOLLIN,
iser_handle_cq_event, dev);
if (err) {
eprintf("tgt_event_add failed, %m\n");
goto out;
}
err = tgt_event_add(dev->ibv_ctxt->async_fd, EPOLLIN,
iser_handle_async_event, dev);
if (err)
return err;
list_add_tail(&dev->list, &iser_dev_list);
out:
return err;
}
static void iser_device_release(struct iser_device *dev)
{
int err;
list_del(&dev->list);
tgt_event_del(dev->ibv_ctxt->async_fd);
tgt_event_del(dev->cq_channel->fd);
tgt_remove_sched_event(&dev->poll_sched);
err = ibv_destroy_cq(dev->cq);
if (err)
eprintf("ibv_destroy_cq failed: (errno=%d %m)\n", errno);
err = ibv_destroy_comp_channel(dev->cq_channel);
if (err)
eprintf("ibv_destroy_comp_channel failed: (errno=%d %m)\n", errno);
iser_destroy_rdma_buf_pool(dev);
err = ibv_dealloc_pd(dev->pd);
if (err)
eprintf("ibv_dealloc_pd failed: (errno=%d %m)\n", errno);
}
static int iser_add_portal(struct addrinfo *res, short int port)
{
int err;
int afonly = 1;
struct sockaddr_storage sock_addr;
struct rdma_cm_id *cma_listen_id;
struct iser_portal *portal = NULL;
portal = zalloc(sizeof(struct iser_portal));
if (!portal) {
eprintf("zalloc failed.\n");
return -1;
}
portal->af = res->ai_family;
portal->port = port;
memset(&sock_addr, 0, sizeof(sock_addr));
if (res->ai_family == AF_INET6) {
((struct sockaddr_in6 *) &sock_addr)->sin6_family = AF_INET6;
((struct sockaddr_in6 *) &sock_addr)->sin6_port = htons(port);
((struct sockaddr_in6 *) &sock_addr)->sin6_addr = in6addr_any;
} else {
((struct sockaddr_in *) &sock_addr)->sin_family = AF_INET;
((struct sockaddr_in *) &sock_addr)->sin_port = htons(port);
((struct sockaddr_in *) &sock_addr)->sin_addr.s_addr = INADDR_ANY;
}
err = rdma_create_id(rdma_evt_channel, &cma_listen_id, NULL,
RDMA_PS_TCP);
if (err) {
eprintf("rdma_create_id failed, %m\n");
return -1;
}
portal->cma_listen_id = cma_listen_id;
rdma_set_option(cma_listen_id, RDMA_OPTION_ID,
RDMA_OPTION_ID_AFONLY, &afonly, sizeof(afonly));
err =
rdma_bind_addr(cma_listen_id, (struct sockaddr *) &sock_addr);
if (err) {
if (err == -1)
eprintf("rdma_bind_addr -1: %m\n");
else
eprintf("rdma_bind_addr: %s\n", strerror(-err));
return -1;
}
/* 0 == maximum backlog */
err = rdma_listen(cma_listen_id, 0);
if (err) {
if (err == -1)
eprintf("rdma_listen -1: %m\n");
else
eprintf("rdma_listen: %s\n", strerror(-err));
return -1;
}
list_add(&portal->iser_portal_siblings, &iser_portals_list);
return err;
}
/*
* Init entire iscsi transport. Begin listening for connections.
*/
static int iser_ib_init(void)
{
int err;
short int port = iser_listen_port;
struct addrinfo hints, *res, *res0;
char servname[64];
rdma_evt_channel = rdma_create_event_channel();
if (!rdma_evt_channel) {
eprintf("Failed to initialize RDMA; load kernel modules?\n");
return -1;
}
memset(servname, 0, sizeof(servname));
snprintf(servname, sizeof(servname), "%d", port);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
err = getaddrinfo(NULL, servname, &hints, &res0);
if (err) {
eprintf("unable to get address info, %m\n");
return -errno;
}
for (res = res0; res; res = res->ai_next) {
err = iser_add_portal(res, port);
if (err) {
freeaddrinfo(res0);
return err;
}
}
freeaddrinfo(res0);
dprintf("listening for iser connections on port %d\n", port);
err = tgt_event_add(rdma_evt_channel->fd, EPOLLIN,
iser_handle_rdmacm, NULL);
if (err)
return err;
return err;
}
void iser_delete_portals(void)
{
int err;
struct iser_portal *portal, *ptmp;
list_for_each_entry_safe(portal, ptmp, &iser_portals_list,
iser_portal_siblings) {
err = rdma_destroy_id(portal->cma_listen_id);
if (err)
eprintf("rdma_destroy_id failed: (errno=%d %m)\n", errno);
list_del(&portal->iser_portal_siblings);
free(portal);
}
}
static void iser_ib_release(void)
{
struct iser_device *dev, *tdev;
assert(list_empty(&iser_conn_list));
list_for_each_entry_safe(dev, tdev, &iser_dev_list, list) {
iser_device_release(dev);
free(dev);
}
if (!list_empty(&iser_portals_list)) {
tgt_event_del(rdma_evt_channel->fd);
iser_delete_portals();
rdma_destroy_event_channel(rdma_evt_channel);
}
}
static int iser_send_nop = 1;
static struct tgt_work nop_work;
#define ISER_TIMER_INT_SEC 5
static void iser_nop_work_handler(void *data)
{
struct iser_conn *conn;
struct iser_task *task;
list_for_each_entry(conn, &iser_conn_list, conn_list) {
if (conn->h.state != STATE_FULL)
continue;
task = conn->nop_in_task;
if (!task)
continue;
conn->nop_in_task = NULL;
iser_send_ping_nop_in(task);
}
add_work(&nop_work, ISER_TIMER_INT_SEC);
}
static int iser_init(int index, char *args)
{
int err;
err = iser_ib_init();
if (err) {
iser_send_nop = 0;
return err;
}
if (iser_send_nop) {
nop_work.func = iser_nop_work_handler;
nop_work.data = &nop_work;
add_work(&nop_work, ISER_TIMER_INT_SEC);
}
return 0;
}
static void iser_exit(void)
{
if (iser_send_nop)
del_work(&nop_work);
iser_ib_release();
}
static int iser_target_create(struct target *t)
{
struct iscsi_target *target;
int err;
err = iscsi_target_create(t);
if (err)
return err;
target = target_find_by_id(t->tid);
assert(target != NULL);
target->rdma = 1;
target->session_param[ISCSI_PARAM_INITIAL_R2T_EN].val = 1;
target->session_param[ISCSI_PARAM_IMM_DATA_EN].val = 0;
return 0;
}
static const char *lld_param_port = "port";
static const char *lld_param_nop = "nop";
static const char *lld_param_on = "on";
static const char *lld_param_off = "off";
static const char *lld_param_pool_sz_mb = "pool_sz_mb";
static const char *lld_param_buf_sz_kb = "buf_sz_kb";
static const char *lld_param_cq_vector = "cq_vector";
static int iser_param_parser(char *p)
{
int err = 0;
char *q;
if (!p)
return 0;
while (*p) {
if (!strncmp(p, lld_param_port, strlen(lld_param_port))) {
short int port_val;
q = p + strlen(lld_param_port) + 1;
port_val = strtol(q, NULL, 10);
if (!errno)
iser_listen_port = port_val;
else {
eprintf("invalid port supplied\n");
err = -EINVAL;
break;
}
} else if (!strncmp(p, lld_param_nop, strlen(lld_param_nop))) {
q = p + strlen(lld_param_nop) + 1;
if (!strncmp(q, lld_param_on, strlen(lld_param_on)))
iser_send_nop = 1;
else
if (!strncmp(q, lld_param_off, strlen(lld_param_off)))
iser_send_nop = 0;
else {
eprintf("unsupported value for param:%s\n",
lld_param_nop);
err = -EINVAL;
break;
}
} else if (!strncmp(p, lld_param_pool_sz_mb,
strlen(lld_param_pool_sz_mb))) {
q = p + strlen(lld_param_pool_sz_mb) + 1;
buf_pool_sz_mb = atoi(q);
if (buf_pool_sz_mb < 128)
buf_pool_sz_mb = 128;
} else if (!strncmp(p, lld_param_buf_sz_kb,
strlen(lld_param_buf_sz_kb))) {
q = p + strlen(lld_param_buf_sz_kb) + 1;
membuf_size = atoi(q);
if (membuf_size < 1)
membuf_size = 1;
membuf_size = membuf_size * 1024;
} else if (!strncmp(p, lld_param_cq_vector,
strlen(lld_param_cq_vector))) {
q = p + strlen(lld_param_cq_vector) + 1;
cq_vector = atoi(q);
if (cq_vector < 0) {
eprintf("unsupported value for param: %s\n",
lld_param_cq_vector);
err = -EINVAL;
break;
}
} else {
dprintf("unsupported param:%s\n", p);
err = -EINVAL;
break;
}
p += strcspn(p, ",");
if (*p == ',')
++p;
}
return err;
}
static struct iscsi_transport iscsi_iser = {
.name = "iser",
.rdma = 1,
.data_padding = 1,
.ep_show = iser_show,
.ep_force_close = iser_conn_force_close,
.ep_getsockname = iser_getsockname,
.ep_getpeername = iser_getpeername,
};
static struct tgt_driver iser = {
.name = "iser",
.init = iser_init,
.exit = iser_exit,
.target_create = iser_target_create,
.target_destroy = iscsi_target_destroy,
.update = iscsi_target_update,
.show = iscsi_target_show,
.stat = iscsi_stat,
.cmd_end_notify = iser_scsi_cmd_done,
.mgmt_end_notify = iser_tm_done,
.transportid = iscsi_transportid,
.default_bst = "rdwr",
};
__attribute__ ((constructor))
static void iser_driver_constructor(void)
{
register_driver(&iser);
setup_param("iser", iser_param_parser);
}
tgt-1.0.80/usr/iscsi/iser.h 0000664 0000000 0000000 00000015466 13747533545 0015470 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2007 Dennis Dalessandro (dennis@osc.edu)
* Copyright (C) 2007 Ananth Devulapalli (ananth@osc.edu)
* Copyright (C) 2007 Pete Wyckoff (pw@osc.edu)
* Copyright (C) 2010 Voltaire, Inc. All rights reserved.
* Copyright (C) 2010 Alexander Nezhinsky (alexandern@voltaire.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef ISER_H
#define ISER_H
#include "iscsid.h"
#include
#include
extern short control_port;
struct iser_portal {
struct list_head iser_portal_siblings;
int port;
int af;
struct rdma_cm_id *cma_listen_id;
};
/*
* The IB-extended version from the kernel. Stags and VAs are in
* big-endian format.
*/
struct iser_hdr {
uint8_t flags;
uint8_t rsvd[3];
uint32_t write_stag; /* write rkey */
uint64_t write_va;
uint32_t read_stag; /* read rkey */
uint64_t read_va;
} __attribute__((packed));
#define ISER_WSV (0x08)
#define ISER_RSV (0x04)
#define ISCSI_CTRL (0x10)
#define ISER_HELLO (0x20)
#define ISER_HELLORPLY (0x30)
struct iser_conn;
enum iser_ib_op_code {
ISER_IB_RECV,
ISER_IB_SEND,
ISER_IB_RDMA_WRITE,
ISER_IB_RDMA_READ,
};
#define ISER_HDRS_SZ (sizeof(struct iser_hdr) + sizeof(struct iscsi_hdr))
/*
* Work requests are either posted Receives for control messages,
* or Send and RDMA ops (also considered "send" by IB)
* They have different work request descriptors.
* During a flush, we need to know the type of op and the
* task to which it is related.
*/
struct iser_work_req {
struct list_head wr_list;
struct iser_task *task;
enum iser_ib_op_code iser_ib_op;
struct ibv_sge sge;
union {
struct ibv_recv_wr recv_wr;
struct ibv_send_wr send_wr;
};
};
/*
* Pre-registered memory. Buffers are allocated by iscsi from us, handed
* to device to fill, then iser can send them directly without registration.
* Also for write path.
*/
struct iser_membuf {
void *addr;
unsigned size;
unsigned offset; /* offset within task data */
struct list_head task_list;
int rdma;
struct list_head pool_list;
};
struct iser_pdu {
struct iser_hdr *iser_hdr;
struct iscsi_hdr *bhs;
unsigned int ahssize;
void *ahs;
/* pdu data only, original buffer is reflected in ibv_sge */
struct iser_membuf membuf;
};
/*
* Each SCSI command may have its own RDMA parameters. These appear on
* the connection then later are assigned to the particular task to be
* used when the target responds.
*/
struct iser_task {
struct iser_conn *conn;
struct iser_pdu pdu;
struct iser_work_req rxd;
struct iser_work_req txd;
struct iser_work_req rdmad;
int opcode;
int is_immediate;
int is_read;
int is_write;
int unsolicited;
uint64_t tag;
uint32_t cmd_sn;
unsigned long flags;
int in_len;
int out_len;
int unsol_sz;
int unsol_remains;
int rdma_rd_sz;
int rdma_rd_remains;
/* int rdma_rd_offset; // ToDo: multiple RDMA-Write buffers */
int rdma_wr_sz;
int rdma_wr_remains;
/* read and write from the initiator's point of view */
uint32_t rem_read_stag, rem_write_stag;
uint64_t rem_read_va, rem_write_va;
struct list_head in_buf_list;
int in_buf_num;
struct list_head out_buf_list;
int out_buf_num;
struct list_head exec_list;
struct list_head rdma_list;
struct list_head tx_list;
struct list_head recv_list;
/* linked to session->cmd_list */
struct list_head session_list;
struct list_head dout_task_list;
int result;
struct scsi_cmd scmd;
unsigned char *extdata;
};
struct iser_device;
enum iser_login_phase
{
LOGIN_PHASE_INIT,
LOGIN_PHASE_START, /* keep 1 send spot and 1 recv posted */
LOGIN_PHASE_LAST_SEND, /* need 1 more send before ff */
LOGIN_PHASE_FF, /* full feature */
NUM_LOGIN_PHASE_VALS
};
/*
* Parallels iscsi_connection. Adds more fields for iser.
*/
struct iser_conn {
struct iscsi_connection h;
struct ibv_qp *qp_hndl;
struct rdma_cm_id *cm_id;
struct iser_device *dev;
struct event_data sched_buf_alloc;
struct list_head buf_alloc_list;
struct event_data sched_rdma_rd;
struct list_head rdma_rd_list;
struct event_data sched_iosubmit;
struct list_head iosubmit_list;
struct event_data sched_tx;
struct list_head resp_tx_list;
struct list_head sent_list;
struct event_data sched_post_recv;
struct list_head post_recv_list;
struct event_data sched_conn_free;
struct sockaddr_storage peer_addr; /* initiator address */
char *peer_name;
struct sockaddr_storage self_addr; /* target address */
char *self_name;
unsigned int ssize, rsize, max_outst_pdu;
/* FF resources */
int ff_res_alloc;
void *task_pool; /* iser_task structures */
void *pdu_data_pool; /* memory for pdu, non-rdma send/recv */
struct ibv_mr *pdu_data_mr; /* memory registration for pdu data */
struct iser_task *nop_in_task;
struct iser_task *text_tx_task;
enum iser_login_phase login_phase;
/* login phase resources, freed at full-feature */
int login_res_alloc;
void *login_task_pool;
void *login_data_pool;
struct ibv_mr *login_data_mr;
struct iser_task *login_rx_task;
struct iser_task *login_tx_task;
/* list of all iser conns */
struct list_head conn_list;
};
static inline struct iser_conn *ISER_CONN(struct iscsi_connection *iscsi_conn)
{
return container_of(iscsi_conn, struct iser_conn, h);
}
/*
* Shared variables for a particular device. The conn[] array will
* have to be broken out when multiple device support is added, maybe with
* a pointer into this "device" struct.
*/
struct iser_device {
struct list_head list;
struct ibv_context *ibv_ctxt;
struct ibv_pd *pd;
struct ibv_cq *cq;
struct ibv_comp_channel *cq_channel;
struct ibv_device_attr device_attr;
/* membuf registered buffer, list area, handle */
void *membuf_regbuf;
void *membuf_listbuf;
struct ibv_mr *membuf_mr;
int waiting_for_mem;
/* shared memory identifier */
int rdma_hugetbl_shmid;
struct event_data poll_sched;
/* free and allocated membuf entries */
struct list_head membuf_free, membuf_alloc;
};
void iser_login_exec(struct iscsi_connection *iscsi_conn,
struct iser_pdu *rx_pdu,
struct iser_pdu *tx_pdu);
int iser_login_complete(struct iscsi_connection *iscsi_conn);
int iser_text_exec(struct iscsi_connection *iscsi_conn,
struct iser_pdu *rx_pdu,
struct iser_pdu *tx_pdu);
void iser_conn_close(struct iser_conn *conn);
#endif /* ISER_H */
tgt-1.0.80/usr/iscsi/iser_text.c 0000664 0000000 0000000 00000061305 13747533545 0016520 0 ustar 00root root 0000000 0000000 /*
* iSCSI extensions for RDMA (iSER)
* LOGIN and TEXT related code
*
* Copyright (C) 2007 Dennis Dalessandro (dennis@osc.edu)
* Copyright (C) 2007 Ananth Devulapalli (ananth@osc.edu)
* Copyright (C) 2007 Pete Wyckoff (pw@osc.edu)
* Copyright (C) 2010 Voltaire, Inc. All rights reserved.
* Copyright (C) 2010 Alexander Nezhinsky (alexandern@voltaire.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include